二叉堆、堆排序、优先队列、topK问题详解及js实现

说明

本文涉及的堆,下标都从0开始,本文算法部分严格按照《算法导论》并参照了维基百科

1. 满二叉树

深度为k的二叉树为满二叉树的充要条件是节点数为 $$2^{k}-1$$

图 1.1

图片描述

2. 完全二叉树

满二叉树也是一种完全二叉树

图 2.1

图片描述

  • 2.1. 叶节点只能出现在最下层和次下层
  • 2.2. 非叶子节点的孩子一定是从左至右依次排列的

3. 二叉堆

图 3.1 最大堆

图片描述

图 3.2 最小堆

图片描述

  • 3.1. 概念

二叉堆是一颗完全二叉树,二叉堆分为最大堆和最小堆,最大堆的任何一个节点的关键字都大于或等于其子节点的关键字,最小堆的任何一个节点的关键字都小于或等于其子节点的关键字

  • 3.2. 性质

    • 任何一个非树根节点的父节点为 ⌊(i - 1) / 2⌋
    • 任何一个非叶子节点的左孩子为 2 * i + 1
    • 任何一个非叶子节点的右孩子为 2 * i + 2
  • 3.3. 存储

我们使用一个一维数组来存储二叉堆的元素,在数组的基础上维持并安装 3.1.3.2. 中描述的关系去访问及存取元素,那么,这个数组加上我们维持的关系共同组成了一个二叉堆

  • 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();

你可能感兴趣的:(算法,二叉堆,优先队列,堆排序,node.js)