各种排序方法的比较与讨论.docx
- 文档编号:9404313
- 上传时间:2023-02-04
- 格式:DOCX
- 页数:12
- 大小:20.98KB
各种排序方法的比较与讨论.docx
《各种排序方法的比较与讨论.docx》由会员分享,可在线阅读,更多相关《各种排序方法的比较与讨论.docx(12页珍藏版)》请在冰豆网上搜索。
各种排序方法的比较与讨论
各种排序方法的比较与讨论
现在流行的排序有:
选择排序、直接插入排序、冒泡排序、希尔排序、快速排序、堆排序、归并排序、基数排序。
一、选择排序
1.基本思想:
每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。
2.排序过程:
【示例】:
初始关键字[4938659776132749]
第一趟排序后13[38659776492749]
第二趟排序后1327[659776493849]
第三趟排序后132738[9776496549]
第四趟排序后13273849[49976576]
第五趟排序后1327384949[979776]
第六趟排序后132738494976[7697]
第七趟排序后13273849497676[97]
最后排序结果1327384949767697
3.
voidselectionSort(Type*arr,longlen)
{
longi=0,j=0;/*iteratorvalue*/
longmaxPos;
assertF(arr!
=NULL,"InInsertSortsort,arrisNULL\n");
for(i=len-1;i>=1;i--)
{
maxPos=i;
for(j=0;j
if(arr[maxPos]
if(maxPos!
=i)swapArrData(arr,maxPos,i);
}
}
选择排序法的第一层循环从起始元素开始选到倒数第二个元素,主要是在每次进入的第二层循环之前,将外层循环的下标赋值给临时变量,接下来的第二层循环中,如果发现有比这个最小位置处的元素更小的元素,则将那个更小的元素的下标赋给临时变量,最后,在二层循环退出后,如果临时变量改变,则说明,有比当前外层循环位置更小的元素,需要将这两个元素交换.
二.直接插入排序
插入排序(InsertionSort)的基本思想是:
每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子文件中的适当位置,直到全部记录插入完成为止。
直接插入排序
直接插入排序(StraightInsertionSort):
将一个记录插入到排好序的有序表中,从而得到一个新的、记录数增1的有序表。
直接插入排序算法
哨兵(监视哨)有两个作用:
一是作为临变量存放R[i](当前要进行比较的关键字)的副本;二是在查找循环中用来监视下标变量j是否越界。
当文件的初始状态不同时,直接插入排序所耗费的时间是有很大差异的。
最好情况是文件初态为正序,此时算法的时间复杂度为O(n),最坏情况是文件初态为反序,相应的时间复杂度为O(n2),算法的平均时间复杂度是O(n2)。
算法的辅助空间复杂度是O
(1),是一个就地排序。
直接插入排序是稳定的排序方法。
三.冒泡排序
[算法思想]:
将被排序的记录数组R[1..n]垂直排列,每个记录R[i]看作是重量为R[i].key的气泡。
根据轻气泡不能在重气泡之下的原则,从下往上扫描数组R:
凡扫描到违反本原则的轻气泡,就使其向上"飘浮"。
如此反复进行,直到最后任何两个气泡都是轻者在上,重者在下为止。
[算法]:
voidBubbleSort(SeqListR){
//R(l..n)是待排序的文件,采用自下向上扫描,对R做冒泡排序
inti,j;
Booleanexchange;//交换标志
for(i=1;i
exchange=FALSE;//本趟排序开始前,交换标志应为假
for(j=n-1;j>=i;j--)//对当前无序区R[i..n]自下向上扫描
if(R[j+1].key
R[0]=R[j+1];//R[0]不是哨兵,仅做暂存单元
R[j+1]=R[j];
R[j]=R[0];
exchange=TRUE;//发生了交换,故将交换标志置为真
}
if(!
exchange)return;//本趟排序未发生交换,提前终止算法
}//endfor(外循环)
}//BubbleSort
[分析]:
起泡排序的结束条件为:
最后一趟没有进行“交换”。
从起泡排序的过程可见,起泡排序是一个增加有序序列长度的过程,也是一个缩小无序序列长度的过程,每经过一趟起泡,无序序列的长度只缩小1。
[算法思想]:
将被排序的记录数组R[1..n]垂直排列,每个记录R[i]看作是重量为R[i].key的气泡。
根据轻气泡不能在重气泡之下的原则,从下往上扫描数组R:
凡扫描到违反本原则的轻气泡,就使其向上"飘浮"。
如此反复进行,直到最后任何两个气泡都是轻者在上,重者在下为止。
[算法]:
voidBubbleSort(SeqListR){
//R(l..n)是待排序的文件,采用自下向上扫描,对R做冒泡排序
inti,j;
Booleanexchange;//交换标志
for(i=1;i
exchange=FALSE;//本趟排序开始前,交换标志应为假
for(j=n-1;j>=i;j--)//对当前无序区R[i..n]自下向上扫描
if(R[j+1].key
R[0]=R[j+1];//R[0]不是哨兵,仅做暂存单元
R[j+1]=R[j];
R[j]=R[0];
exchange=TRUE;//发生了交换,故将交换标志置为真
}
if(!
exchange)return;//本趟排序未发生交换,提前终止算法
}//endfor(外循环)
}//BubbleSort
[分析]:
起泡排序的结束条件为:
最后一趟没有进行“交换”。
从起泡排序的过程可见,起泡排序是一个增加有序序列长度的过程,也是一个缩小无序序列长度的过程,每经过一趟起泡,无序序列的长度只缩小1。
四.希尔排序
基本思想:
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。
所有距离为dl的倍数的记录放在同一个组中。
先在各组内进行直接插人排序;然后,取第二个增量d2
该方法实质上是一种分组插入方法。
给定实例的shell排序的排序过程
假设待排序文件有10个记录,其关键字分别是:
49,38,65,97,76,13,27,49,55,04。
增量序列的取值依次为:
5,3,1
Shell排序的算法实现
1.不设监视哨的算法描述
voidShellPass(SeqListR,intd)
{//希尔排序中的一趟排序,d为当前增量
for(i=d+1;i<=n;i++)//将R[d+1..n]分别插入各组当前的有序区
if(R[i].key
R[0]=R[i];j=i-d;//R[0]只是暂存单元,不是哨兵
do{//查找R[i]的插入位置
R[j+d];=R[j];//后移记录
j=j-d;//查找前一记录
}while(j>0&&R[0].key
R[j+d]=R[0];//插入R[i]到正确的位置上
}//endif
}//ShellPass
voidShellSort(SeqListR)
{
intincrement=n;//增量初值,不妨设n>0
do{
increment=increment/3+1;//求下一增量
ShellPass(R,increment);//一趟增量为increment的Shell插入排序
}while(increment>1)
}//ShellSort
注意:
当增量d=1时,ShellPass和InsertSort基本一致,只是由于没有哨兵而在内循环中增加了一个循环判定条件"j>0",以防下标越界。
2.设监视哨的shell排序算法
算法分析
1.增量序列的选择
Shell排序的执行时间依赖于增量序列。
好的增量序列的共同特征:
①最后一个增量必须为1;
②应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
有人通过大量的实验,给出了目前较好的结果:
当n较大时,比较和移动的次数约在nl.25到1.6n1.25之间。
2.Shell排序的时间性能优于直接插入排序
希尔排序的时间性能优于直接插入排序的原因:
①当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
②当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。
③在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
因此,希尔排序在效率上较直接插人排序有较大的改进。
3.稳定性
希尔排序是不稳定的。
参见上述实例,该例中两个相同关键字49在排序前后的相对次序发生了变化。
五.堆排序
1、堆排序定义
n个关键字序列Kl,K2,…,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质):
(1)ki≤K2i且ki≤K2i+1或
(2)Ki≥K2i且ki≥K2i+1(1≤i≤)
若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:
树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
【例】关键字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分别满足堆性质
(1)和
(2),故它们均是堆,其对应的完全二叉树分别如小根堆示例和大根堆示例所示。
2、大根堆和小根堆
根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆。
根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆。
注意:
①堆中任一子树亦是堆。
②以上讨论的堆实际上是二叉堆(BinaryHeap),类似地可定义k叉堆。
3、堆排序特点
堆排序(HeapSort)是一树形选择排序。
堆排序的特点是:
在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系【参见二叉树的顺序存储结构】,在当前无序区中选择关键字最大(或最小)的记录。
4、堆排序与直接插入排序的区别
直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。
事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。
堆排序可通过树形结构保存部分比较结果,可减少比较次数。
5、堆排序
堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
(1)用大根堆排序的基本思想
①先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
②再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。
然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
……
直到无序区只有一个元素为止。
(2)大根堆排序算法的基本操作:
①初始化操作:
将R[1..n]构造为初始堆;
②每一趟排序的基本操作:
将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。
注意:
①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。
堆排序和直接选择排序相反:
在任何时刻,堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止。
(3)堆排序的算法:
voidHeapSort(SeqIAstR)
{//对R[1..n]进行堆排序,不妨用R[0]做暂存单元
inti;
BuildHeap(R);//将R[1-n]建成初始堆
for(i=n;i>1;i--){//对当前无序区R[1..i]进行堆排序,共做n-1趟。
R[0]=R[1];R[1]=R[i];R[i]=R[0];//将堆顶和堆中最后一个记录交换
Heapify(R,1,i-1);//将R[1..i-1]重新调整为堆,仅有R[1]可能违反堆性质
}//endfor
}//HeapSort
各种排序方法的选择选择合适的排序方法应考虑的因素:
①待排序的记录数目n;
②记录的大小(规模);
③的结构及其初始状态;
④对稳定性的要求;
⑤语言工具的条件;
⑥结构;
⑦时间和辅助空间复杂度等。
各种排序方法的选择
①就平均时间性能而言,快速排序最佳,其所需时间最省,但快速排序在最坏情况下的时间性能不如堆排序和归并排序。
当n较大时,归并排序较堆排序省,但归并排序所需的辅助空间最大。
②简单排序方法中,直接插入排序最简单,当待排序的结点已按键值“基本有序”且n较小时,则应采用直接插入排序或冒泡排序,直接插入排序比冒泡排序更快些,因此经常将直接插入排序和其他的排序方法结合在一起使用。
③当n很大且键值位数较小时,采用基数排序较好;而当键值的最高位分布较均匀时,可先按其最高位将待排序结点分成若干子表,而后对各子表进行直接插入排序。
④从方法的稳定性来比较,直接插入排序、冒泡排序、归并排序和基数排序是稳定的排序方法;而直接选择排序、希尔排序、堆排序和快速排序都是不稳定的排序方法。
我们从以下几个方面对本章介绍过得内排序方法进行比较:
(1)时间复杂性,
(2)空间
复杂性,(3)稳定性。
三种简单的排序方法:
直接插入、直接选择、冒泡排序时间复杂性均为O(n2)。
堆排
序、快速排序和归并排序这三种排序方法的平均情况的时间复杂性是O(nlogn)。
希尔排序介
于O(n2)与O(nlogn)之间。
但在最坏情况下,快速排序的时间复杂性为O(n2)。
最坏情况
对其它排序方法影响不大。
从空间复杂性看,归并排序的空间复杂性为O(n)。
快速排序的空间复杂性为O(logn),
但快速排序在最坏情况下的空间复杂性为O(n)。
其它排序方法的空间复杂性为O
(1)。
各种排序方法中,直接插入、冒泡和归并排序是稳定的,直接选择、快速排序和堆排序
是不稳定的。
除了基数排序以外,本章介绍的所有的排序算法都决定于两个关键字的直接比较。
例如,
冒泡排序不断地比较相邻记录的关键字值,直到升到正确的位置。
相反地,基数排序并没有
直接比较关键字的值,而是取决于关键字值中各位数字的值。
实验数据表明,基于比较的排
序算法是较好的。
对于任何基于比较的的排序算法来说,在最坏情况下能达到的最好的时间复杂性为
O(nlogn)。
综上所述,在本章讨论的排序方法中,没有哪一种是绝对最优的。
有的适用于n较少的
情况,有的适用于n较大的情况,有得适用于排序记录基本有序的情况等等。
因此,在实际
应用时,需要根据不同的情况进行选择,甚至可将多种排序方法结合起来。
简单排序一般只用于n较小的情况。
当序列中的记录“基本有序”时,直接插入排序是最佳的排序方法,常与快速排序、归并排序等其它排序方法结合使用。
快速排序、堆排序和归并排序的平均时间复杂度均为O(nlogn),但实验结果表明,就平均时间性能而言,快速排序是所有排序方法中最好的。
遗憾的是,快速排序在最坏情况下的时间性能为O(n2)。
堆排序和归并排序的最坏时间复杂度仍为O(nlogn),当n较大时,归并排序的时间性能优于堆排序,但它所需的辅助空间最多。
基数排序的时间复杂度可以写成O(d*n)。
因此,它最适用于n值很大而关键字的位数d较小的序列。
从排序的稳定性上来看,基数排序是稳定的,除了简单选择排序,其它各种简单排序法也是稳定的。
然而,快速排序、堆排序、希尔排序等时间性能较好的排序方法,以及简单选择排序都是不稳定的。
多数情况下,排序是按记录的主关键字进行的,此时不用考虑排序方法的稳定性。
如果排序是按记录的次关键字进行的,则应充分考虑排序方法的稳定性。
综上所述,每一种排序方法各有特点,没有哪一种方法是绝对最优的。
我们应根据具体情况选择合适的排序方法,也可以将多种方法结合起来
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 各种 排序 方法 比较 讨论