Les files d'attente prioritaires ont une valeur et des données prioritaires pour chaque entrée.
Ainsi, lors de l'ajout d'un nouvel élément à la file d'attente, il bouillonne jusqu'à la surface s'il a une valeur de priorité plus élevée que les éléments déjà dans la collection.
Quand on appelle pop, on obtient les données de l'élément avec la plus haute priorité.
Qu'est-ce qu'une implémentation efficace d'une telle file d'attente prioritaire en Javascript?
Est-il sensé d'avoir un nouvel objet appelé PriorityQueue, de créer deux méthodes (Push et pop) qui prennent deux paramètres (données, priorité)? Cela a beaucoup de sens pour moi en tant que codeur, mais je ne suis pas sûr de la structure de données à utiliser dans le ventre qui permettra la manipulation de l'ordre des éléments. Ou pouvons-nous simplement tout stocker dans un tableau et parcourir le tableau à chaque fois pour saisir l'élément avec une priorité maximale?
Quelle est la bonne façon de procéder?
Voici ce que je pense être une version vraiment efficace d'un PriorityQueue
qui utilise un tas binaire basé sur un tableau (où la racine est à l'index 0
, et les enfants d'un nœud à l'index i
sont aux indices 2i + 1
et 2i + 2
, respectivement).
Cette implémentation inclut les méthodes classiques de file d'attente prioritaire comme Push
, peek
, pop
et size
, ainsi que les méthodes pratiques isEmpty
et replace
(ce dernier étant un substitut plus efficace d'un pop
suivi immédiatement d'un Push
). Les valeurs ne sont pas stockées sous la forme [value, priority]
paires, mais simplement comme value
s; cela permet une hiérarchisation automatique des types qui peuvent être comparés en mode natif à l'aide du >
opérateur. Une fonction de comparaison personnalisée transmise au constructeur PriorityQueue
peut cependant être utilisée pour émuler le comportement de la sémantique par paire, comme indiqué dans l'exemple ci-dessous.
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%}
Vous devez utiliser des bibliothèques standard comme par exemple la bibliothèque de fermeture (goog.structs.PriorityQueue
):
https://google.github.io/closure-library/api/goog.structs.PriorityQueue.html
En cliquant sur le code source, vous saurez qu'il est en fait un lien vers goog.structs.Heap
que vous pouvez suivre:
https://github.com/google/closure-library/blob/master/closure/goog/structs/heap.js