插入类
直接插入排序
步骤如下:
待排序的记录存放在数组r[1…n],r[1]是一个有序序列,在r[0]出设置监视哨。
(1)确定插入位置: 循环n-1次,使用顺序查找法,查找r[i](i = 2,…,n)在已排好序的序列r[1,…i-1]中得插入位置;
(2)插入元素: 将r[i[ 插入到表长为i-1的有序序列r[1,…i-1]中,直到插入r[n],得到一个表长为n的有序序列。
代码如下:
直接插入排序
void InsertSort(SqList &L)
{
for(int i = 2;i <= L.length;i++){
if(L.r[i].key < L.r[i-1].key){
L.r[0] = L.r[i]; //r[0]作为监视哨,临时保存r[i]的值;
L.r[i] = L.r[i-1];
for(int j = i-2;L.r[0].key < L.r[j].key; --j){
L.r[j+1] = L.r[j]; //找到r[i]或者说此时r[0]中关键字需要插入的位置为j之后j+1处
}
L.r[j+1] = L.r[0];
} //if
} // for
}
算法特点:
(1)稳定排序;
(2)适用于顺序存储结构与链式存储结构,在单链表上无需移动记录,只需修改相应的指针;
(3)适用于初始记录基本有序(正序)的情况。当初始记录无序,n较大,不宜使用。
(4)时间复杂度:O(n²); 空间复杂度: O(1).
希尔排序
算法如下:
将整个待排序记录序列中相隔某个“增量”的记录分成一组,进行直接插入排序,而后逐次增加每组的数据量(即减小“增量”),经过几次分组排序后,整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。
代码如下:
希尔插入排序
//对顺序表L做一趟增量是dk的希尔插入排序
void ShellInsert(SqList &L,int dk)
{
for(i = dk+1;i <= L.length; ++i){
if(L.r[i].key < L.r[i-dk].key){
L.r[0] = L.r[i];
for(j = i-dk;j>0 && L.r[0].key < L.r[j].key;j-=dk){
L.r[j+dk] = L.r[j];
}
L.r[j+dk] = L.r[0];
}//if
} //for
}
//按增量序列dt[0..t-1]对顺序表L作t趟希尔排序
void ShellSort(SqList &L,int dt[],int t){
for(k = 0;k < t;++k)
ShellInsert(L,dt[k]);
}
算法特点:
(1)不稳定排序;
(2)只能用于顺序结构;
(3)增量序列中的值没有除1以外的公因子,且最后一个增量值必须为1;
(4)适合初始记录无序、n较大时的情况
(5)时间复杂度: O(n1.3) 【平均情况下】空间复杂度: O(1).
交换类
冒泡排序
步骤如下:
(1)首先将比较第一、二个记录的关键字,若为逆序,则互换;然后比较第二、三个记录的关键字,一次类推,直至第n-1、n个记录的关键字进行比较为止。其结果使得关键字最大(小)的记录被安置到最后一个记录的位置上。
(2)对前n-1个记录进行同样操作。重复该过程,第i趟是从L.r[1]到L.r[n-i+1]依次比较相邻两个记录的关键字,并在“逆序”时交换顺序。直到在某一趟排序过程中没有进行过交换记录的操作,说明序列已有序。
代码如下:
冒泡排序
void BubbleSort(SqList &L)
{
int m = L.length - 1;
int t,flag = 1;
while(m > 0 && flag == 1){
flag = 0;
for(int j = 1;j <= m;j++){
if(L.r[j].key > L.r[j+1].key){
flag = 1;
t = L.r[j].key;
L.r[j] = L.r[j+1];
L.r[j+1].key = t;
}//if
--m;
}//for
}//while
}
算法特点:
(1)稳定排序;
(2)可用于顺序、链式存储结构;
(3)移动次数较多,算法平均时间性能比直接插入排序差。当初始记录无序、n较大时,不宜采用;
(4)时间复杂度:O(n²); 空间复杂度: O(1)
快速排序
步骤如下:
选择待排序表中的第一个元素作为枢轴,将枢轴放在合适的位置,则将表L“划分”成左右2个逻辑子表,使得该枢轴大于左子表的所有记录的关键字,且小于右子表的所有记录的关键字,而后递归处理左右子表。
(1)将第一个元素作为枢轴,将枢轴记录暂存在r[0]的位置上,设两指针low和high初始值分别指向表L最左和最右的位置上;
(2)当low < high时,重复做如下处理:
low递增移动,将首次遇到比r[low]小的r[high]与r[low]交换;
然后high递减移动,将首次遇到比r[high]大的r[low]与r[high]交换;
(3)直至low = high时为止,此时low或high的位置即为枢轴在此趟排序的最终位置,原表被分为两个子表;
(4)递归处理左右子表;
代码如下:
快速排序
//移动low、high进行记录交换
int Partition(SqList &L,int low,int high)
{
L.r[0] = L.r[low]; //将子表的第一个记录作为枢轴记录;
int 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)
{
int pivotloc;
int low = 1,high = L.length; //low、high初始值
if(low < high){
pivotloc = Partition(L,low,high);
QSort(L,low,pivotloc-1);
QSort(L,pivotloc+1,high);
}
}
//对顺序表L进行快速排序
void QuickSort(SqList &L)
{
QSort(L,1,L.length);
}
算法特点:
(1)不稳定排序;
(2)适用于顺序结构,很难用于链式结构(因为要求确定其上界和下界);
(3)适合初始记录无序、n较大时、当n较大时的情况,在平均情况下快速排序是所有内部排序方法中速度最快的一种。
(4)时间复杂度: O(nlog₂n); 空间复杂度: O(log₂n),最坏情况下为O(n).
选择类
简单选择排序
步骤如下:
(1)第一趟从r[1]开始,通过n-1次比较,从n个记录中选出关键字最小的记录,记为r[k],交换r[1]和r[k],第二趟从r[2]开始,通过n-2次比较,从n个记录中选出关键字最小的记录,记为r[k],交换r[2]和r[k];
(2)依此类推,第一趟从r[i]开始,通过n-i次比较,从n个记录中选出关键字最小的记录,记为r[k],交换r[i]和r[k],经过n-1趟,排序完成。
代码如下:
简单选择排序
void SelectSort(SqList &L)
{
SqList t;
for(int i = 1;i < L.length;i++){
k = i ;
for(int j = i+1;j <= L.length;++j){ //k指向此趟排序中关键字最小的记录
if(L.r[j].key < L.r[k].key)
k = j;
}
if(k != i){ //交换r[k]与r[i]
t = L.r[i];
L.r[i] = L.r[k];
L.r[k] = t;
}
}//for
}
算法特点:
(1)算法本事是稳定排序,但是会出现”不稳定排序“的现象;
(2)可用于顺序、链式存储结构;
(3)移动次数较少,当每一记录占用的空间较多时,此方法比直接插入排序快;
(4)时间复杂度:O(n²); 空间复杂度: O(1).
堆排序
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大根堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小根堆。
步骤如下:
(1)建初堆:按堆的定义将排序序列r[1…n]调整为大根堆,交换r[1]和r[n],则r[n]为关键字最大的记录;
(2)调整堆:将rp1…n-1]调整为堆,交换r[1]和r[n-1],则r[n-1]为关键字次大的记录;
(3)循环n-1次,直到交换了r[1]和r[2],得到l一个非递减的有序序列。
代码如下:
堆排序
//筛选法调整堆
void HeapAdjust(SqList &L,int s,int m)
{//假设r[s+1..m]已经是堆,将r[s..m]调整为以r[s]为根的大根堆
SortList rc = L.r[s];
for(int j = 2*s;j <= m;j*=2){
/*如果 r[s].key < r[2s].key,交换r[s]和r[2s]。交换后,判断r[2s+1]为根的子树是否为堆,
若不是,则重复上述过程,将以r[2s]为根的子树调整为堆,直至到叶子结点 */
if(int i < m && L.r[j].key < L.r[j+1].key)
++j;
//如果r[s].key >= r[2s].key,说明以r[2s]为根的子树已经是堆,不做调整
if(rc.key >= L.r[j].key)
break;
L.r[s] = L.r[j];
s = j;
}
L.r[s] = rc;
}
//建初堆
void CreatHeap(SqList &L)
{//把无序序列L.r[1..n]建成大根堆
int n = L.length;
for(int i = n/2;i > 0;--i) //反复调用HeapAdjust()
HeapAdjust(L,i,n) ;
}
//对顺序表L进行堆排序
void HeapSort(SqList &L)
{
CreatHeap(L);
for(int 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);
} //for
算法特点:
(1)不稳定排序;
(2)只能用于顺序存储结构;
(3)当n较大、关键字基本有序时,可采用堆排序;
(4)时间复杂度: 在最坏情况下,O(nlog₂n),实验研究表明,其平均性能接近于最坏性能 ; 空间复杂度: O(1).
归并类
归并排序
归并排序是将两个或两个以上的有序表合并成一个有序表的过程。假设初始序列含有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到┌n/2┐个长度为2或1的有序子序列;再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止。
步骤如下:
2路归并排序将R[low…high]中的记录归并排序后放入T[low… high]中.当序列长度写于1时,递归结束,否则:
(1)将当前序列一分为二,求出分裂点mid =low+high/2(mid为不大于该数的最大整数);
(2)对子序列R[low. …mid]递归,进行归并排序,结果放人S[low…mid]中:
(3)对子序列R[mid + 1.high]递归,进行归并排序,结果放人S[mid+ L.high]中;
(4)调用算法Merge,将有序的两个子序列S[low…mid]和S[mid + 1…high]归并为一个有序的序列T[Iow… high];
代码如下:
归并排序
//相邻两个有序子序列的归并
void Merge (RedType R[],RedType TI,int low,int mid,int high)
{//将有序表R[low. .mid]和R[mid+1..highJ归并为有序表T[low. .high]
int i = low,j = mid+1,k = low;
while(i <= mid && j <= high){ //将R中记录由小到大地并入T中
if(R[i].key <= R[j].key) T[k++]=R[i++];
else T[k++] = R[j++];
}//while
while(i <= mid) T[k++] = R[i++]; //将剩余的R[i..mid]复制到T中
while(j <= high) T[k++]=R[j++]; //将剩余的R[j.high]复制到T中
}
//进行归并排序递归处理
void MSort(RedType R[],RedType T[],int loow,int high)
{//R[low..high]归并排序后放入T[low..high]
int mid;
if(low == high) T[low] = R[low];
else{
mid = (low + high)/2; //将当前序列一分为二,求出分裂点mid
MSort(R,S,low,mid);
MSort(R,S,mid+1,high);
Merge(S,T,low,mid,high);
}//else
}
//对顺序表L做归并排序
void MergeSort(SqList &L)
{
MSort(L.r,L.r,1,L.length);
}
算法特点:
(1)稳定排序;
(2)可用于顺序存储结构,也可用于链式存储结构,且不需要附加存储空间,但递归实现时仍需要开辟相应的递归工作栈;
(3)当n较大、关键字基本有序时,可采用堆排序;
(4)时间复杂度: O(nlog₂n); 空间复杂度: O(n).
分配类
基数排序
假设记录的逻辑关键字由d个“关键字”组成,每个关键字可能取rd个值。只要从最低数位关键字起,按关键字的不同值将序列中记录“分配”到rd个队列中后再“收集”之,如此重复d次完成排序。按这种方法实现排序称之为基数排序,其中“基”指的是rd的取值范围。
实例说明:{278,109,063,930,589,184,505,269,008,083}排序
(1) “建立分别表示0-9的队列”(先进先出)
(2) 第一趟分配对最低数位关键字,分配到各个队列;分配完成后收集各队列队尾元素,重新链成一个链表;
(3)第二趟分配和收集对次关键字(十位数)进行,过程与个位数相同;
(4)第三趟分配和收集对百位数进行,过程同上。至此排序完成。
代码如下:
基数排序,从小到大,下标从1开始
void RadixSort(int num[], int n) {
int max = num[1], i,j,k, count = 0, *temp[10];
for(i = 2; i <= n; i++) { //找最大数
if(max < num[i]) {
max = num[i];
}
}
//printf("%d",max);
while(max) { //确定最大位数
count++;
max = max/10;
}
//printf("%d",count);
for(i = 0; i < 10; i++) {
temp[i] = (int *)malloc(sizeof(int) * (n+1));
temp[i][0] = 0;
}
max = 1;
while(count--) {
for(i = 1; i <= n; i++) { //分桶
temp[num[i]/max%10][0]++;
temp[num[i]/max%10][temp[num[i]/max%10][0]] = num[i];
}
k = 1;
for(i = 0; i < 10; i++) { //合并
for(j = 1; j <= temp[i][0]; j++) {
num[k++] = temp[i][j];
}
temp[i][0] = 0;
}
// for(i = 1; i <= n; i++){//找最大数
// printf("%5d", num[i]);
// }
max *= 10;
}
for(i = 0; i < 10; i++) {
free(temp[i]);
}
}
算法特点:
(1)稳定排序;
(2)可用于链式和顺序结构;
(3)适用于n值较大但是关键字较小的序列,需要知道各级关键字的主次关系和各级关键字的取值范围;
(4)时间复杂度: O(d(n+rd))【每个记录含d个关键字,关键字的取值范围为rd个值】,可以突破基于关键字比较一类方法的下界O(nlog₂n),达到O(n);空间复杂度: O(n+rd).
后续博客中会附有上述排序算法完整代码(C语言),仅供参考,部分不足之处敬请谅解。