java堆排序解决最大topk问题_二叉堆、堆排序、优先队列、topK问题详解及js实现...

说明

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

1. 满二叉树

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

图 1.1

df63e62b3956891b4a303bab7785fe6e.png

2. 完全二叉树

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

图 2.1

8f794fac96fea016f651adf40447ea34.png

2.1. 叶节点只能出现在最下层和次下层

2.2. 非叶子节点的孩子一定是从左至右依次排列的

3. 二叉堆

图 3.1 最大堆

256f782ce71d5125c2969ecda54dfa09.png

图 3.2 最小堆

94d7742fab87ed4c5ec7b88588a03907.png

3.1. 概念

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

3.2. 性质

任何一个非树根节点的父节点为 ⌊(i - 1) / 2⌋

任何一个非叶子节点的左孩子为 2 * i + 1

任何一个非叶子节点的右孩子为 2 * i + 2

3.3. 存储

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

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

e8b1cee306c1d6cdaa628361dc3f3c48.png

1a37eeec6114d885844522fddc6c50d3.png

9c1b7382fb6db1c2bc75ec93bcae7c26.png

将 图 1.1(最大堆) 的第 0 个节点的值改变为 1 ,这种改变可能会导致二叉堆失衡,我们需要对改变了的元素即第 0 个元素进行向下调整

调整策略

1 让第0个节点作为当前节点,选取当前节点的两个子节点,选择子节点中关键字较大的节点,该节点的下标为i,如果这个节点的关键字大于当前节点的关键自则交换它们的位置,负责算法结束

2 如果没有结束则对下标为i的节点继续进行调整

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

7177f2969145426273283aa6aca617a7.png

5d170fb54f6ed35b1485709394f9adf0.png

2c01dcf8e6f717835be027c8d8d25ff3.png

元素上浮和元素下沉类似,只是将当前节点和其父节点比较并交换

3.6. 堆排序

在 3.7.节中实现的是基于最大堆的排序,排序后元素按照从小至大有序

排序原理

始终通过将堆顶的元素与堆的最后一个元素交换(最后一个元素是指下标为 heap.size - 1的元素),每交换一次都对堆的大小进行调整 heap.size = heap.size - 1;当heap.size - 1为0时排序结束

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值