第10章内排序
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
最坏情况 : 即初始排序码开始是逆序的情况下,因为当插入 第i个排序码时,该算法内循环while要执行i次条件判 断,循环体要执行i-l次,每次要移动1个记录,外循 环共执行n-1次,其循环体内不含内循环每次循环要 进行2次移动操作,所以在最坏情况下,比较次数为 (1+2+…+n)*(n-1),移动次数为 (1+2+2+2+…+n+2)*(n-1)。假设待排序文件中的记录 以各种排列出现的概率相同,因为当插入第i个排序码 时,该算法内循环while平均约要执行i/2次条件判断, 循环体要执行(i-l)/2次,外循环共执行n-1次,所以 平均比较次数约为(2+3+…+n)/2*(n-1),平均移动次 数为(n-1)*(2+1+3+1+…+n+1)/2,也即直接插入排序 算法的时间复杂度为O(n2)。
采用顺序方式存储这个序列,就可以将这个序列 的每一个元素ki看成是一颗有n个结点的完全二叉树的 第i个结点,其中k1是该二叉树的根结点。
把堆对应的一维数组(即该序列的顺序存储结构)看 作一棵完全二叉树的顺序存储,那么堆的特征可解释为, 完全二叉树中任一分支结点的值都小于或等于它的左、 右儿子结点的值。堆的元素序列中的第一个元素k1,, 即对应的完全二叉树根结点的值是所有元素中值最小的。 堆排序方法就是利用这一点来选择最小元素。
10.3.1直接选择排序
苜先从所有n个待排序记录中选择排序码最小的记 录,将该记录与第1个记录交换,再从剩下的n-l个记录 中选出排序码最小的记录和第2个记录交换。重复这样 的操作直到剩下2个记录时,再从中选出排序码最小的 记录和第n-1个记录交换。剩下的那1个记录肯定是排序 码最大的记录,这样排序即告完成。
为了方便,r[0]一般不用 于存放排序码,在一些排序 /*此处还可以定义记录中除排序码外的其它域*/ 算法中它可以用来作为中间 }recordtype; /*记录类型的定义 */ 单元存放临时数据。 length 域是待排序的记录个数,它 typedef struct{ 必须不大于MAXSIZE,这样, recordtype r[MAXSIZE+1]; 第1~length个记录的排序码 int length; /*待排序文件中记录的个数 */ 分别存于 r[1].key~r[length].key 中 }table; /*待排序文件类型 */
10.2 插入排序
插入排序的基本方法是: 将待排序文件中的记录, 逐个地按其排序码值的 大小插入到目前已经排好序的若干个记录组成的文件中 的适当位置,并保持新文件有序。
10.2.1 直接插入排序
直接插入排序算法的思路是:初始可认为文件中的 第1个记录己排好序,然后将第2个到第n个记录依次插 入已排序的记录组成的文件中。在对第i个记录Ri进行 插入时,R1,R2,…,Ri-1已排序,将记录Ri的排序码 keyi与已经排好序的排序码从右向左依次比较,找到Ri 应插入的位置,将该位置以后直到Ri-1各记录顺序后移, 空出该位置让Ri插入。
void simpleselectsort(table *tab)
{ int i,j,k;
for(i=1;i<=tab->length-1;i++) { k=i; /*记下当前最小元素的位置*/
for(j=i+1;j<=tab->length;j++) /*向右查找更小的元素*/
if(tab->r[j].key<tab->r[k].key) k=j; /*修改当前最小元素的位置*/ if(k!=i) /*如果第i次选到的最小元素位置k不等于i,则将第k、i个元素交换*/ { tab->r[0].key=tab->r[k].key; /*以第0个元素作为中间单元进行交换*/ tab->r[k].key=tab->r[i].key; tab->r[i].key=tab->r[0].key; } } } 算法10.5 直接选择排序算法
Shell排序一般而言要比直接插入排序快,但要给 出时间复杂度的分析相当难。 Shell插入排序算法是不稳定的排序算法。
ห้องสมุดไป่ตู้
10.3 选择排序
选择排序的基本思想是:每次从待排序的文件中选 择出排序码最小的记录,将该记录放于已排序文件的最 后一个位置,直到已排序文件记录个数等于初始待排序 文件的记录个数为止。
/*
堆排序算法
*/
void heapsort(table *tab)
{ int i; for(i=tab->length/2;i>=1;i--) sift(tab,i,tab->length); /*对所有元素建堆*/
for(i=tab->length;i>=2;i--) /* i表示当前堆的大小,即等待排序的元素的个数*/
10.2.2 Shell插入排序
Shell插入排序:对有n个记录进行排序,首先取1 个整数d<n,将这n个记录分成d组,所有位置相差为d 的倍数的记录分在同一组,在每组中使用直接插入排 序进行组内排序,然后缩小d的值,重复进行分组和组 内排序,一直到d=1结束。 设待排序的10个记录的关键字为{59,48,59,75, 98,86,23,37, 65,14},初始让d=10/2=5,以 后每次让d缩小一半.
按排序过程中使用到的存储介质来分,可以将排 序分成两大类:内排序和外排序。 内排序是指在排序过程中所有数据均放在内存中 处理,不需要使用外存的排序方法。而对于数据量很 大的文件,在内存不足的情况下,则还需要使用外存, 这种排序方法称为外排序。 排序码相同的记录,若经过排序后,这些记录 仍保持原来的相对次序不变,称这个排序算法是稳 定的。否则,称为不稳定的排序算法。
哨兵 初始 ( )
i=2 (48) i=3 (75)
关键字 [59] 48 75 98 86 23 37 59
[48 59] 75 98 86 23 37 59 [48 59 75] 98 86 23 37 59
i=4 (107)
i=5 (86) i=6 (23)
[48 59 75 98] 86 23 37 59
10.3.2 堆排序
为了既要保存中间比较结果,减少后面的比较次 数,又不占用大量的附加存储空间,使排序算法具有 较好的性能,Willioms和Floyd在1964年提出的称为堆 排序的算法实现了这一想法。 堆是一个序列{k1,k2,…,kn},它满足下面的条件:
ki≤k2i并且ki≤k2i+1,当i=1,2,…,n/2
直接选择排序算法执行过程如图所示
初始序列 第一次排序 第二次排序 第三次排序 第四次排序 第五次排序 第六次排序 第七次排序 最后结果
59 48 75 98 59 23 37 86 [23] 48 75 98 59 59 37 86 [23 37] 75 98 59 59 48 86 [23 37 48] 98 59 59 75 86 [23 37 48 59] 98 59 75 86 [23 37 48 59 59] 98 75 86 [23 37 48 59 59 75] 98 86 [23 37 48 59 59 75 86] 98 [23 37 48 59 59 75 86 98]
图10.2 直接插入排序算法执行过程示意图
直接插入排序算法是稳定的排序算法。 直接插入排序算法执行时间的分析: 最好的情况 : 即初始排序码开始就是有序的情况下,因为当插入第i 个排序码时,该算法内循环while只进行一次条件判断 而不执行循环体,外循环共执行n-1次,其循环体内不 含内循环每次循环要进行2次移动操作,所以在最好情 况下,直接插入排序算法的比较次数为(n-1)次,移动 次数为2*(n-1)次。
i=2: (126)
i=3: (272)
i=4: (226) i=5: (28) i=6: (165) i=7: (123)
[126,272,312],226,28,165,123
[126,226,272,312],28,165,123 [28,126,226,272,312],165,123 [28,126,165,226,272,312],123 [28,123,126,165,226,272,312]
{ tab->r[0].key=tab->r[i].key; tab->r[i].key=tab->r[1].key; tab->r[1].key=tab->r[0].key; /*上述3条语句为将堆中最小元素和最后一个元素交换*/ sift(tab,1,i-1); } } 算法10.7 堆排序算法
第10章 内排序
排序是数据处理过程中经常使用的一种重要的运 算,排序的方法有很多种,本章主要讨论内排序的各 种算法,并对每个排序算法的时间和空间复杂性以及 算法的稳定性等进行了讨论。
10.1 排序的基本概念
假设一个文件是由n个记录R1,R2,…,Rn组成, 所谓排序就是以记录中某个(或几个)字段值不减(或 不增)的次序将这n个记录重新排列,称该字段为排 序码。能唯一标识一个记录的字段称为关键码,关 键码可以作为排序码,但排序码不一定要是关键码。
if(tab->r[0].key<=tab->r[j].key) finished=1;
else } tab->r[i].key=tab->r[0].key; } 算法10.6 筛选算法 { tab->r[i].key=tab->r[j].key; i=j;j=2*j; }
通过筛选算法,可以将一个任意的排序码序列建 成一个堆,堆的第1个元素,即完全二叉树的根结点的 值就是排序码中最小的。将选出的最小排序码从堆中删 除,对剩余的部分重新建堆,可以继续选出其中的最小 者,直到剩余1个元素排序即告结束。
建堆过程如下图所示
/*
筛选算法
*/
void sift(table *tab,int k,int m)
{ int i,j,finished; i=k;j=2*i;tab->r[0].key=tab->r[k].key;finished=0; while((j<=m)&&(!finished)) { if((j<m)&&(tab->r[j+1].key<tab->r[j].key)) j++;
[48 59 75 86 98] 23 37 59 [23 48 59 75 86 98] 37 59
i=7 (37) i=8 (59)
[23 37 48 59 75 86 98] 59 [23 37 48 59 59 75 86 98]
设待排序的 7 记录的排序码为 {312 , 126, 272, 226 ,28, 165 , 123},直接插入排序算法的执行过程如图10.2所示。 哨兵 排序码 [] 312,126,272,226,28,165,123 初始 () [312],126,272,226,28,165,123 [126,312],272,226,28,165,123
评价排序算法优劣的标准 : 首先考虑算法执行所需的时间,这主要是用执行 过程中的比较次数和移动次数来度量;
其次考虑算法执行所需要的附加空间。
当然,保证算法的正确性是不言而喻的,可读性等 也是要考虑的因素。
排序算法如未作特别的说明,使用的有关定义如下 :
/*常见排序算法的头文件,文件名table.h*/ #define MAXSIZE 100 typedef int keytype; typedef struct{ keytype key; /*文件中记录个数的最大值*/ /*定义排序码类型为整数类型*/
一个序列和相 应的完全二叉 树: 这个序列不是一个 堆。堆排序的关键 问题是如何将待排 序记录的排序码建 成一个堆。
从图可以看到,在n=9个 元素组成的序列和它相对 调整是从序号为1的 应的完全二叉树中,序号 结点处开始直到4(=n/2), 为 9,8,7,64 , 5的结点 还是从序号为 的结点开 没有儿子,以它们为根的 始,然后对序号为3,2, 子树显然满足堆的条件。 1的结点依次进行呢? 因为在有n=9个结点的完 全二叉树中,第4=n/2, 3,2,1应该从第 个结点都有儿子,一 4个结点开 般情况下,以它们为根结 始,依次使以第4个结点 点的子树不会满足堆的条 为根的子树变成堆,直到 件,所以,要使该序列变 以第1个结点为根的整个 换成一个堆,必须从这些 完全二叉树具有堆的性质, 结点处进行调整。 则建堆完成。