web-dev-qa-db-ja.com

JavaScriptで優先度キューを実装する効率的な方法は?

優先度キューには、すべてのエントリの優先度値とデータがあります。

したがって、新しい要素をキューに追加するときに、既にコレクション内にある要素よりも高い優先度値を持っている場合、表面にバブルアップします。

Popを呼び出すと、最も優先度の高い要素のデータが取得されます。

Javascriptでのこのような優先度キューの効率的な実装は何ですか?

PriorityQueueという新しいオブジェクトを作成し、2つのパラメーター(データ、優先度)を取得する2つのメソッド(プッシュとポップ)を作成するのは理にかなっていますか?コーダーとしてそれは理にかなっていますが、要素の順序の操作を可能にする下層で使用するデータ構造は不明です。または、配列にすべてを格納し、毎回配列を調べて、最大の優先度で要素を取得できますか?

これを行う良い方法は何ですか?

17
sova

以下は、配列ベースのバイナリヒープを使用するPriorityQueueの本当に効率的なバージョンであると信じるものです(ルートはインデックス0にあり、ノードの子はインデックスiは、それぞれインデックス2i + 1および2i + 2にあります)。

この実装には、Pushpeekpop、およびsizeなどの従来の優先度キューメソッドと、便利なメソッドisEmptyおよびreplaceが含まれます。 (後者は、popのすぐ後に続くPushのより効率的な代替手段です)。値は[value, priority]ペアとしてではなく、単にvaluesとして保存されます。これにより、>演算子を使用してネイティブに比較できる型の自動優先順位付けが可能になります。ただし、以下の例に示すように、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%}
20
gyre

次のような標準ライブラリを使用する必要があります。クロージャーライブラリ(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

7
ashiato