快速排序总结

快速排序由C. A. R. Hoare在1960年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
(1)首先设定一个分界值(这个分界值用枢纽来表示,pivot),通过该分界值将数组分成左右两部分
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

所以这里主要讲解分界值的几种选择方式:
①将数组的第一个元素作为枢纽pivot:
代码流程:

  • 定义一个临时变量pivot,将数组的第一个元素赋值给这个变量pivot
  • 定义两个变量i,j,表示这个数组的起点、终点对应的下标.
  • 先开始从右往左遍历,找到第一个小于pivot的数字arr[j],然后将这个数赋值给arr[i]
  • 从左往右开始遍历,找到哦第一个大于pivot的数字arr[i],然后将这个数赋值给arr[j]
  • 重复上述操作,直到i >= j
  • 将pivot的值赋值给arr[i],这时候我们已经完成了一次快速排序
  • 上诉步骤中的i是中间枢纽下标,即arr[i](即pivot)之前都是小于等于pivot的数,arr[i]之后的都是大于等于pivot的数,从而实现了将一个数组分割成为两个数组,然后对这两个数组进行快速排序(利用递归的方式)。当递归结束之后,整个数组有序。

对应的代码(实现了含有重复数字的快速排序):

#include<stdio.h>
#define MAX_SIZE 1000
int count = 0;//表示进行了多少趟快速排序
void display(int arr[],int len){
  int i;
  for(i = 0; i < len; i++)
    printf("%5d",arr[i]);
  printf("\n");
}
/*
以第一个元素作为中间枢纽:
这时候,从数组的两边开始遍历
1、定义一个临时变量,表示中间枢纽pivot,
2、定义两个下标i,j,其中i = 数组第一个元素的下标low,j = 数组最后一个元素对应的下标high
3、在i < j的前提下,不断进行下面的操作:
    ①从右往左开始遍历,找到第一个小于pivot的数
    找到之后,将这个数赋值给arr[i]
    ②从左往右开始遍历,找到第一个大于等于pivot的数
    找到之后,我们需要判断i是否依旧小于j,如果是,就将
    这个数赋值给arr[j]
4、重复上述的步骤,直到i >= j退出循环,这时候还没有完成,需要将我们定义的pivot赋值给
arr[i],只有这样才会实现一趟快速排序
5、i下标对应的是中间枢纽的下标,它前面都是小于等于pivot的数,后面都是大于等于pivot的数,
这时候我们利用递归的方式,分别对这两个子数组进行快速排序,从而实现了排序
*/
void quickSort2(int arr[],int low,int high,int n){
    if(low < high){
        int pivot,i,j;
        pivot = arr[low];//以数组的第一个数作为枢纽
        i = low;
        j = high;
        while(i < j){
            while(i < j && arr[j] >= pivot) //从右往左开始遍历,直到找到小于pivot的数
                j--;
            if(i < j)
              arr[i] = arr[j];//将小于pivot的数赋值给arr[i],从而实现左边都是小于pivot的数
            while(i < j && arr[i] <= pivot)//从左往右开始遍历,直到找到大于pivot的数
                i++;
            if(i < j) //在没有相遇的时候,将大于pivot的数赋值给arr[j],从而实现右边都是大于pivot的数
              arr[j] = arr[i];
        }
        //退出循环之后,需要将原来的中间枢纽的值赋值给arr[i]
        arr[i] = pivot;
        count++;
        printf("第%d次快速排序:",count);
        display(arr,n);//输出一趟快速排序之后的数组
        //利用递归实现快速排序
        quickSort2(arr,low,i - 1,n);
        quickSort2(arr,i + 1,high,n);
    }
}
int main(){
  int arr[MAX_SIZE],n;
  int i,j;
  scanf("%d",&n);
  for(i = 0; i < n; i++)
    scanf("%d",&arr[i]);
  printf("没有进行排序时:");
  display(arr,n);
  printf("进行快速排序:\n");
  quickSort2(arr,0,n - 1,n);
  return 0;
}

