二叉堆、堆排序、優先隊列、topK問題詳解及js實現

說明

本文涉及的堆,下標都從0開始,本文算法部分嚴格按照《算法導論》並參照了維基百科算法

1. 滿二叉樹

深度爲k的二叉樹爲滿二叉樹的充要條件是節點數爲 $$2^{k}-1$$api

圖 1.1數組

圖片描述

2. 徹底二叉樹

滿二叉樹也是一種徹底二叉樹dom

圖 2.1測試

圖片描述

  • 2.1. 葉節點只能出如今最下層和次下層
  • 2.2. 非葉子節點的孩子必定是從左至右依次排列的

3. 二叉堆

圖 3.1 最大堆ui

圖片描述

圖 3.2 最小堆this

圖片描述

  • 3.1. 概念

二叉堆是一顆徹底二叉樹,二叉堆分爲最大堆和最小堆,最大堆的任何一個節點的關鍵字都大於或等於其子節點的關鍵字,最小堆的任何一個節點的關鍵字都小於或等於其子節點的關鍵字spa

  • 3.2. 性質prototype

    • 任何一個非樹根節點的父節點爲 ⌊(i - 1) / 2⌋
    • 任何一個非葉子節點的左孩子爲 2 * i + 1
    • 任何一個非葉子節點的右孩子爲 2 * i + 2
  • 3.3. 存儲

咱們使用一個一維數組來存儲二叉堆的元素,在數組的基礎上維持並安裝 3.1.3.2. 中描述的關係去訪問及存取元素,那麼,這個數組加上咱們維持的關係共同組成了一個二叉堆3d

  • 3.4. 二叉堆的向下調整(元素的下沉)

圖片描述
圖片描述
圖片描述

圖 1.1(最大堆) 的第 0 個節點的值改變爲 1 ,這種改變可能會致使二叉堆失衡,咱們須要對改變了的元素即第 0 個元素進行向下調整

調整策略

1 讓第0個節點做爲當前節點,選取當前節點的兩個子節點,選擇子節點中關鍵字較大的節點,該節點的下標爲i,若是這個節點的關鍵字大於當前節點的關鍵自則交換它們的位置,負責算法結束

2 若是沒有結束則對下標爲i的節點繼續進行調整

  • 3.5. 二叉堆的向上調整(元素的上浮)

圖片描述
圖片描述
圖片描述

元素上浮和元素下沉相似,只是將當前節點和其父節點比較並交換

  • 3.6. 堆排序

3.7.節中實現的是基於最大堆的排序,排序後元素按照從小至大有序

排序原理

始終經過將堆頂的元素與堆的最後一個元素交換(最後一個元素是指下標爲 heap.size - 1的元素),每交換一次都對堆的大小進行調整 heap.size = heap.size - 1;當heap.size - 10時排序結束

  • 3.7. js 實現最大堆

function MaxBinaryHeap(key) {
    if (!(this instanceof MaxBinaryHeap))
        return new MaxBinaryHeap(key);
    this.key = key; //key表示用來排序的關鍵字
    this.size = 0; //堆大小 這裏堆大小和數組大小一致
    this.list = []; //用於存放堆元素 存放的是對象
}
MaxBinaryHeap.prototype = {
    constructor: MaxBinaryHeap,
    //獲取某個節點的父節點
    parent: function(i) {
        let p = Math.floor((i - 1) / 2);
        if (i > this.size - 1 || p < 0) return null;
        return p; //這裏返回的 p 是在數組中的下標,數組是從0開始的
    },
    //獲取某個節點的左孩子
    left: function(i) {
        let l = 2 * i + 1;
        if (l > this.size - 1) return null;
        return l;
    },
    //獲取某個節點的右孩子
    right: function(i) {
        let r = 2 * i + 2;
        if (r > this.size - 1) return null;
        return r;
    },
    //元素下沉 對下標爲i的元素向下進行調整,使堆保持其性質
    maxHeapify: function(i) {
        let list = this.list;
        let key = this.key;
        let l = this.left(i);
        let r = this.right(i);
        let larget = null;
        if (l != null) { //左孩子爲空則右孩子必定爲空
            if (r == null) larget = l;
            else larget = list[l][key] > list[r][key] ? l : r;
            if (list[i][key] >= list[larget][key]) return;
            else {
                let t = list[i];
                list[i] = list[larget];
                list[larget] = t;
                this.maxHeapify(larget);
            }
        }
    },
    //元素上浮 對下標爲i的元素進行向上調整,使堆保持其性質
    increase: function(i) {
        let list = this.list;
        let p = this.parent(i);
        while (i > 0 && list[p][this.key] < list[i][this.key]) { //i > 0 必定能保證 p != null
            let t = list[i];
            list[i] = list[p];
            list[p] = t;
            i = this.parent(i);
            p = this.parent(i);
        }
    },
    //構建堆
    buildHeap: function(a) {
        this.list = a;
        this.size = a.length;
        for (let i = Math.floor(a.length / 2) - 1; i > -1; i--) {
            this.maxHeapify(i);
        }
    },
    //堆排序 由小到大
    heapSort: function(a) {
        if(a !=null) this.buildHeap(a);
        for (let i = this.size - 1; i > 0; i--) {
            let t = this.list[0];
            this.list[0] = this.list[i];
            this.list[i] = t;
            this.size--;
            this.maxHeapify(0);
        }
        return this.list;
    }
}
//測試用例
var a = [{key:1},{key:7},{key:2},{key:5},{key:3},{key:2},{key:6},{key:10}];
var heap = MaxBinaryHeap('key');
heap.buildHeap(a);
console.log(heap.heapSort());

