算法分析与设计:快速排序

快速排序(Quick Sort)

  • 基于分治策略
  • 核心思想:递归地将待排数组分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

步骤:
  1. 分解:以 a [ s t a r t ] a[start] a[start] 为基准元素,将 a [ s t a r t , e n d ] a[start,end] a[start,end] 划分成3段( a [ s t a r t , q − 1 ] , a [ q ] , a [ q + 1 , e n d ] a[start,q-1], a[q],a[q+1,end] a[start,q1],a[q],a[q+1,end]),使得 a [ s t a r t , q − 1 ] a[start,q-1] a[start,q1] 中的任何元素都小于等于 a [ q ] a[q] a[q],而 [ q + 1 , e n d ] [q+1,end] [q+1,end] 中的任何元素都大于等于 a [ q ] a[q] a[q]。下标 q q q 是在划分的过程中由 a [ s t a r t ] a[start] a[start] 的值决定的。
  2. 递归求解:通过递归,分别对 a [ s t a r t , q − 1 ] a[start,q-1] a[start,q1] a [ q + 1 , e n d ] a[q+1,end] a[q+1,end] 再次进行快速排序的划分。
  3. 合并:因为对 a [ s t a r t , q − 1 ] a[start,q-1] a[start,q1] a [ q + 1 , e n d ] a[q+1,end] a[q+1,end] 的排序是就地进行的,因此他们都排好序后不需要特意进行合并计算, a [ s t a r t , e n d ] a[start,end] a[start,end] 则已经排好序。

C++ 代码

#include <iostream>
using namespace std;
/*******************************************************************************
 * 快速排序(Quick Sort)
       将待排数组分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则
    可分别对这两部分记录继续进行排序,以达到整个序列有序。
********************************************************************************/
// 快速排序核心算法——Partition:
int Partition(int array[], int start, int end)
{
    int i = start, j = end + 1;
    // 将start暂存在x里面,顺便作为基准元素
    int x = array[start];
    // 将小于x的元素交换到左边区域,将大于x的元素交换到右边区域
    while (true)
    {
        while (array[++i] < x && i < end)
            ; // 找到小于基准元素的第一个数
        while (array[--j] > x)
            ; // 找到大于基准元素的第一个数
        if (i >= j)
            break; // 小于基准元素的第一个数在大于基准元素的第一个数的右边,说明划分完毕
        swap(array[i], array[j]);
    }
    array[start] = array[j]; // 将中间位置的元素放在第一个
    array[j] = x;            // 将刚才暂存在x中的start(基准元素放在中间)
    return j;                // 返回中间位置索引
}

int *QuickSort(int array[], int start, int end)
{
    int q = Partition(array, start, end);
    if (q > start)
        QuickSort(array, start, q - 1);
    if (q < end)
        QuickSort(array, q + 1, end);
    return array;
}


快速排序核心算法——Partition

Partition 对 a [ s t a r t , e n d ] a[start,end] a[start,end] 进行划分的时候,以元素 x = a [ s t a r t ] x=a[start] x=a[start] 作为划分的基准,分别从左、右两端开始扩展两个区域 a [ s t a r t , i ] a[start,i] a[start,i] a [ j , e n d ] a[j,end] a[j,end],使 a [ s t a r t , i ] a[start,i] a[start,i] 中的元素小于等于 x x x,而 a [ j , e n d ] a[j,end] a[j,end] 中元素均大于等于 x x x,初始时, i = s t a r t , j = e n d + 1 i=start, j=end+1 i=start,j=end+1

在循环体中,下标 j j j 逐渐减小, i i i 逐渐增大,直到 a [ i ] ≥ x ≥ a [ j ] a[i] \geq x \geq a[j] a[i]xa[j]。如果这两个不等式是严格 > > > 的,此时若 i < j i<j i<j,就应该交换 a [ i ] a[i] a[i] a [ j ] a[j] a[j] 的位置。

w h i l e while while 循环重复直至 i ≥ j i \geq j ij 时结束,这时 a [ s t a r t , e n d ] a[start,end] a[start,end] 已经被进行划分成 a [ s t a r t , q − 1 ] , a [ q ] , a [ q + 1 , e n d ] a[start,q-1], a[q],a[q+1,end] a[start,q1],a[q],a[q+1,end] 三段。在Partition 结束时,返回划分点 q = j q=j q=j

注意:
  • 算法中的下标 i i i j j j 不能超出 a [ s t a r t , e n d ] a[start,end] a[start,end] 的下标界限。
  • 在快速排序算法中选取 a [ s t a r t ] a[start] a[start] 作为基准可以保证算法的正常结束。如果选用 a [ e n d ] a[end] a[end] 作为基准则有可能会陷入死循环。(当 a [ e n d ] a[end] a[end] a [ s t a r t , e n d ] a[start,end] a[start,end] 的最大元素时)

时间复杂度分析

Partition 部分的计算复杂度为 O ( e n d − s t a r t − 1 ) O(end-start-1) O(endstart1),是 O ( n ) O(n) O(n) 复杂度。

【快速排序的运行时间与划分的对称性有关。】

最坏情况——不对称划分

基准元素都是很不巧是两端的极值,则时间复杂度如下:
T ( n ) = { O ( 1 ) n ⩽ 1 T ( n − 1 ) + O ( n ) n > 1 T(n)=\left\{\begin{array}{ll} O(1) & n \leqslant 1 \\ T(n-1)+O(n) & n>1 \end{array}\right. T(n)={O(1)T(n1)+O(n)n1n>1
解得: T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)

最好情况——对称划分

基准元素都是很巧是中值,则时间复杂度如下:
T ( n ) = { O ( 1 ) n ⩽ 1 2 T ( n / 2 ) + O ( n ) n > 1 T(n)=\left\{\begin{array}{ll} O(1) & n \leqslant 1 \\ 2T(n/2)+O(n) & n>1 \end{array}\right. T(n)={O(1)2T(n/2)+O(n)n1n>1
解得: T ( n ) = O ( n log ⁡ n ) T(n)=O(n\log n) T(n)=O(nlogn)


算法改进——基准元素随机化

改进的思想很简单,就是为了避免上述的最坏情况,防止遇到每次都选到极值元素的倒霉蛋。在数组还没被划分时,在 a [ s t a r t , e n d ] a[start,end] a[start,end]随机选出一个元素作为划分基准。


如果想了解其他排序算法可见:算法分析与设计:7大排序算法大汇总(C++)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值