快速排序

257 阅读2分钟

据说是21世纪算法界最大的发明

快速排序

基本思路

单路快排

下图是一个快速排序的例子,排序过程中将数组分成两端,[l+1...j]<=v,[j+1...i-1]>v。j的初始值 j=l,所以循环不变量成立。 快速排序1.png 实现代码如下:

#include <stdio.h>
#include <vector>
#include <cstdlib>
#include <time.h>

using namespace std;

class QuickSort {
public:
    void sort(vector<int>& arr) {
        int size = (int)arr.size();
        sort(arr, 0, size);
    }
    
    void sort(vector<int>& arr, int i,int j) {
        int index = partition(arr, i, j);
        
        if (index <= 0 || index > j-1) {
            return;
        }
        
        sort(arr, 0, index);
        sort(arr, index+1, j);
    }
    
    int partition(vector<int>& arr, int l, int r) {
        srand((unsigned)time(NULL));
        int randNum = l+(r-l-1)/2;
        
        /// j 指向数组中小于第一个元素的最后一个索引
        int j = l;
        /// 交换元素
        swap(arr, randNum, j);
        
        /// 循环不变量:[l+1...j] <= v, [j+1...i-1] > v
        for (int i = l+1; i < r; i++) {
            if (arr[i] <= arr[l]) {
                swap(arr, i, j+1);
                j++;
            }
        }
        
        /// 循环结束时,将第一个元素和t位置的元素交换
        swap(arr, l, j);
        return j;
    }
    
    void swap(vector<int>& arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
};

容易出错的地方:

  • sort(arr, 0, index) 写成了 sort(arr, 0, index-1),实际在 partition 中,不会处理右边界的元素

  • int randNum = i+(j-i-1)/2 写成了 int randNum = (j-i-1)/2,这样算出来的数比较小,会把已排序的元素直接给交换了 注意事项

  • partition中函数入参,最好写成l,r,这样比较贴切,因为递归排序的内容不确定坐标,只知道起始位置,结束为止

双路快排

基本思路:前面的元素遇到比v大的元素停下,后面的元素遇到小于等于v的元素停下,如果此时 j>i,则交换元素。

交换元素后,在循环外面一定注意把第一个元素和j位置的元素进行交换。
为什么需要双路快速排序?是因为当有大量重复元素时,单路快排的时间复杂度会退化成O(n^2)

快速排序2.png 实现代码如下:

#include <stdio.h>
#include <vector>
#include <cstdlib>
#include <time.h>

using namespace std;

class QuickSortExe {
public:
    void sort(vector<int>& arr) {
        int size = (int)arr.size();
        sort(arr, 0, size-1);
    }
    
    void sort(vector<int>& arr, int l,int r) {
        if (l>=r) {
            return;
        }
        
        int index = partition2(arr, l, r);
        
        sort(arr, 0, index-1);
        sort(arr, index+1, r);
    }
    
    /// 双路快排
    int partition2(vector<int>& arr, int l, int r) {
        int randNum = l + (r-l)/2;
        swap(arr, l, randNum);
        
        int i = l + 1;
        int j = r;
        while (true) {
            /// i<=j 也可以替换成 i<=r、i<arr.size
            while (i<=j && arr[i] < arr[l]) {
                i++;
            }
            
            /// 看到有不同风格的写法:j>=i && arr[j] > arr[l],实际上 j>=i 可以不要
            while (arr[j] > arr[l]) {
                j--;
            }
            
            /// 这里要=是因为,最后剩余的一个元素正好等于v,则结束,在循环外层进行交换
            if (i>=j) {
                break;
            }
            
            swap(arr, i, j);
            i++;
            j--;
        }
        
        swap(arr, l, j);
        return j;
    }
    
    void swap(vector<int>& arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
};

三路快排

(待实现)