4. 優先隊列

  • 4.1. 概念

每一個元素都有與之相關的「優先級」,在優先級隊列中,具備高優先級的元素在一個低優先級的元素以前被服務,若是兩個元素具備相同的優先級,那麼它們將按照隊列中的順序進行服務,優先隊列的實現方式不少種,二項堆,斐波那契堆均可以實現,這裏採用二叉堆實現

這一節實現的優先隊列是基於最大堆實現的,因此關鍵字越大優先級越高支持的操做有insert//插入remove//刪除, max//獲取最大, update//更新

  • 4.2. 最大優先隊列的js實現

//優先隊列
function MaxPriorityQueue(key, a) {
    if (!(this instanceof MaxPriorityQueue))
        return new MaxPriorityQueue(key, a);
    this.maxBinaryHeap = MaxBinaryHeap(key);
    if(a != null) this.maxBinaryHeap.buildHeap(a);
    this.key = key;
}
MaxPriorityQueue.prototype = {
    constructor: MaxPriorityQueue,
    insert: function(x) { //加入一個元素
        this.maxBinaryHeap.size++;
        this.maxBinaryHeap.list[this.maxBinaryHeap.size - 1] = x;
        //上浮操做
        this.maxBinaryHeap.increase(this.maxBinaryHeap.size - 1);
    },
    max: function() { //獲取最大元素
        let max = this.maxBinaryHeap.list[0];
        this.removeMax();
        return max;
    },
    removeMax: function() { //移除最大元素
        let list = this.maxBinaryHeap.list;
        let size = this.maxBinaryHeap.size;
        let max = list[0];
        list[0] = list[size - 1];
        list.shift(size - 1); //刪除
        this.maxBinaryHeap.size--;
        this.maxBinaryHeap.maxHeapify(0); //元素下沉操做
        return max;
    },
    update: function(i, x) { //更新元素
        this.maxBinaryHeap.list[i] = x;
        this.maxBinaryHeap.maxHeapify(i); //元素下沉操做
        this.maxBinaryHeap.increase(i); //元素上浮操做
    }
}
  • 4.3. 基於最大堆的優先隊列完整代碼及測試

