JavaScriptによる優先度付きキューの実装

# 概要

昔JavaScriptで実装した各種アルゴリズムによる優先度付きキュー。
いずれも最大ヒープなので、優先度が高い順に取り出される。

昔書いたJavaScriptなので、今どきのコーティングスタイルではないかも。

## 共通

共通のメソッドは下記の4つ。

* `enqueue(priority, data)` : キューに優先度`priority`(数値)の`data`を追加。
* `dequeue()` : キューから最も優先度の高い`data`を取り出して、キューから削除。
* `top()` : 最も優先度の高い`data`を取り出す。削除はしない。
* `size()` : 要素数を返す。

~~~js
var queue = new binary_heap();             // newはあってもなくても可
//var queue = new d_ary_heap(4);           // d-aryのdは指定可能(省略時は4)
//var queue = new pairing_heap();
//var queue = new leftist_heap();
//var queue = new skew_heap();
//var queue = new fibonacci_heap();

queue.enqueue(3, "data1");                 // enqueue(優先度, データ)で追加
queue.enqueue(1, "data2");
queue.enqueue(2, "data3");
console.log(queue.dequeue());  // data1    // 優先度最大のデータを返す(削除する)
console.log(queue.top());      // data3    // 優先度最大のデータを返す(削除しない)
console.log(queue.dequeue());  // data3
console.log(queue.dequeue());  // data2
~~~

## 2分ヒープ(Binary Heap)

構造が単純でそこそこ高速。

~~~js:binary_heap.js
function binary_heap(){
	"use strict";
	var _data = [];
	var _size = 0;
	var enqueue = function(priority, value){
		var data = _data;
		var i = 0;
		var p = 0;
		var ret = null;
		
		if(_size){
			data.push({p: priority, v: value});
			i = _size;
			p = (i - 1) >> 1;//Math.floor((i - 1) * 0.5);	// parent
			while(p >= 0){
				if(data[p].p < data[i].p){
					ret = data[i];
					data[i] = data[p];
					data[p] = ret;
				
					i = p;
					p = (i - 1) >> 1;//Math.floor((i - 1) * 0.5);
				}else{
					break;
				}
			}
		}else{
			data.push({p: priority, v: value});
		}
		_size = _size + 1;
	};
	var dequeue = function(){
		var data = _data;
		var size = _size - 1;
		var result = null;
		var i = 0;
		var c1 = 1;	// left child
		var c2 = 2;	// right child
		var p0 = 0.0;
		var p1 = 0.0;
		var p2 = 0.0;
		var ret = null;
		
		if(_size){
			result = data[0].v;
			data[0] = data[size];
			data.pop();
			
			while(c1 < size){
				if(c2 < size){
					p0 = data[i].p;
					p1 = data[c1].p;
					p2 = data[c2].p;
				
					if((p1 < p2) && (p0 < p2)){
						ret = data[i];
						data[i] = data[c2];
						data[c2] = ret;
						i = c2;
					}else if(p0 < p1){
						ret = data[i];
						data[i] = data[c1];
						data[c1] = ret;
						i = c1;
					}else{
						break;
					}
					c1 = (i << 1) + 1;
					c2 = (i << 1) + 2;
				}else{
					p0 = data[i].p;
					p1 = data[c1].p;
					
					if(p0 < p1){
						ret = data[i];
						data[i] = data[c1];
						data[c1] = ret;
					}
					break;
				}
			}
			
			_size = size;
			return result;
		}else{
			return (void 0);
		}
	};
	var top = function(){
		return _data[0].v;
	};
	var size = function(){
		return _size;
	};
	
	return {
		name: 'Binary Heap',
		enqueue: enqueue,
		dequeue: dequeue,
		top: top,
		size: size
	};
}
~~~

## d-aryヒープ (d-ary Heap)

2分ヒープの一般化版。`d = 2`のときは2分ヒープ。`d = 4`のとき性能が良いと言われる。
FirefoxはChromeに比べ配列の処理が得意なのか、特に高速。

