各种排序算法性能比拼吴元平(数学与应用数学,07121011)摘要:排序算法是数据结构这门课程核心内容之一,它是计算机程序设计、数据库、操作系统、编译原理及人工智能等的重要基础,广泛的应用于信息学、系统工程等各种领域。
学习排序算法是为了将实际问题中涉及的对象在计算机中对它们进行处理。
我将利用Visual Studio 2012开发程序对各种算法进行测试。
该测试系统可以通过操作把数据结构中的主要排序常见的排序算法(直接插入排序、希尔排序、直接选择排序、冒泡排序、快速排序、堆排序、归并排序)的性能用时间的长短表现出来。
引言排序是计算机程序设计中的一种重要操作。
它的功能是将一个数据元素的任意序列,重新排列成一个按关键字有序的序列。
排序算法是在整个计算机科学与技术领域上广泛被使用的术语。
排序算法是计算机程序设计、数据库、操作系统、编译原理及人工智能等的重要基础,广泛的应用于信息学、系统工程等各种领域。
排序是计算机科学中最重要的研究问题之一, 它在计算机图形、计算机辅助设计、机器人、模式识别及统计学等领域具有广泛的应用。
由于它固有的理论上的重要性,其功能是将一个数据元素的任意序列重新排列成一个按关键字有序的序列。
随着计算机技术的日益发展,其应用早已不局限于简单的数值运算。
而涉及到问题的分析、数据结构框架的设计以及插入、删除、排序、查找等复杂的非数值处理和操作。
排序算法的学习就是为以后利用计算机资源高效地开发非数值处理的计算机程序打下坚实的理论、方法和技术基础。
需求分析各种排序算法时间性能的比较一、需求描述对各种排序方法(直接插入排序、希尔排序、冒泡排序、快速排序、直接选择排序、堆排序和归并排序)的时间性能进行比较。
二、要求1.设计并实现上述各种排序算法;2.产生正序和逆序的初始排列分别调用上述排序算法,并比较时间性能;3.产生随机的初始排列分别调用上述排序算法,并比较时间性能。
三、设计思想上述各种排序方法都是基于比较的内排序,其时间主要消耗在排序过程中进行的记录的比较次数和移动次数,因此,统计在相同数据状态下不同排序算法的比较次数和移动次数,即可实现比较各种排序算法的目的。
设计一、直接插入排序1.原理假设待排序的n个记录{R0,R1,…,Rn}顺序存放在数组中,直接插入法在插入记录Ri(i=1,2,…,n-1)时,记录被划分为两个区间[R0,Ri-1]和[Ri+1,Rn-1],其中,前一个子区间已经排好序,后一个子区间是当前未排序的部分,将关键码Ki与Ki-1Ki-2,…,K0依次比较,找出应该插入的位置,将记录Ri插,然后将剩下的i-1个元素按关键词大小依次插入该有序序列,没插入一个元素后依然保持该序列有序,经过i-1趟排序后即成为有序序列。
每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子文件中的适当位置,直到全部记录插入完成为止。
2.时间复杂度分析直接插入排序算法必须进行n-1趟。
最好情况下,即初始序列有序,执行n-1趟,但每一趟只比较一次,移动元素两次,总的比较次数是(n-1),移动元素次数是2(n-1)。
因此最好情况下的时间复杂度就是O(n)。
最坏情况(非递增)下,最多比较i次,因此需要的比较次数是:所以,时间复杂度为O(n2)。
二、Shell排序1.原理Shell排序又称缩小增量排序,Shell排序法是以创建者Donald Shell的名字命名的.Shell排序法是对相邻指定距离(称为间隔)的元素进行比较,已知到使用当前间隔进行比较的元素都按顺序排序为止.Shell把间隔缩小一半,然后继续处理,当间隔最终变为1,并且不再出现变化时,Shell排序也就完成了其处理过程.先取一个整数d1<n,把全部记录分成d1个组,所有距离为d1倍数的记录放在一组中,先在各组内排序;然后去d2<d1重复上诉分组和排序工作;直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入,直到dt=1,即所有记录放在一组中为止.2.时间复杂度分析希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。
所以,希尔排序的时间复杂度会比o(n2)好一些。
由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell 排序是不稳定的。
所以希尔排序是不稳定的,其时间复杂度为o(n2)。
三、直接选择排序1.原理待排序的一组数据元素中,选出最小的一个数据元素与第一个位置的数据元素交换;然后在剩下的数据元素当中再找最小的与第二个位置的数据元素交换,循环到只剩下最后一个数据元素为止,依次类推直到所有记录。
第一趟第n 个记录中找出关键码最小的记录与第n个记录交换;第二趟,从第二个记录开始的,2 -1个记录中再选出关键码最小的记录与第二个记录交换;如此,第i 趟,则从第i个记录开始的n - i + l个记录中选出关键码最小的记录与第i 个记录交换,直到所有记录排好序。
2.时间复杂度分析该算法运行时间与元素的初始排列无关。
不论初始排列如何,该算法都必须执行n-1趟,每趟执行n-i-1次关键字的比较,这样总的比较次数为:所以,简单选择排序的最好、最坏和平均情况的时间复杂度都为O(n2)。
四、冒泡排序1.原理在每一趟冒泡排序中,依次比较相邻的两个关键字大小,若为反序咋交换。
经过一趟起泡后,关键词大的必须移到最后。
按照这种方式进行第一趟在序列(I[0]~I[n-1])中从前往后进行两个相邻元素的比较,若后者小,则交换,比较n-1次;第一趟排序结束,最大元素被交换到I[n-1]中,下一趟只需在子序列(I[0]~I[n-2])中进行;如果在某一趟排序中未交换元素,则不再进行下一趟排序。
将被排序的记录数组J[1..n]垂直排列,每个记录J[i]看作是重量为J[i].key的气泡。
根据轻气泡不能在重气泡之下的原则,从下往上扫描数组R:凡扫描到违反本原则的轻气泡,就使其向上"飘浮"。
如此反复进行,直到最后任何两个气泡都是轻者在上,重者在下为止,最后可完成。
2.时间复杂度分析当原始数据正向有序时,冒泡排序出现最好情况。
此时,只需进行一趟排序,作n-1次关键字比较,因此最好情况下的时间复杂度是O(n)。
当原始数据反向有序时,冒泡排序出现最坏情况。
此时,需进行n-1趟排序,第i趟作(n-i)次关键字间的比较,并且需执行(n-i)次元素交换,所以,比较次数为:因此,最坏情况下的时间复杂度为O(n2)。
五、快速排序1.原理首先我们选择一个中间值middle (程序中我们可使用数组中间值),把比中问值小的放在其左边,比中问值大的放在其右边。
由于这个排序算法较复杂,我们先给出其进行一次排序的程序框架。
在待排序的个记录中任意取一个记录(通常取第一个记录)为区分标准,把所有小于该排序的记录移到左边,把所有大于该排序码的记录移到右边,中级放所选记录,为一趟快速排序。
然后对前后两个子序列分别重新上述过程,直到所有记录都排好序。
对任意给定的序列中某个元素,经过一趟排序后,将原序列分割成两个子序列(R p(0),R p(1),…,R p(s-1))和(R p(s+1),R p(s+2),…,R p(n-1)),其中前一个子序列中的所有元素的关键词均小于或等于该元素的关键词值K p(s),后一个子序列中元素的关键词均大于或等于K p(s)。
称该元素R p(s)为分割元素,子序列(R p(0),R p(1),…,R p(s-1))为其低端序列,(R p(0),R p(1),…,R p(s-1))为其高端序列。
很明显,以后只需对低端和高端序列分别进行快速排序,直到子序列为空或只有一个元素时结束,最后得到有序序列。
总之,每趟使表的第一个元素放在适当位置,将表两分,再对子表进行同样的递归划分,直至划分的子表长度为1。
2.时间复杂度分析如果每一次分划操作后,左、右两个子序列的长度基本相等,则快速排序的效率最高,其最好情况时间复杂度为O(nlog2n);反之,如果每次分划操作所产生的两个子序列,其中之一为空序列,此时,快速排序效率最低,其最坏情况时间复杂度为O(n2)。
如果选择左边第一个元素为主元,则快速排序的最坏情况发生在原始序列正向有序或反向有序时。
快速排序的平均情况时间复杂度为O(nlog2n)。
六、堆排序1.原理堆排序思想很简单,就是每次把关键词调整为堆,取出堆顶元素与堆中最后一个元素交换,同时令堆得大小减一,把剩余的一些元素重新调整为堆,重复此过程,直到堆中只剩一个元素,n个关键字序列kl , k2 ,…,kn称为堆,当且仅当该序列满足如下性质(简称为堆性质): ( l) ki<= k2i 且ki<=k2i+1或(2)ki>= k2i 且ki>=k2i+1。
若将此序列所存储的向量R [1…n]看作是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆。
根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆。
2.时间复杂度分析堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。
堆排序的最坏时间复杂度为O(nlogn)。
堆排序的平均性能较接近于最坏性能。
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是不稳定的,算法时间复杂度O(nlogn)。
程序运行结果以及结果分析一、下图是对随机生成的10000个数进行排序,可以看出快速排序法耗时最少而直接插入排序耗时最多,堆排序和快速排序差不多。
二、下图是对20000个随机数进行的排序,可以看出得出了和上述一样的结果。
对程序结果的评价经过比较我们发现,当规模不断增加时,各种算法之间的差别是很大的。
这七种算法中,快速排序耗时是最少的。
也是最快的一种排序方法。
堆排序和快速排序差不多,属于同一个数量级。
直接选择排序是耗时最多的。
通过这次作业我学到了很多东西:①巩固和加深了对数据结构的理解,提高综合运用本课程所学知识的能力。
②通过自己编写的程序对各种排序性能的比较让我更深入理解了他们的应用。
参考文献[1]严蔚敏,吴伟民,《数据结构(C语言版)》,清华大学出版社2007附录#include<stdlib.h>#include<stdio.h>#include<time.h>#define SWAP(x,y) {int t;t=x; x=y; y=t;}#define N 30000void nixu(int a[],int b[])// 反序{int i;for(i=0;i<N;i++){b[N-1-i]=a[i];}}void popo(int a[],int len)/*冒泡排序*/{int length=len;int i=0;int j=0;for(;i<len;i++){for(;j<length;j++){if(a[j]>a[j+1]){int temp=a[j];a[j]=a[j+1];a[j+1]=temp;}}length--;j=0;}}void select(int array[],int n)//选择排序{int i,j,t,k;for(i=0;i<n-1;i++){k=i;for(j=i+1;j<n;j++)if(array[j]<array[k])k=j; //k存放一轮中的最小数的下标; t=array[i];array[i]=array[k];array[k]=t;}}void Swap(int *a,int *b){int temp;temp = *a;*a = *b;*b = temp;}void InsertSort(int data[],int length)//直接插入排序{int i = 0;int j = 0;for(i = 1;i < length;++i){for(j = i;j > 0;--j){if(data[j] < data[j - 1]){Swap(&data[j], &data[j - 1]);}else{break;}}}}void quickSort(int a[],int left,int right) /*快速排序*/ {int i,j,temp;i=left;j=right;temp=a[left];if(left>right)return;while(i!=j)/*找到最终位置*/{while(a[j]>=temp && j>i)j--;if(j>i)a[i++]=a[j];while(a[i]<=temp && j>i)i++;if(j>i)a[j--]=a[i];}a[i]=temp;quickSort(a,left,i-1);/*递归左边*/quickSort(a,i+1,right);/*递归右边*/}//归并排序//归并排序合并数组函数的具体实现void merge(int a[],int low,int middle,int high) {int h,i,j,k;int b[N];h=low;i=low;j=middle+1;while(h<=middle&&j<=high){if(a[h]<=a[j]){b[i]=a[h];h++;}else{b[i]=a[j];j++;}i++;}if(h>middle)for(k=j;k<=high;k++){b[i]=a[k];i++;}else{for(k=h;k<=middle;k++){b[i]=a[k];i++;}}for(k=low;k<=high;k++){a[k]=b[k];}}//归并排序函数的具体实现void mergesort(int a[],int low,int high) {int middle;if(low<high){middle=(low+high)/2;mergesort(a,low,middle);mergesort(a,middle+1,high);merge(a,low,middle,high);}}void swapa(int a[], int i, int j)//希尔排序{int t = a[i];a[i] = a[j];a[j] = t;}void selsort(int a[], int n, int incr) {int i, j;for (i = incr; i < n; i += incr)for (j = i; (j >= incr)&& (a[j] < a[j-incr]); j -= incr)swapa(a, j, j-incr);}void shellsort(int a[], int n){int i, j;for (i = n / 2; i > 2; i /= 2)for (j = 0; j < i; j++)selsort(&a[j], n - j, i);selsort(a, n, 1);}void shift(int a[],int i,int m)//堆排序{int k,t;t=a[i]; k=2*i+1;while (k<m){if ((k<m-1) && (a[k] < a[k+1])) k ++;if (t<a[k]) {a[i]=a[k];i=k;k=2*i+1;}else break;}a[i]=t;}void heap(int a[],int n) //a 为排序数组,n为数组大小(编号0-n-1){int i,k;for (i=n/2-1;i>=0;i--)shift(a,i,n);for(i=n-1;i>= 1;i--){k=a[0];a[0]=a[i];a[i]=k;shift(a,0,i);}}int main(){int series [N]={0};int series_0[N];int series_a[N];int series_b[N];int series_c[N];int series_d[N];int series_e[N];int series_f[N];int series_g[N];int i,num;srand(time(NULL));for(i=0;i<N;i++)series[i]=rand()%N;for(i=0;i<N;i++){series_0[i]=series[i];}for(num=0;num<2;num++){if(num>0)nixu(series_0,series);printf("待排数据:\n");for(i=0;i<N;i++)printf("%d ",series[i]);putchar(N);for(i=0;i<N;i++){series_a[i]=series[i];series_b[i]=series[i];series_c[i]=series[i];series_d[i]=series[i];series_e[i]=series[i];series_f[i]=series[i];series_g[i]=series[i];}clock_t begin1, end1;/*计算冒泡排序时间*/double cost1;begin1 = clock();popo(series_a,N-1);end1 = clock();cost1 = (double)(end1 - begin1)/ CLOCKS_PER_SEC; printf("冒泡排序耗时:%lf seconds\n",cost1);clock_t begin2,end2;/*计算选择排序时间*/double cost2;begin2=clock();select(series_b,N);end2=clock();cost2=(double)(end2 - begin2)/ CLOCKS_PER_SEC; printf("选择排序耗时:%lf seconds\n",cost2);clock_t begin3, end3;/*计算直接插入排序时间*/ double cost3;begin3=clock();InsertSort(series_c,N);end3=clock();cost3=(double)(end3-begin3)/ CLOCKS_PER_SEC;printf("直接插入排序耗时:%lf seconds\n",cost3);clock_t begin4, end4;/*计算快速排序时间*/double cost4;begin4=clock();quickSort(series_d,0,N-1);end4=clock();cost4=(double)(end4-begin4) / CLOCKS_PER_SEC;printf("快速排序耗时:%lf seconds\n",cost4);clock_t begin5, end5;/*计算归并排序时间*/double cost5;begin5=clock();mergesort(series_e,0,N-1);end5=clock();cost5=(double)(end5-begin5)/ CLOCKS_PER_SEC;printf("归并排序耗时:%lf seconds\n",cost5);clock_t begin6, end6;/*计算希尔排序时间*/double cost6;begin6=clock();shellsort(series_f,N);end6=clock();cost6=(double)(end6-begin6)/ CLOCKS_PER_SEC;printf("希尔排序耗时:%lf seconds\n",cost6);clock_t begin7, end7;/*计算堆排序时间*/double cost7;begin7=clock();heap(series_g,N);end7=clock();cost7=(double)(end7-begin7)/ CLOCKS_PER_SEC;printf("堆排序耗时:%lf seconds\n",cost7);}}。