function MaxBinaryHeap(key) {
    if (!(this instanceof MaxBinaryHeap))
        return new MaxBinaryHeap(key);
    this.key = key; //key表示用來排序的字段
    this.size = 0; //堆大小 這裏堆大小和數組大小一致
    this.list = []; //用於存放堆元素 存放的是對象
}
MaxBinaryHeap.prototype = {
    constructor: MaxBinaryHeap,
    //獲取某個節點的父節點
    parent: function(i) {
        let p = Math.floor((i - 1) / 2);
        if (i > this.size - 1 || p < 0) return null;
        return p; //這裏返回的 p 是在數組中的下標,數組是從0開始的
    },
    //獲取某個節點的左孩子
    left: function(i) {
        let l = 2 * i + 1;
        if (l > this.size - 1) return null;
        return l;
    },
    //獲取某個節點的右孩子
    right: function(i) {
        let r = 2 * i + 2;
        if (r > this.size - 1) return null;
        return r;
    },
    //元素下沉 對下標爲i的元素向下進行調整,使堆保持其性質
    maxHeapify: function(i) {
        let list = this.list;
        let key = this.key;
        let l = this.left(i);
        let r = this.right(i);
        let larget = null;
        if (l != null) { //左孩子爲空則右孩子必定爲空
            if (r == null) larget = l;
            else larget = list[l][key] > list[r][key] ? l : r;
            if (list[i][key] >= list[larget][key]) return;
            else {
                let t = list[i];
                list[i] = list[larget];
                list[larget] = t;
                this.maxHeapify(larget);
            }
        }
    },
    //元素上浮 對下標爲i的元素進行向上調整,使堆保持其性質
    increase: function(i) {
        let list = this.list;
        let p = this.parent(i);
        while (i > 0 && list[p][this.key] < list[i][this.key]) { //i > 0 必定能保證 p != null
            let t = list[i];
            list[i] = list[p];
            list[p] = t;
            i = this.parent(i);
            p = this.parent(i);
        }
    },
    //構建堆
    buildHeap: function(a) {
        this.list = a;
        this.size = a.length;
        for (let i = Math.floor(a.length / 2) - 1; i > -1; i--) {
            this.maxHeapify(i);
        }
    },
    //堆排序 由小到大
    heapSort: function(a) {
        this.buildHeap(a);
        for (let i = this.size - 1; i > 0; i--) {
            let t = this.list[0];
            this.list[0] = this.list[i];
            this.list[i] = t;
            this.size--;
            this.maxHeapify(0);
        }
        return this.list;
    }
}

//優先隊列
function MaxPriorityQueue(key, a) {
    if (!(this instanceof MaxPriorityQueue))
        return new MaxPriorityQueue(key, a);
    this.maxBinaryHeap = MaxBinaryHeap(key);
    if(a != null) this.maxBinaryHeap.buildHeap(a);
    this.key = key;
}
MaxPriorityQueue.prototype = {
    constructor: MaxPriorityQueue,
    insert: function(x) { //加入一個元素
        this.maxBinaryHeap.size++;
        this.maxBinaryHeap.list[this.maxBinaryHeap.size - 1] = x;
        //向上調整
        this.maxBinaryHeap.increase(this.maxBinaryHeap.size - 1);
    },
    max: function() { //獲取最大元素
        let max = this.maxBinaryHeap.list[0];
        this.removeMax();
        return max;
    },
    removeMax: function() { //移除最大元素
        let list = this.maxBinaryHeap.list;
        let size = this.maxBinaryHeap.size;
        let max = list[0];
        list[0] = list[size - 1];
        list.shift(size - 1); //刪除
        this.maxBinaryHeap.size--;
        this.maxBinaryHeap.maxHeapify(0); //元素下沉操做
        return max;
    },
    update: function(i, x) { //更新元素
        this.maxBinaryHeap.list[i] = x;
        this.maxBinaryHeap.maxHeapify(i); //元素下沉操做
        this.maxBinaryHeap.increase(i); //元素上浮操做
    }
}
//測試用例
var a = [{key:1},{key:7},{key:2},{key:5},{key:3},{key:2},{key:6},{key:10}];
var priorityQueue = MaxPriorityQueue('key', a);
priorityQueue.insert({key:11}); //插入一個元素
priorityQueue.max(); //獲取最大元素並刪除
priorityQueue.removeMax(); //刪除最大元素
priorityQueue.update(3,{key:100}); //更新下標爲3的元素
console.log(a);
  • 4.4. 最小堆及基於最小堆的優先隊列及js實現

最小堆和最大對的原理相同,代碼也大部分相同