运行结果:
在这里插入图片描述
通过观察可以发现,这样进行排序的话,即使有重复数字,也可以实现快速排序。但是以数组的第一个元素作为中间枢纽真的合适嘛?如果我一开始输入的元素就已经是有序了,那么这时候,最后造成的快速排序最坏的情况,时间复杂度为O(n²),但是由于输入是有序的,实际却没有任何操作。所以使用第一个元素并不是适用于任何情况。

②利用三数中值法获取枢纽。
在这里插入图片描述
对应的代码(实现了含有重复数字的快速排序):


#include<stdio.h>
#define MAX_SIZE 1000
int count = 0;//表示进行了多少趟快速排序
void display(int arr[],int n){
    int i;
    for(i = 0; i < n; i++)
       printf("%5d",arr[i]);
    printf("\n");
}
void swap(int arr[],int i,int j){
    int tmp;
    tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}
int getPivot(int arr[],int low,int high){
      int mid = (low + high) / 2;
      //将首尾及中间的这三个数进行排序,从而实现arr[low] < arr[mid] < arr[high]
      if(arr[low] > arr[mid])
        swap(arr,low,mid);
      if(arr[low] > arr[high])
        swap(arr,low,high);
      if(arr[mid] > arr[high])
        swap(arr,mid,high);
        //将arr[mid]和arr[high - 1]这两个数进行交换,从而实现下标在[1,high - 1]中
      swap(arr,mid,high - 1);
      return arr[high - 1];//返回中间枢纽
}
void quickSort3(int arr[],int low,int high,int n){
     if(low < high){
        //如果low < high的时候,说明这个子数组不止一个元素,所以需要进行快速排序,否则,不需要再次进行快速排序
        int pivot = getPivot(arr,low,high);//利用三数中值法,获取这个数组中的枢纽
        int i,j;
        i = low;
        j = high - 1; //j下标表示中间枢纽的起初下标
        while(i < j){
            while(arr[++i] <pivot); //这里先进行自增,然后再将对应下标的值进行比较,从而保证了含有重复数字的时候不会陷入死循环
            while(arr[--j] > pivot);
            if(i >= j)
                break;
            else
                swap(arr,i,j);//如果不相等,那么就将两者进行交换
        }
        //退出循环之后,需要将arr[i]和arr[high - 1]进行交换,从而实现一次快速排序的完成
        swap(arr,i,high - 1);
        count++;
        printf("第%d次快速排序:\n",count);
        display(arr,n);
        //对两个子数组进行快速排序
        quickSort3(arr,low,i - 1,n);
        quickSort3(arr,i + 1,high,n);
     }
}
int main(){
  int arr[MAX_SIZE],n;
  int i,j;
  scanf("%d",&n);
  for(i = 0; i < n; i++)
    scanf("%d",&arr[i]);
  printf("没有进行排序时:");
  display(arr,n);
  printf("进行快速排序:\n");
  quickSort3(arr,0,n - 1,n);
  return 0;
}

运行结果:
在这里插入图片描述

有小伙伴会问,三数中值法那里获取pivot里面,将数组的首尾及中间的数字进行排序之后,为什么不直接返回arr[mid],而是返回arr[high - 1]?
如果返回是arr[mid]的话,这时候使用quickSort3方法的时候,j的初始值改变成为high,但是即使这样,对应的结果依旧是错误的。就拿20 15 46 23 60 14 74 15 95 86为例,最后得到的并不是有序的。(具体的大家可以去分析一下)

如果返回的是arr[mid],并且quickSort3的代码改成下面的样子可以嘛?
下面的代码只能实现没有重复数字的排序

