1. 程式人生 > 實用技巧 >h5 Video開啟本地攝像頭和離開頁面關閉攝像頭

h5 Video開啟本地攝像頭和離開頁面關閉攝像頭

什麼是堆

瞭解什麼是堆之前,我們知道佇列的概念,佇列的特點是先進先出,但是有一種特殊的佇列,取出元素的順序是按照元素的優先權(關鍵字)大小,而不是元素進入佇列的先後順序,這就是優先佇列(Priority Queue)。

若採用陣列或者連結串列實現優先佇列,總會有插入、刪除或者查詢中的一項操作的複雜度是$O(N)$ 的。

若採用二叉搜尋樹實現,那麼插入和刪除都跟樹的高度有關,也就是$O(log_2N)$ 的複雜度,但是刪除的時候,由於每次都要刪除最大的或者最小的,這樣操作幾次後,會造成搜尋樹失去平衡,所以不能簡單的使用二叉搜尋樹。

如果採用二叉樹結構,我們更關注的應該是刪除的操作,那麼我們把最大的值放到根結點,左右兩邊也是最大值作為左右子樹的根結點,每次刪除只需要刪除根結點。同時,為了保證樹的平衡性,可以考慮使用完全二叉樹來實現優先佇列。

優先佇列使用完全二叉樹表示如上圖所示,陣列的第 0 個元素空著,後面的按照層序遍歷的順序存放到陣列中。使用完全二叉實現的優先佇列,也可以稱之為堆,堆的特性如下:

  • 結構性:用陣列表示的完全二叉樹。
  • 有序性:任一結點的關鍵字是其子樹所有結點的最大值(或最小值)
    • "最大堆",也稱 "大頂堆":堆頂元素是整個樹的最大值
    • "最小堆",也稱"小頂堆":堆頂元素是整個樹的最小值

如下圖所示的幾個二叉樹,不是堆。

第一和第二棵二叉樹雖然滿足有序性,但是不是完全二叉樹。第三和第四棵二叉樹是完全二叉樹,但是不滿足有序性的特點。

注意:堆從根結點到任意結點路徑上的結點順序都是有序的!

最大堆的建立

堆的資料結構包括儲存完全二叉樹的陣列 data,堆中當前元素個數 size,堆的最大容量 capacity。

陣列的元素從1開始,0的位置定義為哨兵,方便以後更快操作。

public abstract class Heap {
    // 堆的型別定義
    protected int[] data; //儲存元素的陣列
    protected int size;//堆中當前元素個數
    protected int capacity; //堆的最大容量

    public Heap() {
        this.size = 0;
        this.capacity = 0;
    }

    public Heap(int[] data, int capacity) {
        this.data = data;
        this.size = 0;
        this.capacity = capacity;
        this.data[0] = Integer.MAX_VALUE;
    }

    public Heap(int maxSize) {
        this.data = new int[maxSize + 1];//最大元素從1開始
        this.size = 0;
        this.capacity = maxSize;
        this.data[0] = Integer.MAX_VALUE;// 定義哨兵,為大於最大堆中所有可能元素的值
    }

    public boolean isFull() {
        return this.size == this.capacity;
    }

    public boolean isEmpty() {
        return this.size == 0;
    }

    public abstract boolean insert(int element);
}

最大堆的插入

插入元素時,插入到陣列的最後一個位置,這裡插入的結點值為20,檢查插入後仍然符合堆的兩個特性,插入完成。

當插入的值為35的時候,當前堆的有序性被破壞了,將35和31的位置調換後就可以了。

當插入的值為58的時候,58 > 31,跟31對調位置,58 > 44 繼續跟根結點調換位置。調整後保證了有序性,同時,從58 -> 44 -> 31這條線也是按照從大到小的順序。

public boolean insert(int element) {
    // 將元素X插入最大堆H,其中H->Data[0]已經定義為哨兵
    int i;

    if (isFull()) {
        System.out.println("最大堆已滿");
        return false;
    }
    i = ++this.size; // i指向插入後堆中的最後一個元素的位置
    for (; this.data[i / 2] < element; i /= 2) {
        data[i] = data[i / 2]; // 向下過濾結點,對調父結點的位置
    }
    data[i] = element; // 將X插入
    return true;
}

由於我們將陣列的第 0 個元素設定為哨兵,哨兵的值為一個非常大的整數值。如果沒有哨兵結點,我們在迴圈中還需要判斷 i > 1 這個條件,有了哨兵之後,迴圈在 i = 0 的時候就會停下來,可以少寫一個條件,提高程式效率。

最大堆的刪除

最大堆的刪除過程就是取出根結點(最大值)元素,同時刪除堆的一個結點。

刪除下圖的這個堆的最大值:

  1. 把 31 移至根
  2. 找出 31 的較大的孩子

時間複雜度為: $T(N)=O(logN)$

public int deleteMax() {
    // 從最大堆中取出鍵值為最大的元素,並刪除一個結點
    int parent, child;
    int maxItem, temp;//maxItem-堆頂元素,temp-臨時變數
    if (isEmpty()) {
        System.out.println("最大堆已經為空");
        return -1;
    }

    maxItem = this.data[1];//取出根結點最大值
    // 用最大堆中的最後一個元素從根結點開始向上過濾下層結點
    temp = this.data[this.size--];
    for (parent = 1; parent * 2 < this.size; parent = child) {
        child = parent * 2; // 左兒子的位置
        if (child != this.size && this.data[child] < this.data[child + 1]) {
            child++; //child 指向左右結點的較大者
        }
        if (temp > this.data[child]) {//找到位置了
            break;
        } else {//將子結點與父節點對換
            this.data[parent] = this.data[child];
        }
    }
    this.data[parent] = temp;
    return maxItem;
}

最大堆的建立

建立最大堆是將已經存在的N個元素按最大堆的要求存放在一個一維陣列中。

建堆的過程可以從樹的從最後一個結點的父節點開始,到根結點1,將最後一個結點的父節點所在的小堆調整為最大堆,然後向左尋找有兒子的結點,每次調整一個最大堆,直到根結點。

public void buildHeap() {
    //* 調整Data[]中的元素,使滿足最大堆的有序性  *//*
    //* 這裡假設所有Size個元素已經存在Data[]中 *//*

    int i;

    //* 從最後一個結點的父節點開始,到根結點1 *//*
    for (i = this.size / 2; i > 0; i--) {
        preDown(i);
    }
}

private void preDown(int p) {
    //* 下濾:將H中以Data[p]為根的子堆調整為最大堆 *//*
    int parent, child;
    int temp;

    temp = data[p]; //* 取出根結點存放的值 *//*
    for (parent = p; parent * 2 <= size; parent = child) { //這個過程與刪除的過程一樣
        child = parent * 2;
        if ((child != size) && (data[child] < data[child + 1]))
            child++;  //* Child指向左右子結點的較大者 *//*
        if (temp >= data[child]) break; //* 找到了合適位置 *//*
        else  //* 下濾X *//*
            data[parent] = data[child];
    }
    data[parent] = temp;
}

總結

從堆的幾種操作可以發現,刪除和建堆的過程,就是從上往下調整堆的有序性的過程,插入元素的過程是從下往上調整堆的有序性的過程。

參考

【1】資料結構-浙江大學