优先级队列对每个条目都有优先级值和数据。
因此,当向队列添加新元素时,如果它具有比集合中已有元素更高的优先级值,则它会向表面冒泡。
当一个人调用pop时,我们会获得具有最高优先级的元素的数据。
Javascript中这种优先级队列的有效实现是什么?
有一个名为PriorityQueue的新对象,创建两个带有两个参数(数据,优先级)的方法(推送和弹出)是否有意义?作为一个编码器,这对我来说是有意义的,但我不确定在下腹部使用哪种数据结构将允许操纵元素的排序。或者我们可以将它们全部存储在一个数组中并每次遍历数组以获取具有最高优先级的元素?
这是一个很好的方法吗?
答案 0 :(得分:18)
以下是我认为真正有效的PriorityQueue
版本,它使用基于数组的二进制堆(其中根位于索引0
,而节点的子节点位于索引处i
分别位于索引2i + 1
和2i + 2
。
此实现包括经典优先级队列方法,如push
,peek
,pop
和size
,以及便捷方法isEmpty
和{{ 1}}(后者是replace
更有效的替代品,紧接着是pop
。值不是push
对存储,而是[value, priority]
s;这允许自动确定可以使用value
运算符进行本机比较的类型的优先级。传递给>
构造函数的自定义比较器函数可用于模拟成对语义的行为,但是,如下例所示。
PriorityQueue
const top = 0;
const parent = i => ((i + 1) >>> 1) - 1;
const left = i => (i << 1) + 1;
const right = i => (i + 1) << 1;
class PriorityQueue {
constructor(comparator = (a, b) => a > b) {
this._heap = [];
this._comparator = comparator;
}
size() {
return this._heap.length;
}
isEmpty() {
return this.size() == 0;
}
peek() {
return this._heap[top];
}
push(...values) {
values.forEach(value => {
this._heap.push(value);
this._siftUp();
});
return this.size();
}
pop() {
const poppedValue = this.peek();
const bottom = this.size() - 1;
if (bottom > top) {
this._swap(top, bottom);
}
this._heap.pop();
this._siftDown();
return poppedValue;
}
replace(value) {
const replacedValue = this.peek();
this._heap[top] = value;
this._siftDown();
return replacedValue;
}
_greater(i, j) {
return this._comparator(this._heap[i], this._heap[j]);
}
_swap(i, j) {
[this._heap[i], this._heap[j]] = [this._heap[j], this._heap[i]];
}
_siftUp() {
let node = this.size() - 1;
while (node > top && this._greater(node, parent(node))) {
this._swap(node, parent(node));
node = parent(node);
}
}
_siftDown() {
let node = top;
while (
(left(node) < this.size() && this._greater(left(node), node)) ||
(right(node) < this.size() && this._greater(right(node), node))
) {
let maxChild = (right(node) < this.size() && this._greater(right(node), left(node))) ? right(node) : left(node);
this._swap(node, maxChild);
node = maxChild;
}
}
}
&#13;
{const top=0,parent=c=>(c+1>>>1)-1,left=c=>(c<<1)+1,right=c=>c+1<<1;class PriorityQueue{constructor(c=(d,e)=>d>e){this._heap=[],this._comparator=c}size(){return this._heap.length}isEmpty(){return 0==this.size()}peek(){return this._heap[top]}push(...c){return c.forEach(d=>{this._heap.push(d),this._siftUp()}),this.size()}pop(){const c=this.peek(),d=this.size()-1;return d>top&&this._swap(top,d),this._heap.pop(),this._siftDown(),c}replace(c){const d=this.peek();return this._heap[top]=c,this._siftDown(),d}_greater(c,d){return this._comparator(this._heap[c],this._heap[d])}_swap(c,d){[this._heap[c],this._heap[d]]=[this._heap[d],this._heap[c]]}_siftUp(){for(let c=this.size()-1;c>top&&this._greater(c,parent(c));)this._swap(c,parent(c)),c=parent(c)}_siftDown(){for(let d,c=top;left(c)<this.size()&&this._greater(left(c),c)||right(c)<this.size()&&this._greater(right(c),c);)d=right(c)<this.size()&&this._greater(right(c),left(c))?right(c):left(c),this._swap(c,d),c=d}}window.PriorityQueue=PriorityQueue}
// Default comparison semantics
const queue = new PriorityQueue();
queue.push(10, 20, 30, 40, 50);
console.log('Top:', queue.peek()); //=> 50
console.log('Size:', queue.size()); //=> 5
console.log('Contents:');
while (!queue.isEmpty()) {
console.log(queue.pop()); //=> 40, 30, 20, 10
}
// Pairwise comparison semantics
const pairwiseQueue = new PriorityQueue((a, b) => a[1] > b[1]);
pairwiseQueue.push(['low', 0], ['medium', 5], ['high', 10]);
console.log('\nContents:');
while (!pairwiseQueue.isEmpty()) {
console.log(pairwiseQueue.pop()[0]); //=> 'high', 'medium', 'low'
}
&#13;
答案 1 :(得分:7)
您应该使用标准库,例如闭包库(goog.structs.PriorityQueue
):
https://google.github.io/closure-library/api/goog.structs.PriorityQueue.html
点击源代码,您就会知道它实际上是链接到您可以关注的goog.structs.Heap
:
https://github.com/google/closure-library/blob/master/closure/goog/structs/heap.js
答案 2 :(得分:3)
我对现有优先级队列实现的效率不满意,所以我决定自己做:
https://github.com/luciopaiva/heapify
npm i heapify
由于使用了类型化数组,它的运行速度比任何其他公知实现都要快。
在客户端和服务器端均可使用,代码库具有100%的测试覆盖率,纤巧的库(〜100 LoC)。而且,界面真的很简单。这是一些代码:
import Heapify from "heapify";
const queue = new Heapify();
queue.push(1, 10); // insert item with key=1, priority=10
queue.push(2, 5); // insert item with key=2, priority=5
queue.pop(); // 2
queue.peek(); // 1
queue.peekPriority(); // 10
答案 3 :(得分:1)
我在这里提供了我使用的实现。我做出了以下决定:
<
、>
、...)比较这些值。这适用于数字、大整数、字符串和日期实例。如果值是这样排序的对象,则应覆盖它们的 valueOf
以保证所需的排序。或者,此类对象应作为有效负载提供,而真正定义顺序的对象属性应作为值(在第一个数组位置)给出。heapq
模块的工作方式,并给它一种“轻松”的感觉:您可以直接使用自己的数组。没有 new
,没有继承,只有作用于数组的普通函数。下面是函数集合,带有注释,最后是一个简单的演示:
/* MinHeap:
* A collection of functions that operate on an array
* of [key,...data] elements (nodes).
*/
const MinHeap = {
/* siftDown:
* The node at the given index of the given heap is sifted down in
* its subtree until it does not have a child with a lesser value.
*/
siftDown(arr, i=0, value=arr[i]) {
if (i < arr.length) {
let key = value[0]; // Grab the value to compare with
while (true) {
// Choose the child with the least value
let j = i*2+1;
if (j+1 < arr.length && arr[j][0] > arr[j+1][0]) j++;
// If no child has lesser value, then we've found the spot!
if (j >= arr.length || key <= arr[j][0]) break;
// Copy the selected child node one level up...
arr[i] = arr[j];
// ...and consider the child slot for putting our sifted node
i = j;
}
arr[i] = value; // Place the sifted node at the found spot
}
},
/* heapify:
* The given array is reordered in-place so that it becomes a valid heap.
* Elements in the given array must have a [0] property (e.g. arrays).
* That [0] value serves as the key to establish the heap order. The rest
* of such an element is just payload. It also returns the heap.
*/
heapify(arr) {
// Establish heap with an incremental, bottom-up process
for (let i = arr.length>>1; i--; ) this.siftDown(arr, i);
return arr;
},
/* pop:
* Extracts the root of the given heap, and returns it (the subarray).
* Returns undefined if the heap is empty
*/
pop(arr) {
// Pop the last leaf from the given heap, and exchange it with its root
return this.exchange(arr, arr.pop()); // Returns the old root
},
/* exchange:
* Replaces the root node of the given heap with the given node, and
* returns the previous root. Returns the given node if the heap is empty.
* This is similar to a call of pop and push, but is more efficient.
*/
exchange(arr, value) {
if (!arr.length) return value;
// Get the root node, so to return it later
let oldValue = arr[0];
// Inject the replacing node using the sift-down process
this.siftDown(arr, 0, value);
return oldValue;
},
/* push:
* Inserts the given node into the given heap. It returns the heap.
*/
push(arr, value) {
let key = value[0],
// First assume the insertion spot is at the very end (as a leaf)
i = arr.length,
j;
// Then follow the path to the root, moving values down for as long
// as they are greater than the value to be inserted
while ((j = (i-1)>>1) >= 0 && key < arr[j][0]) {
arr[i] = arr[j];
i = j;
}
// Found the insertion spot
arr[i] = value;
return arr;
}
};
// Simple Demo:
let heap = [];
MinHeap.push(heap, [26, "Helen"]);
MinHeap.push(heap, [15, "Mike"]);
MinHeap.push(heap, [20, "Samantha"]);
MinHeap.push(heap, [21, "Timothy"]);
MinHeap.push(heap, [19, "Patricia"]);
let [age, name] = MinHeap.pop(heap);
console.log(`${name} is the youngest with ${age} years`);
([age, name] = MinHeap.pop(heap));
console.log(`Next is ${name} with ${age} years`);
有关更现实的示例,请参阅 Dijkstra's shortest path algorithm 的实现。
这里是同一个 MinHeap
集合,但是缩小了,连同它的 MaxHeap
镜像:
const MinHeap={siftDown(h,i=0,v=h[i]){if(i<h.length){let k=v[0];while(1){let j=i*2+1;if(j+1<h.length&&h[j][0]>h[j+1][0])j++;if(j>=h.length||k<=h[j][0])break;h[i]=h[j];i=j;}h[i]=v}},heapify(h){for(let i=h.length>>1;i--;)this.siftDown(h,i);return h},pop(h){return this.exchange(h,h.pop())},exchange(h,v){if(!h.length)return v;let w=h[0];this.siftDown(h,0,v);return w},push(h,v){let k=v[0],i=h.length,j;while((j=(i-1)>>1)>=0&&k<h[j][0]){h[i]=h[j];i=j}h[i]=v;return h}};
const MaxHeap={siftDown(h,i=0,v=h[i]){if(i<h.length){let k=v[0];while(1){let j=i*2+1;if(j+1<h.length&&h[j][0]<h[j+1][0])j++;if(j>=h.length||k>=h[j][0])break;h[i]=h[j];i=j;}h[i]=v}},heapify(h){for(let i=h.length>>1;i--;)this.siftDown(h,i);return h},pop(h){return this.exchange(h,h.pop())},exchange(h,v){if(!h.length)return v;let w=h[0];this.siftDown(h,0,v);return w},push(h,v){let k=v[0],i=h.length,j;while((j=(i-1)>>1)>=0&&k>h[j][0]){h[i]=h[j];i=j}h[i]=v;return h}};