コンピュータサイエンスにおいて、スキップリスト(またはスキップリスト)は、確率的データ構造であり、検索の平均複雑度と順序付けられたシーケンス内での挿入の平均複雑度要素。そのため、静的配列では不可能な挿入を可能にするリンク リストのような構造を維持しながら、ソート済み配列の最良の機能(検索用)を取得できます。高速検索は、部分シーケンスのリンク階層を維持することによって可能になり、各部分シーケンスは前の部分シーケンスよりもスキップする要素が少なくなります(下の図を参照)。検索は最も疎な部分シーケンスから開始され、検索対象の要素よりも小さい要素と大きい要素が 2 つ連続して見つかるまで続きます。リンク階層を介して、これら 2 つの要素は、次の最も疎な部分シーケンスの要素にリンクされ、検索は最終的にシーケンス全体で検索されるまで続けられます。スキップされる要素は、確率的に[ 2 ]または決定論的に[ 3 ]選択できますが、前者がより一般的です。

スキップリストは階層構造になっています。最下層は通常の順序付き連結リストです。上位の各層は下位のリストへの「特急レーン」として機能し、下位の層の要素は下位のリストに渡されます。レイヤーに表示されますある一定の確率で(一般的に使用される2つの値)はまたは平均すると、各要素はリストがあり、最も背の高い要素(通常はスキップリストの先頭にある特別なヘッド要素)はすべてのリストに表示されます。スキップリストには、(つまり対数の底)の)リスト。
ターゲット要素の検索は、最上位リストの先頭要素から開始され、現在の要素がターゲット以上になるまで水平方向に進み続けます。現在の要素がターゲットと等しい場合、ターゲットが見つかったことになります。現在の要素がターゲットより大きい場合、または検索が連結リストの末尾に達した場合は、前の要素に戻って垂直方向に次の下位リストに移動した後、手順が繰り返されます。各連結リストにおける期待ステップ数は最大でこれは、ターゲットから検索パスを逆方向にたどって、次の上位リストに現れる要素に到達するか、現在のリストの先頭に到達するまで追跡することで確認できます。したがって、検索の総期待コストは次のようになります。それは、 いつは定数です。検索コストとストレージコストを交換することが可能です。たとえば、スキップリストの平均検索時間を最小化する一方、その値は実装を簡素化する。

