web-dev-qa-db-fra.com

Un moyen efficace d'implémenter la file d'attente prioritaire en Javascript?

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?

17
sova

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 values; 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.

Implémentation basée sur le tas:

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;
    }
  }
}

Exemple:

{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%}
20
gyre

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

7
ashiato