function MinBinaryHeap(key) {
    if (!(this instanceof MinBinaryHeap))
        return new MinBinaryHeap(key);
    this.key = key; //key表示用來排序的字段
    this.size = 0; //堆大小 這裏堆大小和數組大小一致
    this.list = []; //用於存放堆元素 存放的是對象
}
MinBinaryHeap.prototype = {
    constructor: MinBinaryHeap,
    //獲取某個節點的父節點
    parent: function(i) {
        let p = Math.floor((i - 1) / 2);
        if (i > this.size - 1 || p < 0) return null;
        return p; //這裏返回的 p 是在數組中的下標,數組是從0開始的
    },
    //獲取某個節點的左孩子
    left: function(i) {
        let l = 2 * i + 1;
        if (l > this.size - 1) return null;
        return l;
    },
    //獲取某個節點的右孩子
    right: function(i) {
        let r = 2 * i + 2;
        if (r > this.size - 1) return null;
        return r;
    },
    minHeapify: function(i) {
        let list = this.list;
        let key = this.key;
        let l = this.left(i);
        let r = this.right(i);
        let smallest = null;
        if (l != null) { //左孩子爲空則右孩子必定爲空
            if (r == null) smallest = l;
            else smallest = list[l][key] < list[r][key] ? l : r;
            if (list[i][key] <= list[smallest][key]) return;
            else {
                let t = list[i];
                list[i] = list[smallest];
                list[smallest] = t;
                this.minHeapify(smallest);
            }
        }
    },
    //元素上浮 對下標爲i的元素進行向上調整,使堆保持其性質
    increase: function(i) {
        let list = this.list;
        let p = this.parent(i);
        while (i > 0 && list[p][this.key] > list[i][this.key]) { //i > 0 必定能保證 p != null
            let t = list[i];
            list[i] = list[p];
            list[p] = t;
            i = this.parent(i);
            p = this.parent(i);
        }
    },
    //構建堆
    buildHeap: function(a) {
        this.list = a;
        this.size = a.length;
        for (let i = Math.floor(a.length / 2) - 1; i > -1; i--) {
            this.minHeapify(i);
        }
    },
    //堆排序 由大到小
    heapSort: function(a) {
        this.buildHeap(a);
        for (let i = this.size - 1; i > 0; i--) {
            let t = this.list[0];
            this.list[0] = this.list[i];
            this.list[i] = t;
            this.size--;
            this.minHeapify(0);
        }
        return this.list;
    }
}

//最小優先隊列
function MinPriorityQueue(key, a) {
    if (!(this instanceof MinPriorityQueue))
        return new MinPriorityQueue(key, a);
    this.minBinaryHeap = MinBinaryHeap(key);
    this.minBinaryHeap.buildHeap(a);
    this.key = key;
}
MinPriorityQueue.prototype = {
    constructor: MinPriorityQueue,
    insert: function(x) { //加入一個元素
        this.minBinaryHeap.size++;
        this.minBinaryHeap.list[this.minBinaryHeap.size - 1] = x;
        //向上調整
        this.minBinaryHeap.increase(this.minBinaryHeap.size - 1);
    },
    //remove 表示獲取後是否刪除 true 刪除 false 不刪除
    min: function(remove) { //獲取最小元素
        let min = this.minBinaryHeap.list[0];
        if(remove) this.removeMin();
        return min;
    },
    removeMin: function() { //移除最小元素
        let list = this.minBinaryHeap.list;
        let size = this.minBinaryHeap.size;
        let min = list[0];
        list[0] = list[size - 1];
        list.shift(size - 1); //刪除
        this.minBinaryHeap.size--;
        this.minBinaryHeap.minHeapify(0);
        return min;
    },
    update: function(i, x) { //更新元素
        this.minBinaryHeap.list[i] = x;
        this.minBinaryHeap.minHeapify(i);
        this.minBinaryHeap.increase(i);
    }
}

var a = [{key:1},{key:7},{key:2},{key:5},{key:3},{key:2},{key:6},{key:10}];
var priorityQueue = MinPriorityQueue('key', a);
priorityQueue.insert({key:11});
console.log(a);

5. topK 問題

  • 5.1.問題描述

如今有 1W 個浮點數,選取其中最大的 100個,要求,在算法實現中只能用長度爲100的數組

  • 5.2. 解決方法

使用基於最小堆的優先隊列,將 浮點數的前一百個元素一個個讀取,並存入數組,以後進行堆排序,將剩餘的元素一個個拿來和堆頂元素進行比較,若是頂元素較小,則對堆頂元素進行更新,直到全部元素被訪問完此時堆中的即是 topK

  • 5.3. js實現

