堆排序實現
1、堆排序算法描寫敘述:
(1)定義
n個keyword序列Kl,K2,…,Kn稱為(Heap)。當且僅當該序列滿足例如以下性質(簡稱為堆性質): 1)ki<=k(2i)且ki<=k(2i+1)(1≤i≤ n/2)。當然。這是小根堆。大根堆則換成>=號。//k(i)相當於二叉樹的非葉子結點,K(2i)則是左子節點。k(2i+1)是右子節點 2)若將此序列所存儲的向量R[1..n]看做是一棵全然二叉樹的存儲結構,則堆實質上是滿足例如以下性質的全然二叉樹: 樹中任一非葉子結點的keyword均不大於(或不小於)其左右孩子(若存在)結點的keyword。
然後再次將R[1..n-1]中keyword最大的記錄R[1]和該區間的最後一個記錄R[n-1]交換。由此得到新的無序區R[1..n-2]和有序區R[n-1..n]。且仍滿足關系R[1..n-2].keys≤R[n-1..n].keys,相同要將R[1..n-2]調整為堆。
…… 直到無序區僅僅有一個元素為止。 (3)大根堆排序算法的基本操作: ①建堆。建堆是不斷調整堆的過程,從len/2處開始調整,一直到第一個節點,此處len是堆中元素的個數。建堆的過程是線性的過程。從len/2到0處一直調用調整堆的過程。相當於o(h1)+o(h2)…+o(hlen/2) 當中h表示節點的深度。len/2表示節點的個數,這是一個求和的過程,結果是線性的O(n)。 ②調整堆:調整堆在構建堆的過程中會用到。並且在堆排序過程中也會用到。利用的思想是比較節點i和它的孩子節點left(i),right(i),選出三者最大(或者最小)者。假設最大(小)值不是節點i而是它的一個孩子節點。那邊交互節點i和該節點,然後再調用調整堆過程,這是一個遞歸的過程。調整堆的過程時間復雜度與堆的深度有關系,是lgn的操作,由於是沿著深度方向進行調整的。 ③堆排序:堆排序是利用上面的兩個過程來進行的。首先是依據元素構建堆。
然後將堆的根節點取出(通常是與最後一個節點進行交換)。將前面len-1個節點繼續進行堆調整的過程,然後再將根節點取出,這樣一直到全部節點都取出。堆排序過程的時間復雜度是O(nlgn)。由於建堆的時間復雜度是O(n)(調用一次)。調整堆的時間復雜度是lgn,調用了n-1次。所以堆排序的時間復雜度是O(nlgn)。 2、堆的存儲 一般用數組來表示堆,若根結點存在序號0處, i結點的父結點下標就為(i-1)/2。
i結點的左右子結點下標分別為2*i+1和2*i+2。
3、堆排序算法實現(最大堆)
#include <iostream> void printArray(int theArray[], int n) { for(int i = 0; i < n; i++) { std::cout << theArray[i] << " "; } std::cout << std::endl; } void adjustHeap(int theArray[], int n, int start) //從start索引處結點開始調整 { if(start < 0 || start >= n) return; int fatherIndex; int leftIndex; int rightIndex; int tmp; fatherIndex = start; leftIndex = 2 * fatherIndex + 1; //start索引處結點的左孩子的索引 rightIndex = leftIndex + 1; //start索引處結點的右孩子的索引 if(rightIndex < n) //start索引處結點存在右孩子結點. { if(theArray[fatherIndex] < theArray[rightIndex]) //右孩子結點大於start索引處結點 。交換 { tmp = theArray[fatherIndex]; theArray[fatherIndex] = theArray[rightIndex]; theArray[rightIndex] = tmp; } } if(leftIndex < n) //start索引處結點存在左孩子結點,但不一定存在右孩子結點 { if(theArray[fatherIndex] < theArray[leftIndex]) //右孩子結點大於左孩子結點樹,交換 { tmp = theArray[fatherIndex]; theArray[fatherIndex] = theArray[leftIndex]; theArray[leftIndex] = tmp; } } adjustHeap(theArray, n, start-1); //再次遞歸調整start索引處的上一個結點 } void constructHeap(int theArray[], int n) { int start = n / 2; adjustHeap(theArray, n, start); //從length/2處開始調整 } void heapSort(int theArray[], int n) { if(n == 1) //當未排序序列中僅僅剩一個元素時,直接跳出遞歸 return; int length = n; //printArray(theArray, length); //打印出每次建立最大堆之後,數組排列情況 //將最大堆的堆頂元素與堆末尾元素交換,並取出該元素作為已排序數組元素 int tmp = theArray[0]; theArray[0] = theArray[length-1]; theArray[length-1] = tmp; //再次調整交換後的堆,使得為滿足最大堆 --length; //未排序序列長度減一 int start = length / 2; adjustHeap(theArray, length, start); heapSort(theArray, length); //遞歸進行堆排序 } int main(int argc, char *argv[]) { int myArray[] = {5,90,28,4,88,58,38,18,19,20}; int length = sizeof(myArray) / sizeof(myArray[0]); constructHeap(myArray, length); heapSort(myArray, length); printArray(myArray, length); return 0; }
4、堆的時間復雜度和空間復雜度 堆的最好、最壞以及平均時間復雜度是O(nlogn). 堆的空間復雜度是O(1). 感想:剛開始實現建立堆時,以為須要建立一個二叉樹輔助幫助,事實上這個所謂堆(即全然二叉樹)僅僅是邏輯結構。真實的物理結構還是那個順序數組。在建立大堆或小堆的過程中,均是以順序數組為基礎,而邏輯上是操作一顆全然二叉樹,故全然不須要建立一個輔助全然二叉樹。
堆排序實現