(图源:大话数据结构)
好文分享:排序算法解析:https://blog.csdn.net/kexuanxiu1163/article/details/103051357
0准备工作
保存排序内容的自定义结构体,其中顺序表的长度,不算哨兵(下标为零的部分)
#define MAX 10
typedef struct SqlistData
{
int r[MAX+1];//把r[0]空出来当哨兵或者临时变量
int length;//记录长度,因为0被空出来了,所以长度和下标此时统一了
}SqList;
交换接口
void swap(SqList * L,int i,int j)//交换
{
int temp = L->r[i];
L->r[i] = L->r[j];
L->r[j] = temp;
}
打印接口
void print(SqList * L)
{
qDebug()<<"开始打印";
for(int i = 1;i<=L->length;++i)
{
qDebug()<<L->r[i];
}
}
顺序表的储存结构如下:
零位下标什么也不储存,所以无论是遍历还是排序,都是从下标1开始,顺序表长度为5,小标和顺序表中的元素序号完全一致。
1快速排序
函数思路也非常简单,两个游标,low和high
low指向最低位,high指向最高位
将低位的元素给哨兵r【0】,高位和哨兵比较,高位大(或等于),合理,则保持高位元素的位置不变,high游标降低一位,继续比较直到遇到一个比哨兵小的值,跳出循环,这个值比哨兵小,理应在低位,和低位进行交换。
低位同理,低位和哨兵比较,低位小(或等于),合理,保持低位元素的位置不变,low游标升高一位,继续比较,直到遇到一个比哨兵大的元素,这个元素显然应该在高位,跳出循环,和高位交换。
快速排序还有一个精髓的地方,就是先判断高位,一点high和low交换后,low指向的元素值,一定小于哨兵,如果这个值等于或大于哨兵,high和low就不会交换,所以之后再次判断low的位置的时候,一定会加1的(也就是说一定会至少进入一次第二个while判断)。
void QSort(SqList * L,int low,int high)
{
int pivot;
if(low<high)//判断条件是为了停止递归的条件,高低位相等的时候就是都指向同一个位置的时候,此时就已经排好序了
{
pivot = Partition(L,low,high);
QSort(L,low,pivot-1);
QSort(L,pivot+1,high);
}
}
int Partition(SqList * L,int low,int high)
{
L->r[0] = L->r[low];
while(low<high)
{
while(low<high&&L->r[high]>=L->r[0])//直到遇见在高位但是比哨兵小的,break,交换
high--;
swap(L,low,high);
while(low<high&&L->r[low]<=L->r[0])//直到遇见在低位但是比哨兵大的,break,交换
low++;
swap(L,low,high);
}
return low;
}
操作:
SqList Data3 = {{0,50,10,90,30,70,40,80,60,20},9};
// SqList Data3 = {{0,50,10},2};
print(&Data3);
QSort(&Data3,1,Data3.length);
qDebug()<<"简单的快速排序后";
print(&Data3);
输出:
归并算法的精髓是放散之后,对分散的元素进行逐个排序。典型的分治法
快速排序则是对双游标的灵活应用,每次排序,都将该元素放置于最合适的地方,每次递归都会排好一个元素。
2快速排序的优化
1,优化选取哨兵
综上可以看出,最大最小都是雷,需要尽可能的避开。
那么这个位于L->r[0]的哨兵到底应该如何选择。
采用三数取中(median-of-three)法,即取三个关键字先进行排序,将中间数作为枢轴,一般是取左端,右端还有中间三个数,
当然也可以随机选取,这样虽然达不到最优,但是起码能做到避开最大值或者最小值。
又因为整个序列是无序的,所以无所谓怎么取这三个值,随机选取就产生随机数这个部分就有开销,尽量避免使用随机选取。
void QSort_MOT(SqList * L,int low,int high)
{
int pivot;
if(low<high)//判断条件是为了停止递归的条件,高低位相等的时候就是都指向同一个位置的时候,此时就已经排好序了
{
pivot = Partition_MOT(L,low,high);
QSort_MOT(L,low,pivot-1);
QSort_MOT(L,pivot+1,high);
}
}
int Partition_MOT(SqList * L,int low,int high)
{
//挑选中间值放置于low位
int m = low + (high - low)/2;
if(L->r[low]>L->r[high])//最大放置high位置
swap(L,low,high);
if(L->r[m]>L->r[high])//最大放置high位置
swap(L,m,high);
if(L->r[m]>L->r[low])//现在m,low,high三个数值中的中间值放置于low为,如果low比m小,换,如果low大于m,则刚好。
swap(L,m,low);
//挑选中间值放置于low位
L->r[0] = L->r[low];
qDebug()<<"中间值:"<<L->r[0];
while(low<high)
{
while(low<high&&L->r[high]>=L->r[0])//直到遇见在高位但是比哨兵小的,break,交换
high--;
swap(L,low,high);
while(low<high&&L->r[low]<=L->r[0])//直到遇见在低位但是比哨兵大的,break,交换
low++;
swap(L,low,high);
}
return low;
}
相比之前,增加了一段中间数的选择:
//挑选中间值放置于low位
int m = low + (high - low)/2;
if(L->r[low]>L->r[high])//最大放置high位置
swap(L,low,high);
if(L->r[m]>L->r[high])//最大放置high位置
swap(L,m,high);
if(L->r[m]>L->r[low])//现在m,low,high三个数值中的中间值放置于low为,如果low比m小,换,如果low大于m,则刚好。
swap(L,m,low);
//挑选中间值放置于low位
具体操作:
SqList Data3 = {{0,50,10,90,30,70,40,80,60,20},9};
print(&Data3);
QSort_MOT(&Data3,1,Data3.length);
qDebug()<<"简单的快速排序(优化后)后";
print(&Data3);
输出:
2优化不必要的交换
对程序中的
用替换代替交换,也是在多种算法中出现的一种策略,只要游标是不断移动的,就可以使用这种方式。
int Partition_Cover(SqList * L,int low,int high)
{
//挑选中间值放置于low位
int m = low + (high - low)/2;
if(L->r[low]>L->r[high])//最大放置high位置
swap(L,low,high);
if(L->r[m]>L->r[high])//最大放置high位置
swap(L,m,high);
if(L->r[m]>L->r[low])//现在m,low,high三个数值中的中间值放置于low为,如果low比m小,换,如果low大于m,则刚好。
swap(L,m,low);
//挑选中间值放置于low位
L->r[0] = L->r[low];
qDebug()<<"中间值:"<<L->r[0];
while(low<high)
{
while(low<high&&L->r[high]>=L->r[0])//直到遇见在高位但是比哨兵小的,break,交换
high--;
L->r[low] = L->r[high];
while(low<high&&L->r[low]<=L->r[0])//直到遇见在低位但是比哨兵大的,break,交换
low++;
L->r[high] = L->r[low];
}
L->r[low] = L->r[0];
return low;
}
操作及输出:
SqList Data3 = {{0,50,10,90,30,70,40,80,60,20},9};
print(&Data3);
QSort_Cover(&Data3,1,Data3.length);
qDebug()<<"简单的快速排序(覆盖版本)后";
print(&Data3);
3优化小数组时候的排序方案
对QSort函数进行限制,避免大炮打苍蝇的情况发生,当序列长度小于一定值的时候,用插入排序。
void QSortWithFlag(SqList * L,int low,int high)
{
int pivot;
if((high-low)>Flag)
{
if(low<high)//判断条件是为了停止递归的条件,高低位相等的时候就是都指向同一个位置的时候,此时就已经排好序了
{
pivot = Partition(L,low,high);
QSortWithFlag(L,low,pivot-1);
QSortWithFlag(L,pivot+1,high);
}
}
else
InsertSort(L);
}
当我们认为小于Flag时,直接插入排序完成对小序列的排序,大序列的排序交给快速排序。
4优化递归操作
对递归操作进行优化,递归操作对内存的要求比较高,因为要累计压栈,非常麻烦,
对QSort进行尾递归优化:
void QSort_Recursive(SqList * L,int low,int high)
{
int pivot;
if((high-low)>7)
{
while(low<high)//判断条件是为了停止递归的条件,高低位相等的时候就是都指向同一个位置的时候,此时就已经排好序了
{
pivot = Partition(L,low,high);
QSort_Recursive(L,low,pivot-1);
low = pivot+1;
}
}
else
InsertSort(L);
}
之前我们可以看出:
找到一个元素的合适位置之后,分别对左右两侧的序列分别排序。
但是新的优化,非常类似于树的中序遍历
随着递归的不断进行,先排完所所有的低位,再去排列高位。
从调用结构上可以看出,先处理低位的序列,然后去处理高位的序列
整个过程对内存的占用也很少。(相比以前)