二叉堆和堆排序

(二叉)堆是一個數組,它可以近似看作完全二叉樹。樹上的每一個節點對應數組中的一個元素。除了最底層,該樹是完全充滿的,而且是從左向右填充。
根據節點下標可以求出對應的子樹和雙親

 PARENT(i)
    return [i/2]   //i表示數組中的第幾個元素,[i/2]表示取整數
 LEFT(i)
    return 2*i
 RIGHT(i)
     return 2*i+1

二叉堆可分爲最大堆和最小堆。堆頂元素(根節點元素)比其它節點元素都大的堆並且任何雙親節點都不小於子節點是最大堆,反之堆頂元素比其它節點元素都小,並且任何雙親節點都不大於子節點的堆是最小堆。
A是數組
最大堆 A[PARENT(i)]>=A[i] (常用用於堆排序)
最小堆A[PARENT(i)]<=A[i] (常用於優先隊列)
堆的一些基本操作,比如建堆,調整堆,堆排序的時間複雜度是O(lg n)。堆的高度是lgn。
下面給出維護堆的僞代碼

MAX-HEAPIFY(A, i)
1 l=LEFT(i) //獲取左子樹位置
2 r=RIGHT(i) //獲取右子樹位置
3 if l<=A.heap-size and A[l]>A[i]
4  largest=l;
5 else largest=I
6 if r<=A.heap-size and A[r]>A[i]
7  largest=r
8 if largest 不等於 i
9   exchange  A[I] with A[largest]
    MAX-HEAPIFY(A, largest) //遞歸調用,因爲交換可能會破壞堆性質

這裏寫圖片描述

我們從完全二叉樹的性質上來分析,字數組A(|n/2|+1…n)中的元素都是樹的葉節點,豆哥葉節點都可以看成只包含一個元素的堆。那麼建堆我們只需要從非A(|n/2|+1…n)的數組元素開始調用建堆過程就可以了。下面給出僞代碼

BUILD-MAX-HEAP(A1 A.heap-size=A.length
2 for I=A[A.length/2] down to 1
3  MAX-HEAPIFY(A,i)

堆的用途之一是用於排序的堆排序。在建立完成最大堆(按升序排序數組,最小堆就是按降序排序數組)之後,注意到整個數組的 最大元素 總是在最大堆的第一個,即a[0]。這時,如果我們拿走第一個元素,放到數組的最後一個位置,然後對第一個元素進行維護最大堆maxHeapify,如此循環進行,便可完成整個數組的排序。下面給出僞代碼

HEAPSORT(A)
1 BUILD-MAX-HEAP(A)
2 for i=A.length down to 2
3  exchange A[1] with A[i]
4  A.heap-size--
5  MAX-HEAPIFY(A,1)

完整示例代碼

import org.junit.Test;

/**
 * Created by WuNanliang on 2017/5/3.
 * 堆數據結構
 */
public class HeapDemo {


    @Test
    public void testDemo() {
        int[] arr = {10, 8, 11, 8, 14, 9, 4, 1, 17};
        heapSort(arr);
        for (int num : arr)
            System.out.print(num + " ");
    }

    /**
     * 左子樹的位置
     *
     * @param parentIndex 雙親節點位置
     * @return
     */
    private int leftChildIndex(int parentIndex) {
        return 2 * parentIndex + 1;//因爲根節點index=0
    }

    /**
     * 右子樹的位置
     *
     * @param parentIndex 雙親節點位置
     * @return
     */
    private int rightChildIndex(int parentIndex) {
        return 2 * parentIndex + 2;//因爲根節點index=0
    }

    private void swap(int[] arr, int i, int j) {
        arr[j] = arr[i] + arr[j];
        arr[i] = arr[j] - arr[i];
        arr[j] = arr[j] - arr[i];
    }

    /**
     * 維護最大堆
     *
     * @param arr          數組
     * @param currentIndex 當前位置
     * @param length       數組的長度
     */
    private void maxHeapify(int[] arr, int currentIndex, int length) {
        int leftIndex = leftChildIndex(currentIndex);
        int rightIndex = rightChildIndex(currentIndex);
        //largestIndex  記錄leftIndex/rightIndex/currentIndex最大值
        int largestIndex;
        if (leftIndex < length && arr[leftIndex] > arr[currentIndex])
            largestIndex = leftIndex;
        else largestIndex = currentIndex;
        if (rightIndex < length && arr[rightIndex] > arr[largestIndex])
            largestIndex = rightIndex;
        if (largestIndex != currentIndex) {
            swap(arr, currentIndex, largestIndex);
            //遞歸維護堆性質
            maxHeapify(arr, largestIndex, length);
        }
        System.out.println("index:" + currentIndex + "->" + arr[currentIndex]);
    }


    /**
     * 建立最大堆
     *
     * @param arr
     */

    private void buildMaxHeap(int[] arr) {
        int len = arr.length;
        int middleIndex = len / 2;

        for (int i = middleIndex + 1; i >= 0; i--)
            maxHeapify(arr, i, len);
    }

    private void heapSort(int[] arr) {
        buildMaxHeap(arr);
        int len = arr.length;
        for (int i = len - 1; i >= 1; i--) {
            swap(arr, i, 0);
            len--;
            maxHeapify(arr, 0, len);
        }
    }


}

參考資料

1《算法導論 原書第3版》
2 堆排序以及最大優先隊列

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章