[英]Efficient way to implement Priority Queue in Javascript?
Priority Queues have a priority value and data, for every entry.对于每个条目,优先级队列都有一个优先级值和数据。
Thus, when adding a new element to the queue, it bubbles up to the surface if it has a higher priority value than elements already in the collection.因此,当向队列中添加一个新元素时,如果它具有比集合中已有元素更高的优先级值,它就会冒泡到表面。
When one calls pop, we get the data for the element with highest priority.当调用 pop 时,我们获得具有最高优先级的元素的数据。
What is an efficient implementation of such a priority queue in Javascript? Javascript 中这种优先级队列的有效实现是什么?
Does it make sense to have a new object called PriorityQueue, create two methods (push and pop) that take two params (data, priority)?有一个名为 PriorityQueue 的新 object 是否有意义,创建两个方法(push 和 pop)接受两个参数(数据,优先级)? That much makes sense to me as a coder, but I'm uncertain of which data structure to use in the underbelly that will allow manipulation of the ordering of elements.
作为一名编码员,这对我来说很有意义,但我不确定在允许操纵元素顺序的软肋中使用哪种数据结构。 Or can we just store it all in an array and walk through the array every time to grab the element with max priority?
或者我们可以将它全部存储在一个数组中并每次遍历该数组以获取具有最大优先级的元素吗?
What's a good way to do this?这样做的好方法是什么?
Below is what I believe to be a truly efficient version of a PriorityQueue
which uses an array-based binary heap (where the root is at index 0
, and the children of a node at index i
are at indices 2i + 1
and 2i + 2
, respectively).下面是我认为是
PriorityQueue
的真正高效版本,它使用基于数组的二元堆(其中根位于索引0
处,索引i
处的节点的子节点位于索引2i + 1
和2i + 2
, 分别)。
This implementation includes the classical priority queue methods like push
, peek
, pop
, and size
, as well as convenience methods isEmpty
and replace
(the latter being a more efficient substitute for a pop
followed immediately by a push
).此实现包括经典的优先级队列方法,如
push
、 peek
、 pop
和size
,以及方便的方法isEmpty
和replace
(后者是一个更有效的替代pop
后紧跟push
)。 Values are stored not as [value, priority]
pairs, but simply as value
s;值不是存储为
[value, priority]
对,而是简单地存储为value
s; this allows for automatic prioritization of types that can be natively compared using the >
operator.这允许自动对可以使用
>
运算符进行本地比较的类型进行优先级排序。 A custom comparator function passed to the PriorityQueue
constructor can be used to emulate the behavior of pairwise semantics, however, as shown in the example below.但是,传递给
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;
}
}
}
{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' }
.as-console-wrapper{min-height:100%}
You should use standard libraries like eg the Closure Library ( goog.structs.PriorityQueue
):您应该使用标准库,例如 Closure Library (
goog.structs.PriorityQueue
):
https://google.github.io/closure-library/api/goog.structs.PriorityQueue.html https://google.github.io/closure-library/api/goog.structs.PriorityQueue.html
By clicking at the source code, you will know it is actually linking to goog.structs.Heap
which you can follow:通过点击源代码,你会知道它实际上是链接到
goog.structs.Heap
,你可以遵循:
https://github.com/google/closure-library/blob/master/closure/goog/structs/heap.js https://github.com/google/closure-library/blob/master/closure/goog/structs/heap.js
I was not satisfied with the efficiency of existing priority queue implementations, so I decided to make my own:我对现有优先级队列实现的效率不满意,所以我决定自己做:
https://github.com/luciopaiva/heapify https://github.com/luciopaiva/heapify
npm i heapify
This will run faster than any other publicly known implementation due to the use of typed arrays.由于使用类型化数组,这将比任何其他公知的实现运行得更快。
Works on both client and server ends, code base with 100% test coverage, tiny library (~100 LoC).适用于客户端和服务器端,具有 100% 测试覆盖率的代码库,小型库(~100 LoC)。 Also, the interface is really simple.
此外,界面非常简单。 Here's some code:
这是一些代码:
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
I provide here the implementation I use.我在这里提供了我使用的实现。 I made the following decisions:
我做了以下决定:
<
, >
, ...).<
、 >
、 ...)比较这些值。 This works fine for numbers, bigints, strings, and Date instances.valueOf
should be overridden to guarantee the desired ordering.valueOf
以保证所需的排序。 Or, such objects should be provided as payload, and the object's property that really defines the order, should be given as the value (in first array position).heapq
module works and gives a "light" feeling to it: You work directly with your own array.heapq
模块的工作方式,并给人一种“轻松”的感觉:您可以直接使用自己的数组。 No new
, no inheritance, just plain functions acting on your array.new
,没有继承,只有作用于数组的普通函数。 Here follows that collection of functions, with comments, and a simple demo at the end:下面是函数集合,带有注释,最后是一个简单的演示:
/* 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 (eg 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`);
For a more realistic example, see the implementation of Dijkstra's shortest path algorithm .有关更现实的示例,请参阅Dijkstra 最短路径算法的实现。
Here is the same MinHeap
collection, but minified, together with its MaxHeap
mirror:这是同一个
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}};
Took some inspiration from @gyre's answer and wrote a minimalistic version in TypeScript, that is about 550 bytes minified.从@gyre 的回答中获得一些灵感,并在 TypeScript 中编写了一个简约版本,缩小了大约 550 个字节。
type Comparator<T> = (valueA: T, valueB: T) => number;
const swap = (arr: unknown[], i: number, j: number) => {
[arr[i], arr[j]] = [arr[j], arr[i]];
};
class PriorityQueue<T> {
#heap;
#isGreater;
constructor(comparator: Comparator<T>);
constructor(comparator: Comparator<T>, init: T[] = []) {
this.#heap = init;
this.#isGreater = (a: number, b: number) =>
comparator(init[a] as T, init[b] as T) > 0;
}
get size(): number {
return this.#heap.length;
}
peek(): T | undefined {
return this.#heap[0];
}
add(value: T): void {
this.#heap.push(value);
this.#siftUp();
}
poll(): T | undefined;
poll(
heap = this.#heap,
value = heap[0],
length = heap.length
): T | undefined {
if (length) {
swap(heap, 0, length - 1);
}
heap.pop();
this.#siftDown();
return value;
}
#siftUp(): void;
#siftUp(node = this.size - 1, parent = ((node + 1) >>> 1) - 1): void {
for (
;
node && this.#isGreater(node, parent);
node = parent, parent = ((node + 1) >>> 1) - 1
) {
swap(this.#heap, node, parent);
}
}
#siftDown(): void;
#siftDown(size = this.size, node = 0, isGreater = this.#isGreater): void {
while (true) {
const leftNode = (node << 1) + 1;
const rightNode = leftNode + 1;
if (
(leftNode >= size || isGreater(node, leftNode)) &&
(rightNode >= size || isGreater(node, rightNode))
) {
break;
}
const maxChild =
rightNode < size && isGreater(rightNode, leftNode)
? rightNode
: leftNode;
swap(this.#heap, node, maxChild);
node = maxChild;
}
}
}
Usage:用法:
const numberComparator: Comparator<number> = (numberA, numberB) => {
return numberA - numberB;
};
const queue = new PriorityQueue(numberComparator);
queue.add(10);
queue.add(30);
queue.add(20);
while (queue.size) {
console.log(queue.poll());
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.