【堆內存】動態圖+代碼五分鐘輕鬆理解學會

 

前言背景

  1. 堆(heap)又被爲優先隊列(priority queue)。儘管名爲優先隊列,但堆並非隊列。
  2. 由於隊列中容許的操做是先進先出(FIFO),在隊尾插入元素,在隊頭取出元素。
  3. 而堆雖然在堆底插入元素,在堆頂取出元素,可是堆中元素的排列不是按照到來的前後順序,而是按照必定的優先順序排列的。

堆的實現

    堆的一個經典的實現是徹底二叉樹(complete binary tree),這樣實現的堆稱爲二叉堆(binary heap)。數組

    這裏來講明一下滿二叉樹的概念與徹底二叉樹的概念以及完滿二叉樹概念。數據結構

  • 滿二叉樹(又稱完美二叉樹)一個深度爲k(>=-1)且有2^(k+1) - 1個結點的二叉樹。直接上圖理解。
  • 徹底二叉樹從根結點到倒數第二層知足完美二叉樹,最後一層能夠不徹底填充,其葉子結點都靠左對齊。
  • 完滿二叉樹全部非葉子結點的度都是2。(只要你有孩子,你就必然是有兩個孩子。) 

    堆的特性:ide

  • 必須是徹底二叉樹函數

  • 任一結點的值是其子樹全部結點的最大值或最小值動畫

  • 最大值時,稱爲「最大堆」,也稱大頂堆;spa

  • 最小值時,稱爲「最小堆」,也稱小頂堆。3d

    

堆的基礎實現

    只要謹記堆的定義特性,實現起來實際上是很容易的。code

  • 特性1.  維持徹底二叉樹  blog

  • 特性2.  子類數字老是大於父類數字  排序

1public class MinHeap <E extends Comparable<E>> {
 2    private Array<E> data;
 3
 4    public MinHeap(int capacity){
 5        data = new Array<>(capacity);
 6    }
 7
 8    public MinHeap(){
 9        data = new Array<>();
10    }
11
12    // 返回堆中的元素個數
13    public int size(){
14        return data.getSize();
15    }
16
17    // 返回一個布爾值, 表示堆中是否爲空
18    public boolean isEmpty(){
19        return data.isEmpty();
20    }
21
22    // 返回徹底二叉樹的數組表示中,一個索引所表示的元素的父親節點的索引
23    private int parent(int index){
24        return (index - 1) / 2;
25    }
26
27    // 返回徹底二叉樹的數組表示中,一個索引所表示的元素的左孩子節點的索引
28    private int leftChild(int index){
29        return index * 2 + 1;
30    }
31
32    // 返回徹底二叉樹的數組表示中,一個索引所表示的元素的右孩子節點的索引
33    private int rightChild(int index){
34        return index * 2 + 2;
35    }
36}

        最小堆的插入(ADD)

        

        假設現有元素 5 須要插入,爲了維持徹底二叉樹的特性,新插入的元素必定是放在結點 6 的右子樹;同時爲了知足任一結點的值要小於左右子樹的值這 一特性,新插入的元素要和其父結點做比較,若是比父結點小,就要把父結點拉下來頂替當前結點的位置,本身則依次不斷向上尋找,找到比本身大的父結點就拉下來,直到沒有符合條件的值爲止。

    動畫講解

  1. 在這裏先將元素 5 插入到末尾,即放在結點 6 的右子樹。

  2. 而後與父類比較, 6 > 5 ,父類數字大於子類數字,子類與父類交換。

  3. 重複此操做,直到不發生替換。

    Show me the code:

    添加一個輔助函數,用來交換傳入的索引兩個位置的元素值

1/**
 2     * 交換傳入的索引兩個位置的元素值
 3     *
 4     * @param i
 5     * @param j
 6     */
 7    public void swap(int i, int j) {
 8        if (i < 0 || i >= size || j < 0 || j >= size)
 9            throw new IllegalArgumentException("Index is illegal.");
10
11        E temp = data[i];
12        data[i] = data[j];
13        data[j] = temp;
14    }

    數組中添加交換兩元素位置的方法,注意下面代碼中註釋的描述特性位置。 

