Скачиваний:
87
Добавлен:
01.05.2014
Размер:
3.82 Mб
Скачать

Original pages: 483-535 261

Теорема B. При 2k−1 N < 2k удачный поиск, использующий алгоритм B, требует (min1; maxk)срав-

нений. Неудачный поиск требует k сравнений при N = 2k − 1 либо k − 1 или k сравнений при

2k−1 N < 2k − 1.

Дальнейший анализ бинарного поиска. [Рекомендуем не интересующимся математикой перейти сразу к соотношениям (4).] Представление в виде дерева позволяет легко подсчитать среднее число сравнений. Через CN обозначим среднее число сравнений при удачном поиске в предположении, что каждый из N ключей с равной вероятностью является аргументом поиска. Среднее число сравнений CN0 соответствует неудачному поиску; предполагается, что все интервалы (их N + 1) между ключами и вне крайних значений равновероятны. Имеем по определению длин внутреннего и внешнего пути

CN

= 1 +

Длина внутреннего пути дерева

;

N

 

 

 

 

 

C

0

=

Длина внешнего пути дерева

:

 

 

N

 

 

N + 1

 

 

 

 

 

 

Из формулы (2.3.4.5-3) видно, что длина внешнего пути на 2N больше длины внутреннего пути; отсюда следует довольно неожиданное соотношение между CN и CN0

CN = 1 +

1

CN0 − 1:

(2)

N

Эта формула, полученная Хиббардом [JACM, 9 (1962), 16–17], справедлива для всех методов поиска, соответствующих бинарным деревьям, т. е. для всех методов, не содержащих лишних сравнений. Дисперсия CN также может быть выражена через дисперсию CN0 (см. упр. 25).

Из приведенных формул видно, что ”наилучшему” способу поиска путем сравнений соответствует дерево с минимальной длиной внешнего пути среди всех бинарных деревьев, содержащих N внутренних узлов. К счастью, можно доказать, что алгоритм B оптимален в этом смысле, так как бинарное дерево имеет минимальную длину пути тогда и только тогда, когда все внешние узлы находятся на одном или двух соседних уровнях. (См. упр. 5.3.1-20.) Следовательно, длина внешнего пути бинарного дерева, соответствующего алгоритму B, равна

(N + 1)(blog2 Nc + 2) − 2blog2 Nc+1:

(3)

(См. (5.3.1-33).) Используя (3) и (2), можно точно вычислить среднее число сравнений, если предположить, что все аргументы поиска равновероятны:

N

=

1 2 3

4

5 6 7

8 9

 

10

11

12

13

14

15

16

CN = 1 121 132 2

251 262 273 285 297

2

9

3

 

3

1

3

2

3

3

3154

3

6

10

12

13

14

16

CN0

= 1 132 2

252 264 276 3

392 3

4

3

6

3

8

31310

31412

31514

4

4

2

10

11

12

17

В общем случае, если k = blog2 Nc, имеем (ср. с (5.3.1-34))

 

 

k+1

− k − 2)=N

= log2 N − 1 + " + (k + 2)=N;

 

CN

= k + 1

