1. 程式人生 > >堆排序演算法學習小記

堆排序演算法學習小記

1.完全二叉樹的概念

      若設二叉樹的深度為h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第 h 層所有的結點都連續集中在最左邊,這就是完全二叉樹。

     完全二叉樹是由滿二叉樹而引出來的。對於深度為K的,有n個結點的二叉樹,當且僅當其每一個結點都與深度為K的滿二叉樹中編號從1至n的結點一一對應時稱之為完全二叉樹。     (1)所有的葉結點都出現在第k層或k-l層(層次最大的兩層)     (2)對任一結點,如果其右子樹的最大層次為L,則其左子樹的最大層次為L或L+l。     一棵二叉樹至多隻有最下面的兩層上的結點的度數可以小於2,並且最下層上的結點都集中在該層最左邊的若干位置上,則此二叉樹成為完全二叉樹,並且最下層上的結點都集中在該層最左邊的若干位置上,而在最後一層上,右邊的若干結點缺失的二叉樹,則此二叉        樹稱為完全二叉樹。       

 

 

 

2.堆的概念

          堆(英語:heap)是電腦科學中一類特殊的資料結構的統稱。堆通常是一個可以被看做一棵樹的陣列物件。堆總是滿足下列性質:

         a.堆中某個節點的值總是不大於或不小於其父節點的值;

         b.堆總是一棵完全二叉樹。

         如果某個節點的值總是小於其父節點的值,稱為大根堆(a),如果總是大於其父節點的值,稱為小根堆(b)。

 

3.堆排序原理

  a.將排序元素構建成堆(升序採用大頂堆,降序採用小頂堆)

  b.取出堆頂元素

  c.將剩下元素繼續構造成堆

  重複b c 步驟即可實現堆排序,下面以array=[4,6,8,5,9]陣列的升序排序具體說明過程

  

 

 第一步:將陣列元素對映成完全二叉樹,如上圖

 

 第二步:找出完全二叉樹中序號最大的非葉子節點(這裡比較難理解),這個數+1也是這棵數的非葉子節點數量,下面給出推導過程:

可以分兩種情形考慮:

①樹的最後一個非葉子節點若只有左孩子

②樹的最後一個非葉子節點有左右兩個孩子

完全二叉樹的性質之一是:如果節點序號為i,在它的左孩子序號為2*i+1,右孩子序號為2*i+2。

 對於①左孩子的序號為n-1,則n-1=2*i-1,推出i=n/2-1;

 對於②左孩子的序號為n-2,在n-2=2*i-1,推出i=(n-1)/2-1;右孩子的序號為n-1,則n-1=2*i+2,推出i=(n-1)/2-1;

很顯然,當完全二叉樹最後一個節點是其父節點的左孩子時,樹的節點數為偶數;當完全二叉樹最後一個節點是其父節點的右孩子時,樹的節點數為奇數。

根據java語法的特徵,整數除不盡時向下取整,則若n為奇數時(n-1)/2-1=n/2-1。

因此對於②最後一個非葉子節點的序號也是n/2-1。

對於上面給出的完全二叉樹結構,根據上面的推導,可以計算出最後一個非葉子節點的  序號 lastNoChildNodeIndex = arr.lenth/2-1=5/2-1=1,即這棵樹中序號為0和1的節點均為非葉子節點。

第三步:開始將這棵完全二叉樹調整為最大堆,從最後一個非葉子節點開始調整,依次往序號較低的葉子節點進行,上面的樹有0,1兩個葉子節點,首先調整序號為1的葉子節點

 

調整思路:序號為1的葉子節點值為6,它的兩個子節點分別為5和9,明顯,三個值中,最大的值為9,要滿足最大堆,9和6交換。按理說,交換值後,需要考慮序號4即6的位置是否滿足最大堆要求,但是由於序號4處是葉子節點,所以滿足要求。

第四步:調整序號為0的非葉子節點

 

 

 調整思路:

 0號節點的值以及兩個子節點三個值中,明顯9最大,交換4和9的值,此時,被交換過的節點1是非葉子節點,且不滿足最大堆要求,下面繼續調整節點1使其滿足最大堆的要求。

