快速排序(Quick Sort)

快速排序是对冒泡排序的一种改进,由 C.A.R.Hoare(Charles Antony Richard Hoare,东尼·霍尔)在1962年提出。快速排序是相同数量级的排序算法中,平均性能最好的算法,是内部排序实现的优选算法。

基本思想

快速排序的基本思想是,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。
从每次划分的结果来看,其思想还是冒泡:将小于中枢值的元素冒泡到一侧,将大于中枢值的元素冒泡到另一侧。与冒泡排序每次只能交换相邻的两个元素不同的是,快速排序是跳跃式的交换,交换的距离很大,因此总的比较和交换次数少了很多,速度也快了不少。
和归并排序一样,快速排序也是基于分治技术实现的算法。与归并排序按照元素在列表中的位置进行划分不同,快速排序是按照元素的值进行划分

伪代码实现

快速排序的核心操作是选择一个元素,对列表中的元素进行划分。因为一次划分,就有一个元素落到最终位置,所以快速排序无需进行合并操作,只需递推的划分列表,直到待划分列表的长度变为1。假设n个元素使用数组存储,快速排序的伪代码实现如下:

假设数组A[0...n-1]表示待排序数组
QuickSort(A[0...n-1])
    QuickSort(A[0...n-1], 0, n-1)

QuickSort(A[0...n-1], l, r)
    // 回归条件:待排序区间小于等于0(只有一个元素)
    if(l >= r)
        return
    pivot = Partition(A[0...n-1], l, r)
    // 递推
    QuickSort(A[0...n-1], l, pivot)
    QuickSort(A[0...n-1], pivot, r)

Partition(A[0...n-1], low, high) 
    // 注意:这里使用待划分数组的第一个元素作为中轴元素
    pivot = A[low]
    while(low < high) then
        while(A[high]>=pivot) then 
            high--;
        Swap(A[0...n-1], high, low) 
        while(A[low]<=pivot) then
            low++;
        Swap(A[0...n-1], low, high)
    return low
Swap(A[0...n-1], src, dst) 
    tmp = A[src]
    A[src] = A[dst]
    A[dst] = tmp

接下来分析该算法的执行效率。根据算法效率分析一文,算法的执行效率受增长次数的影响。显然,在不同的场景下,Partition和QuickSort增长次数可能不同。考虑以下场景,如果每次Partition都位于子数组的中点,那么Partition的执行效率降为 O ( 1 ) O(1) O(1),该算法的执行效率将主要受递归式( O ( l o g 2 n ) O(log_2^n) O(log2n))影响。根据分治法一文,此时可以采用主定理(快速排序是基于分治技术实现的算法)分析其执行效率。基于比较次数 C ( n ) C(n) C(n)的递推关系式如下:
当 n > 1 时 , C ( n ) = 2 C ( n / 2 ) + n 当n>1时,C(n) = 2C(n/2) + n n>1C(n)=2C(n/2)+n
C ( 1 ) = 0 C(1) = 0 C(1)=0
根据代入法,代入主方法公式( T ( n ) = a T ( n / b ) + f ( n ) T(n) = aT(n/b) + f(n) T(n)=aT(n/b)+f(n)),可知a=2,b=2, f ( n ) = n f(n)=n f(n)=n,经计算 n l o g b a = n n{log_b^a} = n nlogba=n。根据主定理 T ( n ) = θ ( n l o g b a l o g k + 1 n ) = θ ( n l o g n ) T(n)= θ(n{log_b^alog^{k+1}n})=θ(nlog n) T(n)=θ(nlogbalogk+1n)=θ(nlogn)
如果每次Partition都处于极端,两个数组有一个子数组为空,而另一个子数组仅比划分的数组少一个元素。当数组已经有序时,会出现这种情况。该场景下,递归式的执行效率降为 O ( 1 ) O(1) O(1),该算法的执行效率将主要受Partition( O ( n ) O(n) O(n))的影响。如果A[0…n-1]是严格递增数组,如果将A[0]作为中枢元素,则从左到右扫描,比较次数是1,从右到左扫描,比较次数是n。依次类推,直到只剩两个待排序元素。此时比较次数是3次。根据求和公式,可得到下面的公式:
C ( n ) = ( n + 1 ) + n + . . . + 3 = ( n + 1 ) + n + . . . + 3 + 2 + 1 − 2 − 1 C(n) = (n+1) + n + ... + 3 = (n+1) + n + ... + 3 + 2 + 1 - 2 -1 C(n)=(n+1)+n+...+3=(n+1)+n+...+3+2+121
C ( n ) = ( n + 1 ) ∗ ( n + 2 ) / 2 − 3 = θ ( n 2 ) C(n) = (n+1)*(n+2)/2 - 3=θ(n^2) C(n)=(n+1)(n+2)/23=θ(n2)
以上两种情况,分别是快速排序的最优执行效率( O ( n l o g n ) O(nlog n) O(nlogn))和最差执行效率( O ( n 2 ) O(n^2) O(n2)),为了了解快速的排序的实用性,还需计算其平均情况下的效率。快速排序的平均执行效率的计算不再本文体现,有兴趣的同学可以阅读《算法设计与分析基础》一书的快速排序一节。从结论来看,快速排序的平均执行效率约为 1.39 n l o g n 1.39nlog n 1.39nlogn,接近最优执行效率。此外,针对快速排序,还有很多的优化策略,这里不再展开。这里给出java版本实现:

public class QuickSort {
    public void sort(int[] array) {
        if (array == null || array.length <= 1) {
            return;
        }
        sort(array, 0, array.length - 1);
    }

    private void sort(int[] array, int left, int right) {
        if (left >= right) {
            return;
        }
        int pivot = partition(array, left, right);
        sort(array, left, pivot - 1);
        sort(array, pivot + 1, right);
    }

    private int partition(int[] array, int low, int high) {
        int pivot = array[low];
        while (low < high) {
            while(low < high && array[high] >= pivot) {
                high--;
            }
            swap(array, high, low);
            while (low < high && array[low] <= pivot) {
                low++;
            }
            swap(array, low, high);
        }
        return low;
    }

    private void swap(int[] array, int srcSubscript, int dstSubscript) {
        if (srcSubscript == dstSubscript) {
            return;
        }
        int temp = array[srcSubscript];
        array[srcSubscript] = array[dstSubscript];
        array[dstSubscript] = temp;
    }
}

总结

快速排序本质是使用分治法实现的冒泡排序。在最坏情况下,快速排序的时间复杂度和冒泡排序一样( n 2 n^2 n2)。而在最优情况下,快速排序的时间复杂度和归并排序的时间复杂度一样( n l o g n nlog n nlogn)。因为快速排序的平均时间复杂度与最优情况下的时间复杂度接近,所以使其成为优选的内部排序算法。

参考

《算法设计与分析基础》 第三版 Anany Levitin 著 潘彦 译
《数据结构 严蔚敏 吴伟民 著
https://leetcode-cn.com/problems/sort-an-array/ 排序数组
http://data.biancheng.net/view/117.html 快速排序算法详解(原理、实现和时间复杂度)
https://blog.csdn.net/wangxufa/article/details/121732018 分治法( Divide and Conquer)
https://blog.csdn.net/wangxufa/article/details/121198034 算法效率分析
https://leetcode-cn.com/problems/sort-an-array/ 排序数组

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值