− (2k

 

(4)

CN0

= k + 2

− 2

+1=(N + 1)

= log2 N + "0;

 

где 0 ", "0 < 0:0861.

Итак, алгоритм B требует максимум blog2 Nc+1 сравнений; среднее число сравнений при удачном поиске приближенно равно log2 N − 1. Ни один метод, основанный на сравнении ключей, не может дать лучших результатов. Среднее время работы программы В составляет примерно

(18 log2 N − 15)u для удачного поиска;

(5)

(18 log2 N + 13)u для неудачного поиска

(предполагается, что все исходы поиска равновероятны).

Одна важная модификация. Соблазнительно вместо трех указателей l, i, uиспользовать лишь два: текущее положение i и величину его изменения ; после каждого сравнения, не давшего равенства, мы могли бы установить i i и =2 (приблизительно). Этот путь реализуем, но он требует особой аккуратности в деталях, как в приведенном ниже алгоритме; более простые подходы обречены на неудачу!

Алгоритм U. (Однородный бинарный поиск.) Алгоритм служит для отыскания аргумента K в таблице записей R1, R2, : : : , RN, ключи которых расположены в возрастающем порядке: K1 < K2 <

262Original pages: 483-535

: : : < KN. При четном N иногда происходит обращение к фиктивному ключу K0, который необходимо

установить равным −1 (или любой величине, меньшей K. Предполагается, что N 1.

U1

[Начальная установка.] Установить i dN=2e, m bN=2c.

U2

[Сравнение.] Если K < Ki, то перейти на U3; если K > Ki, то перейти на U4; при K = Ki алгоритм

 

оканчивается удачно.

U3

[Уменьшение i.] (Мы определили положение интервала, где нужно продолжать поиск. Он содер-

 

жит m или m − 1 записей; i указывает на первый элемент справа от интервала.) Если m = 0, то

 

алгоритм оканчивается неудачно. В противном случае установить i i − dm=2e; m bm=2c и

 

вернуться на U2.

U4

[Увеличение i.] (Ситуация та же, что и в шаге U3, только i указывает на первый элемент слева

 

от интервала.) Если m = 0, то алгоритм оканчивается неудачно. В противном случае установить

 

i i + dm=2e; m bm=2c и вернуться на U2.

 

 

На рис. 6 представлено бинарное дерево, соответствующее поиску при N = 10. При неудачном поиске как раз перед окончанием работы алгоритма может производиться лишнее сравнение; узлы, отвечающие этим сравнениям, заштрихованы. Данный процесс поиска можно назвать однородным, так как разность между числом в узле уровня ` и числом в узле-предшественнике уровня ` − 1 есть постоянная величина для всех узлов уровня `. Обосновать правильность алгоритма U можно следующим образом. Предположим, что поиск нужно произвести в интервале

Picture: Рис. 6. Бинарное дерево для ”однородного” бинарного поиска (N = 10).

длины n − 1; сравнение со средним элементом (если n четно) или с одним из двух средних (если n

нечетно)

выделяет два интервала длины

b

n=

2c−1

и n=

2e−k 1

. После повторения этой процедуры k раз

k

 

d

 

 

− 1 и максимальной длиной n=2

k

− 1.

мы получим 2 интервалов с минимальной длиной

 

n=2

 

Следовательно, на каждом этапе длины двух

интервалов различаются самое большее на 1, что делает

 

 

 

 

 

 

 

 

возможным выбор подходящего ”среднего” элемента без запоминания последовательности точных значений длин.

Важное преимущество алгоритма U состоит в том, что нам совсем не нужно сохранять значение m; нужно лишь ссылаться на коротенькую таблицу значений для каждого уровня. Таким образом, алгоритм сводится к следующей процедуре, одинаково хорошей и для двоичных, и для десятичных ЭВМ.

Алгоритм C. (Однородный бинарный поиск.) Алгоритм аналогичен алгоритму U, но вместо вычислений, относящихся к m, использует вспомогательную таблицу величин

 

DELTA[j] =

N

+ 2

j

1

=

N

округленное;

 

 

 

 

 

1 j blog2 Nc + 2:

(6)

 

 

 

 

 

 

 

2j

 

 

 

2j

C1

[Начальная установка.] Установить i

 

DELTA[1], j 2.

 

 

C2

[Сравнение:] Если K < Ki, то перейти на C3; если K > Ki, то перейти на C4. При K = Ki алгоритм

 

оканчивается удачно.

 

 

 

 

 

 

 

 

 

 

 

 

C3

[Уменьшение i.] Если DELTA[j] = 0, то алгоритм оканчивается неудачно. В противном случае

 

установить i i − DELTA[j], j

 

 

j + 1 и вернуться на C2.

 

 

C4

[Увеличение i.] Если DELTA[j] = 0, алгоритм оканчивается неудачно. В противном случае устано-

 

вить i i + DELTA[j], j

j + 1 и вернуться на C2.

 

 

 

 

 

 

 

 

В упр. 8 будет показано, что алгоритм ссылается на вспомогательный ключ K0 = −1 лишь при четных N.

Программа C. (Однородный бинарный поиск.) Эта программа на базе алгоритма C проделывает ту же работу, что и программа B. Используются rA K, rI1 i, rI2 j, rI3 DELTA[j].

START

ENT1

N+1/2

1

C1.

Начальная установка.

 

ENT2

2

1

j

2.

 

LDA

К

1

 

 

 

JMP

2F

1

 

 

3H

JE

SUCCESS

C1

Переход, если K = Ki.

 

J3Z

FAILURE

C1 − S

Переход, если DELTA[j] = 0.

 

DEC1

0,3

C1 − S − A C3.

Уменьшение i.

5H

INC2

1

C − 1

j

j + 1.

Original pages: 483-535 263

2H

LD3

DELTA,2

C

C2. Сравнение.

 

 

СМРА

KEY.1

C

Переход, если K Ki.

 

 

JLE

3B

C

 

 

INC1

0,3

C2

C4. Увеличение i.

 

 

J3NZ

5B

C2

Переход, если DELTA[j] 6= 0.

FAILURE EQU

*

1 − S

Выход, если нет в таблице.

 

 

 

 

 

 

При удачном поиске этот алгоритм соответствует бинарному дереву с той же длиной внутреннего пути, что и алгоритм В, поэтому среднее число сравнений CN дается формулой (4). При неудачном поиске алгоритм C всегда совершает ровно blog2 Nc+1 сравнений. Полное время работы программы C не вполне симметрично по отношению к левым и правым ветвям, так как C1 имеет вес, больший чем C2, но в упр. 9 будет показано, что случай K > Ki встречается примерно так же часто, как и K < Ki; следовательно, программа C требует приблизительно

(8:5 log2 N − 6)u

на удачный поиск;

(7)

(8:5blog2 Nc + 12)u на неудачный поиск.

 

Это более чем в два раза быстрее программы B, причем не используются специальные свойства двоичных ЭВМ, в то время как в формуле (5) предполагается, что MIX имеет команду ”двоичный сдвиг вправо”.

Другая модификация бинарного поиска, предложенная в 1971 г. Л. Э. Шером, на некоторых ЭВМ допускает еще более быструю реализацию, так как она однородна после первого

Picture: Рис. 7. Бинарное дерево для почта однородного поиска Шера (N = 10).

 

 

шага и не требует вспомогательной таблицы. Сначала мы сравниваем

K

и

K

, где i

 

k, k

= blog2

N .

 

i

 

k

 

= 2k

 

c

Если K < Ki, мы используем однородный поиск с последовательностью = 2

 

−1, 2

 

−2, : : :, 1, 0. С

другой стороны, при K > Ki и N > 2k устанавливаем i = i0 = N + 1 − 2`, где ` =

 

log2(N `− 2k)` + 1, и,

делая вид, что первым сравнением было K > Ki0, используем однородный поиск с = 2 −1, 2 −2, : : : ,

1, 0.

Рисунок 7 иллюстрирует метод Шера при N = 10. В методе Шера никогда не требуется более blog2 Nc+1 сравнений; следовательно, он дает одно из лучших средних чисел сравнения, несмотря на то что иногда проходит через несколько последовательных избыточных шагов (ср. с упр. 12).

Еще одна модификация бинарного поиска, улучшающая все рассмотренные методы при очень больших N, обсуждается в упр. 23. В упр. 24 изложен еще более быстрый метод!

Фибоначчиев поиск. При рассмотрении многофазного слияния (п. 5.4.2) мы видели, что числа Фибоначчи могут играть роль, аналогичную степеням 2. Похожее явление имеет место и в случае поиска, когда с помощью чисел Фибоначчи создаются методы, способные соперничать с бинарным поиском. Предлагаемый метод для некоторых ЭВМ предпочтительнее бинарного, так как он включает лишь сложение и вычитание; нет необходимости в делении на 2. Следует отличать процедуру, которую мы собираемся обсуждать, от известной численной процедуры ”фибоначчиева поиска”, которая используется для нахождения максимума одновершинной функции [ср. с Fibonacci Quarterly, 4 (1966), 265–269]; схожесть названий ведет к некоторой путанице.

На первый взгляд фибоначчиев поиск кажется весьма таинственным; если просто взять программу и попытаться объяснить ее работу, то создастся впечатление, что она работает с помощью магии. Но туман таинственности рассеется, как только мы построим

Picture:

Рис. 8. Дерево Фибоначчи порядка 6.

соответствующее дерево поиска. Поэтому изучение рассматриваемого метода начнем с рассказа о ”фибоначчиевых деревьях”.

На рис. 8 изображено дерево Фибоначчи порядка 6. Заметьте, что оно несколько больше напоминает реальный куст, чем рассматривавшиеся ранее деревья, возможно, потому, что многие природные процессы удовлетворяют закону Фибоначчи. Вообще фибоначчиево дерево порядка k имеет Fk+1 − 1 внутренних (круглых) узлов и Fk+1 внешних (квадратных) узлов; оно строится следующим образом:

Если k = 0 или k = 1, дерево сводится просто к 0 .

Если k 2, корнем является (Fk); левое поддерево есть дерево Фибоначчи порядка k−1; правое поддерево есть дерево Фибоначчи порядка k − 2 с числами в узлах, увеличенными на Fk.

264Original pages: 483-535

Заметим, что, за исключением внешних узлов, числа, соответствующие преемникам каждого внутреннего узла, отличаются от числа в этом узле на одну и ту же величину, а именно на число Фибо-

наччи. Так, 5 = 8 − F4, 11 = 8 + F4 (рис. 8). Если разность была равна Fj, то на следующем уровне соответствующая разность составит для левой ветви Fj−1, а для правой Fj−2. Так, например, 3 = 5−F3, a 10 = 11 − F2.

Эти наблюдения в совокупности с подходящим механизмом распознавания внешних узлов дают

 

Алгоритм F. (Фибоначчиев поиск.) Алгоритм предназначается для поиска аргумента K в таблице

записей R1, R2, : : : , RN, расположенных в порядке возрастания ключей K1 < K2 < : : : < KN.

 

 

Для удобства описания предполагается, что N + 1 есть число Фибоначчи Fk+1. Подходящей на-

чальной установкой данный метод можно сделать пригодным для любого N (см. упр. 14).

 

F1

[Начальная установка.] Установитьi Fk, p Fk−1, q Fk−2. (В этом алгоритме pи q обозначают

 

последовательные числа Фибоначчи.)

 

F2

[Сравнение.] Если K < Ki, то перейти на F3; если K > Ki, то перейти на F4; если K = Ki,

 

алгоритм заканчивается удачно.

i−q,

F3

[Уменьшение i.] Если q = 0, алгоритм заканчивается неудачно. Если q 6= 0, то установить i

 

заменить (p; q) на (q; p − q) и вернуться на F2.

 

F4

[Увеличение i.] Если p = 1, алгоритм заканчивается неудачно. Если p 6= 1, установить i

i + q,

pp − q, q q − p и вернуться на F2.

Вприводимой ниже реализации алгоритма F для машины MIX скорость увеличивается за счет дублирования внутреннего цикла, в одном из экземпляров которого p хранится в rI2, а q—в rI3,

вдругом же регистры меняются ролями; это упрощает шаг F3. На самом деле программа хранит в регистрах p − 1 и q − 1, что упрощает проверку ”p = 1?” в шаге F4.

Программа F. (Фибоначчиев поиск.) Значения регистров: rA K, rI1 i, (rI2; rI3) p − l,

(rI3; rI2) q − l.

START LDA

K

1

F1. Начальная установка.

ENT1

Fk

1

i

Fk.

ENT2

Fk−1 − 1 1

p

Fk−1.

ENT3

Fk−2 − 1 1

q

Fk−2.

 

 

JMP

F2A

1 На F2.

 

 

 

 

 

 

F4A

INC1

1,3

F4B

INC1

1,2

C2

− S − A F4.

Увеличение i. i

i + q.

 

 

DEC2

1,3

 

DEC3

1,2

G2

− S − A p

p − q.

 

 

 

DEC3

1,2

 

DEC2

1,3

G2

− S − A q

q − p.

 

F2A

CMPA

KEY, 1

F2В

CMPA

KEY,1

 

G

F2.

Сравнение.

 

 

 

JL

F3A

 

JL

F3B

 

G

На FЗ, если K < Ki.

 

 

 

JE

SUCCESS

 

JE

SUCCESS

 

G2

Выход, если K = Ki.

 

 

 

J2NZ

F4A

 

J3NZ

F4B

C2 − S

На F4, если p 6= 1.

 

 

 

JMP

FAILURE

 

JMP

FAILURE

 

A

Выход, если нет в таблице.

F3A

DEC1

1,3

F3B

DEC1

1,2

 

C1

F3.

Уменьшение i. i

i − q.

 

 

DEC2

1,3

 

DEC3

1,2

 

C1

p

p − q.

 

 

 

J3NN

F2B

 

J2NN

F2A

 

C1

Смена регистров, если q > 0.

 

 

JMP

FAILURE

 

JMP

FAILURE

1 − S − A

Выход, если нет в таблице.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

В упр. 18 анализируется время работы этой программы. Рисунок 8 показывает, а анализ доказывает, что влево мы идем несколько чаще, чем вправо. Через C, C1 и C2 − S обозначим число выполнений шагов F2, F3 и F4 соответственно. Имеем

C = (ave k=sqrt5 + O(1); maxk − 1);

 

 

p

 

 

 

 

 

 

 

 

C1 = (avek=

5 + O(1); maxk − 1);

(8)

C2

 

1

 

 

p

 

 

 

k=2 ):

 

 

 

 

 

S = (ave

k= 5 + O(1); max

b

 

 

 

 

 

 

 

 

c

Значит, левая ветвь выбирается примерно в = 1:618 раза чаще правой (это можно было предвидеть, так как каждая проба делит рассматриваемый интервал на две части, причем левая часть примерно

Original pages: 483-535 265

в раз длиннее правой). Среднее время работы программы F составляет примерно

 

p

 

 

 

для удачного поиска;

 

(6 k=

 

 

5 − (2 + 22 )=5)u (6:252 log2

N − 4:6)u

(9)

p

 

 

 

для неудачного поиска.

 

(6 k=

 

5 + (58=(27 ))=5)u (6:252 log2

N + 5:8)u

 

Это несколько лучше, чем (7), хотя в наихудшем случае программа F работает примерно (8:6 log2 N)u, т.е. чуть-чуть медленнее программы С.

Интерполяционный поиск. Забудем на минуту о вычислительных машинах и проанализируем, как производит поиск человек. Иногда повседневная жизнь подсказывает путь к созданию хороших алгоритмов.

Представьте, что вы ищете слово в словаре. Маловероятно, что вы сначала заглянете в середину словаря, затем отступите от начала на 1=4 или 3=4 и т. д., как в бинарном поиске, и уж совсем невероятно, что вы воспользуетесь фибоначчиевым поиском!

Если нужное слово начинается с буквы A, вы, по-видимому, начнете поиск где-то в начале словаря. Во многих словарях имеются ”побуквенные высечки” для большого пальца, которые показывают страницу, где начинаются слова на данную букву. Такую пальцевую технику легко приспособить к ЭВМ, что ускорит поиск; соответствующие алгоритмы исследуются в x 6.3.

Когда найдена отправная точка для поиска, ваши дальнейшие действия мало похожи на рассмотренные методы. Если вы заметите, что нужное слово должно находиться гораздо дальше открытой страницы, вы пропустите порядочное их количество, прежде чем сделать следующую попытку. Это в корне отличается от предыдущих алгоритмов, которые не делают различия между ”много больше” и ”чуть больше”.

Мы приходим к такому алгоритму, называемому ”интерполяционным поиском”: если известно, что K лежит между Kl и Ku, то следующую пробу делаем примерно на расстоянии (u − l)(K − Kl)=(Ku − Kl) от l, предполагая, что ключи являются числами, возрастающими приблизительно как арифметическая прогрессия.

Интерполяционный поиск асимптотически предпочтительнее бинарного; по существу, один шаг

бинарного поиска уменьшает количество ”подозреваемых” записей с n до 1n, а один шаг интерполя-

2 p

ционного (если ключи в таблице распределены случайным образом)—с n до n. Можно показать, что интерполяционный поиск требует в среднем около log2 log2 N шагов (упр. 22).

К сожалению, эксперименты на ЭВМ показали, что интерполяционный поиск уменьшает число сравнений не настолько, чтобы компенсировать возникающий дополнительный расход времени, когда таблица, в которой производится поиск, хранится во внутренней (”быстрой”) памяти. Разность между log2 log2 N и log2 N становится существенной лишь для весьма больших N, а типичные файлы недостаточно случайны. Интерполяция успешна до некоторой степени лишь в применении к поиску на внешних запоминающих устройствах. (Заметим, что ручной просмотр словаря есть, в сущности, не внутренний, а внешний поиск; это является темой последующих рассмотрений.)

История и библиография. Первым известным примером длинного перечня элементов, упорядоченных для облегчения поиска, является знаменитая вавилонская таблица обратных величин Inakibit-Anu, датируемая примерно 200 г. до н.э. Эта глиняная пластинка, очевидно, открывала серию из трех табличек, содержащих более 500 многозначных шестидесятеричных чисел и обратных им величин, отсортированных в лексикографическом порядке. Например, встречается такая последовательность:

01

13

09

34

29

08

08

53

20

49

12

27

 

 

 

 

 

 

01

13

14

31

52

30

 

 

 

49

09

07

12

 

 

 

 

 

01

13

43

40

48

 

 

 

 

48

49

41

15

 

 

 

 

 

01

13

48

40

30

 

 

 

 

48

46

22

59

25

25

55

33

20

01

14

04

26

40

 

 

 

 

48

36

 

 

 

 

 

 

 

Сортировка 500 подобных чисел средствами тех времен кажется феноменальной. [См. также D. Е. Knuth, CACM, 15 (1972), 671–677, где содержится много дополнительных сведений.]

Довольно естественно располагать по порядку числа, но отношение порядка между буквами или словами не представляется само собой очевидным. Однако последовательности для сравнивания букв присутствовали уже в наиболее древних алфавитах. Например, многие библейские псалмы содержат стихи, следующие друг за другом в строго алфавитном порядке: первый стих начинается с алефа, второй с бета и т. д.; это облегчало запоминание. Иногда стандартная последовательность букв использовалась семитами и греками для обозначения чисел; например, , , γ обозначали 1, 2, 3 соответственно.

266 Original pages: 483-535

Однако использование алфавитного порядка для слов целиком было, вероятно, гораздо более поздним изобретением; вещи, очевидные сейчас даже для ребенка, когда-то требовалось объяснять взрослым! Некоторые документы, датируемые примерно 300 г. до н.э., найденные на островах Эгейского моря, содержат списки членов нескольких религиозных общин; они упорядочены, но только по первой букве, т. е. представляют собой результат лишь первого прохода слева направо поразрядной сортировки. Греческие папирусы 134–135 г. н. э. содержат фрагменты счетов, в которых фамилии налогоплательщиков упорядочены по первым двум буквам. Аполлоний Софист2использовал алфавитное упорядочение по первым двум, а часто и по последующим буквам в своем ”Словаре гомеровских слов” (I век н. э.). Известно несколько примеров более совершенного алфавитного упорядочения, скажем выдающиеся ”Комментарии к Гиппократу” Галена3(около 200 г.), но это было очень редким явлением. Так, в хронике Св. Исидора4”Etymologiarum” (около 630 г., книга X) слова упорядочены лишь по первой букве, а в наиболее раннем из известных двуязычных словарей ”Corpus Glossary” (около 725 г.) — только по двум первым. Две последние работы были, вероятно, крупнейшими нечисловыми файлами данных, скомпилированными в средние века.

Только в книге Джованни Генуэзского ”Catholicon” (1286 г.) мы находим подробное описание правильного алфавитного порядка. В предисловии Джованни объясняет, что

amo

предшествует bibo

abeo

предшествует adeo

amatus

предшествует amor

imprudens

предшествует impudens

iusticia

предшествует iustus

polisintheton

предшествует polissenus

(т. е. приводит примеры ситуаций, когда порядок определяется по 1, 2, : : : , 6-й буквам) ”и далее аналогично”. Он замечает, что открытие этого правила потребовало значительных усилий. ”Я прошу Вас, уважаемый читатель, не презирать эту мою большую работу и этот порядок как нечто ничего не стоящее”. Развитие алфавитного порядка до момента изобретения книгопечатания подробно изучил Л. У. Дейли (Collection Latomus, 90 (1967), 100 pp.). Он обнаружил несколько интересных старинных рукописей, которые, несомненно, использовались как черновики при сортировке слов по первой букве (см. стр. 87–90 его монографии).

Первый словарь английского языка Роберта Кодри ”Table Alphabeticall” (London, 1604) содержит следующие инструкции:

Если слово, которое тебе нужно найти, начинается с (a), смотри в начало этой книги, а если с (v)—то в конец. Опять если слово начинается с (ca), смотри в начало буквы (c), а если с (cu) то в конец. И так до конца слова.

Интересно заметить, что Кодри во время подготовки словаря, вероятно, сам учился расставлять слова в алфавитном порядке; на нескольких первых страницах встречается много неправильно стоящих слов, зато дальше число ошибок существенно уменьшается.

Бинарный поиск впервые упомянут Джоном Мочли в дискуссии, которая была, пожалуй, первым опубликованным обсуждением методов нечисленного программирования [см. Theory and techniques for the design of electronic digital computers, ed. by G. W. Patterson, 3 (1946); 22.8–22.9]. В течение 50- х годов метод становится ”хорошо известным”, но, кажется, никто не разрабатывал детали алгоритма для N 6= 2n − 1. [См. A. D. Booth, Nature, 176 (1955), 565; A. I. Dumey, Computers and Automation, 5 (December, 1956), 7, где бинарный поиск имеет название ”Двадцать вопросов”; D. D. McCracken, Digital Computer Programming (Wiley, 1957), 201–203; M. Halpern, CACM 1, 2 (February, 1958), 1–3.]

По-видимому, алгоритм бинарного поиска, пригодный для всех N, впервые опубликован Боттенбруком [JACM, 9 (1962), 214]. Он представил интересную модификацию алгоритма B, когда проверки на равенство отодвигаются в конец алгоритма. Используя в шаге B2 i d(l + u)=2e вместо b(l + u)=2c, он устанавливает l i при K Ki; тогда u−l уменьшается после каждого шага. В конце, когда l = u, имеем Kl K < Kl+1, и можно проверить, был ли поиск удачным, произведя еще одно сравнение. (Он предполагал, что первоначально K K1.) Эта идея позволяет несколько ускорить внутренний цикл на многих ЭВМ; то же верно и для всех обсуждавшихся в данном пункте алгоритмов, однако такое изменение оправдано лишь для больших N (см. упр. 23).

К. Э. Айверсон [A Programming Language (Wiley, 1962), 141] привел процедуру алгоритма B, но без рассмотрения возможности неудачного поиска. Д. Кнут (CACM, 6 (1963), 556–558) представил

2 Один из грамматиков древности, родом из Александрии.—Прим. перев.

3 Римский врач и естествоиспытатель, классик античной медицины.—Прим. перев.

4 Исидор Севильский—испанский епископ, выдающийся ученый и писатель.—Прим. перев.

Original pages: 483-535 267

алгоритм B как пример использования автоматической системы построения блок-схем. Однородный бинарный поиск (алгоритм C) предложил автору в 1971 г. А. К. Чандра (Стандфордский университет).

Фибоначчиев поиск изобретен Д. Фергюсоном [CACM, 3 (1960), 648], но его блок-схема и анализ были некорректны. Дерево Фибоначчи (без меток) появилось гораздо раньше как курьез в первом издании популярной книги Гуго Штейнгауза ”Математический калейдоскоп” (М., Гостехиздат, 1949) на стр. 34; оно было нарисовано корнем вниз и выглядело, как обычное дерево; правые ветви были сделаны в два раза длиннее левых, так что все листья оказались на одном уровне.

Интерполяционный поиск предложен У. У. Петерсоном [IBM J. Res. & Devel., 1 (1957), 131– 132]. Он дал теоретическую оценку среднего числа сравнений для случая, когда ключи случайно выбираются из равномерно распределенной последовательности, однако эти оценки расходятся с экспериментальными данными.

 

Упражнения

>1.

[21] Докажите, что если u < l в шаге B2 бинарного поиска, то u = l − 1 и Ku < K < Kl. (Будем

 

считать, что K0 = −1 и KN+1 = 1, хотя алгоритм не использует эти добавочные ключи, т. е. они

 

не должны присутствовать в таблице.)

>2.

[22] Будет ли алгоритм B работать правильно, если (a) заменить в шаге B5 ”l i + 1” на ”l i”?

 

(b) Заменить в шаге B4 ”u i − 1” на ”u i”? (c) Внести оба изменения?

3.

[15] Какой метод поиска соответствует дереву

Picture: Рис. стр. 500

Каково среднее число сравнений при удачном поиске? при неудачном?

4.[20] Если поиск, производимый программой S из x6.1 (последовательный поиск), требует 638 единиц времени, то сколько будет работать программа B (бинарный поиск)?

5.[М24] При каких N программа B в среднем медленнее последовательного поиска (программа 6:1Q0), если предполагается, что поиск удачен?

6.[28] (К. Э. Айверсон) В силу упр. 5 лучше всего было бы иметь некий ”гибридный” метод, переходящий от бинарного поиска к последовательному, когда длина остающегося интервала меньше

 

некоторой разумно выбранной величины. Напишите эффективную программу для MIX и опреде-

 

лите наилучший момент смены методов поиска.

>7.

[M22] Будет ли алгоритм U работать правильно, если мы изменим шаг U1 так, что (a) как i, так и m

 

устанавливаются равными bN=2c; (b) как i, так и m устанавливаются равными dN=2e? [Указание:

 

предположим, что первый шаг выглядел бы так: ”Установить i 0, m N (или N + 1), перейти

 

на U4”.]

8.

[М20] (a) Чему равна сумма 0 jblog2 Nc+2 DELTA[j] приращений в алгоритме C? (b) Каковы ми-

 

значения i, которые могут появиться в шаге C2?

 

нимальное и максимальное P

9.[М26] Проведите частотный анализ для программы C и найдите точную зависимость средних значений C1, C2 и A от N и S.

10.[20] Существуют ли N > 1, при которых алгоритмы В и С абсолютно эквивалентны в том смысле, что они совершают одинаковые, последовательности сравнений при всех аргументах поиска?

11.[21] Объясните, как написать MIX-программу для алгоритма C, содержащую примерно7 log2 N команд, время работы которой составит приблизительно 4:5 log2 N единиц?

12.[20] Нарисуйте дерево бинарного поиска, соответствующее методу Шера при N = 12.

13.[М24] Затабулируйте средние числа сравнений в методе Шера для 1 N < 16, рассматривая как удачные, так и неудачные поиски.

14.[21] Объясните, как приспособить алгоритм F для работы с любым N 1.

15.[21] На рис. 9 изображена диаграмма размножения кроликов в исходной задаче Фибоначчи (ср. с п. 1.2.8). Существует ли простая связь между этой диаграммой и фибоначчиевым деревом решений?

16.[М19] При каких значениях k фибоначчиево дерево порядка k определяет оптимальную процедуру поиска, т. е. такую, при которой среднее число сравнений минимально?

17.[М21] Из упр. 1.2.8-34 (или упр. 5.4.2-10) мы знаем, что любое натуральное число nединственным

образом представляется в виде сумму чисел Фибоначчи: n = Fa1 + Fa2 + + Far , где r 1, aj aj+1 + 2 при 1 j < r и ar 2. Докажите, что в фибоначчиевом дереве порядка k путь от корня до узла (n) имеет длину k + 1 − r − ar.

Picture: Рис. 9. Пары кроликов, размножающиеся по правилу Фибоначчи.

268 Original pages: 483-535

18. [М30] Проведите частотный анализ для программы F и найдите точные формулы для средних значений C1, C2 и A как функций от k, Fk, Fk+1 и S.

19. [М42] Проведите детальный анализ среднего времени работы алгоритма, предложенного в упр. 14.

20. [М22] Число сравнений, требующихся при бинарном поиске, приближенно равно log N, при p 2

фибоначчиевом—( = 5)log N. Цель этого упражнения—показать, что эти формулы являются частными случаями более общего результата.

Пусть p и q—положительные числа и p + q = 1. Рассмотрим алгоритм поиска по таблице из N записей с возрастающими ключами, который, начиная со сравнения аргумента с (pN)-м ключом, повторяет эту процедуру для меньших блоков. (Для бинарного поиска p = q = 1=2; для фибоначчиева p = 1= , q = 1= 2.)

Обозначим среднее число сравнений, требуемых для поиска в таблице размера N, через C(N); оно приближенно удовлетворяет соотношениям

C(1) = 0; C(N) = 1 + pC(pN) + qC(qN) для N > 1.

Это происходит потому, что после первого сравнения поиск примерно с вероятностью p сводится к поиску среди pN элементов и с вероятностью q—к поиску среди qN элементов. При больших N можно пренебречь эффектом низшего порядка, связанным с тем, что числа pN и qN не целые.

a)Покажите, что C(N) = logb N точно удовлетворяет указанным соотношениям при некотором b. Для бинарного и фибоначчиева поисков величина b получается из выведенных ранее формул.

b)Некто рассуждал так: ”С вероятностью p длина рассматриваемого интервала делится на 1=p, с вероятностью q—на 1=q. Следовательно интервал делится в среднем на p(1=p)+q(1=q) = 2, так что алгоритм в точности так же хорош, как и бинарный поиск, независимо от p и q”. Есть ли ошибка в этих рассуждениях?

21.[20] Нарисуйте бинарное дерево, соответствующее интерполяционному поиску при N = 10.

22.[М43] (Э. К. Яо и Ф. Ф. Яо.) Покажите,, что должным образом оформленный интерполяционный

поиск в среднем требует (асимптотически) log2 log2 N сравнений, если N отсортированных ключей имеют независимые равномерные распределения. Более того, все алгоритмы поиска по таким таблицам должны совершать в среднем log2 log2 N сравнений (оценка асимптотическая).

>23. [25] Алгоритм бинарного поиска Г. Боттенбрука, упомянутый в конце пункта, ”откладывает” проверки на равенство до самого конца поиска. (Во время работы алгоритма мы знаем, что Kl K < Ku+1, проверка на равенство проводится лишь при l = u.) Такой трюк сделал бы программу B чуть быстрее при больших N, так как мы избавились бы от команды ”JF,” во внутреннем цикле. (Однако эта идея практически нереальна, так как log2 N обычно мал; лишь при N > 236 компенсируется необходимость дополнительной итерации!)

Покажите, что любой алгоритм поиска, соответствующий бинарному дереву и разветвляющийся по трем направлениям (<, =, или >), можно переделать в алгоритм, разветвляющийся во внутренних узлах лишь по двум направлениям (< или ). В частности, модифицируйте таким способом алгоритм C.

>24. [23] Полное бинарное дерево является удобным способом представления в последовательных ячейках дерева с минимальной длиной пути. (Ср. с п. 2.3.4.5.) Придумайте эффективный метод поиска, основанный на таком представлении. [Указание: можно ли в бинарном поиске использовать умножение на 2 вместо деления на 2?]

>25. [М25] Предположим, что у бинарного дерева имеется ak внутренних и bk внешних узлов на уровне k, k = 0, 1, : : :. (Корень находится на нулевом уровне.) Так, для рис. 8 имеем (a0; a1; : : : ; a6) = (1; 2; 4; 4; 1; 0)и (b0; b1; : : :; b6) = (0; 0; 0; 4; 7; 2). (a) Покажите, что существует простое алгебраическое

соотношение, связывающее производящие функции A(z) =

k akzk и B(z) =

k bkzk. (b) Распре-

деление вероятностей при удачном поиске по бинарному

дереву имеет производящую функцию

 

P

P

g(z) = zA(z)=N, а при неудачном поиске производящая функция есть h(z) = B(z)=(N + 1). (В обозначениях п. 6.2.1 CN = mean(g), CN0 = mean(h), а соотношение (2) связывает эти величины.) Найдите зависимость между var(g) и var(h).

26.[22] Покажите, что дерево Фибоначчи связано с сортировкой многофазным слиянием на трех лентах.

27.[M30] (X. С. Стоун и Дж. Лини.) Рассмотрим процесс поиска, основанный только на сравнениях ключей и использующий одновременно k процессоров. При каждом шаге поиска определяются k индексов i1, i2, : : :, ik и мы совершаем k одновременных сравнений: если K = Kj для некоторого j, поиск кончается удачно, в противном случае переходим к следующему шагу поиска, основываясь на 2k возможных исходах K < Kij или K > Kij , 1 j k.

Original pages: 483-535 269

Покажите, что такой процесс при N ! 1 должен совершать в среднем по крайней мере logk+1 N шагов. (Предполагается, что все ключи в таблице, также как и аргумент поиска, равновероятны.) (Значит, по сравнению с однопроцессорным бинарным поиском мы выигрываем в скорости не в k раз, как можно было бы ожидать, а лишь в log2(k + 1) раз. В этом смысле выгоднее каждый процессор использовать для отдельного поиска, а не объединять их.)

6.2.2.Поиск по бинарному дереву

Впредыдущем пункте мы видели, что использование неявной структуры бинарного дерева облегчает понимание бинарного и фибоначчиева поисков. Рассмотрение соответствующих деревьев позволило заключить, что при данном N среди всех методов поиска путем сравнения ключей бинарный поиск совершает минимальное число сравнений. Но методы предыдущего пункта предназначены главным образом для таблиц фиксированного размера, так как последовательное расположение записей делает вставки и удаления довольно трудоемкими. Если таблица динамически изменяется, то экономия от использования бинарного поиска не покроет затрат на поддержание упорядоченного расположения ключей.

Явное использование структуры бинарного дерева позволяет быстро вставлять и удалять записи

ипроизводить эффективный поиск по таблице. В результате мы получаем метод, полезный как для поиска, так и для сортировки. Такая гибкость достигается

Picture:

Рис. 10. Бинарное дерево поиска.

путем добавления в каждую запись двух полей для хранения ссылок.

Методы поиска по растущим таблицам, часто называют алгоритмами таблиц символов, так как ассемблеры, компиляторы и другие системные программы обычно используют их для хранения определяемых пользователем символов. Например, ключом записи в компиляторе может служить символический идентификатор, обозначающий переменную в некоторой программе на Фортране или Алголе, а остальные поля записи могут содержать информацию о типе переменной и ее расположении

впамяти. Или же ключом может быть символ программы для MIX, а оставшаяся часть записи может содержать эквивалент этого символа. Программы поиска с вставкой по дереву, которые будут описаны

вэтом пункте, отлично подходят для использования в качестве алгоритмов таблиц символов, особенно если желательно распечатывать символы в алфавитном порядке. Другие алгоритмы, таблиц символов описаны в x 6.3 и 6.4.

На рис. 10 изображено бинарное дерево поиска, содержащее названия одиннадцати знаков зодиака5. Если теперь, отправляясь от корня дерева, мы будем искать двенадцатое название, SAGITTARIUS, то увидим, что оно больше, чем CAPRICORN, поэтому нужно идти вправо; оно больше, чем PISCES,—снова идем вправо; оно меньше, чем TAURUS,—идем влево; оно меньше, чем SCORPIO,— и мы попадаем во внешний узел 8 . Поиск был неудачным; теперь по окончании поиска мы можем вставить SAGITTARIUS, ”подвязывая” его к дереву вместо внешнего узла 8 . Таким образом, таблица может расти без перемещения существующих записей. Рисунок 10 получен последовательной вставкой, начиная с пустого дерева, ключей CAPRICORN, AQUARIUS, PISCES, ARIES, TAURUS, GEMINI,

CANCER, LEO, VIRGO, LIBRA, SCORPIO в указанном порядке.

На рис. 10 все ключи левого поддерева корня предшествуют по алфавиту слову CAPRICORN, а в правом поддереве стоят после него. Аналогичное утверждение справедливо для левого и правого поддеревьев любого узла. Отсюда следует, что при обходе дерева в симметричном порядке ключи располагаются строго в алфавитном порядке:

AQUARIUS; ARIES; CANCER; CAPRICORN; GEMINI;

LEO; : : : ; VIRGO;

так как симметричный порядок основан на прохождении сначала левого поддерева каждого узла, затем самого узла, а затем его правого поддерева (ср. с п. 2.3.1).

Ниже дается подробное описание процесса поиска с вставкой.

Алгоритм Т. (Поиск с вставкой по дереву.) Дана таблица записей, образующих бинарное дерево. Производится поиск заданного аргумента K. Если его нет в таблице, то в подходящем месте в дерево вставляется новый узел, содержащий K.

5 Знаки зодиака, упорядоченные по месяцам: Козерог, Водолей, Рыбы, Овен, Телец, Близнецы, Рак, Лев, Дева, Весы, Скорпион, Стрелец,—Прим. перев.

270 Original pages: 483-535

Предполагается, что узлы дерева содержат по крайней мере следующие поля:

KEY(P) = ключ, хранящийся в узле NODE(P);

LLINK(P) = указатель на левое поддерево узла NODE(P);

RLINK(P) = указатель на правое поддерево узла NODE(P):

Пустые поддеревья (внешние узлы на рис. 10) представляются пустыми указателями . Переменная ROOT указывает на корень дерева. Для удобства предполагается, что дерево не пусто (ROOT 6= ), так как при ROOT = операции становятся тривиальными.

Т1 [Начальная установка.] Установить P ROOT. (Указатель P будет продвигаться вниз по дереву.) Т2 [Сравнение.] Если K < KEY(P), то перейти на Т3; если K > KEY(P), то перейти на Т4; если K =

KEY(P), поиск завершен удачно.

 

Т3 [Шаг влево.] Если LLINK(P) 6= , установить P

LLINK(P) и вернуться на Т2. В противном случае

перейти на Т5.

 

Т4 [Шаг вправо.] Если RLINK(P) 6= , установить P

RLINK(P) и вернуться на Т2.

Т5 [Вставка в дерево.] (Поиск неудачен; теперь мы поместим K в дерево.) Выполнить Q ( AVAIL; Q теперь указывает на новый узел. Установить KEY(Q) K, LLINK(Q) RLINK(Q) . (На самом деле нужно произвести начальную установку и других полей нового узла.) Если K было меньше KEY(P), установить LLINK(P) Q; в противном случае установить RLINK(P) Q. (В этот момент мы могли бы присвоить P Q и удачно завершить работу алгоритма.)

Алгоритм сам подсказывает реализацию на языке MIXAL. Предположим, например, что узлы дерева имеют следующую структуру:

Picture: Рис. стр. 505

Возможно, далее расположены дополнительные слова INFO. Как и в гл. 2, AVAIL есть список свободной памяти. Итак, получается следующая программа для MIX.

Программа Т. (Поиск с вставкой по дереву.) rA K, rI1 P, rI2 Q.

LLINK

EQU

2:3

 

 

 

 

 

RLINK

EQU

4:5

 

 

 

 

 

START

LDA

К

1

Т1. Начальная установка.

 

 

LD1

ROOT

1

P

ROOT.

 

 

 

 

JMP

2F

1

 

 

 

 

4H

LD2

0,1(RLINK)

C2

T4. Шаг вправо. Q

RLlNK(P).

 

 

J2Z

5F

C2

На T5, если Q = .

 

1H

ENT1

0,2

C − l

P

Q.

 

 

2H

CMPA

1,1

C

T2. Сравнение.

 

 

 

JG

C

На T4, если K > KEY(P).

 

 

JE

SUCCESS

C1

Выход, если K = KEY(P).

 

 

LD2

0,1 (LLINK)

C1 − S

TЗ. Шаг влево. Q

LLINK(P).

 

 

J2NZ

1B

C1 − S

На T2, если Q 6= .

 

LD2

AVAIL

1 − S

T5 Вставки в дерево.

 

 

J2Z

OVERFLOW

1 − S

 

 

 

 

 

 

LDX

0,2(RLINK)

1 − S

Q ( AVAIL.

 

 

 

 

STX

AVAIL

1 − S

 

 

 

 

STA

1,2

1 − S

KEY(Q) K.

 

.

 

 

STZ

0,2

1 − S

LLINK(Q)

RLINK(Q)

 

 

JL

1F

1 − S

K был меньше KEY(P)?

 

 

ST2

0,1(RLINK)

A

RLINK(P)

Q.

 

 

 

JMP

*+2

A

 

 

 

 

1H

ST2

0,1(LLINK)

1 − S − A LLINK(P)

Q.

 

DONE

EQU

*

1 − S Выход после вставки.

 

 

 

 

 

 

 

 

 

Первые 13 строк программы осуществляют поиск, последние 11 строк—вставку. Время работы поисковой фазы равно (7C + C1 − 3S + 4)u, где

C = число произведенных сравнений;

Cl = число случаев, когда K KEY(P);

S = 1 при удачном поиске и 0 в противном случае:

Соседние файлы в папке Дональд Кнут. Искусство программирования. т.3