資料結構與演算法 排序演算法 快速排序【詳細步驟圖解】

47的菠蘿~發表於2020-05-22

快速排序

給定一個序列:22 33 49 47 33' 12 68 29

進行快速排序

主要思想

  • 從序列中,任選一個記錄k作為軸值 pivot

    選擇策略:

    • 第一個元素
    • 最後一個元素
    • 中間元素
    • 隨機選擇
  • 將剩餘的元素,分割成 左子序列 L右子序列 R

  • L 中所有元素都 < k, R 中所有元素都 > k

  • 對 L 和 R遞迴進行快排,直到子序列中有 0 個 或者 1 個元素,退出

圖解

初始陣列:

選定47為軸值pivot

image-20200522200024944

pivot與最後一個值29進行交換(pivot放到最後面

image-20200522200152016

接下來,以pivot=47為界,分成左子序列 L 和右子序列 R

47大的都放在右邊,比47小的都放在左邊(用的交換)

遍歷陣列

  • 兩個指標leftright
  • left != right的時候
    • arr[left]的,小於等於pivot,且left < right的時候,left右移
      • 如果leftright未相遇,把left的值賦給right對應的值
      • arr[right] = arr[left]
      • left指標停止移動,輪到right移動
    • arr[right]的值,大於等於pivot,且right > left的時候,right左移
      • 如果leftright未相遇。把right的值賦給left對應的值
      • arr[left] = arr[right]
      • right指標停止移動,輪到left移動
  • 注意:軸值用pivot儲存

第一輪分割序列

image-20200522213014584

pivot=47和最後一個值互換

image-20200522205447912

22 <= 47left向右移動

image-20200522205623534

33 <= 47left向右移動

image-20200522205648022

49 > 47,不滿足arr[left] <= pivot

left的值賦給right

arr[right] = arr[left]

image-20200522210144784

賦值過後,left不動,right向左移動

image-20200522211704648

68 >= 47,right向左移動

image-20200522211823993

12 < 47,不滿足arr[right] >= pivot

right的值賦給left

arr[left] = arr[right]

image-20200522211941103

賦值過後,right不動,left向右移動

image-20200522212237784

29 < 47left向右移動

image-20200522212313112

33' < 47left向右移動

image-20200522215707618

向右移動後,left == right,退出迴圈

pivot賦給arr[left]

image-20200522215538138

至此,第一輪分割序列完成

第二輪分割序列 --- 左子序列

經過第一輪分割,47左邊的是左子序列,右邊是右子序列

第二輪對左子序列分割,選擇中間值作為pivot

image-20200522223949257

12和33'進行交換

image-20200522220137128

22 > 12,不滿足arr[left] <= pivot

arr[left]賦給arr[right]

arr[right] = arr[left]

image-20200522220327264

賦值過後,left不動,right向左移動

29、33'、33都比12大,所以right一直移動到下圖位置

image-20200522220446889

33 > 12,right繼續向左移動

image-20200522220515361

此時right == left,終止迴圈

pivot賦給arr[left]

image-20200522220557616

至此,左子序列1也分割完成了

小結

快排就是一個遞迴的過程,分割得到左子序列

再對左子序列進行快排分割,得到左子序列的左子序列....

處理完左邊,再去處理右邊的右子序列

第三輪分割序列 --- 右子序列

右子序列只有47、68、49,選擇48作為軸值 pivot

image-20200522220854832

pivot和最後一個值交換

image-20200522221012928

47、49都比pivot=68小,left一直向右移動,直到left == right

image-20200522221104705

分割之後,只剩下左子序列:47、49

47、49,選49作為軸值,得到左子序列47

子序列只剩下一個元素47,就不必排序了,右邊排序結束

結果:47、49、68

C++實現

選擇中間的值作為軸值

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <stack>
#include <cmath>
#include <map>

using namespace std;


/**
 *
 * @param arr   待分割的序列
 * @param left  左邊界
 * @param right 右邊界
 * @return      分割後軸值的位置
 */

template<class T>
int PartitionArr(vector<T>& arr, int left, int right) {
    T temp = arr[right];
    while (left != right) {
        while (arr[left] <= temp && left < right) {
            left++;
        }
        if (left < right) {
            arr[right] = arr[left];
            // 賦值後,left不動,right向左移
            right--;
        }
        while (arr[right] >= temp && right > left) {
            right--;
        }
        if (left < right) {
            arr[left] = arr[right];
            // 賦值後,right不動,left向右移
            left++;
        }
    }
    // 當left == right,把軸值放回left上
    arr[left] = temp;
    return left;
}

/**
 *
 * @param arr   待排序陣列
 * @param left  左邊界
 * @param right 右邊界
 */
template<class T>
void quickSort(vector<T>& arr, int left, int right) {
    // 子序列剩下0或1個元素,排序結束
    if (right <= left) {
        return;
    }

    //  選擇陣列中間作為軸值
    int pivot = (left + right) / 2;
    // 把軸值放到陣列最後面
    swap(arr[right], arr[pivot]);
    // 分割後軸值的位置
    // 分割後,左邊值 < 軸值 < 右邊值
    pivot = PartitionArr(arr, left, right);
    quickSort(arr, left, pivot - 1);
    quickSort(arr, pivot + 1, right);
}


int main() {
    vector<int> arr = { 22,33,49,47,33,12,68,29 };
    for (auto& i : arr) {
        cout << i << ' ';
    }

    cout << endl << endl;

    quickSort(arr, 0, arr.size() - 1);

    for (auto& i : arr) {
        cout << i << ' ';
    }

    cout << endl << endl;
    system("pause");
    return 0;
}

image-20200522223306345

總結

  • 快排是不穩定的排序演算法

    • 33 33'排序後可能變成33' 33
  • 時間複雜度:

    • 平均:\(O(Nlog_N)\)
    • 最差:\(O(N^2)\),退化為氣泡排序
  • 空間複雜度:

    • 遞迴呼叫消耗棧空間
    • 最優:\(O(log_N)\)
    • 最差:\(O(N)\),退化為氣泡排序

相關文章