要素の挿入、削除、ランダムアクセスが全部高速なリストを作った

スキップリスト(Skip List)は1990年に発表された比較的新しいアルゴリズムで、要素の挿入や削除、検索を平衡木と同等のパフォーマンスで実行可能なリスト構造です。

Skip Listは連結リストの多層構成になっています。路線に例えると、最下層のリンクは各駅停車のように、全要素を結んでいます。一方、上層のリンクは急行や特急のように、途中の要素をスキップするようになっています。この路線を特急→急行→…→各駅と乗り継ぐことで、目的の要素に高速に到達できる仕組みです。もっと詳しい解説はこちらこちらにあります。

で、ここからが本題です。Skip Listの実装はいくつも出ているんですが、Sorted Listとしての実装ばかりで、要素を任意順序で格納できてランダムアクセス(indexを指定してのアクセス)可能なSkip Listが見つからなかったので、自分で作ってみました。

通常のSkip Listでは、各ノードは次のノードへのリンクしか保持していません。ここに少し工夫を加えて、上の画像のように、次ノードまでの「距離」を持たせるようにしました。ノード探索時にこの距離を加算していくことで、indexによる要素の取得をO(logN)で実行できます。

このSkipListに加えて、java.util.Listとjava.util.Setを両方実装したSkipListSetというものも作りました。こちらは内部的にHashMapを併用していて、indexOf()やcontains()も高速になっています。各メソッドの計算量比較は以下の通りです。

メソッド ArrayList SkipList SkipListSet
get(index) O(1) O(logN) O(logN)
set(index, element) O(1) O(logN) O(logN)
add(element) O(1) O(logN) O(logN)
add(index, element) O(N) O(logN) O(logN)
remove(index) O(N) O(logN) O(logN)
remove(element) O(N) O(N) O(logN)
indexOf(element) O(N) O(N) O(logN)
contains(element) O(N) O(N) O(1)
iterator().next() O(1) O(1) O(1)

ソースは以下からどうぞ。