内部排序
快速排序是对冒泡排序的一种改进,不稳定
内部排序方法的讨论比较
排序方法
平均时间 最坏情况 辅助存储
简单排序 O(n^2)
O((n^2) O(1)
快速排序 O(n log n) O(n^2) O(log n)
堆排序 O(n log n) O(log n) O(1)
归并排序 O(log n) O(log n) O(n)
基数排序 O(d(n +rd)) O(d(n+rd)) O(rd)
(1)从平均时间性能而言,快速排序最佳,其所需时间最省,但快速排序在最坏情况下的时间性能不如堆排序和归并排序。而后两者的比较结果是,在n较大时,归并排序所需时间较堆排序省,但它所需的辅助存储量最多
(2) 上表中的“简单排序”包括除希尔排序之外的所有插入排序,起泡排序和简单选择排序,其中直接插入排序最简单,当序列中的记录“基本有序”或n值较小时,他是最佳的排序方法,因此常将它和其他的排序方法,诸如快速排序、归并排序等结合在一起使用
(3)基数排序的时间复杂度也可写成O(d*n)。因此它最适合用于n值很大而关键字较小的序列。若关键字也很大,而序列中大多数记录的“最高位关键字”均不同,则亦可先按“最高位关键字”不同将序列分成若干“小”的子序列,而后进行直接插入排序
(4)从方法的稳定性来比较,基数排序是稳定的内排方法,所有时间复杂度为
O(n^2)
的简单排序法也是稳定的,然而快速排序、堆排序和希尔排序等时间性能较好的排序方法是不稳定的。
一般来说。排序过程中的“比较”是在“相邻的两个记录关键字”间进行的排序方法是稳定的。
直接插入排序
初始序列:
i=1 [46] 58 15 45 90 18 10 62
i=2 [46 58] 15 45 90 18 10 62
┌——┘
i=3 [15 46 58] 45 90 18 10 62
┌——┘
i=4 [15 45 46 58] 90 18 10 62
i=5 [15 45 46 58 90] 18 10 62
┌—————┘
i=6 [15 18 45 46 58 90] 10 62
┌————————┘
i=7 [10 15 18 45 46 58 90] 62
┌—┘
i=8 [10 15 18 45 46 58 62 90]
void InsertSort(SqList &L){
//对顺序表L做直接插入排序
int i, j;
for(i=2; i<=L.length;++i)
if( LT(L.r[i].key, L.r[i-1].key )){//“<”需要将L.r[i]插入有序子表
L.r[0] = L.r[i]; //复制为哨兵
L.r[i] = L.r[i-1];
for(j = i-2; LT(L.r[0].key, L.r[j].key); --j )
L.r[j+1] = L.r[j]; //记录后移
L.r[j+1] = L.r[0]; //插入到正确的位置
}
}
折半插入排序
void BInsertSort(SqList & L){
//对顺序表L做折半插入排序
int i, j;
int low, high;
int m;
for(i=2; i <= L.length; ++i){
L.r[0] = L.r[i];
low = 1; high = i-1;
while( low <= high){ //在r[low...high]中折半查找有序插入的位置
m = (low + high) / 2;
if(LT(L.r[0].key, L.r[m].key)) high = m -1; //插入点在低半区
else low = m + 1;
//插入点在高半区
}
for( j = i -1; j >= high + 1; --j) L.r[j+1] = L.r[j]; //记录后移
L.r[high + 1] = L.r[0]; //插入
}
}
希尔排序
希尔排序基本思想:
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
假设待排序文件有10个记录,其关键字分别是:
49,38,65,97,76,13,27,49,55,04。
增量序列的取值依次为:
5,3,1
49,38,65,97,76,13,27,49,55,04。
增量序列的取值依次为:
5,3,1
希尔排序是不稳定的
void ShellInsert( SqList &L, int dk){
//对顺序表L做一次希尔排序,本算法是和一趟直接插入排序相比较,做了以下修改
//1。前后记录位置的增量是dk,而不是1;
·//2。r[0]只是暂存单元,不是哨兵。当 j<=0 时,插入位置已找到
for( i = dk +1; i <= L.length; i++)
if( LT(L.r[i].key, L.r[i - dk].key)){ //需将L.r[i] 插入有序增量子表
L.r[0] = L.r[i]; //暂存在L.r[0]
for(j = i - dk; j > 0 && LT(L.r[0].key, L.r[j].key); j -= dk)
L.r[j + dk] = L.r[j]; //记录后移,查找插入位置
L.r[j + dk] = L.r[0]; //插入
}
}
void ShellSort( SqList &L, int dlta[], int t){
//按增量序列dlta[0...t-1] 对顺序表L做希尔排序
for(k = 0; k < t; ++k)
ShellInsert(L, dlta[k]); //一趟增量为dlta[k]的插入排序
}
快速排序
冒泡排序,它的时间复杂度为O(n^2),虽然不及堆排序、快速排序的O(nlogn,底数为2),但是有两个优点:1.“编程复杂度”很低,很容易写出代码;2.具有稳定性,这里的稳定性是指原序列中相同元素的相对顺序仍然保持到排序后的序列,而堆排序、快速排序均不具有稳定性。不过,一路、二路归并排序、不平衡二叉树排序的速度均比冒泡排序快,且具有稳定性,但速度不及堆排序、快速排序。
void BubbleSort(SeqList R)
{ //R(l..n)是待排序的文件,采用自下向上扫描,对R做冒泡排序
int i,j;
Boolean exchange; //交换标志
for(i=1;i<n;i++){ //最多做n-1趟排序
exchange=FALSE; //本趟排序开始前,交换标志应为假
for(j=n-1;j>=i;j--) //对当前无序区R[i..n]自下向上扫描
if(R[j+1].key<R[j].key){//交换记录
R[0]=R[j+1]; //R[0]不是哨兵,仅做暂存单元
R[j+1]=R[j];
R[j]=R[0];
exchange=TRUE; //发生了交换,故将交换标志置为真
}
if(!exchange) //本趟排序未发生交换,提前终止算法
return;
} //endfor(外循环)
} //BubbleSort
{ //R(l..n)是待排序的文件,采用自下向上扫描,对R做冒泡排序
int i,j;
Boolean exchange; //交换标志
for(i=1;i<n;i++){ //最多做n-1趟排序
exchange=FALSE; //本趟排序开始前,交换标志应为假
for(j=n-1;j>=i;j--) //对当前无序区R[i..n]自下向上扫描
if(R[j+1].key<R[j].key){//交换记录
R[0]=R[j+1]; //R[0]不是哨兵,仅做暂存单元
R[j+1]=R[j];
R[j]=R[0];
exchange=TRUE; //发生了交换,故将交换标志置为真
}
if(!exchange) //本趟排序未发生交换,提前终止算法
return;
} //endfor(外循环)
} //BubbleSort
void bubble_sort(int a[], int n){
for(i = n - 1; i >= 1; i--)
for(j = 0; j < i; j++)
{
if(a[j] > a[j+1])
{
temp = a[j +1];
a[j+1] = a[j];
a[j] = temp;
}
}
}
快速排序是对冒泡排序的一种改进,不稳定
一趟快速排序的具体做法是:附设两个指针 low 和 high,他们的初值分别为 low 和 high,设枢轴记录的关键字为pivotkey,则首先从high所指位置起向前搜索找到第一个关键字小于pivotkey的记录和枢轴记录互相交换,然后从low所指位置起向后搜索,找到第一个关键字大于pivotkey的记录和枢轴记录互相交换,重复着两部直至 low = high为止。
int Partition (SqList &L, int low, int high){
//交换顺序表L中子表L.r[low...high]的记录,使枢轴记录到位,并返回其所在位置,
//此时在它之前(后)的记录均不大于(小于它)
L.r[0] = L.r[low];
pivotkey = L.r[low].key;
while(low < high) //从表的两端就爱哦题的想中间扫描
{
while(low < high && L.r[high].key >= pivotkey) --high;
L.r[low] = L.r[high]; //将比枢轴记录小的记录移到低端
while(low < high && L.r[low].key <= pivotkey) ++low;
L.r[high] = L.r[low]; //将比枢轴记录大的记录移到高端
}
L.r[low] = L.r[0]; //枢轴记录到位
return low; //返回枢轴位置
}
递归形式的快速排序算法
void QSort(SqList &L, int low, int high)
{
//对顺序表L中的子序列L.r[low...high
]做快速排序
if(low < high)
{
pivotloc = Partition(L, low, high); //将L.r[low...high]一分为二
QSort(L, low, pivotloc -1); //对低子表递归排序,pivotloc是枢轴位置
QSort(L, pivotloc + 1, high); //对高子表递归排序
}
}
void QuickSort(SqList & L){
QSort(L, 1, L.length);
}
注:本文由MonkeyAndy 的笔记整理,有些引用忘记出处,对于原作者表示感谢