~~~js:d_ary_heap.js
function d_ary_heap(d){
	"use strict";
	var _d = d || 4;
	var _data = [];
	var _size = 0;
	var _dinv = 1 / _d;
	var enqueue = function(priority, value){
		var data = _data;
		var ret = null;
		var i = 0;
		var p = 0;
		
		if(_size){
			data.push({p: priority, v: value});
			i = _size;
			p = ~~((i - 1) * _dinv);
			while(p >= 0){
				if(data[p].p < data[i].p){
					ret = data[i];
					data[i] = data[p];
					data[p] = ret;
				
					i = p;
					p = ~~((i - 1) * _dinv);
				}else{
					break;
				}
			}
		}else{
			data.push({p: priority, v: value});
		}
		_size = _size + 1;
	};
	var dequeue = function(){
		var data = _data;
		var size = _size - 1;
		var result = null;
		var i = 0;
		var c = 1;
		var p0 = 0.0;
		var pmax = 0.0;
		var pret = 0.0;
		var cmax = 0;
		var j = 0;
		var jmax = 0;
		var ret = null;
		
		if(_size){
			result = data[0].v;
			data[0] = data[size];
			data.pop();
			
			while(c < size){
				p0 = data[i].p;
				pmax = data[c].p;
				cmax = c;
				
				jmax = c + _d;
				if(jmax > size){
					jmax = size;
				}
				for(j = c + 1; j < jmax; j++){
					pret = data[j].p;
					if(pmax < pret){
						pmax = pret;
						cmax = j;
					}
				}
				if(p0 < pmax){
					ret = data[i];
					data[i] = data[cmax];
					data[cmax] = ret;
				}else{
					break;
				}
				i = cmax;
				c = i * _d + 1;
			}
			
			_size = size;
			return result;
		}else{
			return (void 0);
		}
	};
	var top = function(){
		return _data[0].v;
	};
	var size = function(){
		return _size;
	};
	
	return {
		name: 'd-ary Heap (d=' + _d + ')',
		enqueue: enqueue,
		dequeue: dequeue,
		top: top,
		size: size
	};
}
~~~

## ペアリングヒープ(Pairing Heap)

Chromeだと一番高速になることが多い。データによらず速度が安定している。
配列を使わない実装ではシンプル。

~~~js:pairing_heap.js
function pairing_heap(){
	"use strict";
	var _root = null;
	var _size = 0;
	var _merge = function (i, j){
		var ret = null;

		if(i === null) return j;
		if(j === null) return i;
		
		if(i.p < j.p){
			ret = i;
			i = j;
			j = ret;
		}
		j.next = i.head;
		i.head = j;
		
		return i;
	};
	var _mergeList = function (s){
		var n = null;
		var a = null;
		var b = null;
		var j = null;
		
		while(s !== null){
			a = s;
			b = null;
			s = s.next;
			a.next = null;
			if(s !== null){
				b = s;
				s = s.next;
				b.next = null;
			}
			a = _merge(a, b);
			a.next = n;
			n = a;
		}
		while(n !== null){
			j = n;
			n = n.next;
			s = _merge(j, s);
		}
		return s;
	};
	
	var enqueue = function(priority, value){
		_root = _merge(_root, {
			p: priority,
			v: value,
			next: null,
			head: null
		});
		_size = _size + 1;
	};
	var dequeue = function(){
		var result = null;

		if(_size){
			result = _root.v;
			_root = _mergeList(_root.head);
			_size = _size - 1;
		
			return result;
		}else{
			return (void 0);
		}
	};
	var top = function(){
		return _root.v;
	};
	var size = function(){
		return _size;
	};
	
	return {
		name: 'Pairing Heap',
		enqueue: enqueue,
		dequeue: dequeue,
		top: top,
		size: size
	};
}
~~~

## Leftist Heap

場合によってはPairing Heapよりも高速なこともある。

~~~js:leftist_heap.js
function leftist_heap(){
	"use strict";
	var _root = null;
	var _size = 0;
	var _merge = function(i, j){
		var ret = null;
		
		if(i === null) return j;
		if(j === null) return i;
		
		if(i.p < j.p){
			ret = i;
			i = j;
			j = ret;
		}
		i.right = _merge(i.right, j);
		if((i.left === null) || (i.left.s < i.right.s)){
			ret = i.right;
			i.right = i.left;
			i.left = ret;
		}
		i.s = ((i.right === null) ? 0 : i.right.s) + 1;
		
		return i;
	};
	var enqueue = function(priority, value){
		_root = _merge(_root, {
			p: priority,
			v: value,
			left: null,
			right: null,
			s: 1
		});
		_size = _size + 1;
	};
	var dequeue = function(){
		var result = null;
		
		if(_size){
			result = _root.v;
			_root = _merge(_root.left, _root.right);
			_size = _size - 1;
			
			return result;
		}else{
			return (void 0);
		}
	};
	var top = function(){
		return _root.v;
	};
	var size = function(){
		return _size;
	};
	
	return {
		name: 'Leftist Heap',
		enqueue: enqueue,
		dequeue: dequeue,
		top: top,
		size: size
	};
}
~~~

