【数据结构】——快排详解

上一篇文章我们介绍了八大排序中的七种,今天这篇文章主要来详细介绍一种比较重要也是常用的一种排序算法——快速排序~

一、什么是快排?

快速排序是一种二叉树结构的交换排序方法。相当于是冒泡排序的一种升级,都是属于交换排序类,通过不断比较和移动交换来实现排序

其基本思想是:任意取待排序元素序列中的某个元素作为基准值,按照这个排序码把待排序集合分割成两个子序列,左子序列中所有元素都小于该基准值,右子序列中所有元素都大于该基准值。然后左右两边子序列又重复该过程,直到所有元素都排列在相应的位置上为止。

二、快排的实现

1、对顺序表L作快速排序

void QuickSort(sqList *L)
{
 QSort(L,1,L->length);
}

2、由于需要递归调用,因此我们外封装了一个函数,下面是Sort的实现

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);
 }
}

3、这段代码的核心部分是pivot = Partition(L,low,high);其要做的就是先选取当中的一个关键字,然后想尽办法把它放到第一个位置,使得他左边的值都比他小,右边的值都比他大。

比如说:数组值为[50,10,90,30,70,40,80,60,20]经过Partition(L,1,9)执行后,数组变成{20,10,40,30,50,70,80,60,90},并返回值5给pivot,然后递归调用QSort(L,1,5-1)QSort(L,5+1,9)语句,其实就是在对{20,10,40,30}{70,80,60,90}分别进行同样的Partition操作,知道顺序全部正确为止。

函数实现:

int Partition(SqList *L,int low,int high)
{
 int pivotkey;
 pivotkey = L->r[low];//用子表的第一个记录作为枢轴记录
 while(low < high)
 {
  while(low < high && L->r[high] >= pivotkey)
   high--;
  swap(L,low,high);//将比枢轴记录小的记录交换到低端
  while(low < high && L->r[high] <= pivotkey)
   low++;
  swap(L,low,high);//将比枢轴记录大的记录交换到高端
 }
 return low;//返回枢轴所在位置
}

代码讲解:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、快排的时间复杂度分析

快排的时间性能取决于快排递归的深度。可以用递归树来描述递归算法的执行情况。如果排序n个关键字,其递归树的深度就是【log2n】+1,在最优的情况下,快速排序的**时间复杂度是O(nlogn)**空间复杂度是O(logn)

四、快排的优化

1、优化选取枢轴
如果我们选取的pivotkey是处在整个序列的中间位置,那么我们可以将整个序列分成小数集合和大数集合。但是,假如我们选取的pivotkey不是中间数又如何呢?——可能导致整个系列没有一个实质性的变化。所以pivotkey = L->r[low];变成了一个潜在的性能瓶颈。
于是,我们采取了三数取中的方法,即三个关键字进行排序,将中间数作为枢轴,一般取左端、右端和中间三个数。
代码实现:

nt pivotkey;
int m = low +(high-low)/2;//计算数组中间的元素的下标
if(L->r[low] > L->r[high])
 swap(L,low,high);
if(L->r[m] > L->r[high])
 swap(L,high,m);
if(L->r[m] > L->r[low])
 swap(L,m,low);

2、优化不必要的交换
我们发现,50这个关键字,其位置变化是1->9->3->6->5,可其实他的最终目标是5,当中的交换其实是不需要的。
所以我们对此进行优化:

int Partition(SqList *L,int low,int high)
{
 int pivotkey;
    //这里省略三数取中代码
 pivotkey = L->r[low];
 L->r[0] = pivotkey;//将枢轴关键字备份到L->r[0]
 while(low < high)
 {
  while(low < high && L->r[high] >= pivotkey)
   high--;
  L->r[low] = L->r[high];//采用替换而不是交换的方式进行操作
  while(low < high && L->r[low] <= pivotkey)
   low++;
  L->r[high] = L->r[low];
 }
 L->r[low] = L->r[0];
 return low;//返回枢轴所在位置
}

3、优化小数组时的排序方案
如果数组非常小的情况下,快速排序反而不如直接插入排序来得更好(直接插入排序是简单排序中性能最好的)。
因此改进QSort函数:

#define MAX_LENGTH_INSERT_SORT 7
void QSort(sqList *L,int low,int high)
{
 int pivot;
 //当high-low大于常数时用快排
 if((high - low) > MAX_LENGTH_INSERT_SORT)
 {
  pivot = Partition(L,low,high);
  QSort(L,low,pivot-1);
  QSort(L,pivot+1,high);
 }
 else
  InsertSort(L);

4、优化递归操作
递归对性能有一定影响,栈的大小是有限的,每一次递归调用都会耗费一定的栈空间,函数的参数越多,每次递归耗费的口空间也越多。
对QSort实施尾递归优化:

#define MAX_LENGTH_INSERT_SORT 7
void QSort(sqList *L,int low,int high)
{
 int pivot;
 //当high-low大于常数时用快排
 if((high - low) > MAX_LENGTH_INSERT_SORT)
 {
  while(low<high)
  {
   pivot = Partition(L,low,high);
   QSort(L,low,pivot-1);
   low = pivot + 1;
  }
 }
 else
  InsertSort(L);
}

将if改成while后,因为第一次递归以后,变量low就没有用处了,所以可以将pivot + 1的值赋值给low,再循环后Partition(L,low,high)的效果等同于Partition(L,pivot + 1,high)。
因此采用迭代而不是递归的方法可以缩减堆栈深度,从而提高了整体性能。

发布了62 篇原创文章 · 获赞 7 · 访问量 2580

猜你喜欢

转载自blog.csdn.net/qq_43412060/article/details/104373595