一、插入排序
在各种插入排序方法中,直接插入排序方法最简单。
1.直接插入排序
算法:
void InsertSort(SqList &L)
{ //用直接插入排序方法对L指示的顺序表进行排序
for (i=2; i<=L.length; i++)
if(LT(L.r[i].key,L.r[i-1].key))
{
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];
}
}
排序过程示例:
空间复杂度:O(1),需要一个监视哨的辅助空间
稳定性:是稳定的排序方法
时间复杂度:O(n2)
优点: 在待排序序列是“正序”或接近“正序”时该算法的时间复杂度可提高至O(n)的数量级;由于该算法简单,所以在待排序序列长度n较小时(待排序的记录个数比较少),其时间效率也是较高的
缺点:时间效率整体不高
改进方法:减少记录的移动次数 减少关键字之间的比较次数
2.折半插入排序
折半插入排序思想:在有序表R[1…i-1]中插入R[i]时,由于R[1…i-1]是一个按关键字有序的序列,因此可以用折半查找来确定R[i]的插入位置
过程:
Step1:用折半查找确定R[i]在R[1…i-1]中的插入位置
Step2:最后一个记录到插入位置的记录依次后移一个位置
Step3:插入记录R[i]
算法
void BinInsertSort(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(LT(L.r[0].key,L.r[m].key)) high=m-1;
else low=m+1;
}//while
for(j=i-1;j>=high+1;j--) L.r[j+1]=L.r[j]; //记录后移
L.r[high+1]=L.r[0]; //插入第i个记录
}//for
}//BinInsertSort
折半插入排序:稳定,适用于待排序记录数量很大的情况。
时间复杂度:O(n*n)
3.希尔排序
希尔排序的思想:
先将待排序记录序列按指定的增量间隔分割成若干子序列分别进行直接插入排序
不断缩小增量间隔,重复以上操作
待整个序列中记录“基本有序” 时,最后再对全体记录进行一次直接插入排序。
特点: 相隔某个增量的记录组成一个子序列。
希尔排序又称“缩小增量排序”。
一趟希尔排序算法
void ShellInsert(SqList &L,int dk)
{//对L指示的顺序表进行一趟希尔排序,增量为dk
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];
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];
}
} // ShellInsert
完整算法
void ShellSort (SqList &L,int dlta[],int t)
{//按增量序列dlta[0..t-1]对L指示的顺序表进行希尔排序
int k;
for (k=0; k<t; ++k ) ShellInsert(L,dlta[k]);
} //Shellsort
空间复杂度:O(1),需要一个暂存记录的辅助空间
稳定性:不稳定
时间复杂度:其运行时间取决于增量序列 ,是增量和记录数的函数。 增量序列中的值应没有除1以外的公因子,且最后一个增量值必须是1。
二、交换排序
1.冒泡排序
冒泡排序过程:
第1趟:从第1到第n个记录,若相邻的两个记录逆序,则交换之;否则继续比较下面两个相邻记录……结果使关键字最大的记录被安置到第n个位置上。
第i趟:从第1到第n-i+1个记录,依次比较两个相邻记录的关键字,逆序时交换之, 使其中关键字最大的记录被交换到第n-i+1个位置上。
结束条件:在一趟排序过程中未出现交换记录的操作,则整个序列有序。
算法
void BubbleSort(SqList &L)
{ //对L指示的顺序表进行冒泡排序
for(i=1;i<L.length;i++)
{ //最多进行n-1趟
flag=TRUE; // flag:标志
for(j=1;j<=L.length-i;j++)
if( LT(L.r[j+1].key,L.r[j].key))
{ flag=FALSE;
temp=L.r[j+1];L.r[j+1]=L.r[j];L.r[j]=temp;
}
if(flag) return;
}//for
}//BubbleSort
空间复杂度:O(1),需要一个供交换用的辅助空间
稳定性:是稳定的排序方法
时间复杂度:O(n2)
2.快速排序
快速排序是对冒泡排序的改进,又称划分交换排序。
基本思想:
在待排序序列中任选一个记录作为枢轴,通过一趟排序将待排序记录分割成独立的两部分,其中前一部分记录的关键字均小于等于枢轴的关键字,后一部分记录的关键字都大于等于枢轴的关键字;
分别对这两个子序列按照同样方法再进行快速排序(划分),直到分割出的每个子序列只包含一个记录为止;
此时整个序列达到有序
快排的算法
//一趟快排
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];
}//while
L.r[low]=L.r[0];
return low;
}// Partition
void QSort ( SqList &L,int low, int high )
{ if (low < high)
{ pivotloc=Partition(L,low,high);
QSort (L, low, pivotloc-1) ; //对低端子序列递归排序
QSort (L, pivotloc+1, high ); //对高端子序列递归排序
}
}// QSort
void QuickSort ( SqList &L )
{ //对L指示的顺序表进行快速排序
QSort ( L, 1, L.length );
} // QuickSort
一趟快排过程示例
完整快排示例
时间复杂度:O(nlogn)
就平均时间而言,快速排序性能最好
稳定性:不稳定
三、选择排序
1.简单选择排序
选择排序的思想:每一趟在i…n共n-i+1(i=1,2,……n-1)个记录中选取关键字最小的记录使之成为有序序列的第i个记录。
简单选择排序过程:
第1趟:在第1到第n共n个记录中通过n-1次关键字之间的比较,选取关键字最小的记录,若它不是第1个记录,则交换之;
第i趟:在第i到第n共n-i+1个记录中通过n-i次关键字之间的比较,选取关键字最小的记录,若它不是第i个记录,则交换之;
整个简单选择排序一共要进行n-1趟
算法
void SelectSort ( SqList &L )
{ //对L指示的顺序表进行简单选择排序
for(i=1; i<L.length; ++i)
{ minloc=i;
for(j=i+1;j<=L.length;j++)
if(LT(L.r[j].key, L.r[minloc].key)) minloc=j;
if (i!=minloc)
{L.r[i] <-> L.r[minloc];}
}//for
} // SelectSort
空间复杂度:O(1),需要一个供交换用的辅助空间
稳定性:不稳定
时间复杂度:O(n2)
2.堆排序
堆:由n个关键字构成的序列{ k1, k2, …, kn},若满足以下条件之一:
则称该序列为堆,且分别为小顶堆和大顶堆。
可借助完全二叉树表示堆。存储时按层序遍历顺序依次存放在一个一维数组中
例:
小顶堆
大顶堆
完整的堆排序过程
先将待排序的记录序列建成初始大顶堆;
将堆顶元素(关键字最大的记录)与当前堆中最后一个(第n个)元素交换
调整剩下的n-1个元素重新成为大顶堆
反复上述2步直至堆中仅剩2个元素,将堆顶元素(关键字第二小的记录)与当前堆中最后一个(第2个)元素交换,此时待调整的元素仅剩一个,所以不必再进行调整,原无序序列已经成为按关键字由小到大有序的序列,排序完成
算法
void HeapAdjust(SqList &H, int s, int m)
{ /*已知H->r[s..m]中记录的关键字除H->r[s].key之外均满足堆的定义,本函数调整H->r[s]的关键字,使H->r[s..m]成为一个大顶堆*/
rc= H.r[s]; //rc暂存子树根结点的元素值
for(j=2*s; j<=m; j*=2)
{ if (j<m && LT(H.r[j].key, H.r[j+1].key)) ++j;
if (!LT(rc.key, H.r[j].key)) break;
H.r[s]= H.r[j]; s=j;
}
H.r[s]=rc; //把rc放入到最终应该在的位置
}// HeapAdjust
void HeapSort(SqList &H)
{ // 对H指向的顺序表进行堆排序
for(i=H.length/2; i>0; --i )
HeapAdjust(H, i, H.length);
for(i=H.length; i>1; --i )
{ H.r[1]<->H.r[i];
HeapAdjust(H,1,i-1);
}//for
}// HeapSort
空间复杂度:O(1),需要一个暂存记录的辅助空间
稳定性:不稳定
时间复杂度:O(nlogn)
堆排序的时间主要耗费在初始建堆和调整建新堆时进行的反复筛选上。对记录数较少的序列不值得提倡(初始建堆时间过长),但对n很大的序列堆排序很有效
n个记录的堆对应的完全二叉树的深度为 ,建初始堆时,总共进行关键字的比较次数不会超过4n次,而在其后的n-1趟堆排序过程中总共进行的关键字的比较次数不会超过 次。因此堆排序在最坏的情况下,其时间复杂度也是O(nlog2n)
四、归并排序
基本思想:
通常采用2-路归并算法
将两个或两个以上有序子序列“归并”为一个有序序列
过程:设待排序序列有n个记录
初始时可看成n个有序子序列,每个子序列长度为1
两两归并得到个长度为2或1的有序子序列
再两两归并……直至得到一个长度为n的有序序列为止
空间复杂度:O(n)
稳定性:稳定
时间复杂度:O(nlogn)
五、总结