スキップリストに使用される要素は、複数のリストに参加できるため、複数のポインタを含むことができます。
挿入と削除は、対応するリンクリストの操作とほぼ同じように実装されますが、「背の高い」要素は複数のリンクリストに挿入または削除する必要がある点が異なります。
すべてのノードを昇順で訪問することを強制する操作(リスト全体を出力するなど)は、スキップリストのレベル構造の舞台裏でのデランダム化を最適な方法で実行する機会を提供し、スキップリストを検索時間。(i 番目の有限ノードのレベルを、i を 2 で繰り返し割っても奇数にならない回数に 1 を加えた値に選択します。また、負の無限ヘッダーの場合は i=0 とします。これは、負または正の無限ノードに対して可能な限り高いレベルを選択するという通常の特殊なケースがあるためです。)ただし、これにより、レベル 1 より高いノードがすべてどこにあるかを知ることができ、それらを削除することもできます。
あるいは、レベル構造を以下のように準ランダムにすることもできる。
すべてのノードをレベル1にする j ← 1 レベル j のノード数が 1 より大きい間、レベル j の各 i 番目のノードに対して、 i が奇数で、かつi がレベル j の最後のノードでない場合、以下の処理を実行する。 レベル j+1 に昇格させるかどうかをランダムに選択する それ以外の場合、 iが偶数で、ノードi-1が昇格しなかった場合 レベル j+1 に昇格させる 繰り返し終了 j ← j + 1 繰り返す
非ランダム化バージョンと同様に、準ランダム化は、他の理由がある場合にのみ実行されます。(すべてのノードを訪問する)操作。
この準ランダム性の利点は、非ランダム化の場合ほど、レベル構造に関連する情報を敵対的なユーザーに漏らさないことです。これは望ましいことです。なぜなら、どのノードが最下位レベルにないかを知ることができる敵対的なユーザーは、上位レベルのノードを削除するだけでパフォーマンスを悪化させることができるからです。(ただし、BetheaとReiterは、それでもなお、敵対者は確率的およびタイミング的手法を使用してパフォーマンスの低下を強制できると主張しています。[ 4 ])検索パフォーマンスは依然として対数的であることが保証されています。
次のような「最適化」をしたくなるかもしれません。「次に、各i th について...」の部分で、偶数と奇数のペアごとにコイン投げをするのは忘れてください。偶数だけを昇格させるか奇数だけを昇格させるかを決めるために、コインを一度だけ投げてください。コイン投げでは、それらのうち。残念ながら、これにより、敵対的なユーザーは、偶数番号のノード(レベル 1 以上にあるノードのうち)がすべてレベル 1 より高いと推測した場合に、正解する確率が 50/50 になります。これは、特定のノードが何らかの整数Nに対してレベルNにあると推測する確率が非常に低いという特性にもかかわらずです。
スキップリストは、より伝統的なバランスのとれたツリーデータ構造と同じ絶対的な最悪ケースのパフォーマンス保証を提供するものではありません。なぜなら、スキップリストの構築に使用されるコイン投げによって、バランスの悪い構造が生成される可能性が常にあるからです(ただし、その確率は非常に低いですが[5])。しかし、スキップリストは実際にはうまく機能し、ランダム化バランス方式は、バランスのとれた二分探索木で使用される決定論的バランス方式よりも実装が容易であると主張されています。スキップリストは並列コンピューティングでも有用であり、データ構造の全体的な再バランスを行うことなく、スキップリストの異なる部分に並列に挿入を行うことができます。このような並列処理は、ランダム化されたスキップリストを単一ノードの損失に対して堅牢にすることができるため、アドホック無線ネットワークでのリソース発見に特に有利です。[ 6 ]
前述のように、スキップリストは高速ソートされたシーケンスへの値の挿入と削除は可能ですが、速度が遅いだけです。シーケンス内の特定の位置にある値のルックアップ(つまり、500 番目の値を返す)ですが、わずかな変更を加えることで、ランダムアクセスインデックスルックアップの速度を向上させることができます。 。
各リンクについて、リンクの幅も保存してください。幅は、上位層の「エクスプレスレーン」リンクのそれぞれが通過する下位層のリンクの数として定義されます。
例えば、ページ上部の例にあるリンクの幅は以下のとおりです。
1 10 o---> o---------------------------------------------------------> o トップレベル 1 3 2 5 o---> o---------------> o-----------> o----------------------> o レベル 3 1 2 1 2 3 2 o---> o--------> o---> o----------> o-----> o---------> o レベル 2 1 1 1 1 1 1 1 1 1 1 1 o---> o---> o---> o---> o---> o---> o---> o---> o---> o---> o---> o 最下位レベル ヘッド 1位 2位 3位 4位 5位 6位 7位 8位 9位 10位 0位 ノード ノード ノード ノード ノード ノード ノード ノード ノード ノード
上位レベルのリンクの幅は、その下位にある構成要素リンクの幅の合計であることに注意してください(つまり、幅10のリンクは、その直下にある幅3、2、5のリンクをまたいでいます)。したがって、すべてのレベルで幅の合計は同じになります(10 + 1 = 1 + 3 + 2 + 5 = 1 + 2 + 1 + 2 + 3 + 2)。
スキップリストをインデックス付けして i 番目の値を見つけるには、スキップリストを走査しながら、走査した各リンクの幅をカウントダウンします。次のリンクの幅が大きすぎる場合は、レベルを1つ下げます。
例えば、5 番目の位置にあるノード (ノード 5) を見つけるには、最上位レベルで幅 1 のリンクをたどります。これでさらに 4 歩必要ですが、このレベルの次の幅は 10 で大きすぎるため、1 レベル下げます。幅 3 のリンクを 1 つたどります。幅 2 のもう 1 つのステップは遠すぎるため、最下位レベルまで下げます。最後に幅 1 のリンクをたどって、目標の合計 5 (1+3+1) に到達します。
function lookupByPositionIndex(i) ノード ← ヘッド i ← i + 1 #上から下へのレベルのステップとしてヘッドをカウントしないdo while i ≥ node.width[level] do # 次のステップが遠すぎない場合 i ← i - node.width[level] # 現在の幅を減算 node ← node.next[level] # 現在のレベルで前方にトラバースするrepeat repeat return node.value end function
このインデックス作成の実装方法は、ウィリアム・ピュー著「スキップリストクックブック」[ 7 ]に詳しく記載されています。
スキップリストは、1989年にウィリアム・ピューによって初めて記述された。[ 8 ]
著者の言葉を引用すると:
スキップリストは確率的データ構造であり、多くのアプリケーションにおいて、バランスツリーに代わる実装方法として有力視されています。スキップリストアルゴリズムは、バランスツリーと同じ漸近的な期待時間範囲を持ちながら、よりシンプルで高速、かつメモリ使用量も少なくて済みます。
—ウィリアム・ピュー著『スキップリストの同時保守』(1989年)
スキップリストを使用するアプリケーションとフレームワークの一覧:
スキップリストは、分散アプリケーション(ノードが物理コンピュータを表し、ポインタがネットワーク接続を表す)や、ロック競合が少ない、あるいはロックなしの、拡張性の高い並行優先度キューの実装にも使用されます。 [ 17 ] [ 18 ] [ 19 ] [ 20 ]また、ロックフリーの並行辞書にも使用されます。 [ 21 ]スキップリストを使用して(ロックレス)優先度キューと並行辞書を実装するための米国特許もいくつかあります。[ 22 ]