排序問題
算法問題的基礎問題之一,便是排序問題:
輸入:n個數的一個序列,<a1, a2,..., an>。
輸出:一個排列<a1',a2', ... , an'>,滿足a1' ≤ a2' ≤... ≤ an' 。(輸出亦可爲降序,左邊給出的例子爲升序)
一.算法描述
(1)堆
二叉堆:是一個數組,能近似的看做完全二叉樹。除了最底層,樹是完全充滿的,而且從左至右填充。
最大堆:除了根節點外,所有的節點i都滿足A[parent(i)] ≥A[i]的堆。
最小堆:除了根節點外,所有的節點i都滿足A[parent(i)] ≤A[i]的堆。
例如下圖中,如果我們將數組記作A[1~6],那麼將數組中的六個元素依次由上至下由左至右得添加到完全二叉樹中,就能得到下面的堆,而且恰好是一個最大堆。
使用這種方法可以總結出如下規律:
i的父節點 parent(i) = ⌊i / 2⌋
i的左子節點 left(i) = 2i
i的右子節點 right(i) = 2i + 1
(這裏由於書本上是用起始下標爲1來講解的,本文的講解和代碼都基於arr[1]來存放序列的第一個元素,且主要說明最大堆的排序過程)
(2)維護堆的性質Max-Heapify
對每一個非葉子節點i,假設以其左子節點和右子節點爲根的兩個堆都是最大堆,如果要保持以i爲根的堆也爲最大堆,就要保證A[i]不小於A[left(i)]和A[right(i)],如果A[i]不是三者最大,就要將A[i]與largest = max(A[left(i)], A[right(i)])交換,但是交換後可能破壞A[largest]爲根的堆是最大堆的性質,所以還需要對交換後的節點調用Max-Heapify。由於最多將根節點交換到最底層的葉子節點,時間複雜度爲O(H),H爲樹的高度。
(3)建堆
自底向上地對所有的非葉子節點使用Max-Heapify,最終就能得到一個最大堆。
(4)堆排序
由於最大堆的根節點是堆中的最大值,所以我們每次將根取出後,補上一個葉子節點,然後使用Max-Heapify讓剩餘元素的最大值繼續交換到根節點,循環下去取出的值就是降序排列的。
二.代碼實現
下面是插入排序的C++實現:
#include<iostream> #include<vector> using namespace std; //三個內聯函數,用來求每個節點的父、左子、右子節點 inline int left(int parent) { return parent << 1; } inline int right(int parent) { return 1 + (parent << 1); } inline int parent(int child) { return child >> 1; } //維護最大堆的性質 void Max_Heapify(vector<int> &v, int i) { int size = v.size() - 1; int largest; int l = left(i); int r = right(i); //左子大於父節點,large = l if(l <= size && v[l] > v[i]) largest = l; else largest = i; //large取右子和左子、父中的較大值節點 if(r <= size && v[r] > v[largest]){ largest = r; } if(largest != i){ swap(v[i],v[largest]); Max_Heapify(v,largest); } } //從最大parent節點往上維護最大堆性質,就能形成最大堆 void Build_Max_Heap(vector<int> &v) { //最後一個子節點v.size()/2向下取整是號碼最大的parent節點,所以直接從這裏count down for(int i = v.size()/2; i > 0; i--) { Max_Heapify(v,i); } } //堆排序 void Heapsort(vector<int> &v) { vector<int> ret; Build_Max_Heap(v); for(int i = v.size()-1; i > 1; i--) { swap(v[i], v[1]); ret.push_back(v[i]); v.pop_back(); Max_Heapify(v,1); } ret.push_back(v[1]); v = ret; } int main() { //arr爲要排序的數組 int arr[] = {5,2,4,7,10,9,8,1,6,3}; //把數組放入向量中,首部添0模擬圖示中的序號 vector<int> v(arr, arr + sizeof(arr)/sizeof(int)); v.insert(v.begin(),0); //對向量使用堆排序 Heapsort(v); //按順序打印排序後向量中的所有元素 copy (v.begin(), v.end(), ostream_iterator<int> (cout, " ")); cout << endl; }
三.算法分析
(1)時間複雜度
由於建堆時對所有的非葉子節點都要進行一次Max-Heapify,複雜度爲O(h),而高度爲h的節點不超過⌈n/pow(2,h+1)⌉,可計算出建堆的複雜度爲O(n)。 然後要進行n-1次取最大值並Max-Heapify操作,每一次的複雜度爲O(H) = O(logn),所以算法的總共時間複雜度爲O(nlogn)。
(2)穩定性
不穩定。
(3)適合範圍
適合於相對有序的序列。還可以用最大堆實現最大優先隊列,最小堆實現k個非降序有序鏈表的k路歸併。