1.直接排序法
整个排序过程为n-1趟插入,即先将序列中第1个记录看成是一个有序子序列,然后从第2个记录开始,逐个进行插入,直至整个序列有序。
例:
21,25,49,25*,16,08(*表示后一个25)
直接插入排序算法:
void InsertSort(SqList &L){
int i,j;
for(i=2;i<=L.length;++i)
if( 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; L.r[0].key<L.r[j].key;--j)
L.r[j+1]=L.r[j]; // 记录后移
L.r[j+1]=L.r[0]; //插入到正确位置
}
}
时间复杂度为O(n2);
空间复杂度为O(1);
直接插入排序算法小节:
1.是一种稳定的排序方法。
2.算法简便,且容易实现。
3.既适用于记录序列的顺序存储结构,也适用于链式存储结构,只是在单链表上无需移动记录,只需要修改相应的指针。
4.更加适合于初始记录本身基本有序(正序)的情况;当初始记录无序,且n较大时,算法的时间复杂度较高,不宜采用。
2.折半插入排序
void BInsertSort ( SqList &L ){
for ( i = 2; i <= L.length ; ++i )
{ L.r[0] = L.r[i]; low = 1 ; high = i-1 ;
while ( low <= high )
{ m = ( low + high ) / 2 ;
if ( 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]; //插入r[0]到正确位置
}
} // BInsertSort
时间复杂度为O(n2);
空间复杂度为 O(1);
折半插入排序算法小节:
1.是一种稳定的排序方法。
2.因为要进行折半查找,所以只能用于顺序存储结构,不能用于链式存储结构。
3.适合于初始记录无序,n较大时的情况。
3.希尔排序
先将整个待排记录序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。
//希尔排序算法(其中某一趟的排序操作)
void ShellInsert(SqList &L,int dk) {
//对顺序表L进行一趟增量为dk的Shell排序,dk为步长因子
for (i=dk+1;i<=L.length; ++ i)
if(r[i].key < r[i-dk].key) { //需要将r[i] 插入有序增量子表
r[0]=r[i];//暂存在r[0]
for(j=i-dk; j>0 &&(r[0].key<r[j].key); j=j-dk)
r[j+dk]=r[j];//关键字较大的记录在子表中后移
r[j+dk]=r[0];//在本趟结束时将r[i]插入到正确位置
}
}
//希尔排序算法-主程序
void ShellSort(SqList &L,int dlta[ ],int t){
//按增量序列dlta[0…t-1]对顺序表L作Shell排序
for(k=0;k<t;++k)
ShellInsert(L,dlta[k]);
//增量为dlta[k]的一趟插入排序
} // ShellSort
时间复杂度是n和d的函数:
O(n1.25)~O(1.6n1.25)—经验公式
空间复杂度为O(1)
希尔排序算法小节:
1.一种不稳定的排序方法,因为在排序过程,当增量大于1时,关键字较小的记录是跳跃式移动的。
2.实质是一种分组插入排序方法。
3.增量序列可以有各种取法,但应该使增量序列中的值没有除1之外的因子,并且最后一个增量值必等于1。
4.记录总的比较次数和移动次数都比直接插入排序要少,n越大时效果越明显,故适用于初始记录无序、n较大的情况。
4.冒泡排序
基本思想:每趟不断将相邻的记录两两比较,并按“前小后大” 规则交换。
void bubble_sort(SqList &L){
m=L.length-1;flag=1; //flag记录某一趟排序是否发生交换
while((m>0)&&(flag==1))
{ flag=0; //flag置为0,若本趟没发生交换,则不执行下一趟
for(j=1;j<=m;j++)
if(L.r[j].key>L.r[j+1].key)
{ flag=1;
x=L.r[j];L.r[j]=L.r[j+1];L.r[j+1]=x; //交换
}//endif
m--;
}//endwhile
}
时间复杂度为O(n2);
空间复杂度为O(1);
冒泡排序算法小节:
1.是一种稳定的排序方法。
2.既适用于记录序列的顺序存储结构,也适用于链式存储结构。
3.移动记录次数较多,算法平均时间性能比直接插入排序差。当初始记录无序,且n较大时,算法的时间复杂度较高,不宜采用。
5.快速排序
一趟快速排序的具体步骤如下:
(1)选择待排序表中的第一个记录作为枢轴,将枢轴记录暂存于r[0]的位置。附设两个指针low和high,初始时分别指向表中的下界和上界(第一趟时low=1;high=L.length)。
(2)从表的最右侧(high的位置)依次向左搜索,找到第一个关键字小于枢轴关键字pivotkey的记录,将其移动到low处,即当low<high时,若high所指记录的关键字大于等于pivotkey,则向左移动high(–high);否则将high所指向记录与枢轴记录交换。
int Partition ( SqList &L,int low, int 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 )
{ //调用前置初值:low=1;high=L.length
if ( low < high )
{ pivotloc = Partition(L, low, high ) ;
Qsort (L, low, pivotloc-1) ;
Qsort (L, pivotloc+1, high );
}
}
void QuickSort (SqList &L )
{
QSort ( L, 1, L.length );
}
平均时间复杂度O(nlog2n)。
快速排序算法小结:
1.由于存在记录的非顺次移动,所以此方法是不稳定的。
2.适合于顺序表,很难用于链式结构,因为在排序过程中需要定位表的下界和上界。
3.当n比较大时,在平均情况快速排序是所有内部排序方法中速度最快的一种,所以其适合于初始记录无序、n较大的情况。
6.简单选择排序
基本思想:每一趟在后面 n-i +1个中选出关键字最小的记录, 按顺序放在已经排好序的记录序列的最后(即已经有序序列的第 i 个记录)。
算法代码:
void SelectSort(SqList &L)
{
for (i=1; i<L.length; ++i)
{ //在L.r[i..L.length] 中选择key最小的记录
k=i;
for( j=i+1;j<=L.length ; j++)
if ( L.r[j].key <L.r[k].key) k=j;
if(k!=i)
L.r[i]←→L.r[k];
}
}
时间复杂度:O(n²)
空间复杂度:O(1)
简单选择排序算法小结
1.可用于链式存储结构。
2.移动记录次数较少,当每一记录占用的空间较好时,此方法快于直接插入排序。
7.堆排序
基本思想:
(1)将序列r[1…n] 建初堆,交换r[1]和r[n],则r[n]为关键字最大的记录。
(2)将r[1…n-1]重新调整为堆,交换r[1]和r[n-1] ,则r[n-1]为关键字次大的记录。
(3)循环n-1次,直到交换了r[1]和r[2]为止,得到了一个非递减的有序序列r[1…n]。
无序序列建成堆 :
堆的调整:将根结点r[1]与左、右子树根结点比较,并与小者交换;重复直至叶子结点,得到新的堆。
堆排序算法-筛选调整
void HeapAdjust(SqList &L,int s,int n){
//设r[s+1,..,m]已是堆,将r[s..m]调整为以r[s]为根的大根堆
rc=L.r[s];
for(j=2*s;j<=m;j*=2){ //沿key较大的孩子结点向下筛选
if(j<m && L.r[j].key<L.r[j+1].key) ++j;
if(rc.key>=L.r[j].key) break;
L.r[s]=L.r[j;
s=j;
}
L.r[s]=rc;
}
堆排序算法-建初堆
void CreateHeap(SqList &L){
//将无序序列L.r[1..n]建成初堆
n=L.Length;
for(i=n/2;i>0;--i)
HeapAdjust(L,i,n);
}
堆排序算法
void HeapSort(SqList &L){
CreateHeap(SqList &L);
for(i=L.length;i>1;--i){
x=L.r[1]; L.r[1]=L.r[i]; L.r[i]=x;
HeapAdjust(L,1,i-1);
}
}
时间效率:O(nlog2n)
空间效率:O(1)
稳 定 性:不稳定
适用场合:顺序存储结构,且n 较大的情况
8.归并排序
排序过程:
1.将初始序列看成n个有序子序列,其中每个子序列长度为1。
2.两两合并,得到n/2个长度为2或1的有序子序列。
3.再两两合并,重复直至得到一个长度为n的有序序列为止。
void MSort(RedType R[],RedTypeT[],int low,int high)
{//R[low..high]归并排序后放入T[low..high]中
if(low==high) T[low]=R[low];
else
{
mid=(low+high)/2;//将当前序列一分为二,求出分裂点mid
MSort(R,S,low,mid);//对子序列R[low..high]递归归并排序,结果放入S[low..high]
MSort(R,S,mid+1,high);//对子序列R[mid+1..high]递归归并排序,结果放入S[mid+1..high]
Merge(S,T,low,mid,high);//将S[low..mid]和S[mid+1..high]归并到T[low..high]
}
}
void MergeSort(SqList &L)
{//对顺序表L做归并排序
MSort(L.r,L.r,1,L.length);
}
时间效率:O(nlog2n)
空间效率:O(n)
稳 定 性:稳定
适 用 性:可用于链式结构,且不需要附加存储空间,但递归实现时仍要开辟相应的递归工作栈。
排序算法比较图:
排序算法选择规则:
n较大时:
(1)分布随机,稳定性不做要求,则采用快速排序;
(2)内存允许,要求排序稳定时,则采用归并排序;
(3)可能会出现正序或逆序,稳定性不做要求,则采用堆排序或归并排序。
n较小时:
(1)基本有序,要求稳定,则采用直接插入排序;
(2)分布随机,稳定性不做要求,则采用直接选择排序,若排序码不接近逆序,也可以采用直接插入排序。
以上是根据老师上课整理的笔记。