一、概述:
排序是一种最基本的、应用最广泛的数据操作,通常排序操作的数据量都非常的大,因而为了节省排序的时间,对排序算法的速度要求一般都比较高。特别是一些大型的数据库,由于数据量非常庞大,如何选择一个高效的排序算法就显得非常的重要了。在数据量小的时候,各种排序算法的性能都差不多,但在数据量很大的时候,一些好的排序算法的优点就非常的明显了。衡量排序算法优劣的标准有很多,但最常用的为时间复杂度和空间复杂度。对于内排序而言,往往不考虑空间复杂度,原因有二:一是各种内排序算法的空间复杂度都差不多,二是随着大规模集成电路的飞速发展,存储器的价格不断下降,空间复杂度已不是主要的问题。而现有的计算机特别是用户数量最多的桌面微机,由于计算能力相当有限,时间复杂度就成为一个核心的问题。
限于时间这里只对以下六种最常用的算法进行了分析比较:
1、冒泡排序;
2、交换排序;
3、插入排序;
4、快速排序;
5、选择排序;
6、希尔排序;
由于每台计算机的配置都不一样,因而获取一个排序算法对在特定的机器上对一定的数据量进行排序花费的时间没有太大的意义,但如果在相同的条件下(这里相同的概念也是一个假设,因为实际中不可能完全相同,只是非常的接近罢了)比较某几个算法的相对时间复杂度则很有意义,因为不管在任何机器上获得的结果都是几乎相同的。本次实验要求证的正是这一结果,程序中对各种排序算法花费的时间进行了归一化处理,结果显示的数值只是一个相对的概念。
本次实验中给出了各种排序算法对最多10000个随机整数进行排序的相对时间复杂度,这里随机数是指伪随机数,为了接近随机,这里用了系统时间来初始化随机种子,这样每次的到的随机数都是不一样的。
二、六种排序算法分析:
1、冒泡排序:
冒泡排序的基本思想是每一趟循环比较都将小的数往上浮,将大的数往下沉,最终实现从小到大的顺序排列。这好比将每个记录R[i]看作是重量为R[i].key的气泡。根据轻气泡不能在重气泡之下的原则,从下往上扫描数组R:凡扫描到违反本原则的轻气泡,就使其向上"飘浮"。如此反复进行,直到最后任何两个气泡都是轻者在上,重者在下为止。
因为每一趟排序都使有序区增加了一个气泡,在经过n-1趟排序之后,有序区中就有n-1个气泡,而无序区中气泡的重量总是大于等于有序区中气泡的重量,所以整个冒泡排序过程至多需要进行n-1趟排序。若在某一趟排序中未发现气泡位置的交换,则说明待排序的无序区中所有气泡均满足轻者在上,重者在下的原则,因此,冒泡排序过程可在此趟排序后终止。
冒泡排序的平均时间复杂度为O(n^2),由此看来冒泡排序是一种很慢的排序算法,在数据量比较大是不宜采用。
2、交换排序:
交换排序算法与冒泡排序算法基本一样,只不过比较的次序不一样,因而时间复杂度也与冒泡排序算法一样。
3、插入排序:
插入排序的基本思想是,经过i-1遍处理后,L[1..i-1]己排好序。第i遍处理仅将L[i]插入L[1..i-1]的适当位置,使得L[1..i]又是排好序的序列。要达到这个目的,我们可以用顺序比较的方法。首先比较L[i]和L[i-1],如果L[i-1]≤ L[i],则L[1..i]已排好序,第i遍处理就结束了;否则交换L[i]与L[i-1]的位置,继续比较L[i-1]和L[i-2],直到找到某一个位置j(1≤j≤i-1),使得L[j] ≤L[j+1]时为止。
简言之,插入排序就是每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕。
对于确定的i,内while循环的次数为O(i),所以整个循环体内执行了∑O(i)=O(∑i),其中i从2到n。即比较次数为O(n2)。如果输入序列是从大到小排列的,那么内while循环次数为i-1次,所以整个循环体执行了∑(i-1)=n(n-1)/2次。由此可知,最坏情况下,插入排序要比较O(n2)次。
4、快速排序:
快速排序是一种有效的排序算法。虽然算法在最坏的情况下运行时间为O(n^2),但由于平均运行时间为O(nlogn),并且在内存使用、程序实现复杂性上表现优秀,尤其是对快速排序算法进行随机化的可能,使得快速排序在一般情况下是最实用的排序方法之一,快速排序被认为是当前最优秀的内部排序方法。
快速排序的实现基于分治法,具体分为三个步骤。假设待排序的序列为L[m..n]:
快速排序每次将待排序数组分为两个部分,在理想状况下,每一次都将待排序数组划分成等长两个部分,则需要logn次划分。而在最坏情况下,即数组已经有序或大致有序的情况下,每次划分只能减少一个元素,快速排序将不幸退化为冒泡排序,所以快速排序时间复杂度下界为O(nlogn),最坏情况为O(n^2)。在实际应用中,快速排序的平均时间复杂度为O(nlogn)。
选择排序的基本思想是:第i趟在n-i+1(i=1,2,...,n-1)个记录中选取键值最小的记录作为有序序列的第i个记录。
直接选择排序是一种很简单的排序方法,它的做法是:首先在所有的记录中选出键值最小的记录,把它与第一个记录交换;然后在其余的记录中再选出键值最小的记录与第二个换;依此类推,直至所有记录排序完成。在第i趟中,通过n-i次键值比较选出所需记录。
直接选择排序算法,由于其主要部分为两层嵌套的for循环,显然可以看出,其时间复杂性为O(n2)。
6、希尔排序:
基本思想是:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插人排序;然后,取第二个增量d21重复上述的分组和排序,直至所取的增量dt=1(dtt-l<…21),即所有记录放在同一组中进行直接插入排序为止。
①当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
②当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。
③在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
三、部分重要程序:
1、冒泡排序程序:
void CSort::BubbleSort(int *pData, int Count)
{
}
2、交换排序程序:
void CSort::ExchangeSort(int *pData, int Count)
{
}
3、插入排序程序:
void CSort::InsertSort(int *pData, int Count)
{
}
4、快速排序程序:
void CSort::QuickSort(int *pData, int left, int right)
{
}
5、选择排序:
void CSort::SelectSort(int *pData, int Count)
{
}
6、Shell排序:
void CSort::ShellSort(int *pData, int Count)
{
}
四、实验结果:(程序界面采用Codejock公司的Xtteme Toolkit开发)
正如前面所说,这里不关心具体的机器配置,对每个排序算法花费的绝对时间不予关注,只关心六种算法的相对时间复杂度。
1、对10000个随机数进行排序的时间复杂度比较:
2、同样对10000个另外的一组随机数进行排序,结果如下:
3、对5000个随机数进行排序的结果:
从以上结果可以看到,前面所做的各种假设都是正确的,冒泡和交换排序算法的效率非常低,而快速排序算法的效率则要高很多,此外Shell排序算法的效率也接近与快速排序算法。从图中可以看到,不同排序算法的效率相差非常的大。