数据结构——排序(Part5快速排序)

(图源:大话数据结构)

好文分享:排序算法解析: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);

}

之前我们可以看出:

 找到一个元素的合适位置之后,分别对左右两侧的序列分别排序。

但是新的优化,非常类似于树的中序遍历

随着递归的不断进行,先排完所所有的低位,再去排列高位。

                                       

                                

从调用结构上可以看出,先处理低位的序列,然后去处理高位的序列 

整个过程对内存的占用也很少。(相比以前)

发布了85 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41605114/article/details/104847422