對於表示堆的數組arr[0…n-1],我們以arr[0]爲根,給定某個節點下標i,令其父節點和左右後代節點的下標爲:
parent(i) = (i-1)/2;
left(i) = 2*i+1;
right(i) = 2*i+2;
堆分爲最大堆和最小堆,上面就是最大堆,特點就是:除根節點以外的每個節點i,都有arr[ parent(i) ] >= arr[i]。最小堆的特點則是:除根節點以外的每個節點i,都有arr[ parent(i) ] <= arr[i]。
堆排序一般使用最大堆,最大堆中的最大元素位於根節點。
因爲具有n個元素的堆是基於一顆完全二叉樹的,所以其高度爲O(log n)。
2、如何保持堆中的性質:首先,我們假定以節點i的左右兒子爲根的兩棵二叉樹都是最大堆,而以節點i爲根的二叉樹可能不是最大堆,則調整的過程如下:
- 從元素arr[i], arr[left(i)], arr[right(i)]中找出最大的元素,將下標存在largest中;
- 如果arr[i]是最大的,說明以節點i爲根的二叉樹是最大堆,無須調整,程序結束;否則,交換arr[i]和arr[largest],於是arr[i], arr[left(i)], arr[right(i)]三者滿足了最大堆的性質,但是交換後,下標爲largest的節點存放arr[i]的值,以該節點爲根的子樹又可能違反最大堆的性質,因此需要對該子樹遞歸調用本調整過程
//arr[0...size-1]
void maxHeapify(int arr[], int i, int size)
{
int l = left(i);
int r = right(i);
int largest = i;
if (l < size && arr[l] > arr[largest])
largest = l;
if (r < size && arr[r] > arr[largest])
largest = r;
if (largest != i)
{
exchange(arr[i], arr[largest]);
maxHeapify(arr, largest, size);
}
}
上面保持堆的性質是一個鋪墊,它也是堆算法中的核心部分,後面我們將利用它完成建堆和堆排序。
我們先看看如何使用maxHeapify()來將一個數組arr[0…size-1]變成一個最大堆。
對於每一片樹葉,我們都可以看作是一個只含一個元素的堆。於是對於葉子節點的父親節點(左右子樹都是最大堆),我們可以調用maxHeapify()來進行調整。調整之後,我們得到更大的堆,對於這些堆的父節點,我們又可以調用maxHeapify()來進行調整。
爲保證maxHeapify()的調用前提,我們只需從最下面的非葉子節點開始調整,一直到根節點,整個堆建立完畢。
那麼,最下面的非葉子節點的下標是多少?
在這裏我只給出結論,有興趣的讀者可以嘗試證明一下:
當用數組表示存儲了n個元素的堆時,葉子節點的下標爲:n/2, n/2+1, … , n-1。 (n/2表示向下取整)
於是我們的調整順序爲n/2-1, … , 0:
代碼:buildMaxHeap(int arr[], int size)
{
for (int i = size/2-1; i >= 0; --i)
maxHeapify(arr, i, size);
}
4、堆排序:
爲進行原地排序,我們引入另一個變量:heap_size,它用來表示堆的大小,而用size來表示數組的大小。
於是數組arr[0…size-1]中,arr[0…heap_size-1]爲堆,arr[heap_size, size-1]爲排好序的元素。
由最大堆的性質可知道,arr[0]存放着堆中最大的元素,於是可以利用該性質如下排序:
- 初始heap_size = size,調用buildMaxHeap(arr, heap_size)建立最大堆;
- 令i = size-1,交換arr[0]和arr[i],heap_size–,i–;
- 交換後,原來根的子女仍是最大堆,而根元素則可能違背了最大堆的性質,所以調用maxHeapify(arr, 0, heap_size)進行調整;
- 重複以上過程,直到堆的大小變爲2,此時再重複一次以上過程,整個數組便從小到大排好序了。
void heapSort(int arr[], int size)
{
if (NULL == arr || size <= 0)
return ;
int heap_size = size;
buildMaxHeap(arr, heap_size);
for (int i = size-1; i >= 1; --i)
{
exchange(arr[0], arr[i]);
--heap_size;
maxHeapify(arr, 0, heap_size);
}
}
堆排序(heapsort)的時間複雜度爲O(n logn),不穩定。