二分探索アルゴリズムの視覚化。探索目標値は7である。
二分探索 (英 : binary search )もしくはバイナリサーチ とは、計算機科学 において整列配列 (英語版 ) の中から目標の値の位置を見つけ出す探索 アルゴリズム[ 2] 。このアルゴリズムでは、探索目標と、配列の中央に位置する要素とをまず比較する。値が等しくない場合、目標が存在する可能性がない側の半分の部分配列を除外し、残りの半分について中央要素との比較を再び行う。この手続きを目標値が見つかるまで繰り返す。配列が空になって終了したなら、元の配列に目標値が存在しなかったということである。
二分探索は対数時間 (英語版 ) で動作するアルゴリズムであり、最悪のケースにおいて比較演算を
O
(
log
n
)
{\displaystyle O(\log n)}
回行う( n は配列の要素数)[ 注釈 1] [ 3] 。二分探索は配列が特に小さくなければ線形探索 より高速であるが、実行するには配列があらかじめソート されていなければならない。ハッシュテーブル のように探索速度に特化したデータ構造 ならば二分探索よりも効率よく探索を行うことができるが、二分探索は配列に目標の値が含まれない場合に最近傍の値を見つけるような応用がある。
二分探索には多くの変種がある。そのうちフラクショナル・カスケーディング (英語版 ) は、複数の配列に対して同じ値の二分探索を行う高速な手法であり、計算幾何学 など多くの分野における探索問題を効率的に解くことができる。指数探索 (英語版 ) は二分探索を無限長のリストに拡張したものである。二分探索木 やB木 といったデータ構造は二分探索に基づいている。
アルゴリズム
二分探索は整列配列に対して用いられる。まず配列の中央の要素と探索対象の値を比較し、一致している場合はその位置を返す。探索対象が中央要素より小さかったなら、配列の前半部に対して同じ手続きを行う。探索対象が中央の要素より大きかったなら後半部で探索をする。このように、各回の反復において、配列を二つに分けて探索対象が存在しないとわかった側の領域を除外していく。
手続き
長さ
n
{\displaystyle n}
の配列
A
{\displaystyle A}
があり、要素の値もしくはレコード
A
0
,
A
1
,
A
2
,
…
,
A
n
−
1
{\displaystyle A_{0},A_{1},A_{2},\ldots ,A_{n-1}}
は
A
0
≤
A
1
≤
A
2
≤
⋯
≤
A
n
−
1
{\displaystyle A_{0}\leq A_{1}\leq A_{2}\leq \cdots \leq A_{n-1}}
のようにソートされているとする。また探索目標値を
T
{\displaystyle T}
とする。以下のサブルーチン は二分探索によって
A
{\displaystyle A}
内の要素
T
{\displaystyle T}
のインデックスを求める。
変数
L
{\displaystyle L}
に
0
{\displaystyle 0}
を、
R
{\displaystyle R}
に
n
−
1
{\displaystyle n-1}
を代入する。
L
>
R
{\displaystyle L>R}
ならば探索は失敗して終了する。
配列
A
{\displaystyle A}
の中央要素の位置を表す変数
m
{\displaystyle m}
に
L
+
f
l
o
o
r
(
R
−
L
2
)
{\displaystyle L+\mathrm {floor} {\biggl (}{\frac {R-L}{2}}{\biggl )}}
を代入する。床関数
f
l
o
o
r
{\displaystyle \mathrm {floor} }
は引数
R
−
L
2
{\displaystyle {\frac {R-L}{2}}}
以下の最大の整数を与える。
A
m
<
T
{\displaystyle A_{m}<T}
ならば
L
{\displaystyle L}
に
m
+
1
{\displaystyle m+1}
を代入し、ステップ2に戻る。
A
m
>
T
{\displaystyle A_{m}>T}
ならば
R
{\displaystyle R}
に
m
−
1
{\displaystyle m-1}
を代入し、ステップ2に戻る。
A
m
=
T
{\displaystyle A_{m}=T}
となったら探索は完了する。
m
{\displaystyle m}
の値を返す。
この反復手続きは探索範囲を変数
L
{\displaystyle L}
および
R
{\displaystyle R}
によって管理している。擬似コード で表すと以下のようになる。変数名と型は上記と同じであり、floor は床関数、unsuccessful は探索が失敗したことを表す変数値である。
二分探索の手順。
function binary_search(A, n, T) is
L := 0
R := n − 1
while L ≤ R do
m := L + floor((R - L) / 2)
if A[m] < T then
L := m + 1
else if A[m] > T then
R := m − 1
else :
return m
return unsuccessful
R
−
L
2
{\displaystyle {\frac {R-L}{2}}}
に対して床関数ではなく天井関数 を用いるアルゴリズムもある。その場合、探索目標の値が配列内に複数存在するとき結果が変わる可能性がある。
代替手法
上記の手続きでは、中央の要素 (
A
m
{\displaystyle A_{m}}
) が目標値 (
T
{\displaystyle T}
) と等しいかの判定を毎回の反復ごとに行っている。一部の実装では、この比較を反復の中では行わず、要素が1つだけ残ったとき(
L
=
R
{\displaystyle L=R}
になったとき)にのみ実行する。これにより、反復中の比較処理が1回減るためループが高速化されるが、反復回数は平均で1回増えるだけで済む[ 5] 。以降ではこの方法を「代替手法」と呼ぶ。
ヘルマン・ボッテンブルッフ (英語版 ) は1962年に等価判定を省略する実装を最初に公開した[ 5] [ 6] 。
L
{\displaystyle L}
に
0
{\displaystyle 0}
を、
R
{\displaystyle R}
に
n
−
1
{\displaystyle n-1}
を代入する。
L
≠
R
{\displaystyle L\neq R}
である間、以下を繰り返す。
中央要素の位置
m
{\displaystyle m}
に
L
+
c
e
i
l
i
n
g
(
R
−
L
2
)
{\displaystyle L+\mathrm {ceiling} {\biggl (}{\frac {R-L}{2}}{\biggl )}}
を代入する。天井関数
c
e
i
l
i
n
g
{\displaystyle \mathrm {ceiling} }
は
R
−
L
2
{\displaystyle {\frac {R-L}{2}}}
以上の最小の整数を与える。
A
m
>
T
{\displaystyle A_{m}>T}
であるならば、
R
{\displaystyle R}
に
m
−
1
{\displaystyle m-1}
を代入する。
そうでないならば
A
m
≤
T
{\displaystyle A_{m}\leq T}
である。
L
{\displaystyle L}
に
m
{\displaystyle m}
を代入する。
L
=
R
{\displaystyle L=R}
となった時点で探索完了。
A
L
=
T
{\displaystyle A_{L}=T}
ならば
L
{\displaystyle L}
を返す。そうでなければ、目標値が見つからずに終了したことになる。
このバージョンの疑似コードは以下のようになる。
function binary_search_alternative(A, n, T) is
L := 0
R := n − 1
while L != R do
m := L + ceil((R - L) / 2)
if A[m] > T then
R := m − 1
else :
L := m
if A[L] = T then
return L
return unsuccessful
重複要素
配列内に重複する要素が存在する場合、この手続きは任意の目標値と等しい要素のインデックスを返す。たとえば、探索対象の配列が
[
1
,
2
,
3
,
4
,
4
,
5
,
6
,
7
]
{\displaystyle [1,2,3,4,4,5,6,7]}
、目標値が
4
{\displaystyle 4}
であれば、第4要素(インデックス3)と第5要素(インデックス4)のいずれを返してもアルゴリズムは正しく機能している。床関数を用いる手続きであれば第4要素を返す。重複要素の左端が常に返ってくるわけではなく、
[
1
,
2
,
4
,
4
,
4
,
5
,
6
,
7
]
{\displaystyle [1,2,4,4,4,5,6,7]}
のような配列でも第4要素が返される。場合によっては、配列内で目標値が重複しているときその中の左端もしくは右端の要素を見つける必要がある。
[
1
,
2
,
3
,
4
,
4
,
5
,
6
,
7
]
{\displaystyle [1,2,3,4,4,5,6,7]}
の例では、目標値
4
{\displaystyle 4}
を持つ要素のうち第4要素が左端、第5要素が右端である。等価比較を省略する代替手法は、配列中に目標値が存在するならば常に右端の要素を返す[ 6] 。
左端の要素を探索する手続き
以下の手続きによれば目標値と等しい要素の左端を見つけることができる。
L
{\displaystyle L}
に
0
{\displaystyle 0}
を、
R
{\displaystyle R}
に
n
{\displaystyle n}
を代入する。
L
<
R
{\displaystyle L<R}
である間、以下を繰り返す。
中央要素の位置
m
{\displaystyle m}
を
L
+
f
l
o
o
r
(
R
−
L
2
)
{\displaystyle L+\mathrm {floor} {\biggl (}{\frac {R-L}{2}}{\biggl )}}
とする。
A
m
<
T
{\displaystyle A_{m}<T}
ならば
L
{\displaystyle L}
に
m
+
1
{\displaystyle m+1}
を代入する。
そうでないならば
A
m
≥
T
{\displaystyle A_{m}\geq T}
である。
R
{\displaystyle R}
に
m
{\displaystyle m}
を代入する。
L
{\displaystyle L}
を返す。
L
<
n
{\displaystyle L<n}
かつ
A
L
=
T
{\displaystyle A_{L}=T}
であれば、
A
L
{\displaystyle A_{L}}
が
T
{\displaystyle T}
に等しい要素の左端である。
T
{\displaystyle T}
が配列に含まれなかったとしても、
L
{\displaystyle L}
は配列
A
{\displaystyle A}
における
T
{\displaystyle T}
の順位(
T
{\displaystyle T}
より小さい要素の数)を表している。
このバージョンの疑似コードは以下になる。
function binary_search_leftmost(A, n, T):
L := 0
R := n
while L < R:
m := L + floor((R - L) / 2)
if A[m] < T:
L := m + 1
else :
R := m
return L
右端の要素を探索する手続き
以下の手続きで右端の要素を見つけることができる。
L
{\displaystyle L}
に
0
{\displaystyle 0}
を、
R
{\displaystyle R}
に
n
{\displaystyle n}
を代入する。
L
<
R
{\displaystyle L<R}
である間、以下を繰り返す。
中央要素の位置
m
{\displaystyle m}
に
L
+
f
l
o
o
r
(
R
−
L
2
)
{\displaystyle L+\mathrm {floor} {\biggl (}{\frac {R-L}{2}}{\biggl )}}
を代入する。
A
m
>
T
{\displaystyle A_{m}>T}
ならば
R
{\displaystyle R}
に
m
{\displaystyle m}
を代入する。
そうでないならば
A
m
≤
T
{\displaystyle A_{m}\leq T}
である。
L
{\displaystyle L}
に
m
+
1
{\displaystyle m+1}
を代入する。
R
−
1
{\displaystyle R-1}
を返す。
R
>
0
{\displaystyle R>0}
かつ
A
R
−
1
=
T
{\displaystyle A_{R-1}=T}
であれば、
A
R
−
1
{\displaystyle A_{R-1}}
が
T
{\displaystyle T}
に等しい要素の右端である。
T
{\displaystyle T}
が配列に含まれなかったとしても、
n
−
R
{\displaystyle n-R}
は配列
A
{\displaystyle A}
のうち
T
{\displaystyle T}
より大きい要素の数を表している。
疑似コードは以下の通りである。
function binary_search_rightmost(A, n, T):
L := 0
R := n
while L < R:
m := L + floor((R - L) / 2)
if A[m] > T:
R := m
else :
L := m + 1
return R - 1
近似一致
二分探索は近似一致の計算にも応用できる。探索対象値の5が配列中に存在しない場合にも二分探索によって順位 (Rank)、直前要素 (Predecessor)、直後要素 (Successor)、最近接要素 (Nearest neighbor) が取得できる。
上記の手続きは完全一致の判定のみを行って目標値の位置を見つけるものである。しかし、二分探索は整列配列を対象としているため、近似一致の判定を行うように拡張することは容易である。たとえば、与えられた値に対して、順位(より小さい要素の数)、直前要素(次に値が小さい要素)、直後要素(次に値が大きい要素)、最も近い要素 を求めるなどである。2つの値について順位を求めることで、それらの間にある要素数を求める範囲クエリ (英語版 ) も実行可能である[ 8] 。
順位クエリは左端要素を求める手続きによって実行できる[ 8] 。
直前要素クエリは順位クエリによって実行できる。目標値の順位が
r
{\displaystyle r}
なら、直前要素のインデックスは
r
−
1
{\displaystyle r-1}
である。
直後要素クエリは右端要素を求める手続きによって実行できる。返ってきたインデックスが
r
{\displaystyle r}
なら、直後要素は
r
+
1
{\displaystyle r+1}
である。
目標値の最近傍要素は、直前要素と直後要素のうち目標値に近い方である。
範囲クエリも容易に行うことができる。2つの値の順位を求めて差をとれば、大きい方の値未満で小さい方の値以上の要素数になる。端点を範囲に含めるかどうか、また配列に端点に等しい要素が存在するかどうかによって求めた数を1ずつ増減して調整する。
性能
二分探索を表す木構造 。ここで探索対象となっている配列は
[
20
,
30
,
40
,
50
,
80
,
90
,
100
]
{\displaystyle [20,30,40,50,80,90,100]}
、探索目標値は
40
{\displaystyle 40}
である。
二分探索の最悪ケースでは、木構造の探索が最も深いレベルに到達する。最良ケースとなるのは目標値が配列の中央要素と一致したときである。
二分探索の性能のうち比較回数に関する部分は、手続きを二分探索木 で表すことで評価できる。二分探索木の根ノード は配列の中央要素である。その子ノードは、左側が配列の前半の中央要素、右側が後半の中央要素となる。それ以降も同様に構築される。探索は根ノードから開始され、現在のノードの値を目標値と比較して大小に応じて左右の部分木をたどっていく[ 3] 。
最悪の場合、二分探索は比較ループを
⌊
log
2
(
n
)
+
1
⌋
{\textstyle \lfloor \log _{2}(n)+1\rfloor }
回反復する。
⌊
⋅
⌋
{\textstyle \lfloor \cdot \rfloor }
は引数以下の最大の整数を返す床関数 、
log
2
{\textstyle \log _{2}}
は二進対数 である。二分探索木の階層数は常に
⌊
log
2
(
n
)
+
1
⌋
{\textstyle \lfloor \log _{2}(n)+1\rfloor }
になり、その最深層に到達するのが最悪ケースにあたる。
目標の要素が配列内に存在しないならば、場合によって最悪ケースとなる。
n
{\textstyle n}
が2のべき乗 から1を引いた値であれば常にこれが当てはまる。それ以外の配列長では、反復数
⌊
log
2
(
n
)
+
1
⌋
{\textstyle \lfloor \log _{2}(n)+1\rfloor }
回で探索木の最深層に到達することもあり、反復数
⌊
log
2
(
n
)
⌋
{\textstyle \lfloor \log _{2}(n)\rfloor }
回(最深層より一つ上)で探索が終了することもある。
全要素が等しい確率で探索対象となるとして反復回数を平均すると、目標要素が配列内に存在する場合には
⌊
log
2
(
n
)
⌋
+
1
−
(
2
⌊
log
2
(
n
)
⌋
+
1
−
⌊
log
2
(
n
)
⌋
−
2
)
/
n
{\displaystyle \lfloor \log _{2}(n)\rfloor +1-(2^{\lfloor \log _{2}(n)\rfloor +1}-\lfloor \log _{2}(n)\rfloor -2)/n}
回となる。これは
log
2
(
n
)
−
1
{\displaystyle \log _{2}(n)-1}
回におよそ等しい。目標要素が配列内に存在しない場合、目標要素の挿入位置が両端も含めた要素間の中から等確率で選ばれるならば、反復回数は
⌊
log
2
(
n
)
⌋
+
2
−
2
⌊
log
2
(
n
)
⌋
+
1
/
(
n
+
1
)
{\displaystyle \lfloor \log _{2}(n)\rfloor +2-2^{\lfloor \log _{2}(n)\rfloor +1}/(n+1)}
回となる。
最良ケースとなるのは目標値が配列の中央要素と一致している場合で、反復1回のみでその位置が返される。
反復回数の点では、要素同士の比較のみに基づく探索アルゴリズムのうち、平均および最悪ケースにおいて二分探索より優れた性能を示すものは存在しない。二分探索を表す比較木は最下層を除くすべての階層が完全に埋まっているため、階層数は限界まで最小化されている[ 注釈 2] 。木構造がそれ以外のものになると、1回の反復で除外できる要素数が減ることになるため、平均および最悪ケースでの反復回数が増える。このため、二分探索以外の比較ベースのアルゴリズムは、特定の目標値に対しては二分探索より速い場合があるとしても、すべての要素にわたって平均した性能では劣る。二分探索は配列を二分割することで部分配列のサイズを可能な限り均等に保っている。
空間計算量
二分探索では、配列の大きさにかかわらず、要素を指定するためのポインタ(配列インデックス、もしくはメモリ位置へのポインタ)を3つ必要とする。すなわち、Word RAM (英語版 ) モデルにおいて二分探索の空間計算量 (英語版 ) は
O
(
1
)
{\displaystyle O(1)}
である。
平均ケースの導出
二分探索における平均の反復回数は、探索対象がどのような確率で選ばれるかに依存する。また探索が成功する場合と失敗する場合でも異なる。ここでは、成功する場合については、すべての配列要素が等確率で探索対象になると仮定する。失敗する場合では、すべての要素間および両端の外の各区間が等確率で選ばれるとする。成功時の平均ケースは、すべての要素を一度ずつ探索したときに必要な反復回数の合計を要素数
n
{\displaystyle n}
で割ったものである。失敗時の平均ケースは、各区間で一度ずつ探索を行ったときの反復回数の合計を区間数
n
+
1
{\displaystyle n+1}
で割ったものである。
探索対象が見つかる場合
二分木 表現において、探索対象が見つかる場合の探索は根から目標ノードまでのパス として表される。これを内部パス (internal path) と呼ぶ。パスの長さは含まれるエッジ(ノード間の接続)の数で与えられる。パス長を
l
{\displaystyle l}
とすれば、その探索の反復回数は初回の比較を含めて
l
+
1
{\displaystyle l+1}
回となる。すべての内部パスの長さの合計を内部パス長 (internal path length) とする。各ノードには根からのパスが一意に存在するため、一つの内部パスが一つの要素を対象とする探索を表すことになる。要素の数が正整数 n であり、かつ内部パス長が
I
(
n
)
{\displaystyle I(n)}
なら、探索が成功する場合の反復回数の平均は
T
(
n
)
=
1
+
I
(
n
)
n
{\displaystyle T(n)=1+{\frac {I(n)}{n}}}
で与えられる。加算されている1はすべての探索で必要になる初回の比較を表す。
二分探索は比較に基づく探索アルゴリズムとして最適であるため、探索成功時の平均反復回数は、ノード数 n のあらゆる二分木 のうち最小の内部パス長を求める問題に帰着する。その値は以下で与えられる。
I
(
n
)
=
∑
k
=
1
n
⌊
log
2
(
k
)
⌋
{\displaystyle I(n)=\sum _{k=1}^{n}\left\lfloor \log _{2}(k)\right\rfloor }
たとえば要素数7の配列では、根ノードが表す探索は反復回数が1回のみであり、根の下に位置する2要素はそれぞれ2回、その下の4要素はそれぞれ3回の反復を必要とする。この場合の内部経路長は以下である。
∑
k
=
1
7
⌊
log
2
(
k
)
⌋
=
0
+
2
×
1
+
4
×
2
=
2
+
8
=
10
{\displaystyle \sum _{k=1}^{7}\left\lfloor \log _{2}(k)\right\rfloor =0+2\times 1+4\times 2=2+8=10}
平均の反復回数は
T
(
n
)
=
1
+
10
7
=
2
3
7
{\displaystyle T(n)=1+{\frac {10}{7}}=2{\frac {3}{7}}}
となる。
I
(
n
)
{\displaystyle I(n)}
の総和は以下のようにも書き換えられる。
I
(
n
)
=
∑
k
=
1
n
⌊
log
2
(
k
)
⌋
=
(
n
+
1
)
⌊
log
2
(
n
+
1
)
⌋
−
2
⌊
log
2
(
n
+
1
)
⌋
+
1
+
2
{\displaystyle I(n)=\sum _{k=1}^{n}\left\lfloor \log _{2}(k)\right\rfloor =(n+1)\left\lfloor \log _{2}(n+1)\right\rfloor -2^{\left\lfloor \log _{2}(n+1)\right\rfloor +1}+2}
この
I
(
n
)
{\displaystyle I(n)}
を
T
(
n
)
{\displaystyle T(n)}
の式に代入すると以下が得られる。
T
(
n
)
=
1
+
(
n
+
1
)
⌊
log
2
(
n
+
1
)
⌋
−
2
⌊
log
2
(
n
+
1
)
⌋
+
1
+
2
n
=
⌊
log
2
(
n
)
⌋
+
1
−
(
2
⌊
log
2
(
n
)
⌋
+
1
−
⌊
log
2
(
n
)
⌋
−
2
)
/
n
{\displaystyle T(n)=1+{\frac {(n+1)\left\lfloor \log _{2}(n+1)\right\rfloor -2^{\left\lfloor \log _{2}(n+1)\right\rfloor +1}+2}{n}}=\lfloor \log _{2}(n)\rfloor +1-(2^{\lfloor \log _{2}(n)\rfloor +1}-\lfloor \log _{2}(n)\rfloor -2)/n}
n が整数であれば、上式は、成功する探索について先に述べた平均ケースの式と等価である。
探索対象が見つからない場合
配列中に目標値が見つからないような探索は、木構造に外部ノード (external nodes) を追加することで表現できる。そのようにして得られる木を拡張二分木 (extended binary tree) と呼ぶ。内部ノード(木に元々含まれていたノード)すべてについて、子ノードを2つ未満しか持たないならば外部ノードを子として追加することで子を必ず2つ持つようにする。これにより、失敗する探索は一つの外部ノードへのパスとして表される。その外部ノードの親は、探索中の最後の反復で残った配列要素に対応する。根から外部ノードに至る経路を外部パス (external path)、すべての外部パスの長さの総和を外部パス長 (external path length) と呼ぶ。要素数が正整数
n
{\displaystyle n}
、外部パス長が
E
(
n
)
{\displaystyle E(n)}
であれば、目標値が見つからない探索における平均反復回数は、最初の反復を含めて
T
′
(
n
)
=
E
(
n
)
n
+
1
{\displaystyle T'(n)={\frac {E(n)}{n+1}}}
となる。外部パス長が
n
{\displaystyle n}
ではなく
n
+
1
{\displaystyle n+1}
で割られているのは、配列の各要素の間、および両端に存在する区間に対応して
n
+
1
{\displaystyle n+1}
個の外部経路が存在するためである。
目標値が見つかる探索の場合と同様に、この問題もノード数 n のあらゆる二分木のうち最小の外部パス長を求める問題に帰着する。いかなる二分木も外部パス長は内部パス長より
2
n
{\displaystyle 2n}
だけ多いので、
I
(
n
)
{\displaystyle I(n)}
の式を代入して
E
(
n
)
=
I
(
n
)
+
2
n
=
[
(
n
+
1
)
⌊
log
2
(
n
+
1
)
⌋
−
2
⌊
log
2
(
n
+
1
)
⌋
+
1
+
2
]
+
2
n
=
(
n
+
1
)
(
⌊
log
2
(
n
)
⌋
+
2
)
−
2
⌊
log
2
(
n
)
⌋
+
1
{\displaystyle E(n)=I(n)+2n=\left[(n+1)\left\lfloor \log _{2}(n+1)\right\rfloor -2^{\left\lfloor \log _{2}(n+1)\right\rfloor +1}+2\right]+2n=(n+1)(\lfloor \log _{2}(n)\rfloor +2)-2^{\lfloor \log _{2}(n)\rfloor +1}}
となる。この
E
(
n
)
{\displaystyle E(n)}
を
T
′
(
n
)
{\displaystyle T'(n)}
に代入することで、探索が失敗する場合の平均ケースを求められる。
T
′
(
n
)
=
(
n
+
1
)
(
⌊
log
2
(
n
)
⌋
+
2
)
−
2
⌊
log
2
(
n
)
⌋
+
1
(
n
+
1
)
=
⌊
log
2
(
n
)
⌋
+
2
−
2
⌊
log
2
(
n
)
⌋
+
1
/
(
n
+
1
)
{\displaystyle T'(n)={\frac {(n+1)(\lfloor \log _{2}(n)\rfloor +2)-2^{\lfloor \log _{2}(n)\rfloor +1}}{(n+1)}}=\lfloor \log _{2}(n)\rfloor +2-2^{\lfloor \log _{2}(n)\rfloor +1}/(n+1)}
代替手法の性能
本項の初めに定義した二分探索の手続きでは、毎回の反復で中央要素が探索目標と等しいかをチェックする。比較回数は反復ごとに1回もしくは2回であり、すべての要素が等しい確率で探索されるならば平均して反復ごとに1.5回となる。それに対し、すべての反復が終わった後に残った要素に対して1回だけ探索目標と等しいかのチェックを行う代替手法では、反復ごとに平均で0.5回の比較が省略できる。そのかわりに必ず最大の回数の反復が行われるため、反復回数は1つの探索につき平均1回増加してしまう。比較ループは最悪ケースでも
⌊
log
2
(
n
)
+
1
⌋
{\textstyle \lfloor \log _{2}(n)+1\rfloor }
回しか行われないため、反復ごとの効率がわずかに上昇するとしても、
n
{\textstyle n}
が非常に大きい場合を除けば余分な1回の反復を埋め合わせるには十分ではない[ 注釈 3] [ 16] 。
実行時間とキャッシュ使用
二分探索の性能評価では要素の比較に要する時間も考慮の対象となる。整数や文字列の比較時間は通常そのエンコーディング長(多くの場合ビット 数)に比例して増加する。たとえば、64ビット符号なし整数同士の比較であれば、32ビットの場合と比べて最大で2倍のビット数を比較する必要がある。比較対象の整数が同値である場合に最悪の処理時間となる。このコストは巨大な整数型や長い文字列など要素のエンコーディング長が大きい場合に無視できないものとなる。また、実数 のデジタル表現として最も一般的な形式である浮動小数点 の比較は、整数や短い文字列の比較よりも処理コストが高くなることが多い。
多くのコンピュータアーキテクチャでは、プロセッサ はRAM とは別にハードウェアキャッシュ を備えている。キャッシュはプロセッサ自体に内蔵されているためRAMよりはるかに高速だが、記憶容量は一般に少ない。そのため、プロセッサは最近アクセスされたメモリ位置とその近傍のデータをキャッシュに保持するのが一般的である。たとえば、配列の要素が読み出されるときは、その要素とRAM中で物理的に近いメモリ位置にある要素も同時にキャッシュに格納される。インデックスが近い要素に順次アクセスする処理はこれにより高速化されうる(参照の局所性 )。しかし、整列配列に対する二分探索は、線形探索 やハッシュテーブル における線形探査 (英語版 ) のような順次アクセスを行うアルゴリズムとは異なり、配列が大きいと遠く離れたメモリ位置へジャンプすることがある。このため、多くのシステムでは大規模な配列に対する二分探索の実行時間がわずかに増加する[ 17] 。
二分探索とほかの方式の比較
整列配列に対する二分探索は、探索と同時に挿入や削除の操作を行う場合には非常に非効率であり、それらの操作ごとに
O
(
n
)
{\textstyle O(n)}
の時間を必要とする。また整列配列はメモリ管理を複雑にする可能性があり、特に要素の挿入が頻繁に行われる状況ではそれが問題になる。整列配列よりも挿入や削除を効率的に行えるデータ構造も存在する。また、二分探索は完全一致の検索や集合 への所属判定(検索対象の値がコレクションに含まれているかの確認)にも使えるが、それらの処理についてもより高速に実行可能なデータ構造が存在する。しかし、多くの探索方式と比べて二分探索は近似一致の処理の効率が良く、値の構造や型に関係なく
O
(
log
n
)
{\textstyle O(\log n)}
時間で実行できることが多い[ 19] 。加えて、最小値や最大値の取得など整列配列において効率よく実行できる操作もある[ 8] 。
線形探索
線形探索 とは目標値を見つけるまですべてのレコードを順に調べていく単純な探索アルゴリズムであり、配列より挿入や削除が高速な連結リスト に対しても行うことができる。整列配列に対する二分探索は配列が短い場合を除いて線形探索より高速だが、事前に配列をソートする必要がある[ 注釈 4] 。クイックソート やマージソート など、要素間の比較に基づくすべてのソートアルゴリズム は、最悪ケースで最低
O
(
n
log
n
)
{\textstyle O(n\log n)}
回の比較が必要になる。一方で、二分探索は線形探索と比べて近似一致探索を効率的に行うことができる。さらに、整列配列では最小値や最大値を高速に取得することができるが、未整列の配列では難しい。
木構造
二分探索木 は二分探索と類似したアルゴリズムで検索される。
二分探索木 は二分探索の原理に基づいて機能する二分木 のデータ構造である。すべてのレコードが整列しており、各レコードの探索は二分探索に似たアルゴリズムによって平均して対数時間で行うことができる。二分探索木では挿入や削除の処理も平均で対数時間となり、整列配列における線形時間の挿入・削除より高速となりうる。また二分木は範囲検索や近似検索など整列配列で可能なすべての操作が実行可能である[ 19] [ 24] 。
しかしながら、実際の二分探索木は完全に平衡ではない可能性が高く、そのため検索効率では整列配列の二分探索に劣るのが普通である。ノードの自動的な再配置によって自己平衡を行う平衡二分探索木 においても、理想的な最小階層数の構造が得られることはまれである。平衡でない二分探索木では、内部ノードの多くが子ノードを1つしか持たず探索時間が平均・最悪ともに
n
{\textstyle n}
に近づくおそれがある[ 注釈 5] 。また、二分探索木は整列配列よりメモリ空間の消費が大きい。
その一方、二分探索木はファイルシステム の構造に効率的に組み込めるため、ハードディスクに格納された外部メモリの高速検索に適している。このような構造化を一般化したB木 はデータベース やファイルシステムなどの長期記憶領域に広く用いられている[ 27] 。
ハッシュ法
連想配列 を実装するにあたっては、ハッシュ関数 を用いてキーをレコード に対応付けるハッシュテーブル 構造の方が、整列されたレコード配列に対する二分探索よりも一般に高速である。ほとんどのハッシュテーブルの実装ではならし 定数時間で処理が可能である[ 注釈 6] [ 31] 。しかしながらハッシュ法は近似一致には適さない。たとえば、検索対象の次に小さい/次に大きい/最も近いキーを求めたいとしても、ハッシュ法による検索が失敗したときに得られる情報はレコード中に対象のキーが存在しないことだけである[ 32] 。二分探索はこうした近似一致の操作に構造的にも適しており、対数時間で実行できる。さらに、最小値や最大値の取得のような操作は整列配列では効率的に実行できるが、ハッシュテーブルでは困難である[ 19] 。
集合所属判定アルゴリズム
探索に関連した問題に集合 への所属判定がある。二分探索のような検索アルゴリズムはいずれも集合所属判定に利用できる。ただし集合所属判定に特化した手法も存在する。ビット配列 (英語版 ) はそのうち最も単純な構造であり、キーの取り得る範囲が限られている場合に有効である。ビット配列は取りうるキーそれぞれにビット を1つずつ割り当てることで集合をコンパクトに保持する。所属判定は非常に高速で、
O
(
1
)
{\textstyle O(1)}
時間で実行可能である[ 33] 。ジュディ配列 (英語版 ) (Judy1型)は64ビットキーを高効率で扱うことができる[ 34] 。
近似的な結果を許容する場合にはブルームフィルタ が有力である。ブルームフィルタはハッシュ法に基づく確率的データ構造で、ビット配列と複数のハッシュ関数を用いてキーを符号化することで集合を格納する。ブルームフィルタはほとんどの場合ビット配列より空間効率がはるかに高く、速度ではそれほど見劣りしない。ハッシュ関数を
k
{\textstyle k}
個使用する場合、所属判定の計算量は
O
(
k
)
{\textstyle O(k)}
に抑えられる。ただしブルームフィルタの所属判定には偽陽性の問題がある[ 注釈 7] [ 注釈 8] [ 36] 。
その他のデータ構造
探索のみならず整列配列で可能な各種操作において二分探索よりも効率で上回りうるデータ構造も存在する。たとえば、探索や近似一致、そのほか整列配列に基づく各種の操作は、van Emde Boas木 、フュージョン木 (英語版 ) 、トライ木 、ビット配列 などの特殊なデータ構造を用いることで二分探索よりも効率よく実行できることがある。ただし、これらの特殊な構造が高速であるのは特定の性質を持つキーに最適化されているためであり(典型的にはキーが小さな整数である場合)、そのような性質を持たないキーに対しては時間や空間のコストがかさむことがある[ 19] 。キーに順序性がある限り、整列配列を用いれば、キーの性質にかかわらずある程度効率的にこれらの操作は実行可能である。Judy配列のような構造は、複数の手法を組み合わせることでキー特性への依存性を緩和しながら高効率と近似一致の機能を両立させている[ 34] 。
バリエーション
ユニフォーム二分探索
ユニフォーム二分探索では、上限・下限のインデックスではなく、現在の中点と2つの次の中点候補との間のインデックス差を記憶する。
ユニフォーム二分探索 (uniform binary search ) では、配列インデックス範囲の上限と下限を記憶するのでなく、現在の中央要素と次の反復における中央要素のインデックス差を記憶する。具体的には、各回の反復におけるインデックス差を格納したルックアップテーブル をあらかじめ計算しておく。たとえば、0から10までのインデックスを持つ11要素の配列を探索する場合、最初の中央要素のインデックス
m
{\displaystyle m}
は5である。このとき左側の部分配列のインデックスは0から4であり、中央要素は2となる。右側の部分配列の中央要素は8である。どちらのインデックスも
m
=
5
{\displaystyle m=5}
から等しく3ずつ離れているため、この差分3があらかじめ格納される。インデックス範囲の上限と下限の平均を都度計算するのではなく、現在の中央要素インデックスに差分を加算もしくは減算することで次の中央要素インデックスを作るのである。10進コンピュータ (英語版 ) のように平均を計算する効率が低いシステムは、ユニフォーム二分探索によって速度が改善される可能性がある。
指数探索
指数探索の概念図。長い配列から探索対象値を含む範囲の部分配列を抜き出し、それに対して二分探索を実行する。
指数探索 (英語版 ) は二分探索を無限長のリストに拡張したものである。このアルゴリズムはまず「インデックスが2のべき乗であり、かつ探索対象の値より大きい最初の要素」を見つけることから始め、そのインデックスを上限に設定した上で二分探索に切り替える。探索対象の位置を
x
{\textstyle x}
として、前半の処理で
⌊
log
2
x
+
1
⌋
{\textstyle \lfloor \log _{2}x+1\rfloor }
回、後半の二分探索部分で最大
⌊
log
2
x
⌋
{\textstyle \lfloor \log _{2}x\rfloor }
回の比較が行われる。指数探索は有限長のリストでも機能するが、配列の先頭付近に探索対象がある場合に限って二分探索よりも高速になる。
内挿探索
線形補間による内挿探索の概念図。このケースでは、配列内の目標値の位置が正確に推定されため探索が不要となっている。実装によっては目標値の位置を推定するために別の関数が用いられる。
内挿探索 (英語版 ) では、中間点を計算するのではなく、要素の最小値と最大値、ならびに配列の長さを考慮して目標値の位置を推定する。その前提には、中間点は多くの場合で最良の推定位置ではないという点がある。たとえば、目標値が要素の最大値に近い場合、その値は配列の末尾近くに位置している可能性が高い。
一般的には内挿関数 として線形補間が用いられる。配列を
A
{\displaystyle A}
、インデックスの上限と下限を
L
{\displaystyle L}
と
R
{\displaystyle R}
、探索目標値を
T
{\displaystyle T}
とすると、目標値は
L
{\displaystyle L}
から
R
{\displaystyle R}
までの範囲のうち
(
T
−
A
L
)
/
(
A
R
−
A
L
)
{\displaystyle (T-A_{L})/(A_{R}-A_{L})}
程度の位置にあると推定される。配列内の要素が一様またはほぼ一様に分布している場合、線形補間を用いた内挿探索では比較回数は
O
(
log
log
n
)
{\textstyle O(\log \log n)}
回となる[ 42] 。
実用上は、内挿探索は追加の計算を必要とするため小さな配列に対しては二分探索より遅くなる。内挿探索の時間計算量は二分探索よりも緩やかに増加するが、大きな配列にならなければ追加の計算のコストを相殺できない。
フラクショナル・カスケーディング
フラクショナル・カスケーディングでは、カスケードされた配列のそれぞれに、前段の配列から1つおきで抽出した要素へのポインタが織り込まれる。これにより、最後の配列に対して二分探索を行うだけで全ての配列を探索するのに近い結果が得られる。
フラクショナル・カスケーディング (英語版 ) は、複数の整列配列に対して同じ要素を探索する際に、二分探索によって高速な探索を行う手法である。配列を個別に探索する場合、
k
{\textstyle k}
を配列数として所要時間は
O
(
k
log
n
)
{\textstyle O(k\log n)}
となる。フラクショナル・カスケーディングは配列中に他の配列の要素とその位置に関する情報を繰り込むことによって探索時間を
O
(
k
+
log
n
)
{\textstyle O(k+\log n)}
に圧縮する[ 43] [ 44] 。
フラクショナル・カスケーディングは計算幾何学 の各種の問題を効率的に解くために開発された手法で、データマイニング やインターネット・プロトコル のルーティング のような分野にも応用されている[ 43] 。
グラフへの一般化
二分探索はある種のグラフ にも適用できるよう一般化できる。この場合、値が格納されるのは配列要素ではなくグラフ頂点である。二分探索木 はその一例であり、木構造の頂点(ノード)を照会することで、その頂点が目標と等しいかどうか、そうでなければどちらの部分木に目標が含まれるかを判定するアルゴリズムになる。さらにこれを一般化したアルゴリズムでは、正の重みを持つ重み付き無向グラフ と探索目標が与えられたとき、ある頂点を照会すると、その頂点が目標であるかどうかを判定し、そうでない場合にはその頂点に接続する辺のうち目標頂点への最短経路上にあるものを選ぶ。いかなる正の重みを持つ重み付き無向グラフに対しても、最悪の場合でも
O
(
log
n
)
{\displaystyle O(\log n)}
回の照会で目標の頂点を見つけ出すようなアルゴリズムが存在する[ 45] 。
ノイズのある二分探索
ノイズのある二分探索では、比較処理が一定の確率で誤った結果を返す。
ノイズのある二分探索 (noisy binary search) は配列要素の比較に確実性がない問題に対するアルゴリズムであり、各回の比較結果が一定の確率で誤っているにもかかわらず所定の正確さで目標値の位置を見つけ出すことができる。この種の手法はいずれも、平均して少なくとも
(
1
−
τ
)
log
2
(
n
)
H
(
p
)
−
10
H
(
p
)
{\displaystyle (1-\tau ){\frac {\log _{2}(n)}{H(p)}}-{\frac {10}{H(p)}}}
回の比較を必要とする。ここで
H
(
p
)
=
−
p
log
2
(
p
)
−
(
1
−
p
)
log
2
(
1
−
p
)
{\displaystyle H(p)=-p\log _{2}(p)-(1-p)\log _{2}(1-p)}
は二値エントロピー関数 、
p
{\displaystyle p}
は各回の比較の正答率、
τ
{\displaystyle \tau }
は探索によって返される位置が誤っている確率である[ 46] [ 47] [ 48] 。ノイズのある二分探索の問題はレーニ=ウラムのゲーム (英語版 ) (20の質問 の変種で回答が確率的に誤る) [ 49] の一種とみなすことができる[ 50] 。
量子二分探索
古典コンピュータでは、二分探索の最悪ケースで正確に
⌊
log
2
n
+
1
⌋
{\textstyle \lfloor \log _{2}n+1\rfloor }
回の反復が必要となる。量子アルゴリズム (英語版 ) による二分探索でもクエリ回数(古典計算における反復回数にあたる)は
log
2
n
{\textstyle \log _{2}n}
の定数倍が上限となるが、その係数は1より小さい。厳密な(常に正しい結果を返す)量子二分探索手続きは最悪ケースで少なくとも
1
π
(
ln
n
−
1
)
≈
0.22
log
2
n
{\textstyle {\frac {1}{\pi }}(\ln n-1)\approx 0.22\log _{2}n}
回のクエリが必要となる。ここで
ln
{\textstyle \ln }
は自然対数 を表す[ 51] 。また、最悪ケースで
4
log
605
n
≈
0.433
log
2
n
{\textstyle 4\log _{605}n\approx 0.433\log _{2}n}
回で動作する厳密な量子二分探索の手法も存在する[ 52] 。なお、非整列リストからの要素探索ではグローバーのアルゴリズム が最適な量子アルゴリズムであり、必要なクエリ数は
O
(
n
)
{\displaystyle O({\sqrt {n}})}
回である[ 53] 。
歴史
検索を高速化するためリスト項目をソートする発想は古代にまで遡る。最古の例として知られているのは紀元前200年ごろのバビロニアで作られた Inakibit-Anu 粘土板 である。この粘土板にはおよそ500個の60進数 が探しやすいように辞書式順 に並べられ、それぞれの逆数 が記されていた。エーゲ海諸島 でも頭文字で整列された名簿が複数発見されている。1286年に編纂されたラテン語辞典『カトリコン (英語版 ) 』は、頭文字付近だけでなく完全なアルファベット順で単語を整列する規則について記した最初の文献である[ 6] 。
1946年には、先駆的な計算機科学 の大学講座「ムーア・スクール・レクチャーズ (英語版 ) 」においてジョン・モークリー が二分探索に初めて言及した[ 6] 。1957年、ウィリアム・ウェスレイ・ピーターソン が内挿探索の最初の手法を発表した[ 6] [ 54] 。この時期に発表された二分探索アルゴリズムはいずれも配列の長さが2の累乗から1を引いた形[ 注釈 9] でなければ機能しなかったが、1960年にデリック・ヘンリー・レーマー (英語版 ) が任意の長さの配列で機能する二分探索アルゴリズムを発表した[ 56] 。1962年にはヘルマン・ボッテンブルッフが、検索対象との等値比較を反復の最後に移すことで、反復回数が平均で1回増加するかわりに反復あたりの比較回数を1回に減らす代替手法のアルゴリズムをALGOL 60 (英語版 ) への実装として発表した[ 5] 。1971年にはスタンフォード大学 のA・K・チャンドラによってユニフォーム二分探索が開発された[ 6] 。1986年にはバーナード・チャゼル (英語版 ) とレオニダス・J・ギバス (英語版 ) が計算幾何学 における各種の探索問題を解決する手法としてフラクショナル・カスケーディングを開発した[ 43] [ 57] [ 58] 。
実装上の問題
二分探索の基本的なアイディアは比較的分かりやすいが、細部は驚くほど厄介だ。
—ドナルド・クヌース
ジョン・ベントリー (英語版 ) は、職業的なプログラマ向けの講座で二分探索を課題にしたところ、90%の受講者が数時間かけても正しい解法を得られなかったと伝えている。それらの受講者による実装はまれなエッジケース (英語版 ) において誤動作を起こしたり、誤った解答を返した。1988年の研究によると、二分探索の正確なコードを掲載していた教科書は20冊中5冊に過ぎなかった[ 61] 。ベントリー自身の1986年の著書 Programming Pearls (『珠玉のプログラミング』2000年、丸善出版)に掲載されていた二分探索の実装にもオーバーフローのバグ (英語版 ) が含まれており、20年以上にわたって見過ごされた。Java言語 のライブラリに含まれていた二分探索の実装にも9年以上にわたって同様のオーバーフローのバグがあった[ 62] 。
実際の実装では、インデックスを表す変数が固定長(整数型)であることが多く、非常に大きな配列に対しては数値のオーバーフローが生じる可能性がある。区間の中点を
L
+
R
2
{\displaystyle {\frac {L+R}{2}}}
として計算すると、
L
{\displaystyle L}
および
R
{\displaystyle R}
自体はデータ型の範囲内であっても
L
+
R
{\displaystyle L+R}
が範囲を超えてしまうことがある。
L
{\displaystyle L}
と
R
{\displaystyle R}
が非負である場合は
L
+
R
−
L
2
{\displaystyle L+{\frac {R-L}{2}}}
として計算すればこの問題を避けられる[ 63] 。
そのほか、ループの終了条件が正しく定義されていないと無限ループが発生する可能性がある。
L
{\displaystyle L}
が
R
{\displaystyle R}
を超えることがあれば、その時点で探索は失敗であり、明示的に失敗の処理を行わなければならない。また目標の要素が見つかった場合にもループから抜ける必要がある。目標値との等値判定を毎回のループで行わない代替手法の実装では、探索が成功したか失敗したかの判定を適切な場所に置く必要がある。ベントリーによれば、二分探索を誤って実装したプログラマの多くは、この終了条件の定義に誤りを犯していた[ 5] 。
ライブラリによるサポート
多くのプログラム言語の標準ライブラリ には二分探索のルーチンが含まれている。
C言語 では標準ライブラリ に bsearch()
関数 がある。この関数は二分探索で実装されるのが一般的だが、公式の標準でそのように規定されているわけではない[ 65] 。
C++ の標準ライブラリ では binary_search()
、lower_bound()
、upper_bound()
、equal_range()
のような関数が提供されている。
D言語 の標準ライブラリである Phobos の std.range
モジュールには、sort()
関数および assumeSorted()
関数によって得られる SortedRange()
型がある。この型には contains()
、equalRange()
、lowerBound()
、trisect()
といったメソッドが用意されており、対象となるデータがランダムアクセス可能な場合には、これらのメソッドはデフォルトで二分探索として実行される[ 67] 。
COBOL では順序付き表に二分探索を行うための SEARCH ALL
命令が提供されている[ 68] 。
Go の標準ライブラリに含まれる sort
パッケージには、一般的な二分探索を行う関数として Search
があるほか、整数・浮動小数点数・文字列のスライスを対象とする SearchInts
、SearchFloat64s
、SearchStrings
が用意されている[ 69] 。
Java の標準的な java.util
パッケージに含まれる Arrays
クラスおよび Collections
クラスでは、それぞれJava配列および List
に対して二分探索を行うため、オーバーロード された一連の静的メソッドである binarySearch()
が用意されている[ 70] [ 71] 。
Microsoft の.NET Framework 2.0 では、代表的なコレクション型に対しては、ジェネリックプログラミング に基づいた二分探索アルゴリズムが主に静的メソッドとして提供されている。たとえば、System.Array
クラスには、任意の型に対して適用可能な BinarySearch<T>(T[] array, T value)
メソッドがある[ 72] 。
Objective-C では、Cocoa フレームワークにおいて、NSArray
クラスの indexOfObject : inSortedRange : options : usingComparator :
メソッドが Mac OS X 10.6 以降で利用可能である[ 73] 。また Apple が提供するC言語フレームワークである Core Foundation にも CFArrayBSearchValues()
関数が用意されている[ 74] 。
Python では bisect
モジュールが提供されており、リストに挿入を行うたびにソートを実行しなくても整列済みの状態を保つことができる[ 75] 。
Ruby の Array
クラスは近似一致が組み込まれた bsearch
メソッドを持つ。
Rust の slice
プリミティブ型には binary_search()
、binary_search_by()
、binary_search_by_key()
、partition_point()
のメソッドがある[ 77] 。
関連項目
脚注
本記事の2025年5月22日 (木) 22:46版 の翻訳元である英語版Wikipedia記事「Binary search 」は2018年に WikiJournal of Science 誌に投稿され、外部の専門家によるピアレビュー を受けた(レビュー結果 )。修正を加えた版は2019年にCC-BY-SA-3.0 ライセンスでWikipedia上で再度公開されている(修正履歴 )。レビュー直後の版は以下の通り。
注釈
^
O
{\displaystyle O}
はランダウの記号 、
log
{\displaystyle \log }
は対数 を表す。ランダウの記法では対数の底は問題にしない。ある底での対数は別の底での対数の定数倍になるためである。すなわち
log
b
(
n
)
=
log
k
(
n
)
÷
log
k
(
b
)
{\displaystyle \log _{b}(n)=\log _{k}(n)\div \log _{k}(b)}
が成り立ち、
log
k
(
b
)
{\displaystyle \log _{k}(b)}
が定数となる。
^ 比較のみに基づく探索アルゴリズムはいずれも二分比較木によって表現することができる。内部パス (internal path) とは、根ノードから始まり、木に初めから含まれているいずれかのノードまで続くパスを指す。
I
{\displaystyle I}
をすべての内部パスの長さの総和と定義し、内部パス長と呼ぶ。各要素が等確率で探索されると仮定すると、平均ケースにおける比較回数は
1
+
I
n
{\displaystyle 1+{\frac {I}{n}}}
となる。すなわち木におけるすべての内部パスの長さの平均に1を加えたものである。これは、それぞれの内部パスが、対応する要素を発見する探索試行を表しているためである。内部パスの長さは根ノード以降に必要となる反復の回数を表している。その長さの平均に根ノードで行われる1回の比較を加えることで平均ケースの比較回数が得られる。したがって、平均的な比較回数を最小化するには内部パス長
I
{\displaystyle I}
を最小化しなければならない。実際、二分探索の木構造は内部パス長を最小化することがわかっている。Knuth 1998 は、外部パス長(external path length、初めから存在するすべてのノードが二つずつ子ノードを持つとした場合の、すべてのパス長の総和)が最小となるのは、外部ノード(子を持たないノード)が木の連続した2層内にのみ存在する場合であることを証明した。このとき内部パス長も最小となる。内部パス長
I
{\displaystyle I}
は 外部パス長
E
{\displaystyle E}
と線形の関係にあるためである。任意のノード数
n
{\displaystyle n}
を持つ木に対して
I
=
E
−
2
n
{\displaystyle I=E-2n}
が成り立つ。すべてのノードについて左右の部分木がほとんど同じ数のノードを持つ場合、すなわち配列が各反復でほぼ半分ずつに分割される場合、外部ノードおよびそれらの内部の親ノードは2層に収まることになる[訳語疑問点 ] 。二分探索の比較木は内部パス長が最小であるから、二分探索において平均比較回数は最小化される。
^ Knuth 1998 は通常の計算機を模して設計したモデルMIX を用いて、このバージョンの二分探索が探索目標値を見つけ出すときの平均実行時間が
17.5
log
2
n
+
17
{\textstyle 17.5\log _{2}n+17}
単位時間であることを示した。標準的な二分探索の
18
log
2
n
−
16
{\textstyle 18\log _{2}n-16}
単位時間と比較して、このバージョンの実行時間は
n
{\textstyle n}
に対してわずかにゆっくり増加するが、その代わり初期の実行時間が大きい。
^ Knuth 1998 は、線形探索と二分探索の理論的な実行時間評価を行った。Knuth が通常の計算機を模して設計した MIXコンピュータ上では、二分探索が目標値を発見するのに平均して
18
log
n
−
16
{\textstyle 18\log n-16}
単位時間を要するのに対し、リスト末尾に番兵 を置いた線形探索は、平均して
1.75
n
+
8.5
−
n
mod
2
4
n
{\textstyle 1.75n+8.5-{\frac {n{\text{ mod }}2}{4n}}}
単位時間を要する。線形探索は、初期段階では計算量が最小限で済むため処理負荷は低いが、急速に二分探索よりも非効率になる。MIX コンピュータ上では、番兵付きの線形探索に対して二分探索が性能的に優越するのは
n
>
44
{\textstyle n>44}
からである。
^ 値をソート順に挿入したり、最小と最大のキーを交互に挿入していくと、平均および最悪の探索時間が最大となる二分探索木が生成される。
^ 一部のハッシュテーブルの実装では、定数時間での探索が保証される。
^ あるキーに対してハッシュ関数が指すビットを単純にすべて立てると、一つ以上のハッシュ関数によるハッシュ位置を共有する別のキーに対するクエリに影響が出るためである[ 35] 。
^ カッコウハッシュ法 (英語版 ) を利用するカッコウフィルタなど、計算コストを改善したり削除操作を可能にしたブルームフィルタの改良版が存在する[ 35] 。
^ すなわち、配列長が 1, 3, 7, 15, 31 ... の場合[ 55] 。
出典
^ Weisstein, Eric W. “Binary search” . mathworld.wolfram.com (英語).
^ a b Flores, Ivan; Madpis, George (1 September 1971). “Average binary search length for dense ordered lists”. Communications of the ACM 14 (9): 602–603. doi :10.1145/362663.362752 . ISSN 0001-0782 .
^ a b c d Bottenbruch, Hermann (1 April 1962). “Structure and use of ALGOL 60”. Journal of the ACM 9 (2): 161–221. doi :10.1145/321119.321120 . ISSN 0004-5411 . Procedure is described at p. 214 (§43), titled "Program for Binary Search".
^ a b c d e f Knuth 1998 , §6.2.1 ("Searching an ordered table"), subsection "History and bibliography".
^ a b c Sedgewick & Wayne 2011 , §3.1, subsection "Rank and selection".
^ Rolfe, Timothy J. (1997). “Analytic derivation of comparisons in binary search”. ACM SIGNUM Newsletter 32 (4): 15–19. doi :10.1145/289251.289255 .
^ Khuong, Paul-Virak; Morin, Pat (2017). “Array Layouts for Comparison-Based Searching”. Journal of Experimental Algorithmics 22 . arXiv :1509.05053 . doi :10.1145/3053370 .
^ a b c d Beame, Paul; Fich, Faith E. (2001). “Optimal bounds for the predecessor problem and related problems”. Journal of Computer and System Sciences 65 (1): 38–72. doi :10.1006/jcss.2002.1822 .
^ Sedgewick & Wayne 2011 , §3.2 ("Binary Search Trees"), subsection "Order-based methods and deletion".
^ Knuth 1998 , §5.4.9 ("Disks and Drums").
^ Dietzfelbinger, Martin; Karlin, Anna; Mehlhorn, Kurt; Meyer auf der Heide, Friedhelm; Rohnert, Hans; Tarjan, Robert E. (August 1994). “Dynamic perfect hashing: upper and lower bounds”. SIAM Journal on Computing 23 (4): 738–761. doi :10.1137/S0097539791194094 .
^ Morin. “Hash tables ”. p. 1. 2022年10月9日時点のオリジナルよりアーカイブ 。2016年3月28日閲覧。
^ Knuth 2011 , §7.1.3 ("Bitwise Tricks and Techniques").
^ a b Silverstein, Alan, Judy IV shop manual , Hewlett-Packard, pp. 80–81, オリジナル の2022-10-09時点におけるアーカイブ。, https://ghostarchive.org/archive/20221009/http://judy.sourceforge.net/doc/shop_interm.pdf
^ a b Fan, Bin; Andersen, Dave G.; Kaminsky, Michael; Mitzenmacher, Michael D. (2014). Cuckoo filter: practically better than Bloom . Proceedings of the 10th ACM International on Conference on Emerging Networking Experiments and Technologies. pp. 75– 88. doi :10.1145/2674005.2674994 .
^ Bloom, Burton H. (1970). “Space/time trade-offs in hash coding with allowable errors”. Communications of the ACM 13 (7): 422–426. doi :10.1145/362686.362692 .
^ Perl, Yehoshua; Itai, Alon; Avni, Haim (1978). “Interpolation search—a log log n search”. Communications of the ACM 21 (7): 550–553. doi :10.1145/359545.359557 .
^ a b c Chazelle, Bernard; Liu, Ding (6 July 2001). “Lower bounds for intersection searching and fractional cascading in higher dimension” . STOC '01: Proceedings of the thirty-third annual ACM symposium on Theory of computing . 33rd Symposium on Theory of Computing. ACM. pp. 322– 329. doi :10.1145/380752.380818 . ISBN 978-1-58113-349-3 . 2018年6月30日閲覧 .
^ Chazelle, Bernard; Liu, Ding (1 March 2004). “Lower bounds for intersection searching and fractional cascading in higher dimension” (英語). Journal of Computer and System Sciences 68 (2): 269–284. doi :10.1016/j.jcss.2003.07.003 . ISSN 0022-0000 . http://www.cs.princeton.edu/~chazelle/pubs/FClowerbounds.pdf 2018年6月30日閲覧。 .
^ Emamjomeh-Zadeh, Ehsan; Kempe, David; Singhal, Vikrant (2016). “Deterministic and probabilistic binary search in graphs”. STOC '16: Proceedings of the forty-eighth annual ACM symposium on Theory of Computing . 48th ACM Symposium on Theory of Computing. pp. 519– 532. doi :10.1145/2897518.2897656 .
^ Ben-Or, Michael; Hassidim, Avinatan (2008). “The Bayesian learner is optimal for noisy binary search (and pretty good for quantum as well)” (PDF) . 49th Symposium on Foundations of Computer Science . pp. 221– 230. doi :10.1109/FOCS.2008.58 . ISBN 978-0-7695-3436-7 . 2022年10月9日時点のオリジナル (PDF) よりアーカイブ.
^ Pelc, Andrzej (1989). “Searching with known error probability”. Theoretical Computer Science 63 (2): 185–202. doi :10.1016/0304-3975(89)90077-7 .
^ Rivest, Ronald L.; Meyer, Albert R.; Kleitman, Daniel J.; Winklmann, K. Coping with errors in binary search procedures . 10th ACM Symposium on Theory of Computing. doi :10.1145/800133.804351 .
^ Rényi, Alfréd (1961). “On a problem in information theory” (ハンガリー語). Magyar Tudományos Akadémia Matematikai Kutató Intézetének Közleményei 6 : 505–516. MR 0143666 .
^ Pelc, Andrzej (2002). “Searching games with errors—fifty years of coping with liars”. Theoretical Computer Science 270 (1–2): 71–109. doi :10.1016/S0304-3975(01)00303-6 .
^ Høyer, Peter; Neerbek, Jan; Shi, Yaoyun (2002). “Quantum complexities of ordered searching, sorting, and element distinctness”. Algorithmica 34 (4): 429–448. arXiv :quant-ph/0102078 . doi :10.1007/s00453-002-0976-3 .
^ Childs, Andrew M.; Landahl, Andrew J.; Parrilo, Pablo A. (2007). “Quantum algorithms for the ordered search problem via semidefinite programming”. Physical Review A 75 (3). arXiv :quant-ph/0608161 . Bibcode : 2007PhRvA..75c2335C . doi :10.1103/PhysRevA.75.032335 .
^ Grover, Lov K. (1996). A fast quantum mechanical algorithm for database search . 28th ACM Symposium on Theory of Computing . Philadelphia, PA. pp. 212– 219. doi :10.1145/237814.237866 .
^ Peterson, William Wesley (1957). “Addressing for random-access storage”. IBM Journal of Research and Development 1 (2): 130–146. doi :10.1147/rd.12.0130 .
^ "2n −1". OEIS A000225 Archived 8 June 2016 at the Wayback Machine .. Retrieved 7 May 2016.
^ Lehmer, Derrick (1960). “Teaching combinatorial tricks to a computer”. Combinatorial Analysis . Proceedings of Symposia in Applied Mathematics. 10 . pp. 180–181. doi :10.1090/psapm/010/0113289 . ISBN 9780821813102 . MR 0113289
^ Chazelle, Bernard; Guibas, Leonidas J. (1986). “Fractional cascading: I. A data structuring technique” . Algorithmica 1 (1–4): 133–162. doi :10.1007/BF01840440 . http://www.cs.princeton.edu/~chazelle/pubs/FractionalCascading1.pdf .
^ Chazelle, Bernard; Guibas, Leonidas J. (1986), “Fractional cascading: II. Applications” , Algorithmica 1 (1–4): 163–191, doi :10.1007/BF01840441 , http://www.cs.princeton.edu/~chazelle/pubs/FractionalCascading2.pdf
^ Pattis, Richard E. (1988). “Textbook errors in binary searching”. SIGCSE Bulletin 20 : 190–194. doi :10.1145/52965.53012 .
^ Bloch (2006年6月2日). “Extra, extra – read all about it: nearly all binary searches and mergesorts are broken ”. Google Research Blog . 2016年4月1日時点のオリジナルよりアーカイブ 。2016年4月21日閲覧。
^ Ruggieri, Salvatore (2003). “On computing the semi-sum of two integers” . Information Processing Letters 87 (2): 67–71. doi :10.1016/S0020-0190(03)00263-1 . http://www.di.unipi.it/~ruggieri/Papers/semisum.pdf 2016年3月19日閲覧。 .
^ “bsearch – binary search a sorted table ”. The Open Group Base Specifications . The Open Group (2013年). 2016年3月21日時点のオリジナルよりアーカイブ 。2016年3月28日閲覧。
^ “std.range - D Programming Language ”. dlang.org . 2020年4月29日閲覧。
^ Unisys (2012), COBOL ANSI-85 programming reference manual , 1 , pp. 598–601
^ “Package sort ”. The Go Programming Language . 2016年4月25日時点のオリジナルよりアーカイブ 。2016年4月28日閲覧。
^ “java.util.Arrays ”. Java Platform Standard Edition 8 Documentation . Oracle Corporation . 2016年4月29日時点のオリジナルよりアーカイブ 。2016年5月1日閲覧。
^ “java.util.Collections ”. Java Platform Standard Edition 8 Documentation . Oracle Corporation . 2016年4月23日時点のオリジナルよりアーカイブ 。2016年5月1日閲覧。
^ “List<T>.BinarySearch method (T) ”. Microsoft Developer Network . 2016年5月7日時点のオリジナルよりアーカイブ 。2016年4月10日閲覧。
^ “NSArray ”. Mac Developer Library . Apple Inc. . 2016年4月17日時点のオリジナルよりアーカイブ 。2016年5月1日閲覧。
^ “CFArray ”. Mac Developer Library . Apple Inc. . 2016年4月20日時点のオリジナルよりアーカイブ 。2016年5月1日閲覧。
^ “8.6. bisect — Array bisection algorithm ”. The Python Standard Library . Python Software Foundation. 2018年3月25日時点のオリジナルよりアーカイブ 。2018年3月26日閲覧。
^ “Primitive Type slice
”. The Rust Standard Library . The Rust Foundation (2024年). 2024年5月25日閲覧。
参考文献
Bentley, Jon (2000). Programming pearls (2nd ed.). Addison-Wesley. ISBN 978-0-201-65788-3
Butterfield, Andrew; Ngondi, Gerard E. (2016). A dictionary of computer science (7th ed.). Oxford, UK: Oxford University Press. ISBN 978-0-19-968897-5
Chang, Shi-Kuo (2003). Data structures and algorithms . Software Engineering and Knowledge Engineering. 13 . Singapore: World Scientific. ISBN 978-981-238-348-8
Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L. ; Stein, Clifford (2009). Introduction to algorithms (3rd ed.). MIT Press and McGraw-Hill. ISBN 978-0-262-03384-8
Fitzgerald, Michael (2015). Ruby pocket reference . Sebastopol, California: O'Reilly Media. ISBN 978-1-4919-2601-7
Goldman, Sally A.; Goldman, Kenneth J. (2008). A practical guide to data structures and algorithms using Java . Boca Raton, Florida: CRC Press. ISBN 978-1-58488-455-2
Kasahara, Masahiro; Morishita, Shinichi (2006). Large-scale genome sequence processing . London, UK: Imperial College Press. ISBN 978-1-86094-635-6
Knuth, Donald (1997). Fundamental algorithms . The Art of Computer Programming. 1 (3rd ed.). Reading, MA: Addison-Wesley Professional. ISBN 978-0-201-89683-1
Knuth, Donald (1998). Sorting and searching . The Art of Computer Programming. 3 (2nd ed.). Reading, MA: Addison-Wesley Professional. ISBN 978-0-201-89685-5
Knuth, Donald (2011). Combinatorial algorithms . The Art of Computer Programming. 4A (1st ed.). Reading, MA: Addison-Wesley Professional. ISBN 978-0-201-03804-0
Moffat, Alistair; Turpin, Andrew (2002). Compression and coding algorithms . Hamburg, Germany: Kluwer Academic Publishers. doi :10.1007/978-1-4615-0935-6 . ISBN 978-0-7923-7668-2
Sedgewick, Robert; Wayne, Kevin (2011). Algorithms (4th ed.). Upper Saddle River, New Jersey: Addison-Wesley Professional. ISBN 978-0-321-57351-3 . http://algs4.cs.princeton.edu/home/ Condensed web version ; book version .
Stroustrup, Bjarne (2013). The C++ programming language (4th ed.). Upper Saddle River, New Jersey: Addison-Wesley Professional. ISBN 978-0-321-56384-2
外部リンク