補間検索は、キーに割り当てられた数値 (キー値)によって順序付けられた配列内でキーを検索するアルゴリズムです。これは 1957 年に WW Peterson によって初めて説明されました。 [1]補間検索は、電話帳で名前 (本の項目を順序付けるキー値) を検索する方法に似ています。各ステップで、アルゴリズムは、検索空間の境界にあるキー値と検索キーの値に基づいて、残りの検索空間のどこに検索項目があるかを計算します (通常は線形補間を使用) 。次に、この推定位置で実際に見つかったキー値が、検索されているキー値と比較されます。等しくない場合、比較に応じて、残りの検索空間は推定位置の前または後の部分に縮小されます。この方法は、キー値間の差の大きさの計算が合理的である場合にのみ機能します。
24 が目標値である補間検索アルゴリズムの視覚化。 | |
| クラス | 検索アルゴリズム |
|---|---|
| データ構造 | 配列 |
| 最悪の場合の パフォーマンス | の上) |
| 最高の パフォーマンス | お(1) |
| 平均的 なパフォーマンス | O (log(log( n ))) [2] |
| 最悪の場合の 空間複雑度 | お(1) |
| 最適 | はい |
それに比べて、バイナリ検索では、推定位置で見つかったキーと検索対象のキーの比較に応じて、残りの検索空間の中央を常に選択し、片方の半分を破棄します。キーの数値は必要なく、キーの全体的な順序だけが必要です。残りの検索空間は、推定位置の前または後の部分に縮小されます。線形検索では、最初から要素を 1 つずつ比較し、並べ替えを無視するため、等式のみが使用されます。
平均すると、補間検索では約 log(log( n )) 回の比較が行われます (要素が均一に分散されている場合)。ここで、n は検索する要素の数です。最悪の場合 (たとえば、キーの数値が指数関数的に増加する場合)、最大O ( n ) 回の比較が行われることがあります。
補間順次検索では、補間を使用して検索対象の項目に近い項目を見つけ、次に線形検索を使用して正確な項目を見つけます。
パフォーマンス
ビッグO表記法を使用すると、サイズnのデータセットに対する補間アルゴリズムのパフォーマンスはO ( n )です。ただし、補間に使用される線形スケール上のデータの均一分布を仮定すると、パフォーマンスはO (log log n )であることが示されます。[3] [4] [5]
動的補間探索はo (log log n )境界を他の分布に拡張し、O (log n )の挿入と削除もサポートします。[6] [7]
補間検索の実際のパフォーマンスは、プローブ数の削減が、各プローブに必要なより複雑な計算を上回るかどうかによって決まります。これは、各プローブにディスク シークが含まれ、補間演算よりもはるかに遅い、ディスク上の大きなソート済みファイル内のレコードの検索に役立ちます。
B ツリーのようなインデックス構造もディスク アクセス回数を減らし、多くの種類のデータをインデックスでき、オンラインで更新できるため、ディスク上のデータのインデックス作成によく使用されます。ただし、ソートされているがインデックス付けされていない特定のディスク上のデータセットを検索する必要がある場合は、補間検索が役立つ場合があります。
さまざまなデータセットへの適応
データセットのソートキーが均一に分布した数値である場合、線形補間は簡単に実装でき、求める値に非常に近いインデックスが見つかります。
一方、名前でソートされた電話帳の場合、補間検索の直接的なアプローチは適用されません。ただし、同じ高レベルの原則は適用できます。つまり、名前の文字の相対的な頻度を使用して電話帳内の名前の位置を推定し、それをプローブの位置として使用することができます。
一部の補間検索実装は、等しいキー値が連続して存在する場合、期待どおりに動作しないことがあります。補間検索の最も単純な実装では、必ずしもそのような連続の最初の (または最後の) 要素が選択されるわけではありません。
書籍ベースの検索
電話帳の名前を何らかの番号に変換しても、均一に分布した番号にはなりません (名前を分類して、名前 #1、名前 #2 などと呼ぶなどの膨大な労力を費やした場合を除く)。さらに、一部の名前は他の名前よりもはるかに一般的であることはよく知られています (Smith、Jones、など)。辞書でも同様で、一部の文字で始まる単語は他の文字で始まる単語よりもはるかに多くあります。出版社によっては、各文字のマーカーを示すために欄外注釈を作成したり、ページの横を切り取って、一目でセグメント化された補間を実行できるようにしたりする労力を費やしています。
サンプル実装
次のC++コード例は、単純な実装です。各段階でプローブの位置を計算し、バイナリ検索と同様に、上限または下限のいずれかを移動して、検索値を含むより小さな間隔を定義します。各段階で間隔のサイズが半分になることを保証するバイナリ検索とは異なり、誤った補間により、O( n ) の効率が低下する可能性があります。
#include <cassert>
/*
arr[low, high)がソートされ、この配列内のデータ「キー」を検索します。
「キー」が見つかった場合は、対応するインデックスを返します(必ずしも最高のインデックスとは限りません)。
「キー」が見つからない場合は、low - 1 を返します。
アルゴリズムが正しいことをどうやって確認するのでしょうか?
証拠:
(有限性:1回のループ後、[low, high]の幅は厳密に減少します)
拳、高 <--- 高 - 1
シナリオ1. 低 = 高の場合
シナリオ2. low < highの場合、arr[low] = arr[high]
シナリオ 3. low < high、arr[low] < arr[high]、key < arr[low]、または key > arr[high] の場合
シナリオ 4. low < high、arr[low] < arr[high]、arr[low] <= key <= arr[high] の場合
次にシナリオ 4 を分析してみましょう。
「while」ループに入ると、low <= middle <= high
1回のループの後で(戻らない場合は)、"low > high" が発生するかどうかを分析します。
1 ループ後:
ケース a1: このループで「low」ブランチが実行されました
arr[中央] < キー <= arr[高]
つまり、中位<高位
このループの後には
低 <= 高
ケースa2: このループで「高」分岐が実行されました
arr[low] <= key < arr[middle]
つまり、低位<中位
このループの後には
低 <= 高
1回のループの後(戻らない場合は)、"low <= high" になります。
「while」ループを終了するとき:
ケースb1: arr[low] >= arr[high]
最後のループで「low」分岐が実行されると、
arr[下限 - 1] < k <= arr[上限]
arr[低] >= arr[高]
低 <= 高
だから我々は
arr[low - 1] < k <= arr[low] = arr[high]
最後のループで「high」分岐が実行されると、
arr[low] <= キー < arr[high + 1]
arr[低] >= arr[高]
低 <= 高
だから我々は
arr[low] = arr[high] <= キー < arr[high + 1]
ケースb2: (arr[low] < arr[high]) && (arr[low] > key):
最後のループでは、「low」が変更されているはずです
だから我々は
arr[low - 1] < キー
だから我々は
arr[low - 1] < キー < arr[low]
ケースb3: (arr[low] < arr[high]) && (key > arr[high])
最後のループでは、「high」が変更されているはずです
だから我々は
キー < arr[高 + 1]
だから我々は
arr[low] < arr[high] < キー < arr[high + 1]
*/
テンプレート<型名T >
静的ランクinterpolation_search_v001 ( T * arr 、const T & key 、ランク低、ランク高)
{
高さ-= 1 ;
int中間;
int initial_low =低い;
(( arr [低] < arr [高]) && ( arr [低] <=キー) && (キー< = arr [高])) {
中間=低+ ((キー- arr [低]) * (高-低)) / ( arr [高] - arr [低]);
アサート((低<=中) && (中<=高));
if ( arr [中央] <キー)
低=中+ 1 ;
そうでない場合(キー< arr [中間])
高=中- 1 ;
それ以外
真ん中を返す;
}
if (キー== arr [低])
低値を返す;
それ以外
initial_low - 1を返します。
}
/*
ソートされた配列 arr[low, high) で「キー」を検索します。
戻り値: arr[i] <= key となる最大のインデックス i
アルゴリズムが正しいことをどうやって確認するのでしょうか?
証拠:
有限性:1回のループの後、[low, high]の幅は厳密に減少する
拳、高 <---- 高 - 1
シナリオ1. 低 = 高の場合
シナリオ2. low < high、key < arr[low]、またはarr[high] <= keyの場合
シナリオ3. low < high の場合、arr[low] <= key < arr[high]
次にシナリオ 3 を分析してみましょう。
「while」ループに入ると、low <= middle < high
「while」ループを終了するとき:
ケースa1: キー < arr[low]
最後のループで「low」が変更されたので、
arr[low - 1] <= キー < arr[low]
ケースa2: arr[high] <= key
最後のループで「high」が変更されたので、
キー < arr[high]、不可能
結論: 「low - 1」を返す必要があります
*/
テンプレート<型名T >
静的ランクinterpolation_search_v002 ( T * arr 、const T & key 、ランク低、ランク高) {
高さ-= 1 ;
アサート(低<=高);
ランク中位;
if (キー< arr [低]) {
low - 1を返します。
}
if ( arr [高] <=キー) {
高く返す;
}
// 現在 low < high 、 arr[low] <= key < arr[high]
while (( arr [低] <=キー) && (キー< arr [高] )) {
中間=低+ ((高-低) * (キー- arr [低])) / ( arr [高] - arr [低]);
アサート((低<=中) && (中<高));
if (キー< arr [中間]) {
高=中;
}それ以外{
低=中+ 1 ;
}
}
low - 1を返します。
}
ループ制御管理上の理由から、このコードではインデックスmidでリストをプローブした後、 highまたはlowのいずれかをmidではなく隣接するインデックスに設定し、その位置が次の反復中にプローブされることに注意してください。隣接するエントリの値はあまり変わらないため、この 1 ステップの調整では、ディスクなどの遠隔メモリへの追加参照を犠牲にして、補間計算はあまり改善されません。
上記コードの各反復には、 5 ~ 6 回の比較 (余分な回数は、 3 方向比較がない場合にバイナリ比較を介して < > と = の 3 つの状態を区別するために必要な繰り返しによる) といくつかの面倒な計算が必要ですが、バイナリ検索アルゴリズムは反復ごとに 1 つの比較で記述でき、単純な整数計算のみを使用します。これにより、100 万要素の配列を 20 回以下の比較 (配列要素が格納されている低速メモリへのアクセスを含む) で検索できます。これを克服するには、上記で記述した補間検索では、反復を 3 回以下に抑えます。
参照
参考文献
- ^ WW Peterson (1957). 「ランダムアクセスストレージのアドレス指定」IBM J. Res. Dev . 1 (2): 130–146. doi :10.1147/rd.12.0130.
- ^ Simon Yuan. 「補間検索の複雑さを理解する、高度なアルゴリズムとデータ構造セミナー」(PDF)。
- ^ Weiss, Mark Allen (2006). Java を使用したデータ構造と問題解決、Pearson Addison Wesley
- ^ Armenakis, AC, Garey, LE, Gupta, RD、「ルート検索法の順序付きディスク ファイルの検索への適応」、BIT Numerical Mathematics、第 25 巻、第 4 号 / 1985 年 12 月。
- ^ セジウィック、ロバート (1990)、C アルゴリズム、アディソン・ウェズリー
- ^ Mehlhorn, Kurt; Tsakalidis, Athanasios (1993). 「動的補間検索」Journal of the ACM . 40 (3): 621–634. doi :10.1145/174130.174139. ISSN 0004-5411.
- ^ Andersson, Arne; Mattsson, Christer (1993). 「o(log log n) 時間での動的補間検索」.オートマトン、言語、プログラミング. 第 700 巻. ベルリン、ハイデルベルク: Springer Berlin Heidelberg. p. 15–27. doi :10.1007/3-540-56939-1_58. ISBN 978-3-540-56939-8。
- ^ Mohammed, Adnan Saher; Amrahov, Şahin Emrah; Çelebi, Fatih V. (2021年10月1日). 「補間バイナリ検索:順序付きデータセットに対する効率的なハイブリッド検索アルゴリズム」.工学科学技術. 24 (5): 1072–1079. doi : 10.1016/j.jestch.2021.02.009 . ISSN 2215-0986.
外部リンク
- 補間検索
- 国立標準技術研究所
- 補間検索 - Log LogN 検索
