第十章 内部排序
合集下载
第十章内部排序
最坏情况 O(n2) O(n2)
O(nlogn) O(nlogn)
辅助存储 O(1)
O(logn) O(1) O(n)
Data Structure
33
成为一个新堆?
Data Structure
27
第十章 内部排序
3、调整新堆 输出堆顶,以最后一个代替,然后和左、右
孩子比较,找小的代替,再与被破坏的堆的左、 右孩子比较,……,直至叶子结点。
称自堆顶至叶子的调整过程为“筛选”。 4、建堆
是一个反复筛选的过程,且只需从第 n/2 个 元素开始。 5、性能分析
二、性能分析
需log2n趟、需和待排记录等量的辅助空间、
时间复杂度O(nlog2n)、稳定、但很少使用。
Data Structure
31
Data Structure
32
第十章 内部排序
各种内部排序方法的比较讨论
排序方法 简单排序 快速排序
堆排序 归并排序
平均时间 O(n2)
O(nlogn) O(nlogn) O(nlogn)
1
第十章 内部排序
2、分类 内部排序:待排记录量小、随机存储器; 外部排序:待排记录量大、随机存储器+外存。
3、排序过程中两种基本操作 1)比较两个关键字的大小;
2)将记录从一个位置移动到另一个位置。 4、待排记录的存储方式
1)存放在地址连续的一组存储单元上; 2)存放在静态链表中; 3)记录本身存放在一组地址连续的存储单元内,同时 另设一个指示各个记录存储位置的地址向量。
Data Structure
19
Data Structure
20
Data Structure
数据结构-第十章-内部排序
0
1
2
3
4
5
6
7
8
i=5
MAXINT 49 2 3
MAXINT 49 6 3 MAXINT 49 6 3 MAXINT 49 6 8
38 1
38 1 38 1 38 1
65 97 5 0
65 5 65 5 65 5 97 0 97 0 97 0
76 4
76 4 76 4 76 4
13
27
49
i=6
最坏情况下,待排记录按关键字非递增有序 排列(逆序)时,第 i 趟时第 i+1 个对象 必须与前面 i 个对象都做排序码比较, 并且 每做1次比较就要做1次数据移动。总比较 次 数 为 (n+2)(n-1)/2 次 , 总 移 动 次 数 为 (n+4)(n-1)/2。 在平均情况下的排序码比较次数和对象移 动次数约为 n2/4。因此,直接插入排序的 时间复杂度为 O(n2)。 直接插入排序是一种稳定的排序方法。
折半插入排序 (Binary Insertsort)
基本思想 既然每个要插入记录之前的纪录 已经按关键字有序排列,在查找插入位 臵时就没有必要逐个关键字比较,可以 使用折半查找来实现。由此进行的插入 排序称之为折半插入排序。
折半插入排序的算法
void BInsertSort (SqList &L){ for (i=2;i<=L.length;++i){ L.r[0]=L.r[i]; low=1;high=i-1; //查找范围由1到i-1 while(low<=high){ m=(low+high)/2; if LT(L.r[0].key,L.r[m].key) high=m-1; else low=m+1; }//while 折半查找 for (j=i-1;j>=high+1;--j) L.r[j+1]=L.r[j]; //折半查找结束后high+1位臵即为插入位臵 L.r[high+1]=L.r[0]; }//for }//BInsertSort
第十章内部排序
10.2 插入排序
1、直接插入排序:续 •实现算法
Status InsertSort ( SqList &L ) { for ( i = 2; i <= L.length ; ++i ) if ( LT( L.r[i].key, L.r[i-1].key ) ) { L.r[0].key = L.r[i].key; //复制为哨兵 for ( j=i-1; LT( L.r[0].key, L.r[j].key ) ; - -j ) L.r[j+1] = L.r[j]; //后移记录 L.r[j+1] = L.r[0]; } } //InsertSort
1 物料管理
第十章 内部排序
学习内容
10.1 概述 10.2 插入排序 10.3 快速排序 10.4 选择排序 10.5 归并排序
SORT
2 物料管理
第十章 内部排序
10.1 概述
•定义
设有记录序列:{ R1、R2 ……….. Rn } 其相应的关键字序列为: { K1、K2 ……….. Kn }; 若存在一种确定的关系: Kx <= Ky <= … <= Kz,则将记录序列 { R1、R2 ……….. Rn } 排成按 该关键字有序的序列:{ Rx、Ry ……….. Rz } 的操作,称之为排序。
•性能分析 减少了查找比较次数,但并未降低时间复杂度,仍为O(n2)。 7
SORT
8 物料管理
第十章 内部排序
10.2 插入排序
3、 希尔(shell) 排序 •基本思想 先将整个待排记录序列分割成为若干个子序列分别进行直接插入排序,待整个序列 中的记录基本有序时,再对全体记录进行一次直接插入排序。
第10章 内部排序
例如:原序列如下所示。 ቤተ መጻሕፍቲ ባይዱk=5、3、1 dk=5
49 38 65 97 76 13 27 49 55 04
13 27 49 55 04 13 27 49 55 04 49 38 65 97 49 38 65 97 76 76
dk=3
13 27 49 55 04 49 38 65 97
76
13 04
排序的时间开销: 排序的时间开销是衡量算法 好坏的最重要的标志。排序时间开销可用算法 执行中的数据比较次数与数据移动次数来衡量。
算法运行时间代价的大略估算一般都按平均情 况进行估算。对于那些受
待排记录(对象)的关键字初始序列 记录(对象)的个数影响
较大的算法,需要按最好情况和最坏情况估算。
⑵折半插入(Binary Insertsort)
插入排序的基本操作是在一个有序表中,进行查 找和插入,在有序表中的查找过程可以利用折半查 找,即折半插入排序方法。
21 25 49 25 16 08
i=2
low
high
i=3
21
25
49
25
16
08
low m high
21
25
49
25
16
08
low/m high
21 25 49 25 16 8 l h
h移 动 8<21, l与 h交 换 记 录 , 同时交换角色 l移 动 h 25>21, l与 h交 换 记 录 , 同时交换角色 h移 动
8 25 49 25 16 21 l 8 25 49 25 16 21 l 8 21 49 25 16 25 l h h
第10章 内部排序
图10-10 堆的筛选过程
49
27 37
10. 7 各种内部排序的比较
各种内部排序按所采用的基本思想(策略)可分为: 插入排序、交换排序、选择排序、归并排序和基
数排序,它们的基本策略分别是:
1 插入排序:依次将无序序列中的一个记录,按关
键字值的大小插入到已排好序一个子序列的适当位置, 直到所有的记录都插入为止。具体的方法有:直接插入、 表插入、2-路插入和shell排序。
41 67 67 67 67
23 22
22 23 22 23 22 23 22 23
38
23 23 23 15
23
38 31 15 23
45
31 15 31 31
31
15 38 38 38
15
41 41 41 41
41 67
第二趟排序后:
第三趟排序后: 第四趟排序后: 第五趟排序后: 第六趟排序后:
◆ Ki是主关键字:排序后得到的结果是唯一的;
◆ Ki是次关键字:排序后得到的结果是不唯一的。
⑵ 排序的稳定性
若记录序列中有两个或两个以上关键字相等的记录: Ki =Kj(i≠j,i, j=1, 2, … n),且在排序前Ri先于Rj(i<j),排 序后的记录序列仍然是Ri先于Rj,称排序方法是稳定的,否 则是不稳定的。
2 排序示例
设有10个待排序的记录,关键字分别为9, 13, 8, 2, 5, 13, 7, 1, 15, 11,增量序列是5, 3, 1,希尔排序的过程如图 10-5所示。
初始关键字序列: 9 13 7 8 2 5 13 7 13 1 15 11
第一趟排序过程:
第一趟排序后: 9 第二趟排序后: 2 第三趟排序后: 1 7
49
27 37
10. 7 各种内部排序的比较
各种内部排序按所采用的基本思想(策略)可分为: 插入排序、交换排序、选择排序、归并排序和基
数排序,它们的基本策略分别是:
1 插入排序:依次将无序序列中的一个记录,按关
键字值的大小插入到已排好序一个子序列的适当位置, 直到所有的记录都插入为止。具体的方法有:直接插入、 表插入、2-路插入和shell排序。
41 67 67 67 67
23 22
22 23 22 23 22 23 22 23
38
23 23 23 15
23
38 31 15 23
45
31 15 31 31
31
15 38 38 38
15
41 41 41 41
41 67
第二趟排序后:
第三趟排序后: 第四趟排序后: 第五趟排序后: 第六趟排序后:
◆ Ki是主关键字:排序后得到的结果是唯一的;
◆ Ki是次关键字:排序后得到的结果是不唯一的。
⑵ 排序的稳定性
若记录序列中有两个或两个以上关键字相等的记录: Ki =Kj(i≠j,i, j=1, 2, … n),且在排序前Ri先于Rj(i<j),排 序后的记录序列仍然是Ri先于Rj,称排序方法是稳定的,否 则是不稳定的。
2 排序示例
设有10个待排序的记录,关键字分别为9, 13, 8, 2, 5, 13, 7, 1, 15, 11,增量序列是5, 3, 1,希尔排序的过程如图 10-5所示。
初始关键字序列: 9 13 7 8 2 5 13 7 13 1 15 11
第一趟排序过程:
第一趟排序后: 9 第二趟排序后: 2 第三趟排序后: 1 7
第十章 内部排序
希尔排序C 语言实现
/* 31,15,7,3,1 … */ void shell_sort(void) { int t, k, i, j, dk, rc; t = 0; while ((1 << t) < n) t++; for (k = 1; k < t; k++) { dk = (1 << (t - k)) - 1; for (i = dk + 1; i <= n; i++) { if (r[i] < r[i - dk]) { rc = r[i]; for (j = i - dk; j > 0 && rc < r[j]; j -= dk) r[j + dk] = r[j]; r[j + dk] = rc; } } }
起泡排序
原理: 序列中有n个元素,进行n-1趟(条件满足时可以提前结束) 第1趟,针对r[1~n]元素进行。 第2趟,针对r[1~(n-1)]元素进行。 …… 第n-1趟,针对 r[1~2]元素进行。 每一趟进行的过程:从第一个元素开始,比较两个相邻的元素。若相邻的元 素的相对位置不正确,则进行交换;否则继续比较下面两个相邻的元素。 结束条件:在任何一趟进行过程中,未出现交换 时间复杂性: 正序时 O(n),逆序时 O(n2)。平均时间复杂性 O(n2)
dk = delta(k); /* 增量序列函数,如: ……15,7,3,1 */
for (i = dk + 1; i <= n; i++) { rc = r[i]; for (j = i - dk; j > 0 && rc < r[j]; j -= dk)
第10章 内部排序
最坏的情况(关键码在记录序列中逆序有序):
“比较”次数为(n+2)(n-1)/2 ,“移动”次数为(n+4)(n-1)/2 总的说来,直接插入排序所需进行关键码间的比较次数和记录移动次数均
为n2/4,所以直接插入排序的时间复杂度为O(n2)。 空间效率:需要一个记录的辅助空间,因此空间复杂度为O(1)
性能分析: 时间效率 最好的情况只需进行一趟起泡:“比较”次数为n-1,“移动”次数为0
最坏的情况需进行n-1趟起泡:“比较”的次数为
数与“比较”的次数 相同 。 “移动”次数是“交换”次数的 3倍 所以时间复杂度为:O(n2)—因为要考虑最坏情况
“交换”次
空间效率: O(1)—只在交换时用到一个缓冲单元 稳定性:稳定的
10.3.2 快速排序 基本思想:任取待排序序列中的某个元素作为基准(一般取第一个 元素),通过一趟排序,将待排元素分为左右两个子序列,左子序列元
素的排序码均小于基准元素的排序码,右子序列的排序码则大于等于基
准元素的排序码,然后分别对两个子序列继续进行排序,直至整个序列 有序。 例子:初始序列
第1次交换后 第2次交换后 第3次交换后 第4次交换后 完成一趟排序 【 46 i 【 17 i 【 17 【 17 【 17 【 17 i 05 i 05 05 13 13 42 55 i 13 13 42 42 94 94 i j 94 55 55 70 】 70 】 05 j 13 42 94 05 j 55 j 55 70 】 70 】 基准元素 55 13 42 94 05 17 j 70 】 j 70 】
例子: 初态
第一趟结果 第二趟结果
[49 38 65 97 76 13 27 49] 13[38 65 97 76 49 27 49] 13 27[65 97 76 49 38 49]
第十章内部排序
折半插入排序只能减少排序过程中关键字比较的次数,并不能减少 记录移动的次数,因此折半插入排序的时间复杂度仍为O (n2)
希尔排序
基本思想 先取一个正整数d1<n作为第一个增量,把所有相隔d1 的记录放一组,组内进行直接插入排序;然后取d2<d1作 为第二个增量,重复上述分组和排序操作;直至di=1,即 所有记录放进一个组中排序为止。
13 20 30 39
void BInsertSort (SqList &L) { // 对顺序表L作折半插入排序 for ( i=2; i<=L.length; ++i ) // 共需L.length-1趟排序 { L.r[0] = L.r[i]; // 将L.r[i]暂存到L.r[0] low = 1; high = i-1; while (low<=high) // 在r[low..high]中折半查找插入位置 { m = (low+high)/2; // 中间点 if (L.r[0].key < L.r[m].key)) high = m-1; // 插入点在低半区 else low = m+1; // 插入点在高半区 } // while for ( j=i-1; j>=low; --j ) L.r[j+1] = L.r[j]; // 记录后移 L.r[high+1] = L.r[0]; // 插入 } } // BInsertSort
(60,50,40,30,20,10) » 若待排序记录按关键字从大到小排列(逆序) n 1 (n 2)(n 1) ( i 1) 关键字比较次数:
2 (n 4)(n 1) ( i 2) 记录移动次数: 2 i 1
数据结构I6--第10章内部排序
算法评价: 总比较次数: 最小值:Cmin= n-1 ≈ n
n
(正序) (逆序)
最大值:Cmax= ∑ i
i=2
= (n+2)(n-1) ≈ 2 总移动次数: 最小值:Mmin= 0
n
n2 2
(正序)
n2 (逆序) 最大值:Mmax= ∑ (i+1) ≈ i=2 2 1 ( n2 + n ) ≈ n2 平均比较次数: 4 2 2 1 ( n2 + 0 ) ≈ n2 平均移动次数: 4 2 2 时间复杂度:O(n2) 稳定排序 空间复杂度:O(1) 1个辅助空间
void Qsort ( SqList &L , int low , int high ) { if ( low < high ) { pivotloc = Partition ( L , low , high ) ; Qsort ( L , low , pivotloc – 1 ) ; Qsort (L , pivotloc + 1 , high ) ; } }
8 76 97 49 4 0 3
2 6 1 P=6, P=7 P=2 P=1 P=8 P=3 P=5
i=1---SL.length-1 循环 i=2 i=3 P=7 i=4 P=6 i=5 i=6 P=7 i=7 P=8
表插入排序重排记录算法 void Arrange ( SLinkListType &SL ) { p = SL.r[ 0 ].next ; for ( i = 1 ; i < SL.length ; ++i ) { while ( p < i ) p = SL.r[ p ].next ; q = SL.r[ p ].next ; if ( p != i ) { SL.r[ p ] ←→ SL.r[ i ] ; SL.r[ i ].next = p ; } p=q; } }
第十章 内部排序
④ 如果某一趟起泡排序过程中没有进行一次记录 的交换,则排序过程结束。
快速排序 (Quick Sort)
• 基本思想: – 任取待排序记录序列中的某个记录 (例如取第一 个记录) 作为基准,按照该记录的关键字大小, 将整个记录序列划分为左右两个子序列:
• 左侧子序列中所有记录的关键字都小于或等于基准记录的关键字 • 右侧子序列中所有记录的关键字都大于或等于基准记录的关键字
3 5 21 4
3 4 5 21 8
3 4 5 8 21 16
3 4 5 8 16 21
i=1
i=2 i=3 i=4 i=5
折半插入排序的算法:
void BInsertSort(SqList &L) {
for( i=2; i<=L.length; ++i ){
L.r[0]=L.r[i];
// 将L.r[i]暂存到L.r[0]
第十章 内部排序
概述 插入排序 快速排序 选择排序 归并排序 基数排序 总结
概述
• 排序:将一组杂乱无章的记录按一定的 规律顺次排列起来。
• 关键字(key): 通常数据记录有多个属性 域,即多个数据成员组成,其中有一个 属性域可用来区分记录,作为排序依据。 该域即为关键字。
i=3
08
16
21
25*
25
49
一趟快速排序算法描述:
int Partition(SqList &L, int low, int high) {
L.r[0] = L.r[low]; // 用子表的第一个记录作基准记录
pivotkey = L.r[low].key; // 基准记录关键字
while( low<high ){ // 从表的两端交替地向中间扫描
快速排序 (Quick Sort)
• 基本思想: – 任取待排序记录序列中的某个记录 (例如取第一 个记录) 作为基准,按照该记录的关键字大小, 将整个记录序列划分为左右两个子序列:
• 左侧子序列中所有记录的关键字都小于或等于基准记录的关键字 • 右侧子序列中所有记录的关键字都大于或等于基准记录的关键字
3 5 21 4
3 4 5 21 8
3 4 5 8 21 16
3 4 5 8 16 21
i=1
i=2 i=3 i=4 i=5
折半插入排序的算法:
void BInsertSort(SqList &L) {
for( i=2; i<=L.length; ++i ){
L.r[0]=L.r[i];
// 将L.r[i]暂存到L.r[0]
第十章 内部排序
概述 插入排序 快速排序 选择排序 归并排序 基数排序 总结
概述
• 排序:将一组杂乱无章的记录按一定的 规律顺次排列起来。
• 关键字(key): 通常数据记录有多个属性 域,即多个数据成员组成,其中有一个 属性域可用来区分记录,作为排序依据。 该域即为关键字。
i=3
08
16
21
25*
25
49
一趟快速排序算法描述:
int Partition(SqList &L, int low, int high) {
L.r[0] = L.r[low]; // 用子表的第一个记录作基准记录
pivotkey = L.r[low].key; // 基准记录关键字
while( low<high ){ // 从表的两端交替地向中间扫描
数据结构第十章 排序
7
10.2 插入排序 插入排序
直接插入排序 折半插入排序 2-路插入排序 表插入排序 希尔排序
10.2.1 直接插入排序
基本操作:将一个记录插入到已排好序的有序表中, 从而得到一个新的、记录数增1的有序表。
例:有一组待排序的记录的关键字初始序列如下:
(49,38,65,97,76,13,27,49`)
(4)归并排序 (5)基数排序
按内排过程中所需的工作量分类:
(1)简单的排序方法,其时间复杂度为O(n×n)
(2)先进的排序方法,其时间复杂度为O(nlogn);
(3)基数排序,其时间复杂度为O(d(n+rd))
排序算法的两种基本操作:
(1)比较两个关键字的大小; (2)将记录从一个位置移至另一个位置;
算法实现的关键设计:
将d看成是一个循环数组,并设两个指针first和final分别指示排序过 程中得到的有序序列中的第一个记录和最后一个记录在d中的位置.
例:有一组待排序的记录的关键字初始排列如下:
(49,38,65,97,76,13,27,49`) 16
[初始关键字] 49 38 65 97 76 13 27 49`
18
10.2.3 希尔排序 从直接插入排序
待排序序列基本有序可提高效率 回顾 待排序序列的记录数n很小时可提高效率
希尔排序的基本思想:
先将整个待排记录序列分割成为若干子序列分别进行
直接插入排序,待整个序列中的记录“基本有序”时,再对 全
体记例录:有进一行组一待次排直序接的插记入录排的序关. 键字初始排列如下: (49,38,65,97,76,13,27,49`)
} 12
直接插入排序的性能分析: 10. 3
(1)空间:只需一个记录的辅助空间r[0].
10.2 插入排序 插入排序
直接插入排序 折半插入排序 2-路插入排序 表插入排序 希尔排序
10.2.1 直接插入排序
基本操作:将一个记录插入到已排好序的有序表中, 从而得到一个新的、记录数增1的有序表。
例:有一组待排序的记录的关键字初始序列如下:
(49,38,65,97,76,13,27,49`)
(4)归并排序 (5)基数排序
按内排过程中所需的工作量分类:
(1)简单的排序方法,其时间复杂度为O(n×n)
(2)先进的排序方法,其时间复杂度为O(nlogn);
(3)基数排序,其时间复杂度为O(d(n+rd))
排序算法的两种基本操作:
(1)比较两个关键字的大小; (2)将记录从一个位置移至另一个位置;
算法实现的关键设计:
将d看成是一个循环数组,并设两个指针first和final分别指示排序过 程中得到的有序序列中的第一个记录和最后一个记录在d中的位置.
例:有一组待排序的记录的关键字初始排列如下:
(49,38,65,97,76,13,27,49`) 16
[初始关键字] 49 38 65 97 76 13 27 49`
18
10.2.3 希尔排序 从直接插入排序
待排序序列基本有序可提高效率 回顾 待排序序列的记录数n很小时可提高效率
希尔排序的基本思想:
先将整个待排记录序列分割成为若干子序列分别进行
直接插入排序,待整个序列中的记录“基本有序”时,再对 全
体记例录:有进一行组一待次排直序接的插记入录排的序关. 键字初始排列如下: (49,38,65,97,76,13,27,49`)
} 12
直接插入排序的性能分析: 10. 3
(1)空间:只需一个记录的辅助空间r[0].
数据结构课件第10章_内排序
按排序过程中使用到的存储介质来分,可以将排 序分成两大类 内排序和外排序。 序分成两大类:内排序和外排序。 两大类: 内排序是指在排序过程中所有数据均放在内存中 内排序是指在排序过程中所有数据均放在内存中 处理,不需要使用外存的排序方法。而对于数据量很 大的文件,在内存不足的情况下,则还需要使用外存, 这种排序方法称为外排序 这种排序方法称为外排序。 外排序。 排序码相同的记录,若经过排序后,这些记录 仍保持原来的相对次序不变,称这个排序算法是稳 仍保持原来的相对次序不变,称这个排序算法是稳 不稳定的排序算法。 定的。否则,称为不稳定的排序算法 定的。否则,称为不稳定的排序算法。
为了方便, 为了方便,r[0]一般不用 一般不用 于存放排序码, 于存放排序码,在一些排序 /*此处还可以定义记录中除排序码外的其它域* /*此处还可以定义记录中除排序码外的其它域*/ 算法中它可以用来作为中间 }recordtype; }recordtype; /*记录类型的定义*/ /*记录类型的定义* 单元存放临时数据。 单元存放临时数据。length 域是待排序的记录个数, 域是待排序的记录个数,它 typedef struct{ 必须不大于MAXSIZE,这样, 必须不大于 ,这样, recordtype r[MAXSIZE+1]; r[MAXSIZE+1 第1~length个记录的排序码 个记录的排序码 int length; length; /*待排序文件中记录的个数*/ /*待排序文件中记录的个数* 分别存于 r[1].key~r[length].key中 中 }table; /*待排序文件类型* /*待排序文件类型*/
最坏情况 : 即初始排序码开始是逆序的情况下,因为当插入 第i个排序码时,该算法内循环while要执行i次条件判 个排序码时,该算法内循环while要执行i 断,循环体要执行i 次,每次要移动1 断,循环体要执行i-l次,每次要移动1个记录,外循 环共执行n 环共执行n-1次,其循环体内不含内循环每次循环要 进行2 进行2次移动操作,所以在最坏情况下,比较次数为 (1+2+…+n)*(n-1),移动次数为 (1+2+…+n)*(n-1),移动次数为 (1+2+2+2+…+n+2)*(n-1)。假设待排序文件中的记录 (1+2+2+2+…+n+2)*(n-1)。假设待排序文件中的记录 以各种排列出现的概率相同,因为当插入第i 以各种排列出现的概率相同,因为当插入第i个排序码 时,该算法内循环while平均约要执行i/2次条件判断, 时,该算法内循环while平均约要执行i/2次条件判断, 循环体要执行(i 循环体要执行(i-l)/2次,外循环共执行n-1次,所以 /2次,外循环共执行n 平均比较次数约为(2+3+…+n)/2*(n-1),平均移动次 平均比较次数约为(2+3+…+n)/2*(n-1),平均移动次 数为(n-1)*(2+1+3+1+…+n+1)/2,也即直接插入排序 数为(n-1)*(2+1+3+1+…+n+1)/2,也即直接插入排序 算法的时间复杂度为O(n 算法的时间复杂度为O(n2)。
第十章 内部排序
共进行n-1趟排序, 共进行n(n-1)/2次比较 T(n)=O(n2)
二、快速排序
• 算法思想:
1. 指定枢轴(通常为第一个记录)
2. 通过一趟排序将以枢轴为中心,把待排记录 分割为独立的两部分,使得左边记录的关键 字小于枢轴值,右边记录的关键字大于枢轴 值
3. 对左右两部分记录序列重复上述过程,依次 类推,直到子序列中只剩下一个记录或不含 记录为止。
27 38 13 49 76 97 65 49 13 27 38 49 65 76 97
具体实现: low=s; high=t; pivotkey=L.r[low].key; 1. 从high开始往前找第一个关键字小于pivotkey的记 录,与枢轴记录交换 2. 从 low 开始往后找第一个关键字大于 pivotkey 的记 录,与枢轴记录交换 3. 重复上述两步直到low= =high为止 4. 此时枢轴记录所在的位置i=low=high
int partition(SqList &L, int low, int high) { //交换顺序表L中的子表L.r[low..high]的记录,枢轴记录到位,并返回其所在位置 //此时在它之前(后)的记录均不大(小)于它 piovtkey=L.r[low].key; //用子表的第一个记录作枢轴记录 while(low<high) //从表的两端交替地向中间扫描 { while(low<high&&L.r[high].key>=piovtkey) - -high; L.r[low] L.r[high]; //将比枢轴记录小的记录交换到低端 while(low<high&&L.r[low].key<=priotkey) ++low; L.r[low] L.r[high]; //将比枢轴记录大的记录交换到高端 } return low; //返回枢轴所在的位置 }//Partition
数据结构第10章内部排序
*
直接插入排序算法描述如下: void InserSort ( SqList &L) { // 对顺序表L作直接插入排序。 for ( i=2; i<=L.length; ++i ) if (LT(L.r[i].key ,L.r[i-1].key)) { L.r[0] = L.r[i]; // 复制为监视哨 for ( j=i-1; LT(L.r[i].key ,L.r[i-1].key) ; --j ) L.r[j+1] = L.r[j]; // 记录后移 L.r[j+1] = L.r[0]; // 插入到正确位置 } } // InsertSort
空间效率: O(1)
当待排序记录的数量n很小时,直接插入排序是一种简便的方法;但当记录数量n很大时,则不宜采用直接插入排序。
时间效率:虽然比较次数大大减少,可惜移动次数并未减少,所以排序效率仍为O(n2) 。
稳定性:稳定
由于插入排序的基本操作是在有序表R[1..i-1]中进行查找和插入的;则可以利用折半查找实现“在R[1..i-1]中查找R[i]的插入位置”,并在适当位置插入,把原来位置上的元素向后顺移。如此实现的插入排序为折半插入排序。
38
例:关键字序列 T=(49,38,65,97, 76, 13, 27, 49*,55, 04),请写出希尔排序的具体实现过程。
0
1
2
3
4
5
6
7
8
9
10
49
38
65
97
76
13
27
49*
55
04
初态:
第1趟 (dk=5)
第2趟 (dk=3)
第3趟 (dk=1)
直接插入排序算法描述如下: void InserSort ( SqList &L) { // 对顺序表L作直接插入排序。 for ( i=2; i<=L.length; ++i ) if (LT(L.r[i].key ,L.r[i-1].key)) { L.r[0] = L.r[i]; // 复制为监视哨 for ( j=i-1; LT(L.r[i].key ,L.r[i-1].key) ; --j ) L.r[j+1] = L.r[j]; // 记录后移 L.r[j+1] = L.r[0]; // 插入到正确位置 } } // InsertSort
空间效率: O(1)
当待排序记录的数量n很小时,直接插入排序是一种简便的方法;但当记录数量n很大时,则不宜采用直接插入排序。
时间效率:虽然比较次数大大减少,可惜移动次数并未减少,所以排序效率仍为O(n2) 。
稳定性:稳定
由于插入排序的基本操作是在有序表R[1..i-1]中进行查找和插入的;则可以利用折半查找实现“在R[1..i-1]中查找R[i]的插入位置”,并在适当位置插入,把原来位置上的元素向后顺移。如此实现的插入排序为折半插入排序。
38
例:关键字序列 T=(49,38,65,97, 76, 13, 27, 49*,55, 04),请写出希尔排序的具体实现过程。
0
1
2
3
4
5
6
7
8
9
10
49
38
65
97
76
13
27
49*
55
04
初态:
第1趟 (dk=5)
第2趟 (dk=3)
第3趟 (dk=1)
数据结构内部排序
13
first
i=5 (30) 39 70 85
13
final
first
i=6 (30) 39 42 70 85
13
final
first
i=7 (30) 39 42 70 85
6 13
final first
i=8 (30) 39 42 70 85 6 13 20
final first
数据结构
第10章 内部排序
i=2 38 (38 49) 65 97 76 13 27
i=3 65 (38 49 65) 97 76 13 27
i=4 97 (38 49 65 97) 76 13 27
i=5 76 (38 49 65 76 97) 13 27 i=6 13 (13 38 49 65 76 97) 27
i=7
(13 2378 3489 6459 6756 7967 ) 2977
while( low <= high ) { mid = (low+high)/2;
if( x < D.r[mid].key ) high = mid -1; else low = mid+1; } for(k = fnal; k >= high+1; k++ ) D.r[k+1] = D.r[k]; D.r[high+1] = L.r[i] ; fianl++; } }//for }//Two_InsertSort
数据结构
基本内容
概述 插入排序 交换排序 选择排序 归并排序 基数排序
各种排序方法的比较
第10章 内部排序
数据结构
概述
数据结构-内部排序
{a[j]
a[j+1]; change=TRUE;}
}
}
时间复杂度:O(n2)
冒泡排序的算法分析
•最好情况:初始排列已经有序,只执行一趟起泡,做 n-1
次关键码比较,不移动对象。
•最坏情形:初始排列逆序,算法要执行n-1趟起泡,第i趟 (1 i n) 做了n- i 次关键码比较,执行了n-i 次对象 交换。此时的比较总次12,89,73排序
第一趟 28,14,12,39,73,89 第二趟 14,12,28,39,73,89 第三趟 12,14,28,39,73,89 第四趟 12,14,28,39,73,89
起泡排序(冒泡排序)(2)
第二趟:对前n-1个记录进行上述同样操作, 其结果使关键字次大的记录安置到第n-1个 记录位置上;
❖ 例:
Dk=3
初始值 28,39,14,12,89,73
第一趟结果 12,39,14,28,89,73
Dk=2
第二趟结果 12,28,14,39,89,73
Dk=1
第三趟结果 12,14,28,39,73,89
希尔排序算法
Void ShellSort(SqList &L,int dlta[ ],int t) //希尔排序. t 为排序趟数,dlta[ ] 为增量序列 { for (k=0;k<t;++k) ShellInsert(L,dlta[k]); }
…… 最后一趟:在该趟排序过程中没有进行过记
录交换的操作
起泡排序(3)
❖ 算法
Void bubble-Sort(int a[ ], int n)
{ for (i=n-1,change=TRUE;i≥1&&change;--i)
第10章 内部排序
待排记录的数据类型: 待排记录的数据类型 #define MAXSIZE 20 typedef struct{ KeyType key; }RedType; typedef struct{ RedType r[MAXSIZE+1]; int length; }SqList; /*待排序文件中记录的个数 待排序文件中记录的个数*/ 待排序文件中记录的个数 /*待排序文件类型 待排序文件类型*/ 待排序文件类型 /*记录类型的定义 记录类型的定义*/ 记录类型的定义 /*文件中记录个数的最大值 文件中记录个数的最大值*/ 文件中记录个数的最大值 typedef int KeyType; /*定义排序码类型为整数类型 定义排序码类型为整数类型*/ 定义排序码类型为整数类型
二、堆排序 ——只需要一个记录大小的辅助空间 只需要一个记录大小的辅助空间, ——只需要一个记录大小的辅助空间,每个待排序 的记录仅占有一个存储空间 堆是一个序列{k 它满足下面的条件: 堆是一个序列{k1,k2,…,kn},它满足下面的条件: 并且k i=1,2,…,n/2 ki≤k2i并且ki≤k2i+1,当i=1,2,…,n/2 小根堆 i=1,2,…,n/2 ki≥k2i并且ki≥k2i+1,当i=1,2,…,n/2 大根堆 k 并且k k 采用顺序方式存储这个序列, 采用顺序方式存储这个序列,就可以将这个序列的每 一个元素k 看成是一棵有n个结点的完全二叉树的第i 一个元素ki看成是一棵有n个结点的完全二叉树的第i 个结点,其中k 是该二叉树的根结点。 个结点,其中k1是该二叉树的根结点。
设待排序的7记录的排序码为{312,126,272,226, 设待排序的7记录的排序码为{312,126,272,226, {312 28,165,123},初始让d=7/2=3 以后每次让d d=7/2=3, 28,165,123},初始让d=7/2=3,以后每次让d缩小 一半, 一半,其排序过程如图所示
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
62 }
62 55 48 35
77
77 } 62 55 48
55
55 77 } 62 55
14
14 14 77 } 62
35
35 35 35 77 }
98
98 98 98 98 返回主目录
H) { 14
35
35
48
55
62
77
98 }
假设待排序记录存放在r[1..n]之中,为了提高效率,我们附设 一个监视哨r[0],使得r[0]始终存放待插入的记录。
返回主目录
下面给出了一个完整的直接插入排序实例。图中 大括号内为当前已排好序的记录子集合。
A) { 48 } B) { 48 62 62 } 35 35 77 77 55 55 14 14 35 35 98 98
C) { 35
D) { 35 E) { 35 F) { 14 G) { 14
48
48 48 35 35
3 8 4 9
1
6 5 3 8 6 5
1 3
2 7 1 3 2 7 4 9
2
3 8
6 5
7 6 9 7
2 7
9 7
7 6
4 9
1
3 8
6 5
(a) 选 出 最 小 关键字13
7 6
∞
2 7
4 9
2
返回主目录
(b) 选 出 次 小 关 键字27
算法分析:
在树型选择排序中,被选中的关键字都是走了 一条由叶子结点到根结点的比较的过程,由于含有n个 叶子节点的完全二叉数的深度为log2n +1,则在树 型选择排序中,每选择一个小关键字需要进行 log2n次比较,因此其时间复杂度为O(nlog2n)。移 动记录次数不超过比较次数,故总的算法时间复杂 度为O(nlog2n)。
返回主目录
/* 确定插入位置l */
}
}
算法分析: 采用折半插入排序法,可减少关键字的比较次 数。每插入一个元素,需要比较的次数最大为折半 判定树的深度,如插入第i个元素时,设i=2j,则需进
行log2i次比较,因此插入n-1个元素的平均关键字的
比较次数为O(nlog2n)。 虽然折半插入排序法与直接插入排序法相比较, 改善了算法中比较次数的数量级,但其并未改变移 动元素的时间耗费,所以折半插入排序的总的时间
第九章 内部排序
9.1 排序的基本概念
9.2 插入类排序
9.3 交换类排序法
9.4 选择类排序法 9.5 归并排序 9.6 分配类排序 9.7 各种排序方法的综合比较
返回主目录
9.1 排序的基本概念 排序:有n个记录的序列{R1,R2,…,Rn},其相应 关键字的序列是{K1,K2, …,Kn },相应的下标 序列为1,2,…, n。通过排序,要求找出当前下
标序列1,2,…, n的一种排列p1,p2, …,pn,
使得相应关键字满足如下的非递减(或非递增)关
系,即:Kp1≤ Kp2≤…≤ Kpn ,这样就得到一个按
关键字有序的记录序列:{Rp1, Rp2, …, Rpn}。
返回主目录
内部排序:整个排序过程完全在内存中进行,称为 内部排序。 外部排序:由于待排序记录数据量太大,内存无 法容纳全部数据,排序需要借助外部存储设备才 能完成,称为外部排序。 稳定排序和不稳定排序:假设Ki=Kj(1≤i≤n, 1≤j≤n,i≠j),若在排序前的序列中Ri领先于 Rj(即i<j),经过排序后得到的序列中Ri仍领先于Rj, 则称所用的排序方法是稳定的 ;反之,当相同关键 字的领先关系在排序过程中发生变化者,则称所用 的排序方法是不稳定的。
x=r[k].key ;i=k ;j=2*i ;finished=FALSE ; while( j<=m && ! finished ) {
void InsSort(RecordType r[],int length)
直 接 插 入 排 序 算 法
/*对记录数组r做直接插入排序,length为数组的长度*/ { for ( i=2 ; i< length ; i++ ) { r[0]=r[i]; j=i-1; /*将待插入记录存放到r[0]中*/ /* 寻找插入位置 */
r[i].key≥r[2i].key并且 r[i].key≥r[2i+1].key(i=1,2, ... n/2 ),满 足这个条件的完全二叉树为堆。
返回主目录
堆中根结点的关键字最大,称为大根堆。反之,如 果这棵完全二叉树中任意结点的关键字大于或者等 于其左孩子和右孩子的关键字(当有左孩子或右孩 子时),对应的堆为小根堆。 一个大根堆和一个小根堆的示例见p242的图9.7所示。
直接插入排序方法是稳定的排序方法。
返回主目录
9.2.2 折半插入排序
void BinSort (RecordType r[],int length) /*对记录数组r进行折半插入排序,length为数组的长度*/ {for ( i=2 ; i<=length ; ++i ) {x= r[i];low=1; high=i-1; while (low<=high ) {mid=(low+high) / 2; if ( x.key< r[mid].key ) high=mid-1; else low=mid+1;} for ( j=i-1 ; j>= low; --j ) r[j+1]= r[j]; r[low]=x; /* 记录依次向后移动 */ /* 插入记录 */
复杂度仍然是O(n2)。
返回主目录
9.2.3 表插入排序
表插入排序是采用链表存储结构进行插入排序的方法。 表插入排序的基本思想是:先在待插入记录之前的有序子链 表中查找应插入位置,然后将待插入记录插入链表。 类型说明如下: typedef int KeyType; typedef struct { KeyType key; OtherType other_data; int next;
选择排序示例见P240的图9.5所示。
返回主目录
简单选择排序的算法描述如下:
void SelectSort(RecordType r[], int length)
/*对记录数组r做简单选择排序,length为数组的长度*/
{ n=length; for ( i=1 ; i<= n-1; ++i) {k=i; for ( j=i+1 ; j<= n ; ++j) if (r[j].key < r[k].key ) k=j; if ( k!=i) { x= r[i]; r[i]= r[k]; r[k]=x; } } } /* SelectSort */
返回主目录
} RecordType1;
表插入排序算法
void SLinkListSort(RecordType1 r[],int length)
{
int n=length; r[0].next=n; r[n].next=0;
for ( i=n-1 ; i>= 1; --i)
{ p= r[0].next; q=0; p>0 && r[p].key< r[i].key ) /* 寻找插入位置 */
返回主目录
具体做法: 把待排序的记录的关键字存放在数组r[1..n]之 中,将r 看成是一棵完全二叉树的顺序表示,每个 结点表示一个记录,第一个记录r[1]作为二叉树的 根,以下各记录r[2...n]依次逐层从左到右顺序排列, 任意结点r[i]的左孩子是r[2i],右孩子是r[2i+1],双亲 是r[i/2 ]。对这棵完全二叉树进行调整,使各结 点的关键字值满足下列条件:
返回主目录
在排序过程中,一般进行两种基本操作:
(1)比较两个关键字的大小;
(2)将记录从一个位置移动到另一个位置。 对于第二种操作,需要采用适当地存储方式,即向 量结构、链表结构以及记录向量与地址向量结合的 表示方法。
我们重点来讨论在向量存储结构上各种排序方法的 实现。
返回主目录
9.2 插入类排序
while(
{q=p;p= r[p].next; }
r[q].next=i; } r[i].next=p; /* 修改指针,完成插入 */
返回主目录
} /*
SLinkListSort */
算法分析:
每插入一条记录,最大的比较次数等于已排好序的 记录个数,即当前循环链表长度。总的比较次数为: ∑i=
i=1 n-1
n(n-1) 2
≈
n2 2
因此表插入排序的时间复杂度为T(n)=O(n2)。
返回主目录
9.4 选择类排序法
选择排序的基本思想是:每一趟在n-i+1(i=1, 2,…n-1)个记录中选取关键字最小的记录作为有 序序列中第i个记录。 9.4.1 简单选择排序
基本思想:第i趟简单选择排序是指通过n-i次关键字 的比较,从n-i+1个记录中选出关键字最小的记录, 并和第i个记录进行交换。共需进行i-1趟比较,直到 所有记录排序完成为止。
返回主目录
9.4.3 堆排序
堆排序是对树型选择排序的进一步改进。采用 堆排序时,只需要一个记录大小的辅助空间。堆排 序是在排序过程中,将向量中存储的数据看成一棵 完全二叉树,利用完全二叉树中双亲结点和孩子结 点之间的内在关系来选择关键字最小的记录,即待 排序记录仍采用向量数组方式存储,并非采用树的 存储结构,而仅仅是采用完全二叉树的顺序结构的 特征进行分析而已。
while (r[0].key< r[j].key )
{r[j+1]= r[j]; j=j-1;}