// 辞書は二分探索木により実現している
// 二分探索木の挿入・削除操作は2色木で実装している
// 2色木のアルゴリズムは近代科学社の「アルゴリズムイントロダクション第3版」(以降 IA3) を参考にした
/**
* 2色木の番兵 (T.nil)
*
* 根の親、葉の子、または空辞書の root を表現する。
*
* @see IA3/13.1 2色木の性質
*/
let T_nil: Item;
type Item = OrderedMap.Item;
/**
* 順序あり辞書
*
* キーの値により順序付けされる辞書である。
*
* 等価 (equivalent) キーを持つ複数のアイテムは存在できない。
*
* this はキーと値の参照を保有している。保有しているキーのインスタンスを変更すると動作は保証されない。
* @internal
*/
class OrderedMap {
private _compare: any;
private _root: any;
private _size: number;
/**
* @param compare キー比較関数
*/
constructor( compare: OrderedMap.Compare )
{
this._compare = compare;
this._root = T_nil;
this._size = 0;
}
/**
* 要素数
*/
get size(): number { return this._size; }
/**
* インスタンスを複製
*
* キー比較関数、キー、値はシャローコピーされる。
*
* 計算量: 要素数 n に対して O(n)<
*
* @return this の複製
*/
clone(): OrderedMap
{
let cloned = new OrderedMap( this._compare );
if ( this._root !== T_nil ) {
cloned._root = this._root._clone( T_nil );
}
cloned._size = this._size;
return cloned;
}
/**
* 要素は存在しないか?
*
* 計算量: 要素数 n に対して O(1)
*
* @return 要素が存在しないとき true, そうでないとき false
*/
isEmpty(): boolean
{
return this._root === T_nil;
}
/**
* 先頭要素を検索
*
* 順序が最初の要素を検索する。
*
* 計算量: 要素数 n に対して O(log n)
*
* @return 検索されたアイテム (this が空なら null)
*/
findFirst(): Item | null
{
if ( this._root !== T_nil ) {
return this._root._findMinimum();
}
else {
return null;
}
}
/**
* 末尾要素を検索
*
* 順序が最後の要素を検索する。
*
* 計算量: 要素数 n に対して O(log n)
*
* @return 検索されたアイテム (this が空なら null)
*/
findLast(): Item | null
{
if ( this._root !== T_nil ) {
return this._root._findMaximum();
}
else {
return null;
}
}
/**
* 下限要素を検索
*
* bound と同じまたは後になるキーが存在すれば、その中で最初の要素を返す。
*
* そのような要素が存在しない場合は null を返す。
*
* 計算量: 要素数 n に対して O(log n)
*
* @param bound 境界キー
*
* @return 検索された要素、存在しなければ null
*/
findLower( bound: Key ): Item | null
{
return this._root._findLowerBound( bound, this._compare );
}
/**
* 上限要素を検索
*
* bound より後になるキーが存在すれば、その中で最初の要素を返す。
*
* そのような要素が存在しない場合は null を返す。
*
* 計算量: 要素数 n に対して O(log n)
*
* @param bound 境界キー
*
* @return 検索された要素、存在しなければ null
*/
findUpper( bound: Key ): Item | null
{
return this._root._findUpperBound( bound, this._compare );
}
/**
* 要素を検索
*
* key と同じキーの要素が存在すれば返す。
*
* そのような要素が存在しない場合は null を返す。
*
* 計算量: 要素数 n に対して O(log n)
*
* @param key キー
*
* @return 検索された要素、存在しなければ null
*/
findEqual( key: Key ): Item | null
{
return this._root._findEqual( key, this._compare );
}
/**
* すべての要素を削除
*
* 計算量: 要素数 n に対して O(1)
*/
clear(): void
{
this._root = T_nil;
this._size = 0;
}
/**
* 要素を挿入
*
* キーを key として value を挿入し、そのアイテムを返す。
*
* 計算量: 要素数 n に対して O(log n)
*
* @param key キー
* @param value 値
*
* @return 挿入された要素
*/
insert( key: Key, value: Value ): Item
{
// 参照: IA3/13.3 挿入
let trail = this._root;
let parent = T_nil;
const comp = this._compare;
while ( trail !== T_nil ) {
parent = trail;
if ( comp( key, trail.key ) ) {
// 左へ下る
trail = trail._child_L;
}
else if ( comp( trail.key, key ) ) {
// 右へ下る
trail = trail._child_R;
}
else {
// キーが一致したアイテムの値を更新して返す
trail._value = value;
return trail;
}
}
// 新しいアイテムを追加
const item = new OrderedMap.Item( parent, key, value );
item._is_red = true; // 黒 → 赤
if ( parent === T_nil ) {
this._root = item;
}
else if ( comp( key, parent.key ) ) {
parent._child_L = item;
}
else {
parent._child_R = item;
}
// 要素数を増加
++this._size;
// 2色木条件の回復
this._insert_fixup( item );
return item;
}
/**
* 挿入後に2色木条件を満たすように木を修正
*
* 計算量: 要素数 n に対して最悪 O(log n)
*
* @param item 挿入されたアイテム
*
* @see IA3/13.3 挿入
*/
private _insert_fixup( item: Item )
{
let trail = item;
while ( trail._parent._is_red /* 赤 */ ) {
// ここでは、常に不変式 a, b, c を満たす
if ( trail._parent === trail._parent._parent._child_L ) {
// trail の親が祖父の左側
let uncle = trail._parent._parent._child_R;
if ( uncle._is_red /* 赤 */ ) {
// 場合 1
trail._parent._is_red = false; // 黒
uncle._is_red = false; // 黒
trail._parent._parent._is_red = true; // 赤
trail = trail._parent._parent;
}
else {
if ( trail === trail._parent._child_R ) {
// 場合 2
trail = trail._parent;
this._rotate_L( trail );
}
// 場合 2,3
trail._parent._is_red = false; // 黒
trail._parent._parent._is_red = true; // 赤
this._rotate_R( trail._parent._parent );
}
}
else {
// trail の親が祖父の右側
let uncle = trail._parent._parent._child_L;
if ( uncle._is_red /* 赤 */ ) {
// 場合 1
trail._parent._is_red = false; // 黒
uncle._is_red = false; // 黒
trail._parent._parent._is_red = true; // 赤
trail = trail._parent._parent;
}
else {
if ( trail === trail._parent._child_L ) {
// 場合 2
trail = trail._parent;
this._rotate_R( trail );
}
// 場合 2,3
trail._parent._is_red = false; // 黒
trail._parent._parent._is_red = true; // 赤
this._rotate_L( trail._parent._parent );
}
}
}
this._root._is_red = false; // 黒
// ここで2色木条件を完全に満たす
}
/**
* 要素を削除
*
* last を省略したときは first を削除して、first の後続を返す。このとき first に null
* を指定することはできない。
*
* last を指定したときは first から last の前までの要素を削除して last を返す。last は
* first と同じか後の要素でなければならない。
*
* null は this の末尾要素の次の要素を表す。
*
* 計算量: 1 要素の削除の場合、要素数 n に対して O(log n)
*
* todo: 複数要素の削除の計算量を分析
*
* @param first 削除する先頭の要素
* @param last 削除する最後の要素の次
*
* @return 削除された要素の次の要素
*/
remove( first: Item, last?: Item ): Item | null
{
if ( last === undefined ) {
return this._remove( first );
}
else {
for ( let item = first; item != last; ) {
item = this._remove( item ) as Item; // null にならない
}
return last;
}
}
/**
* アイテムを削除
*
* 計算量: 全体ツリーのアイテム数 n に対して最悪 O(log n)
*
* @param item 削除対象
*
* @return item の後続、存在しなければ null
*
* @see IA3/13.4 削除
*/
private _remove( item: Item ): Item | null
{
// item の後続 (無ければ null)
const succ = item.findSuccessor();
let orgY_is_red;
let x_item;
if ( item._child_L === T_nil ) {
// (a) 左側なし
orgY_is_red = item._is_red;
x_item = item._child_R;
this._replace( item._child_R, item );
}
else if ( item._child_R === T_nil ) {
// (b) 右側なし (左側あり)
orgY_is_red = item._is_red;
x_item = item._child_L;
this._replace( item._child_L, item );
}
else {
// 左右あり (succ は null ではない)
const _succ = succ as Item;
orgY_is_red = _succ._is_red;
x_item = _succ._child_R;
if ( _succ._parent === item ) {
// (c) item の後続が item の右の子
// x_item が T_nil であっても親を設定
x_item._parent = _succ;
}
else {
// (d) item の後続が item の右の子の左側
this._replace( _succ._child_R, _succ );
_succ._child_R = item._child_R;
_succ._child_R._parent = _succ;
}
// (c), (d)
this._replace( _succ, item );
_succ._child_L = item._child_L;
_succ._child_L._parent = _succ;
_succ._is_red = item._is_red;
}
// 要素数を減少
--this._size;
if ( !orgY_is_red /* 黒 */ ) {
// 2色木条件の回復
this._remove_fixup( x_item );
}
return succ;
}
/**
* 削除後に2色木条件を満たすように木を修正
*
* @param x_item
*
* @see IA3/13.4 削除
*/
private _remove_fixup( x_item: Item )
{
let trail = x_item;
while ( trail !== this._root && !trail._is_red /* 黒 */ ) {
if ( trail === trail._parent._child_L ) {
// trail は親の左側
let sibling = trail._parent._child_R;
if ( sibling._is_red /* 赤 */ ) {
// 場合 1
sibling._is_red = false; // 黒
trail._parent._is_red = true; // 赤
this._rotate_L( trail._parent );
sibling = trail._parent._child_R;
}
if ( !sibling._child_L._is_red /* 黒 */ &&
!sibling._child_R._is_red /* 黒 */ ) {
// 場合 2
sibling._is_red = true; // 赤
trail = trail._parent;
}
else {
if ( !sibling._child_R._is_red /* 黒 */ ) {
// 場合 3
sibling._child_L._is_red = false; // 黒
sibling._is_red = true; // 赤
this._rotate_R( sibling );
sibling = trail._parent._child_R;
}
// 場合 3,4
sibling._is_red = trail._parent._is_red;
trail._parent._is_red = false; // 黒
sibling._child_R._is_red = false; // 黒
this._rotate_L( trail._parent );
trail = this._root;
}
}
else {
// trail は親の右側
let sibling = trail._parent._child_L;
if ( sibling._is_red /* 赤 */ ) {
// 場合 1
sibling._is_red = false; // 黒
trail._parent._is_red = true; // 赤
this._rotate_R( trail._parent );
sibling = trail._parent._child_L;
}
if ( !sibling._child_R._is_red /* 黒 */ &&
!sibling._child_L._is_red /* 黒 */ ) {
// 場合 2
sibling._is_red = true; // 赤
trail = trail._parent;
}
else {
if ( !sibling._child_L._is_red /* 黒 */ ) {
// 場合 3
sibling._child_R._is_red = false; // 黒
sibling._is_red = true; // 赤
this._rotate_L( sibling );
sibling = trail._parent._child_L;
}
// 場合 3,4
sibling._is_red = trail._parent._is_red;
trail._parent._is_red = false; // 黒
sibling._child_L._is_red = false; // 黒
this._rotate_R( trail._parent );
trail = this._root;
}
}
}
trail._is_red = false; // 黒
}
/**
* アイテムの置き換え
*
* dst の場所を src アイテムで置き換える。src が T_nil のときは dst の場所は葉になる。
*
* dst の親の左または右の子供 (または this._root) と src._parent は変更されるが、dst
* 自身の内容は変更されない。
*
* @param {Item} src
* @param {Item} dst
*/
private _replace( src: Item, dst: Item )
{
let dp = dst._parent;
if ( dp !== T_nil ) {
if ( dp._child_L === dst ) {
// dst は dp の左側
dp._child_L = src;
}
else {
// dst は dp の右側
dp._child_R = src;
}
}
else {
// dst は最上位
this._root = src;
}
// src の親を変更
src._parent = dp;
}
/**
* アイテムを左回転
*
* 計算量: O(1)
*
* @see IA3/13.2 回転
*
* @param pivot 回転中心のアイテム
*/
private _rotate_L( pivot: Item )
{
// next は回転後に pivot の位置になる
let next = pivot._child_R;
// pivot の右側を next の左側に設定
pivot._child_R = next._child_L;
if ( next._child_L !== T_nil ) {
next._child_L._parent = pivot;
}
// next の親を pivot の元の親に設定
next._parent = pivot._parent;
if ( pivot._parent === T_nil ) {
this._root = next;
}
else if ( pivot === pivot._parent._child_L ) {
pivot._parent._child_L = next;
}
else {
pivot._parent._child_R = next;
}
// next の左側を pivot に設定
next._child_L = pivot;
pivot._parent = next;
}
/**
* アイテムを右回転
*
* 計算量: O(1)
*
* @see IA3/13.2 回転
*
* @param pivot 回転中心のアイテム
*/
private _rotate_R( pivot: Item )
{
// next は回転後に pivot の位置になる
let next = pivot._child_L;
// pivot の左側を next の右側に設定
pivot._child_L = next._child_R;
if ( next._child_R !== T_nil ) {
next._child_R._parent = pivot;
}
// next の親を pivot の元の親に設定
next._parent = pivot._parent;
if ( pivot._parent === T_nil ) {
this._root = next;
}
else if ( pivot === pivot._parent._child_R ) {
pivot._parent._child_R = next;
}
else {
pivot._parent._child_L = next;
}
// next の右側を pivot に設定
next._child_R = pivot;
pivot._parent = next;
}
}
namespace OrderedMap {
/**
* OrderedMap のアイテム
*
* すべての this._child_L のアイテム L に対して Compare( L._key, this._key ) が成り立つ。
*
* すべての this._child_R のアイテム R に対して Compare( this._key, R._key ) が成り立つ。
*
* @internal
*/
export class Item {
_parent: Item;
// 左側ツリー
_child_L: Item;
// 右側ツリー
_child_R: Item;
// 色: 黒=false, 赤=true
_is_red: boolean;
private _key: Key;
private _value: Value;
/**
* 色は黒に設定される。
*
* @param parent 親アイテム
* @param key アイテムのキー
* @param value アイテムの値
*/
constructor( parent: Item, key: Key, value: Value )
{
this._parent = parent;
this._child_L = T_nil;
this._child_R = T_nil;
this._is_red = false;
this._key = key;
this._value = value;
}
/** キー */
get key(): Key { return this._key; }
/** 値 */
get value(): Value { return this._value; }
/**
* インスタンスを複製
*
* キー、値はシャローコピーされる。
*
* @param parant 親アイテム (this が根のときは T_nil)
*
* @return this の複製
*/
private _clone( parent: Item ): Item
{
// 子孫と色以外を複製
let cloned = new Item( parent, this._key, this._value );
// 左側子孫を複製
if ( this._child_L !== T_nil ) {
cloned._child_L = this._child_L._clone( cloned );
}
// 右側子孫を複製
if ( this._child_R !== T_nil ) {
cloned._child_R = this._child_R._clone( cloned );
}
// 色を複製
cloned._is_red = this._is_red;
return cloned;
}
/**
* 先頭アイテムの検索
*
* this ツリーの中で最も先の順序のアイテムを検索する。
*
* 計算量: this ツリーのアイテム数 n に対して O(log n)
*
* @return 検索されたアイテム
*/
private _findMinimum(): OrderedMap.Item
{
let item: Item = this; // 追跡ポインタ
while ( item._child_L !== T_nil ) {
item = item._child_L;
}
return item;
}
/**
* 後尾アイテムの検索
*
* this ツリーの中で最も後の順序のアイテムを検索する。
*
* 計算量: this ツリーのアイテム数 n に対して O(log n)
*
* @return 検索されたアイテム
*/
private _findMaximum(): OrderedMap.Item
{
let item: Item = this; // 追跡ポインタ
while ( item._child_R !== T_nil ) {
item = item._child_R;
}
return item;
}
/**
* 前のアイテムの検索
*
* root ツリーから this の前の順序のアイテムを検索する。this が先頭なら null を返す。
*
* 計算量: 辞書の要素数 n に対して最悪 O(log n)
*
* todo: 平均計算量を分析する
*
* @return 検索されたアイテム、存在しなければ null
*/
findPredecessor(): OrderedMap.Item | null
{
// 左側子孫がいれば、左側子孫の後尾
if ( this._child_L !== T_nil ) {
return this._child_L._findMaximum();
}
// 左側子孫がいなければ、this を右側子孫として持つ最も近い祖先
// それがなければ this は全体ツリーの先頭なので検索失敗
let item:Item = this;
let parent = item._parent;
while ( parent !== T_nil && item === parent._child_L ) {
item = parent;
parent = item._parent;
}
return (parent !== T_nil) ? parent : null;
}
/**
* 次のアイテムの検索
*
* root ツリーから this の次の順序のアイテムを検索する。this が後尾なら null を返す。
*
* 計算量: 辞書の要素数 n に対して最悪 O(log n)
*
* todo: 平均計算量を分析する
*
* @return 検索されたアイテム、存在しなければ null
*/
findSuccessor(): OrderedMap.Item | null
{
// 右側子孫がいれば、右側子孫の先頭
if ( this._child_R !== T_nil ) {
return this._child_R._findMinimum();
}
// 右側子孫がいなければ、this を左側子孫として持つ最も近い祖先
// それがなければ this は全体ツリーの後尾なので検索失敗
let item: Item = this;
let parent = item._parent;
while ( parent !== T_nil && item === parent._child_R ) {
item = parent;
parent = item._parent;
}
return (parent !== T_nil) ? parent : null;
}
/**
* 下限アイテムを検索
*
* this ツリーの中で !comp(item.key, bkey) となる最初のアイテムを検索する。
*
* つまり bkey と同じまたは後になるキーが this ツリーに存在すれば、その中で最初のアイテムを返す。
*
* そのようなアイテムが存在しない場合は null を返す。
*
* this が T_nil の場合は null を返す。
*
* 計算量: this ツリーのアイテム数 n に対して最悪 O(log n)
*
* @param bkey 境界キー
* @param comp キー比較関数
*
* @return 検索されたアイテム、存在しなければ null
*/
private _findLowerBound( bkey: Key, comp: OrderedMap.Compare ): Item | null
{
let item: Item = this;
while ( item !== T_nil ) {
if ( comp( bkey, item._key ) ) {
// bkey < item.key
if ( item._child_L !== T_nil ) {
let found = item._child_L._findLowerBound( bkey, comp );
if ( found !== null ) return found;
}
return item;
}
else if ( comp( item._key, bkey ) ) {
// bkey > item.key
item = item._child_R;
}
else {
// bkey == item.key (等価)
return item;
}
}
return null;
}
/**
* 上限アイテムを検索
*
* this ツリーの中で comp(bkey, item.key) となる最初のアイテムを検索する。
*
* つまり bkey より後になるキーが this ツリーに存在すれば、その中で最初のアイテムを返す。
*
* そのようなアイテムが存在しない場合は null を返す。
*
* this が T_nil の場合は null を返す。
*
* 計算量: this ツリーのアイテム数 n に対して最悪 O(log n)
*
* @param bkey 境界キー
* @param comp キー比較関数
*
* @return 検索されたアイテム、存在しなければ null
*/
private _findUpperBound( bkey: Key, comp: OrderedMap.Compare ): Item | null
{
let item: Item = this;
while ( item !== T_nil ) {
if ( comp( bkey, item._key ) ) {
// bkey < item.key
if ( item._child_L !== T_nil ) {
let found = item._child_L._findUpperBound( bkey, comp );
if ( found !== null ) return found;
}
return item;
}
else {
// bkey >= item.key
item = item._child_R;
}
}
return null;
}
/**
* 等価キーのアイテムを検索
*
* this ツリーの中で !comp(key, item.key) かつ !comp(item.key, key) となるアイテムを検索する。
*
* そのようなアイテムが存在しない場合は null を返す。
*
* this == T_nil の場合は null を返す。
*
* 計算量: this ツリーのアイテム数 n に対して最悪 O(log n)
*
* @param key キー
* @param comp キー比較関数
*
* @return {?mapray.OrderedMap.Item} 検索されたアイテム、存在しなければ null
*/
private _findEqual( key: Key, comp: OrderedMap.Compare )
{
let item: Item = this;
while ( item !== T_nil ) {
if ( comp( key, item._key ) ) {
// key < item.key
item = item._child_L;
}
else if ( comp( item._key, key ) ) {
// bkey > item.key
item = item._child_R;
}
else {
// bkey == item.key (等価)
return item;
}
}
return null;
}
/**
* 下限アイテムを検索 (検討中)
*
* root ツリーの中で !comp(item.key, bkey) となる最初のアイテムを検索する。
*
* つまり bkey と同じまたは後になるキーが root ツリーに存在すれば、その中で最初のアイテムを返す。
*
* そのようなアイテムが存在しない場合は null を返す。
*
* 計算量: root ツリーのアイテム数 n に対して最悪 O(log^2 n)
*
* @param bkey 境界キー
* @param comp キー比較関数
*
* @return 検索されたアイテム、存在しなければ null
*/
private _findLowerBoundR( bkey: Key, comp: OrderedMap.Compare ): OrderedMap.Item | null
{
let item: Item = this;
if ( item._parent !== T_nil ) {
// item == root
return item._findLowerBound( bkey, comp );
}
let imin = item._findMinimum();
let imax = item._findMaximum();
do {
if ( !comp( bkey, imin._key ) && !comp( imax._key, bkey ) ) {
// imin <= bkey <= imax なので
// item._findLowerBound() で必ず見つかる
break;
}
if ( item._parent._child_L === item ) {
// item は parent の左側なので、登ると imax のみが変化
imax = item._findMaximum();
}
else {
// item は parent の右側なので、登ると imin のみが変化
imin = item._findMinimum();
}
// item は登る
item = item._parent;
} while ( item._parent !== T_nil );
// item == root
return item._findLowerBound( bkey, comp );
}
}
// @ts-ignore
T_nil = new Item();
/**
* キー比較関数
*
* a が b より順序が先なら true を返す。そうでないなら false を返す。
*
* すべての x について !Compare(x, x) であること。
*
* Compare(x, y) && Compare(y, z) なら Compare(x, z) であること。
*
* equiv(a, b) を !Compare(a, b) && !Compare(b, a) と定義するとき、
* equiv(x, y) && equiv(y, z) なら equiv(x, z) であること。
*
* @param a キー値 a
* @param b キー値 b
*
* @return a が b より順序が先なら true, そうでないなら false
* @internal
*/
export type Compare = (a: Key, b: Key) => boolean;
} // namespace OrderedMap
export default OrderedMap;