快速排序圖解
給定一個序列:22 33 49 47 33' 12 68 29
進行快速排序
主要思想
-
從序列中,任選一個記錄
k作為軸值pivot選擇策略:
- 第一個元素
- 最后一個元素
- 中間元素
- 隨機選擇
-
將剩余的元素,分割成 左子序列 L 和 右子序列 R
-
L 中所有元素都 < k, R 中所有元素都 > k
-
對 L 和 R遞歸進行快排,直到子序列中有 0 個 或者 1 個元素,退出
圖解
初始數組:
選定47為軸值pivot

pivot與最后一個值29進行交換(把pivot放到最后面)

接下來,以pivot=47為界,分成左子序列 L 和右子序列 R
比47大的都放在右邊,比47小的都放在左邊(用的交換)
遍歷數組
- 兩個指針
left和right - 當
left != right的時候- 若
arr[left]的,小於等於pivot,且left < right的時候,left右移- 如果
left和right未相遇,把left的值賦給right對應的值 arr[right] = arr[left]left指針停止移動,輪到right移動
- 如果
- 當
arr[right]的值,大於等於pivot,且right > left的時候,right左移- 如果
left和right未相遇。把right的值賦給left對應的值 arr[left] = arr[right]right指針停止移動,輪到left移動
- 如果
- 若
- 注意:軸值用
pivot保存
第一輪分割序列

pivot=47和最后一個值互換

22 <= 47,left向右移動

33 <= 47,left向右移動

49 > 47,不滿足arr[left] <= pivot
把left的值賦給right
arr[right] = arr[left]

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

68 >= 47,right向左移動

12 < 47,不滿足arr[right] >= pivot
把right的值賦給left
arr[left] = arr[right]

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

29 < 47,left向右移動

33' < 47,left向右移動

向右移動后,left == right,退出循環
將pivot賦給arr[left]

至此,第一輪分割序列完成
第二輪分割序列 --- 左子序列
經過第一輪分割,47左邊的是左子序列,右邊是右子序列
第二輪對左子序列分割,選擇中間值作為pivot

12和33'進行交換

22 > 12,不滿足arr[left] <= pivot
把arr[left]賦給arr[right]
arr[right] = arr[left]

賦值過后,left不動,right向左移動
29、33'、33都比12大,所以right一直移動到下圖位置

33 > 12,right繼續向左移動

此時right == left,終止循環
把pivot賦給arr[left]

至此,左子序列1也分割完成了
小結
快排就是一個遞歸的過程,分割得到左子序列
再對左子序列進行快排分割,得到左子序列的左子序列....
處理完左邊,再去處理右邊的右子序列
第三輪分割序列 --- 右子序列
右子序列只有47、68、49,選擇48作為軸值 pivot

pivot和最后一個值交換

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

分割之后,只剩下左子序列: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;
}

總結
-
快排是不穩定的排序算法
33 33'排序后可能變成33' 33
-
時間復雜度:
- 平均:\(O(Nlog_N)\)
- 最差:\(O(N^2)\),退化為冒泡排序
-
空間復雜度:
- 遞歸調用消耗棧空間
- 最優:\(O(log_N)\)
- 最差:\(O(N)\),退化為冒泡排序
