快速排序
給定一個序列: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)\),退化為氣泡排序