1    /**
 2     * 堆中添加元素方法。
 3     *
 4     * @param e
 5     */
 6    public void add(E e) {
 7        //特性1:新插入的元素首先放在數組最後,保持徹底二叉樹的特性
 8        data.addLast(e);
 9        siftUp(data.getSize() - 1);
10    }
11
12    /**
13     * index 爲i位置元素上浮。
14     *
15     * @param i
16     */
17    private void siftUp(int i) {
18         //特性2:比較插入值和其父結點的大小關係,小於父結點則用父結點替換當前值,index位置上升爲父結點
19        // 當上浮元素大於父親,繼續上浮。而且不能上浮到0之上
20        // 直到i 等於 0 或 比 父親節點小了。
21        while (i > 0 && data.get(i).compareTo(data.get(parent(i))) > 0) {
22            // 數組Array中添加方法swap
23            data.swap(i, parent(i));
24            i = parent(i); // 這句話讓i來到新的位置,使得循環能夠查看新的位置是否還要大。
25        }
26    }

    最小堆的刪除(DELETE)

    

    

    核心點:將最後一個元素填充到堆頂,而後不斷的下沉這個元素。

     假設要從節點 1 ,也能夠稱爲取出節點 1 ,爲了維持徹底二叉樹的特性 ,咱們將最後一個元素 6 去替代這個 1 ;而後比較 1 和其子樹的大小關係,若是比左右子樹大(若是存在的話),就要從左右子樹中找一個較小的值替換它,而它能本身就要跑到對應子樹的位置,再次循環這種操做,直到沒有子樹比它小。

    經過這樣的操做,堆依然是堆,總結一下:

  • 找到要刪除的節點(取出的節點)在數組中的位置

  • 用數組中最後一個元素替代這個位置的元素

  • 當前位置和其左右子樹比較,保證符合最小堆的節點間規則

  • 刪除最後一個元素

    Show me the code:

1    public E findMin() {
 2        return data.get(0);
 3    }
 4
 5    public E extractMin() {
 6
 7        E ret = findMin();
 8
 9        data.swap(0, data.getSize() - 1); // 0位置元素和最後一個元素互換。
10        data.removeLast(); // 刪除此時的最後一個元素(最小值)
11        siftDown(0); // 對於0處進行siftDown操做
12
13        return ret;
14    }
15
16    /**
17     * k位置元素下移
18     *
19     * @param k
20     */
21    private void siftDown(int k) {
22
23         while(leftChild(k) < data.getSize()){
24            int j = leftChild(k); // 在此輪循環中,data[k]和data[j]交換位置
25            if( j + 1 < data.getSize() &&
26                    data.get(j + 1).compareTo(data.get(j)) < 0 )
27                j ++;
28            // data[j] 是 leftChild 和 rightChild 中的最小值
29
30            if(data.get(k).compareTo(data.get(j)) >= 0 )
31                break;
32
33            data.swap(k, j);
34            k = j;
35        }
36    }

時間複雜度 

對於有 n 個節點的堆來講,其高度 d = log2n + 1。 根爲第 0 層,則第 i 層結點個數爲 2i,
考慮一個元素在堆中向下移動的距離。

  • 大約一半的結點深度爲 d-1 ,不移動(葉)。

  • 四分之一的結點深度爲 d-2 ,而它們至多能向下移動一層。

  • 樹中每向上一層,結點的數目爲前一層的一半,而子樹高度加一

堆有logn層深,因此插入刪除的平均時間和最差時間都是O(logN)

優先隊列(priority_queue)

普通隊列是一種先進先出的數據結構,先放進隊列的元素取值時優先被取出來。而優先隊列是一種具備最高優先級元素先出的數據結構,好比每次取值都取最大的元素。

優先隊列支持下面的操做:

  • a. 找出優先級最高的元素(最大或最小元素);

  • b. 刪除一個具備最高優先級的元素;

  • c. 添加一個元素到集合中。

代碼實現

1public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {
 2
 3    private MaxHeap<E> maxHeap;
 4
 5    public PriorityQueue(){
 6        maxHeap = new MaxHeap<>();
 7    }
 8
 9    @Override
10    public int getSize(){
11        return maxHeap.size();
12    }
13
14    @Override
15    public boolean isEmpty(){
16        return maxHeap.isEmpty();
17    }
18
19    @Override
20    public E getFront(){
21        return maxHeap.findMax();
22    }
23
24    @Override
25    public void enqueue(E e){
26        maxHeap.add(e);
27    }
28
29    @Override
30    public E dequeue(){
31        return maxHeap.extractMax();
32    }
33}

堆排序 

理解了優先隊列,堆排序的邏輯十分簡單。

  • 第一步:讓數組造成堆有序狀態;
  • 第二步:把堆頂的元素放到數組最末尾,末尾的放到堆頂,在剩下的元素中下沉到正確位置,重複操做便可。

 

相關文章
相關標籤/搜索