## Skew Heap

Leftist Heapよりも少し単純な実装だが少し遅め。

~~~js:skew_heap.js
function skew_heap(){
	"use strict";
	var _root = null;
	var _size = 0;
	var _merge = function(i, j){
		var ret = null;

		if(i === null) return j;
		if(j === null) return i;
		
		if(i.p < j.p){
			ret = i;
			i = j;
			j = ret;
		}
		i.right = _merge(i.right, j);
		ret = i.right;
		i.right = i.left;
		i.left = ret;
		
		return i;
	};
	var enqueue = function(priority, value){
		_root = _merge(_root, {
			p: priority,
			v: value,
			left: null,
			right: null
		});
		_size = _size + 1;
	};
	var dequeue = function(){
		var result = null;
		
		if(_size){
			result = _root.v;
			_root = _merge(_root.left, _root.right);
			_size = _size - 1;
			
			return result;
		}else{
			return (void 0);
		}
	};
	var top = function(){
		return _root.v;
	};
	var size = function(){
		return _size;
	};
	
	return {
		name: 'Skew Heap',
		enqueue: enqueue,
		dequeue: dequeue,
		top: top,
		size: size
	};
}
~~~

## フィボナッチヒープ(Fibonacci Heap)

今いち理解できない。他のヒープに比べて遅い(2〜3倍時間がかかる)。
もっと速くできるかも。

~~~js:fibonacci_heap.js
function fibonacci_heap(){
	"use strict";
	var _top = null;
	var _size = 0;
	var _mergeList = function(i, j){
		var ret = null;

		if(i === null) return j;
		if(j === null) return i;
		
		ret = i.next;
		i.next = j.next;
		i.next.prev = i;
		j.next = ret;
		j.next.prev = j;

		return i.p > j.p ? i : j;
	};
	
	var enqueue = function(priority, value){
		var newnode = {
			p: priority,
			v: value,
			marked: false,
			rank: 0,
			next: null,
			prev: null,
			firstchild: null
		};
		newnode.next = newnode;
		newnode.prev = newnode;
		
		_top = _mergeList(_top, newnode);
		_size = _size + 1;
	};
	var dequeue = function(){
		var top = _top;
		var result = top;
		var ranks = [];
		var roots = [];
		var i = 0;
		var l = 0;
		var curr = null;
		var other = null;
		var min = null;
		var max = null;

		if(_size){
			_size = _size - 1;
			
			if(top.next === top){
				top = null;
			}else{
				top.prev.next = top.next;
				top.next.prev = top.prev;
				top = top.next;
			}
			
			top = _mergeList(top, result.firstchild);
			
			if(top === null){
				_top = top;
				return result.v;
			}

			curr = top;
			do{
				roots.push(curr);
				curr = curr.next;
			} while(curr !== top);
			
			for(i = 0, l = roots.length; i < l; i++){
				curr = roots[i];
				while(true){
					if(ranks[curr.rank] === undefined){
						ranks[curr.rank] = curr;
						break;
					}
					other = ranks[curr.rank];
					ranks[curr.rank] = undefined;
					
					if(curr.p < other.p){
						min = curr;
						max = other;
					}else{
						min = other;
						max = curr;
					}
					
					min.next.prev = min.prev;
					min.prev.next = min.next;
					
					min.next = min.prev = min;
					max.firstchild = _mergeList(max.firstchild, min);
					
					min.marked = false;
					max.rank = max.rank + 1;
					
					curr = max;
				}
				if(curr.p > top.p){
					top = curr;
				}

			}
			
			_top = top;
			return result.v;
		}else{
			return (void 0);
		}
	};
	var top = function(){
		return _top.v;
	};
	var size = function(){
		return _size;
	};
	
	return {
		name: 'Fibonacci Heap',
		enqueue: enqueue,
		dequeue: dequeue,
		top: top,
		size: size
	};
}
~~~

# ライセンス

いずれもMITライセンス。

* もともと英語だけで公開していたGitHub: https://github.com/330k/priorityqueue_js
* ベンチマーク: https://330k.github.io/priorityqueue_js/benchmark.html