一、复杂度对比
前面我们学习了各种内部排序的方法,我们现在关于复杂度作一对比。
依据这些因素,可得出如下几点结论:
(1) 若n较小(如n值小于50),对排序稳定性不作要求时,宜采用选择排序方法,若关键字的值不接近逆序,亦可采用直接插入排序法。但如果规模相同,且记录本身所包含的信息域比较多的情况下应首选简单选择排序方法。因为直接插入排序方法中记录位置的移动操作次数比直接选择排序多,所以选用直接选择排序为宜。
(2) 如果序列的初始状态已经是一个按关键字基本有序的序列,则选择直接插入排序方法和冒泡排序方法比较合适,因为“基本”有序的序列在排序时进行记录位置的移动次数比较少。
(3) 如果n较大,则应采用时间复杂度为O(nlog2n)的排序方法,即快速排序、堆排序或归并排序方法。快速排序是目前公认的内部排序的最好方法,当待排序的关键字是随机分布时,快速排序所需的平均时间最少;堆排序所需的时间与快速排序相同,但辅助空间少于快速排序,并且不会出现最坏情况下时间复杂性达到O(n2)的状况。这两种排序方法都是不稳定的,若要求排序稳定则可选用归并排序。通常可以将它和直接插入排序结合在一起用。先利用直接插入排序求得两个子文件,然后,再进行两两归并
(4)从平均时间看,快速排序最佳,所需时间最省,但快速排序在最坏情况下的时间性能不如堆排序和归并排序。而后两者相比较,在n较大时,归并排序所需时间较堆排序省。
(5)基数排序的时间复杂度可写成O(d*n),它最适用于n值很大而关键字较小的序列。如果关键字很大,而序列中大多数记录的“最高位关键字”均不同,则亦可先按“最高位关键字”不同将序列分成若干“小”的子序列,而后进行直接插入排序。
一般来说,排序过程中的“比较”是在“相邻的两个记录关键字”间进行的排序是稳定的。
二、存储结构的讨论
我们前面讨论的多数排序算法是在顺序存储结构上实现的,因此在排序过程中需进行大量记录的移动。当记录很大(即每个记录所占空间较多)时,时间耗费很大,此时可采用静态链表作存储结构。如表插入排序,链式基数排序,以修改指针代替移动记录。但有的排序方法,如快速排序和堆排序,这种方法可能不是很好,此时,我们可以进行“地址排序”,即另设一下地址向量指示相应记录,同时在排序过程中不移动记录而移动地址向量中相应分量的内容。如:
其描述算法如下:
我们可以看出,除了在每个小循环中要暂存一次记录外,所有记录都是一次到位。而每个小循环至少移动两个记录,则这样的小循环至多有ceil(n/2)个,所以重排记录算法中至多移动记录floor(3n/2)次。我们以基数排序为例,来说明这个问题。
void Sort(SLList L,int adr[]) // 改此句(类型)
{ // 求得adr[1..L.length],adr[i]为静态链表L的第i个最小记录的序号
inti=1,p=L.r[0].next;
while(p)
{
adr[i++]=p;
p=L.r[p].next;
}
}
voidRearrange(SLList &L,int adr[]) // 改此句(类型)
{ // adr给出静态链表L的有序次序,即L.r[adr[i]]是第i小的记录。
// 本算法按adr重排L.r,使其有序。算法10.18(L的类型有变)
int i,j,k;
for(i=1;i<L.recnum;++i) // 改此句(类型)
if(adr[i]!=i)
{
j=i;
L.r[0]=L.r[i]; // 暂存记录L.r[i]
while(adr[j]!=i)
{ // 调整L.r[adr[j]]的记录到位直到adr[j]=i为止
k=adr[j];
L.r[j]=L.r[k];
adr[j]=j;
j=k; //记录按序到位
}
L.r[j]=L.r[0];
adr[j]=j;
}
}
#define N 10
int main()
{
RedType d[N]={{278,1},{109,2},{63,3},{930,4},{589,5},{184,6},{505,7},{269,8},{8,9},{83,10}};
SLList l;
int *adr;
InitList(l,d,N);
printf("排序前(next域还没赋值):\n");
print(l);
RadixSort(l);
printf("排序后(静态链表):\n");
print(l);
adr=(int*)malloc((l.recnum)*sizeof(int));
Sort(l,adr);
Rearrange(l,adr);
printf("排序后(重排记录):\n");
print(l);
return OK;
}
结果如图示:
三、内部排序可能达到的最快速度
由于含有n个记录的序列可能出现的初始状态有n!个,则描述n个记录排序过程的判定数必须有n!个叶子结点。我们知道,二叉树的高度为h,则叶子结点的个数不超过2h-1;反之,有u个叶子结点,则二叉树的高度至少为ceil(log2u)+1。由此:任何一个借助“比较”进行排序的算法,在最坏情况下所需进行的比较次数至少为ceil(log2(n!))。根据斯特林公式,有ceil(log2(n!))=O(nlogn)。由此,我们知道,借助于“比较”进行排序的算法在最坏情况下能达到的最好的时间复杂充为O(nlogn)。