第五步:節點1的三個值中,明顯6的值最大,交換6和4的值。

 到這裡一個最大堆就建立起來了。堆頂元素9即為整個陣列的最大值。

 第六步:將9和4進行交換,其實就是把9放在樹的末尾節點,9後面就不參與排序了。

 

 

 第七步:將剩下的節點用上述方式重新調整為最大堆

 

 

 第八步:將堆頂元素8和最後的子節點元素5交換,此時,第二大元素8產生,交換後不再參與排序。

 

 

 重複上面上述的構建最大堆,將最大元素交換到樹節點尾,最後得到的結果如下:

 

 到此,完成了所有排序。

4.堆排序java程式碼實現

public class heapSorted {

    public static void main(String[] args) {
        int[] arr = {4, 6, 8, 5, 9};
        for(int i = 0;i< arr.length;i++){
            System.out.println(arr[i]);
        }
        arr =  sortHeap(arr);

        for(int i = 0;i< arr.length;i++){
            System.out.println(arr[i]);
        }

    }


    private static int[] sortHeap(int[] arr) {
        int len = arr.length;
        //構建最大堆
        buildMaxHeap(arr, len);

        for (int i = len - 1; i > 0; i--) {
            //堆頂元素(序號為0)和當前堆的最後一個節點(序號i)交換
            swap(arr, 0, i);
            //堆元素總數減一,即將當前樹結構最後一個節點排除,不參與下一輪堆結構構建
            len--;
            //重新構建大頂堆
            reBuildHeap(arr, 0, len);
        }
        return arr;
    }

    /**
     * 構建大頂堆
     * @param arr
     * @param len
     */
    private static void buildMaxHeap(int[] arr, int len) {
        //(int) Math.floor(len >> 1) = len/2-1 即最大非葉子節點的序號
        for (int i = (int) Math.floor(len >> 1); i >= 0; i--) {
            //從最大非葉子節點開始,依次調整每個非葉子節點,使其滿足最大堆要求(比如說最大非葉子節點序號為3,那麼一次要調整的非葉子節點為3、2、1、0)
            //調整完畢,最大堆就構建完成
            reBuildHeap(arr, i, len);
        }
    }

    /**
     * 堆調整
     *
     * @param arr
     * @param i
     * @param len
     */
    private static void reBuildHeap(int[] arr, int i, int len) {
        //1. i為傳進來的非子樹節點的序號
        //2. len為排序陣列的長度
        //3. left為i節點的左子樹節點序號
        int left = 2 * i + 1;
        //4. right為i節點的右子樹節點序號
        int right = 2 * i + 2;
        //5. largest為i、left、right三個節點中值最大的節點序號,暫時假定i節點值最大
        int largest = i;
        //6.如果左節點存在並且左節點的值大於最大值節點(目前為i)的值,則將左節點序號賦給值最大節點序號
        if (left < len && arr[left] > arr[largest]) {
            largest = left;
        }
        //7.如果右節點存在並且右節點的值大於最大值節點的值,則將右節點序號賦給值最大節點序號
        if (right < len && arr[right] > arr[largest]) {
            largest = right;
        }
        //8.上述兩個判斷,其實就是為了找到三個節點中,值最大的序號的值largest;

        if (largest != i) {
            //9.i和largest不相等,說明i,right,left三個節點中,其中一個子節點(left或者right)比父節點i大,因為是大頂堆,此時要交換父節點和最大子節點的值
            swap(arr, i, largest);
            //10.由於交換了父親節點和其中一個子的位置,所以被交換成父親值的子節點可能不滿足最大堆的要求,因此重建該子節點的堆結構
            //注意:此時的largest序號處變成了父親節點的值,也就是說,他已經不再是最大值!!
            reBuildHeap(arr, largest, len);
        }

    }

    /**
     * 值交換函式
     *
     * @param arr
     * @param i
     * @param j
     */
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

5.總結

  要完全理解堆排序需要深刻搞懂一下幾點

 a.什麼是堆

 b.堆排序的思想

 c.序號最大的非葉子節點的計算原理

 d.怎麼將堆頂元素提取出來

 f.提取出堆頂元素後,剩下的元素怎麼再構建成堆

 

備註:文章中的圖都是從網上找的,程式碼也是參考敲了一遍,理解了再敲一遍更容易理解~