摘要java
堆排序須要用到一種數據結構,大頂堆。大頂堆是一種二叉樹結構,本質是父節點的數大於它的左右子節點的數,左右子節點的大小順序不限制,也就是根節點是最大的值。數據結構
這裏就是不斷的將大頂堆的根節點的元素和尾部元素交換,交換到大頂堆沒有能夠被交換的元素爲止。後面再說大頂堆的邏輯。code
首先將序列經過大頂堆排序。而後不斷的從堆中取出頂部元素放在尾部,直到大頂堆元素爲空。排序
下面在代碼中解釋原地建堆和自下而上的下濾這兩個詞的邏輯。索引
首先進行原地建堆。原地建堆是先將序列按照大頂堆的排序邏輯處理序列。element
大頂堆的序列邏輯是父節點的值大於它的左右子節點的值,能夠想象成一個二叉樹。這裏的原地排序用到了siftDown
方法,並且在循環中只循環到序列一半數量,爲何?這個在下面看siftDown
方法時詳細探究一下。class
// 原地建堆 // 自下而上的下濾 heapSize = array.length; for (int i = (heapSize >> 1) - 1; i >= 0; i--) { siftDown(i); }
交換堆頂和尾部元素,而後將須要比較的序列元素數量減小1,並將要進行比較的序列再使用siftDown
方法過濾,保持序列的大頂堆的性質。而後繼續開始的交換,直到能夠比較的序列數量爲 1 就截止。二叉樹
while (heapSize > 1) { // 交換堆頂元素和尾部元素 swap(0, --heapSize); // 對 0 位置進行 siftDown(恢復堆的性質) siftDown(0); }
siftDown
方法這裏來探究一下siftDown
(下濾)。循環
二叉樹的父節點和子節點的關係符合這樣的公式方法
- leftChilder = partner * 2 + 1
- rightChilder = parnter * 2 + 1 + 1
- half (葉子)節點的數量是總節點數量的 1/2
siftDown
方法主要是將 index
位置上的元素放在合適的位置上。那麼什麼位置是合適的位置呢?
依據大頂堆的父節點值大於左右子節點的值的性質來看,只要是保證 index
位置的元素大於它的左右子節點就好。
看下面代碼,若是 index < half
才進行循環比較,那麼就有一個問題,當 index >= half
爲何不用比較?
這就要提到很巧妙的點,首先看大頂堆的性質,左右子節點沒有具體順序的要求,其次子節點的值小於父節點。那麼就能夠依據二叉樹的葉子節點性質,若是index
的位置是在葉子節點位置,那麼就原本比它的父節點要小,就不用比較(這個是創建在序列原本符合大頂堆的順序,出現一個位置的元素有變化時進行的過濾處理)。
這也是上面的原地排序中,只從一半的位置開始,是由於從這個位置開始,確定會給它的子節點比較,過濾出大的,並放在合適位置。
代碼中有三個巧妙的點
rightIndex<heapSize
。由於大頂堆是符合徹底二叉樹的(儘可能往左子樹安排元素)。/* * 讓 index 位置的元素下濾 */ private void siftDown(int index) { E element = array[index]; int half = heapSize >> 1; // 取出非葉子節點 // 第一個葉子結點的索引 == 非葉子節點的數量 // 必須保證 index 是非葉子節點 while (index < half) { // index 的節點有2種狀況 // 一、只有左子節點 // 二、同時有左右子節點 // 默認左子節點跟它進行比較 int childIndex = (index << 1) + 1; E child = array[childIndex]; // 右子節點 int rightIndex = childIndex + 1; if (rightIndex < heapSize && cmp(array[rightIndex], child) > 0) { child = array[ childIndex = rightIndex]; } if (cmp(child, element) < 0) break; // 將子節點存放到index位置 array[index] = child; // 從新設置 index index = childIndex; } array[index] = element; }
此次的排序用到了二叉樹和大頂堆的一些知識,可能看下來有諸多疑問,這裏就先請諸位看官有個印象,後續我會分享二叉樹的知識,而後在回過頭來看堆排序,會讓你思路大開。