快速排序的优化-三值取中法

本文介绍了快速排序的基本原理,重点讨论了如何通过三值取中法优化快速排序,避免在数据有序时退化为冒泡排序,并提供了修改后的代码示例。
摘要由CSDN通过智能技术生成


声明:本篇文章主要是为了讲解快速排序的一种优化方式,对于快速排序的基本原理和初级代码讲解的并不详细。大家有这方面的需求可参考其他文章。

快速排序的基本原理

快速排序又称为“填坑法”,采用“分治”的思想,是经典排序算法之一。
在这里插入图片描述

该方法的基本思想是:
1.先从数列中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。
根据以上原理,我们可以得到如下代码:

#include <stdio.h>
#include <stdlib.h>

int partition(int *x,int low,int high){
     //枢轴的选取与暂存
     x[0]=x[low];
     int pivotkey;
     pivotkey=x[low];
     //填坑操作
     while(low<high){
        while(low<high && x[high]>=pivotkey){
            --high;
        }
        x[low]=x[high];
        while(low<high && x[low]<=pivotkey){
            ++low;
        }
        x[high]=x[low];
     }
     //枢轴的位置
     x[low]=x[0];
     return low;  //返回此位置
}

void Qsort(int *x,int s,int t){
    int pivotloc;
    if(s<t){
        pivotloc=partition(x,s,t);
        //递归:对枢轴两侧的数据进行排序
        Qsort(x,s,pivotloc-1);
        Qsort(x,pivotloc+1,t);
    }
}


int main()
{
    int n;
    scanf("%d",&n);
    int x[n+1];
    for(int i=1;i<n+1;i++){
        scanf("%d",&x[i]);
    }
    
    Qsort(x,1,n);

    printf("%d",x[1]);
    for(int i=2;i<n+1;i++){
        printf(" %d",x[i]);
    }
    return 0;
}

平均时间复杂度: O ( N l o g N ) O(NlogN) O(NlogN)
最佳时间复杂度: O ( N l o g N ) O(NlogN) O(NlogN)
最差时间复杂度: O ( N 2 ) O(N^2) O(N2)

根据快速排序的原理不难发现,快速排序在数据为顺序有序和逆序有序的情况下,性能最差,将退化为冒泡排序,时间复杂度为 O ( N 2 ) O(N^2) O(N2)
采用PTA平台上的一道题目,来进行一下性能测试:
pta-排序
上面的代码运行结果如下:
在这里插入图片描述
三值取中法,是对快速排序一种最常见、最简便的优化方式。

快速排序的优化–三值取中法

所谓“三值取中法”,就是取数据最左端、最右端和中间的三个数的中间大的那个数。但是,假设我们找到了这个值,我们如何用上面的代码去实现呢?
我们注意到,上面的代码取的是数据最左端的元素,如果要保持数据不动,那我们在找到中间值的时候还必须知道它的下标,从而帮助我们来判断是在数据的那个位置,而且这样做就会破坏数据两侧的两个指针的移动规律,因此这种做法不妥。
所以,我们为什么不让数据变动一下呢?将中值元素和数据最左端原本的数据交换一下不就可以了吗?代码仅需做一个功能的添加即可,非常简单清晰:

#include <stdio.h>
#include <stdlib.h>

int partition(int *x,int low,int high){
     int index=getMiddle(x,low,high);   //取中函数,返回中值元素的数组下标
     if(index!=low){
         int temp;
         temp=x[low];
         x[low]=x[index];
         x[index]=temp;
     }
     //仅需加入这几行代码,其余部分都不变
     int pivotkey;
     x[0]=x[low];
     pivotkey=x[low];
     while(low<high){
        while(low<high && x[high]>=pivotkey){
            --high;
        }
        x[low]=x[high];
        while(low<high && x[low]<=pivotkey){
            ++low;
        }
        x[high]=x[low];
     }
     x[low]=x[0];
     return low;
}

void Qsort(int *x,int s,int t){
    int pivotloc;
    if(s<t){
        pivotloc=partition(x,s,t);
        Qsort(x,s,pivotloc-1);
        Qsort(x,pivotloc+1,t);
    }
}


int main()
{
    int n;
    scanf("%d",&n);
    int x[n+1];
    for(int i=1;i<n+1;i++){
        scanf("%d",&x[i]);
    }
    
    Qsort(x,1,n);

    printf("%d",x[1]);
    for(int i=2;i<n+1;i++){
        printf(" %d",x[i]);
    }
    return 0;
}

现在的问题是:取中函数如何设计呢?这个问题基本上等同于三个数比较大小,返回中间那个数字,但是如果我们采用if-else这种分类讨论的思路来写代码,或许十分容易出错,因此我们可以借助求最大值和最小值的函数来设计取中函数,具体代码如下:

int max(int *x,int low,int high){
    return x[low]>x[high]?low:high;
}
int min(int *x,int low,int high){
    return x[low]<x[high]?low:high;
}
// 取中函数
int getMiddle(int *x,int low,int high){
     int mid=(low+high)/2;
     int m=max(x,low,high);
     return min(x,m,mid);
}

这样写出来的代码既简洁明了,又思路清晰,不易出错。基本思路也很简单:两个数字中较大的那个数和第三个数中较小的那个数,一定是中间大的那个数。
采用三值取中法的代码,运行结果如下:
在这里插入图片描述
由此可见:采用这种方法极大的提高了快速排序的速度,避免了快速排序在序列基本有序的情况下退化为冒泡排序。

拓展–由大到小排序

上面的代码都是对数据进行由小到大排序,如果要实现由大到小排序,应该怎么办呢?改动非常小,明白了原理即可,我就不过多解释了,直接上代码:

#include <stdio.h>
#include <stdlib.h>

int max(int *x,int low,int high){
    return x[low]>x[high]?low:high;
}
int min(int *x,int low,int high){
    return x[low]<x[high]?low:high;
}
int getMiddle(int *x,int low,int high){
     int mid=(low+high)/2;
     int m=max(x,low,high);
     return min(x,m,mid);
}

int partition(int *x,int low,int high){
     int index=getMiddle(x,low,high);  
     if(index!=low){
         int temp;
         temp=x[low];
         x[low]=x[index];
         x[index]=temp;
     }
     
     int pivotkey;
     x[0]=x[low];
     pivotkey=x[low];
     while(low<high){
        while(low<high && x[high]<=pivotkey){  // <= 改为 >=
            --high;
        }
        x[low]=x[high];
        while(low<high && x[low]>=pivotkey){    // >= 改为 <=
            ++low;
        }
        x[high]=x[low];
     }
     x[low]=x[0];
     return low;
}

void Qsort(int *x,int s,int t){
    int pivotloc;
    if(s<t){
        pivotloc=partition(x,s,t);
        Qsort(x,s,pivotloc-1);
        Qsort(x,pivotloc+1,t);
    }
}


int main()
{
    int n;
    scanf("%d",&n);
    int x[n+1];
    for(int i=1;i<n+1;i++){
        scanf("%d",&x[i]);
    }
    
    Qsort(x,1,n);

    printf("%d",x[1]);
    for(int i=2;i<n+1;i++){
        printf(" %d",x[i]);
    }
    return 0;
}

以上都是我在复习数据结构时的一些心得和体会,以及一些拓展的思路。第一次写这种博客,如有错误,欢迎大家批评指正!

  • 37
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值