快速排序
劃分
- 給定一個數列A[lo...hi]
- 重排數列A[lo...hi],使其成爲兩個子數組(可能爲空)A[lo...mi-1]和A[mi+1...hi]
對任何且,都有
x稱爲軸(pivot)。
快速排序的步驟:
- 先調用劃分
- 遞歸排序A[lo...mi-1]和A[mi+1...hi]
劃分的思想:
使用A[hi]作爲軸,從左向右擴展分區。
- 初始化(i,j) = (lo-1,lo)
- j每次加1,必要時i加1(當A[j] > x時,只增加j;當A[j] <= x時,還需增加i,並交換i,j號元素)
- 當j==hi時,結束
c++實現:
/**************************************************************
快速排序一次劃分 (Partition)
時間複雜度:O(n)
備註:[lo,hi]爲閉區間
***************************************************************/
int Partition(vector<int>& v, int lo, int hi)
{
int p = lo + rand()%(hi-lo+1); //隨機取軸
//swap v[p] and v[hi]
int temp = v[p];
v[p] = v[hi];
v[hi] = temp;
int pivot = v[hi];
int i = lo - 1;
for(int j = lo; j < hi; ++j){
if(v[j] <= pivot){
++i;
//swap v[i] and v[j]
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
}
//swap v[i+1] and v[hi]
temp = v[i+1];
v[i+1] = pivot;
v[hi] = temp;
return i+1;
}
/******************************************************************
快速排序 (QuickSort)
時間複雜度:O(nlogn)
備註:[lo,hi)爲左閉右開區間
*******************************************************************/
void QuickSort(vector<int>& v, int lo, int hi)
{
if(hi - lo < 2) //單元素區間自然有序
return;
int mi = Partition(v,lo,hi-1);
QuickSort(v,lo,mi);
QuickSort(v,mi+1,hi);
}
堆排序
優先級隊列
優先級隊列是一種抽象數據結構,支持以下兩種操作:
- 插入(Insert):插入堆新的元素
- 提取最小值(Extract-Min):從隊列中移除並返回最小元素
二叉堆(Binary Heap):完全二叉樹
堆序:A[parent(i)] <= A[i](最小堆)
堆的性質:
如果維持好堆序,堆可以高效支持以下兩種操作:(假設隊中有n個元素)
- 插入(Insert):O(logn)時間複雜度
- 提取最小值(Extract-Min):O(logn)時間複雜度
插入(Insertion)
- 插入新元素到堆末
- 恢復最小堆的性質(滲透:如果該元素的父節點比該元素更大,交換它們)
- 正確性:每次交換後,新元素爲根的子樹滿足最小堆的性質
- 時間複雜度:O(logn)
提取最小值(Extract-Min)
- 複製最後元素到根
- 通過下滲透恢復最小堆的性質(如果該元素比它的兩個孩子中之一更大,將該元素與較小的孩子交換)
- 正確性:每次交換後,除了包含該元素的節點外其他所有節點都滿足最小堆的性質
- 時間複雜度:O(logn)
堆排序
- 構建一個n個元素的二叉堆(一個一個插入n個元素,更高效的方法見《算法導論》)
- 執行Extract-Min操作n次
c++實現:
/******************************************************************
堆插入(HeapInsert)
時間複雜度:O(logn)
備註1:v爲最小堆,k爲插入元素
備註2:堆數組從索引0開始,計算父子索引時略有改變
*******************************************************************/
void HeapInsert(vector<int>& v, int k)
{
v.push_back(k);
int i = v.size() - 1;
int p = (i - 1) / 2; //父節點索引
while (i > 0 && v[p] > v[i]) {
int temp = v[p];
v[p] = v[i];
v[i] = temp;
i = p;
p = (i - 1) / 2;
}
return;
}
/******************************************************************
提取最小值(Extract-Min)
時間複雜度:O(logn)
備註1:v爲最小堆,返回堆頂元素
備註2:堆數組從索引0開始,計算父子索引時略有改變
*******************************************************************/
int ExtractMin(vector<int>& v)
{
int r = v[0];
int n = v.size();
v[0] = v[n - 1];
v.pop_back();
--n;
int i = 0;
int j = 2 * i + 1; //左子索引
while (j < n) { //存在左子
if (j < n - 1 && v[j] > v[j + 1]) //如果右子存在且較小
++j;
if (v[i] < v[j])
break;
else {
int temp = v[i];
v[i] = v[j];
v[j] = temp;
i = j;
j = 2 * i + 1;
}
}
return r;
}
/******************************************************************
堆排序(HeapSort)
先通過n次插入方式建堆,再n次提取堆頂元素排序
時間複雜度:O(nlogn)
*******************************************************************/
void HeapSort(vector<int>& v)
{
int n = v.size();
vector<int> t;
for (int i = 0; i < n; ++i)
HeapInsert(t, v[i]); //建堆
for (int i = 0; i < n; ++i)
v[i] = ExtractMin(t); //提取堆頂元素
return;
}
基於比較的排序算法的時間複雜度下界
基於比較的排序算法的時間複雜度下界:。(證明:決策樹的高度)
最快的排序算法
Which algorithm is the best in practice?
三種排序算法的運行時間對比可知:實際中,快速排序的用時最短,但與STL中實現的差距很大,可優化的空間還很大。