function MinBinaryHeap(key) {
    if (!(this instanceof MinBinaryHeap))
        return new MinBinaryHeap(key);
    this.key = key; //key表示用來排序的字段
    this.size = 0; //堆大小 這裏堆大小和數組大小一致
    this.list = []; //用於存放堆元素 存放的是對象
}
MinBinaryHeap.prototype = {
    constructor: MinBinaryHeap,
    //獲取某個節點的父節點
    parent: function(i) {
        let p = Math.floor((i - 1) / 2);
        if (i > this.size - 1 || p < 0) return null;
        return p; //這裏返回的 p 是在數組中的下標,數組是從0開始的
    },
    //獲取某個節點的左孩子
    left: function(i) {
        let l = 2 * i + 1;
        if (l > this.size - 1) return null;
        return l;
    },
    //獲取某個節點的右孩子
    right: function(i) {
        let r = 2 * i + 2;
        if (r > this.size - 1) return null;
        return r;
    },
    minHeapify: function(i) {
        let list = this.list;
        let key = this.key;
        let l = this.left(i);
        let r = this.right(i);
        let smallest = null;
        if (l != null) { //左孩子爲空則右孩子必定爲空
            if (r == null) smallest = l;
            else smallest = list[l][key] < list[r][key] ? l : r;
            if (list[i][key] <= list[smallest][key]) return;
            else {
                let t = list[i];
                list[i] = list[smallest];
                list[smallest] = t;
                this.minHeapify(smallest);
            }
        }
    },
    //元素上浮 對下標爲i的元素進行向上調整,使堆保持其性質
    increase: function(i) {
        let list = this.list;
        let p = this.parent(i);
        while (i > 0 && list[p][this.key] > list[i][this.key]) { //i > 0 必定能保證 p != null
            let t = list[i];
            list[i] = list[p];
            list[p] = t;
            i = this.parent(i);
            p = this.parent(i);
        }
    },
    //構建堆
    buildHeap: function(a) {
        this.list = a;
        this.size = a.length;
        for (let i = Math.floor(a.length / 2) - 1; i > -1; i--) {
            this.minHeapify(i);
        }
    },
    //堆排序 由大到小
    heapSort: function(a) {
        if (a != null) this.buildHeap(a);
        for (let i = this.size - 1; i > 0; i--) {
            let t = this.list[0];
            this.list[0] = this.list[i];
            this.list[i] = t;
            this.size--;
            this.minHeapify(0);
        }
        return this.list;
    }
}

//最小優先隊列
function MinPriorityQueue(key, a) {
    if (!(this instanceof MinPriorityQueue))
        return new MinPriorityQueue(key, a);
    this.minBinaryHeap = MinBinaryHeap(key);
    this.minBinaryHeap.buildHeap(a);
    this.key = key;
}
MinPriorityQueue.prototype = {
    constructor: MinPriorityQueue,
    insert: function(x) { //加入一個元素
        this.minBinaryHeap.size++;
        this.minBinaryHeap.list[this.minBinaryHeap.size - 1] = x;
        //向上調整
        this.minBinaryHeap.increase(this.minBinaryHeap.size - 1);
    },
    min: function(remove) { //獲取最小元素
        let min = this.minBinaryHeap.list[0];
        if (remove) this.removeMin();
        return min;
    },
    removeMin: function() { //移除最小元素
        let list = this.minBinaryHeap.list;
        let size = this.minBinaryHeap.size;
        let min = list[0];
        list[0] = list[size - 1];
        list.shift(size - 1); //刪除
        this.minBinaryHeap.size--;
        this.minBinaryHeap.minHeapify(0);
        return min;
    },
    update: function(i, x) { //更新元素
        this.minBinaryHeap.list[i] = x;
        this.minBinaryHeap.minHeapify(i);
        this.minBinaryHeap.increase(i);
    }
}

//生成1w個浮點數
function getDataSource() {
    let list = [];
    for (let i = 0; i < 10000; i++) {
        list.push(Math.random() * 1000);
    }
    return list;
}

function top100() {
    var dataSource = getDataSource();
    //獲取前100個元素
    let top = [];
    for (let i = 0; i < 100; i++) {
        top.push({
            key: dataSource[i]
        });
    }
    //構建最小優先隊列
    var priorityQueue = MinPriorityQueue('key', top);
    let key = priorityQueue.key;
    //處理其它元素
    for (let i = 100; i < 10000; i++) {
        let min = priorityQueue.min(false);
        if (min[key] < dataSource[i]) {
            priorityQueue.update(0, {
                key: dataSource[i]
            });
        }
    }
    //對結果排序
    priorityQueue.minBinaryHeap.heapSort();
    return top;
}
top100();
相關文章
相關標籤/搜索