说明
本文涉及的堆,下标都从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 - 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();