void quickSort3(int arr[],int low,int high,int n){
     if(low < high){
        //如果low < high的时候,说明这个子数组不止一个元素,所以需要进行快速排序,否则,不需要再次进行快速排序
        int pivot = getPivot(arr,low,high);//利用三数中值法,获取这个数组中的枢纽
        int i,j;
        /*
        返回的pivot是arr[mid]的时候,这时候,代码可以实现没有重复数字的快
        速排序,但是如果含有重复数字,并且这个数字不巧的作为了pivot,那么
        就会陷入死循环中
        */
        i = low + 1;//low下标的数是小于pivot的数
        j = high - 1;//high下标的数是大于pivot的数
        while(i < j){
            while(i < j && arr[i] <pivot)
                i++;
            while(i < j && arr[j] > pivot)
                j--;
            if(i >= j)
                break;
            else
                swap(arr,i,j);//如果不相等,那么就将两者进行交换
        }
        */
        //退出循环之后,需要将arr[i]和arr[high - 1]进行交换,从而实现一次快速排序的完成
        count++;
        printf("第%d次快速排序:\n",count);
        display(arr,n);
        //对两个子数组进行快速排序
        quickSort3(arr,low,i - 1,n);
        quickSort3(arr,i + 1,high,n);
     }
}

运行结果:
在这里插入图片描述
但是如果枢纽重复的情况时,就会陷入死循环中。所以如果使用上面的代码,并且返回的是arr[mid]的时候,那么这时候这个代码只能适用于没有重复数字的快速排序

快速排序的优化:
在这里插入图片描述

int getPivot(int arr[],int low,int high){
      int mid = (low + high) / 2;
      //将首尾及中间的这三个数进行排序,从而实现arr[low] < arr[mid] < arr[high]
      if(arr[low] > arr[mid])
        swap(arr,low,mid);
      if(arr[low] > arr[high])
        swap(arr,low,high);
      if(arr[mid] > arr[high])
        swap(arr,mid,high);
        /*
        如果直接返回arr[mid]的话,那么这时候,如果使用原来返回arr[high - 
        1]的快速排序的代码,那么会造成一定的错误(例如20 15 46 23 60 14 
        74 15 95 86这个例子)如果更改对应的代码,那么虽然可以实现pivot不重
        复的快速排序,但是考虑到数字的随机性,所以
        这样是不构完善的
        */
        //将arr[mid]和arr[high - 1]这两个数进行交换,从而实现下标在[1,high - 1]中
      swap(arr,mid,high - 1);
      return arr[high - 1];//返回中间枢纽
     //return arr[mid];
}
void insertSort(int arr[],int low,int high){
     int i,j,tmp;
     for(i = low + 1; i < high; i++){
        tmp = arr[i];
        for(j = i - 1; j >= 0; j--){
            if(arr[j] <= tmp)
                break;
            arr[j+1] = arr[j];
        }
        arr[j + 1] = tmp;
     }
}
void quickSort3(int arr[],int low,int high,int n){
     if(low + N < high){
        //如果low < high的时候,说明这个子数组不止一个元素,所以需要进行快速排序,否则,不需要再次进行快速排序
        int pivot = getPivot(arr,low,high);//利用三数中值法,获取这个数组中的枢纽
        int i,j;
        i = low;
        j = high - 1; //j下标表示中间枢纽的起初下标
        while(i < j){
            while(i < j && arr[++i] <pivot); //这里先进行自增,然后再将对应下标的值进行比较,从而保证了含有重复数字的时候不会陷入死循环
            while(i < j && arr[--j] > pivot);
            if(i >= j)
                break;
            else
                swap(arr,i,j);//如果不相等,那么就将两者进行交换
        }
        swap(arr,i,high - 1);
        count++;
        printf("第%d次排序:\n",count);
        display(arr,n);
        //对两个子数组进行快速排序
        quickSort3(arr,low,i - 1,n);
        quickSort3(arr,i + 1,high,n);
     }else{
     //当数组的个数少于10个时候,那么就是(小数组)用插入排序
        count++;
        insertSort(arr,low,high);
        printf("第%d次排序:\n",count);
        display(arr,n);
     }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值