堆(Heap)詳解——Java實現


Heap

堆定義:(這里只講二叉堆)堆實為二叉樹的一種,分為最小堆和最大堆,具有以下性質:

  • 任意節點小於/大於它的所有后裔,最小/大元在堆的根上。
  • 堆總是一棵完全二叉樹

  將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆

堆的相關操作:

  • 建立
  • 插入
  • 刪除

應用:

  • 堆排序
  • 優先隊列
  • 合並容器元素
  • 找出第k大元素

 

Java實現:

/** * Created by XuTao on 2018/11/5 22:10 * ···最小堆··· * 《注意: 實際上並不需要用節點來真正構造一顆樹,我們只是在數組中操作排序,調整好的數組就是一個堆的層遍歷結果》 * * 插入: * 也是插入末尾,然后調整,調整也應該是一個連續向上的過程,建樹就是一個連續插入的過程 * * 刪除最小: * 即刪除root: * 用末尾一個代替root,刪除末尾,然后siftDown,如果子節點有更小的,每次只需要找到最小的子節點,然后交換即可。 * * siftDown: * 如果建樹得以保證,那么如果子節點有更小的,每次只需要找到最小的子節點,然后交換即可。 * 如果是一個亂的樹,那么就要考慮,比較麻煩, 解決方式: * i 從 最后一個節點的父節點出開始迭代,直到i = 0; * 每次檢查時,將大的節點交換到末尾——就是要到底,如果大,就要成為葉節點,不是只交換一次(用循環) * * 那么就有兩種構建方法: * 1.亂序構建,調整( 一個一個添加(從數組中),添加到最后一個,樹的最右最下方的那個,然后siftUp,從下往上調整就可以了 ,O(log2(n))) * 2.一次一節點,依次調整 * * <p> * 思考題: 設計算法檢查一個完全二叉樹是不是堆,是的話是最大堆還是最小堆。 * 思路:元素1個,同為最大最小堆 * 元素>1個: * 判斷第一二個大小 * 第一個大: 可能為最大堆,然后遞歸校驗,如果每一個節點都比子節點大,那么是最大堆,否則不是堆 * 第二個大: 可能為最小堆,然后遞歸校驗,如果每一個節點都比子節點小,那么是最小堆,否則不是堆 * <p> * *時間復雜度分析: * 建樹:兩種方式都是 O(nlog2(n)) * 插入: O(log2(n)) * 刪除: O(log2(n)) */


public class Heap { private int[] data; private final int maxSize = 128;  //預設大小,足夠就行
    private int heapSize; //實際大小

    public Heap(int[] input) { data = new int[maxSize]; heapSize = input.length; for (int i = 0; i < heapSize; i++) {//這個地方其實並不好,只是將傳入的數組讀入我的數組中,一方有不斷插入操作,如果沒有插入操作則不必要;
            data[i] = input[i]; } } public void build_1() { /** * 建樹方法1: * 每次插入一個節點 */
        int a = heapSize; heapSize = 0; for (int i = 0; i < a; i++) { insert(data[i]); } } public void build_2() { /** * 建樹方法2: * 以原來的亂序進行調整:siftDown */
        if (heapSize <= 1) return; for (int i = getParent(heapSize - 1); i >= 0; i--) {  // 從末元素的父節點開始,一次一次進行siftDown
 siftDown(i); } } /** * 由上而下調整, sift——篩 * @param start */
    public void siftDown(int start) { //start至少1子,不用擔心溢出問題
        while (getLeft(start) < heapSize) {  //注意,這里必須是小於,不能等於,如果該節點的左節點是末尾節點則結束,條件是getLeft(start)==heapSize-1
            int min = 0;//判別有沒有發生交換的條件 //無右子
            if (getRight(start) >= heapSize) { if (data[start] > data[getLeft(start)]) { min = getLeft(start); swap(start, min); } } //2子
            else { min = data[getLeft(start)] > data[getRight(start)] ? getRight(start) : getLeft(start); if (data[start] > data[min]) { swap(start, min); } } if (min == 0) break;//滿足堆條件,退出
            start = min; //不滿足堆條件,還可以調整,繼續循環
 } } /** * 由下而上調整 * @param start 開始的下標 */
    public void siftUp(int start) { if (start <= 0) return; while (data[start] < data[getParent(start)]) {  //一直發生交換,直到滿足條件
 swap(start, getParent(start)); start = getParent(start); if (start <= 0) break;// root
 } } public void insert(int a) { /** * 插入的話會使數組長度加一,比較麻煩,於是我建立一個比較大的樹,用一個較大的量maxSize來限定堆的最大容量,用heapSize來聲明實際的容量 */ data[heapSize] = a; siftUp(heapSize); heapSize++; } public int getLeft(int i) { return 2 * i + 1; } public int getRight(int i) { return 2 * i + 2; } public int getParent(int i) { if (i == 0) return -1; return (i - 1) >> 1;  //除以2
 } public void swap(int i, int j) { int temp = data[i]; data[i] = data[j]; data[j] = temp; } public void display() { for (int i = 0; i < heapSize; i++) { System.out.print(data[i] + "  "); } System.out.println(); } public static void main(String[] args) { int[] a = new int[]{8, 12, 2, 5, 3, 7, -1, 44, 23}; Heap heap = new Heap(a); heap.display(); // heap.build_1();
 heap.build_2(); heap.insert(-4); heap.display(); } }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM