排序演算法5:堆排序
- 簡介
堆排序是利用堆這種資料結構進行的排序。堆通常使用一維陣列來實現,是一種近似完全二叉樹的結構。堆排序分為大根堆和小根堆。堆排序滿足這樣的一個特性:大根堆父節點的值總是大於子節點,小根堆的父節點值總是小於子節點。
- 思想
我們在拿到一個數組時,首先將它構造成為一個大根堆/小根堆,這個過程我們叫做建堆。然後將根節點元素和最末尾元素交換,此時最末尾成為有序區。除去最末尾元素的剩下元素所構成的樹此時已經不叫堆了,因為它不滿足堆的特性。所以我們繼續進行調整,使得最小(或最大)的值再次移到根節點,隨後交換它與此時堆中的末尾節點,有序區+1...如此反覆,直至堆的大小為1(無序區中只有一個數字,代表排序完成)。
- 具體程式碼(升序,陣列實現)
首先我們寫出堆排序的主體:
void HeapSort(int arr[],int len)
{
int heap_size = len;
BuildHeap(arr,len);
while(heap_size > 1)
{
change(arr,0,--heap_size);
HeapAdjust(arr,0,heap_size);
}
}
上面這個HeapSort函式就是我們的堆排序的主體部分,首先根據實參傳入的陣列長度決定堆的大小,進行建堆(BuildHeap函式將在下面書寫)。建堆函式要完成的功能就是將一個無序的陣列調整成為一個大根堆或者小根堆。建堆完成之後,如果heap_size(無序區)中存留的數字大於1,即這個堆中還有超過一個以上的數字那麼證明還需要繼續排序。進入迴圈,不論是剛建成一個堆還是已經經過調整,我們首先都需要將arr[0]這個根節點的元素和當前的堆的末尾元素進行交換,交換完成之後將它放入有序區,即heapsize--,然後對交換過後已經有可能被破壞的堆進行重新調整,重複選出數字放入有序區的這個過程。
接下來我們編寫建堆函式,此處要求升序排序我們需要建立大根堆。
void BuildHeap(int arr[],int len)
{
int heap_size = len;
for(int i = heap_size/2 - 1;i > 0; i--)
{
HeapAdjust(arr,i,,heap_size);
}
}
建立堆的第一步就是從二叉樹的倒數第一個非葉子節點(len/2-1)開始,從下至上對每一個根節點進行調整。
最後編寫調整函式。它只需要注意處理上層節點調整之後下層節點同樣有可能需要調整的情況,可以使用遞迴解決。其餘只需要實現簡單的資料交換即可。
void HeapAdjust(int arr[],int i,int len)
{
int leftchild = 2*i + 1;
int rightchild = 2*i + 2;
int max = i; //代表從i節點開始進行調整,有可能需要繼續向下調整
if(leftchild < len && arr[max] < arr[leftchild])
max = leftchild;
if(rightchild < len && arr[max] < arr[rightchild])
max = rightchild;
if(i != max)
{
change(arr,i,max);
HeapAdjust(arr,max,len);
}
}
引數中的i就代表從i節點開始進行調整,經過資料交換後(如果有這個必要的話),將繼續下探(因為如果這個和它的父節點交換過的節點有子節點的話,交換之後可能不滿足堆的定義,所以需要下探調整。)直至調整完成,此時又是一個新的堆。