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

соотношение с помощью матрицы

0−3

B 3

B

B 0

W(z) = B .

B .

B . @ 0

0

Original pages: 572-600 301

0

: : :

 

0

 

2z

1

−4

: : :

 

0

 

0

4..

 

 

0..

 

0..

C

: : :

C

.

 

.

.

C

 

 

 

C

0

: : :

 

M 1

 

A

 

0

C

0

: : :

 

M + 1

 

2C

и сопоставить CN0 с многочленом N-й степени в W(l).]

6.3. Цифровой поиск

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

Если развить идею ”побуквенных высечек”, мы придем к схеме поиска, основанной на ”индексировании”, как показано в табл. 1. Предположим, требуется проверить, принадлежит ли данный аргумент поиска к 31 наиболее употребительному английскому слову (ср. с рис. 12 и 13, п. 6.2.2). В табл. 1 данные представлены в виде так называемой структуры ”бора”; термин введен Э. Фрэдкином [CACM, 3 (1960), 490–500] как часть слова выборка6(информации). Бор в сущности является M-арным деревом, узлы которого суть M-местные векторы с компонентами, соответствующими цифрам или буквам. Каждый узел уровня l представляет множество всех ключей, начинающихся с определенной последовательности из l литер; узел определяет M-путевое разветвление в зависимости от (l + 1)-й литеры.

Например, бор табл. 1 имеет 12 узлов; узел 1—корень, и первую букву следует искать здесь. Если первой оказалась, скажем, буква N, из таблицы видно, что нашим словом будет NOT (или же, что его нет в таблице). С другой стороны, если первая буква—W, узел (1) направит нас к узлу (9), где мы должны аналогичным образом отыскать вторую букву; узел (9) укажет, что этой буквой должна быть A, H или I.

Узлы-векторы в табл. 1 организованы в соответствии с кодом литер, принятым для MIX. Это означает, что поиск по бору будет довольно быстрым, так как мы должны просто выбирать слова из массива, используя в качестве индексов литеры нашего ключа. Методы быстрого многопутевого разветвления с помощью индексирования называются ”просмотром таблиц” (”Table Look-At”) в противоположность ”поиску по таблицам” (”Table Look-Up”) [см. P. M. Sherman, CACM, 4 (1961), 172–173, 175].

6 В оригинале соответственно trie (искаженное ”tree”) и retrieval—Прим. перев.

302 Original pages: 572-600

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

Таблица 1

 

 

 

”Бор” для 31 наиболее употребительного английского слова

 

 

 

(1)

(2)

(3)

(4)

(5)

(6)

(7)

(8)

(9)

(10)

(11)

(12)

 

---

A

---

---

---

I

---

---

---

---

HE

---

 

 

 

 

 

 

 

 

 

 

 

 

 

A

(2)

---

---

---

(10)

---

---

---

WAS

---

---

THAT

 

 

 

 

 

 

 

 

 

 

 

 

 

B

(3)

---

---

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

C

---

---

---

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

D

---

---

---

---

---

---

---

---

---

HAD

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

E

---

---

BE

---

(11)

---

---

---

---

---

---

THE

 

 

 

 

 

 

 

 

 

 

 

 

 

F

(4)

---

---

---

---

---

OF

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

G

---

---

---

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

H

(5)

---

---

---

---

---

---

(12)

WHICH

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

I

(6)

---

---

---

HIS

---

---

---

WITH

---

---

THIS

 

 

 

 

 

 

 

 

 

 

 

 

 

 

---

---

---

---

---

---

---

---

---

---

---

---

J

---

---

---

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

K

---

---

---

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

L

---

---

---

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

M

---

---

---

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

N

NOT

AND

---

---

---

IN

ON

---

---

---

---

---

O

(7)

---

---

FOR

---

---

---

TO

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

P

---

---

---

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

Q

---

---

---

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

R

---

ARE

---

FROM

---

---

OR

---

---

---

HER

---

 

 

 

 

 

 

 

 

 

 

 

 

 

 

---

---

---

---

---

---

---

---

---

---

---

---

 

---

---

---

---

---

---

---

---

---

---

---

---

S

---

AS

---

---

---

IS

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

T

(8)

AT

---

---

---

IT

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

U

---

---

BUT

---

---

---

---

---

---

---

---

---

V

---

---

---

---

---

---

---

---

---

HAVE

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

W

(9)

---

---

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

X

---

---

---

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

Y

YOU

---

BY

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

Z

---

---

---

---

---

---

---

---

---

---

---

---

 

 

 

 

 

 

 

 

 

 

 

 

 

Узлы бора представляют собой векторы, индексы которых изменяются от 0 до M − 1; каждая компонента этих векторов есть либо ключ, либо ссылка (возможно, пустая).

Т1 [Начальная установка.) Установить указатель P так, чтобы он указывал на корень бора.

Т2 [Разветвление.] Занести в k следующую (стоящую правее) литеру аргумента K. (Если аргумент полностью обработан, мы засылаем в k ”пробел” или символ конца слова. Литера должна быть представлена целым числом в диапазоне 0 k < M.) Через X обозначим k-й элемент в узле NODE(P). Если X—ссылка, перейти на Т3; если X—ключ, то перейти на Т4.

Т3 [Продвижение.] Если X 6= , установить P X и вернуться на Т2; в противном случае алгоритм заканчивается неудачно.

Т4 [Сравнение.] Если K = X, алгоритм заканчивается удачно; в противном случае он заканчивается неудачно.

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

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

Original pages: 572-600 303

Программа Т. (Поиск по бору.) Предполагается, что все ключи занимают одно слово машины MIX; если ключ состоит менее чем из пяти литер, он дополняется справа пробелами. Так как мы используем для представления литер код MIX, каждый байт аргумента поиска должен содержать число, меньшее 30. Ссылки представлены как отрицательные числа в поле 0 : 2. Значения регистров: rI1 P, rX необработанная часть K.

START

LDX

К

1

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

 

ENT1

ROOT

1

P указатель на корень бора.

2H

SLAX

1

C

T2. Разветвление.

 

STA

*+1(2:2) C

Выделение следующей литеры k.

 

ENT2

0,1

C

Q P + k.

 

LD1N

0,2(0:2) C

P LINK(Q).

 

J1P

2B

C

TЗ. Продвижение. На T2, если P—ссылка 6= .

 

LDA

0,2

1

Т4. Сравнение. rA KEY(Q).

 

CMPA

K

1

 

 

JE

SUCCESS

1

Удачный выход, если rA = k.

FAILURE EQU

*

1

Выход, если K нет в бору.

Время работы программы равно (8C + 8)u, где C—число проверяемых литер. Так как C 5, поиск не займет более 48 единиц времени.

Если теперь сравнить эффективность этой программы (использующей бор табл. 1) и программы 6.2.2Т (использующей оптимальное бинарное дерево поиска (рис. 13)), можно заметить следующее:

1.Бор занимает гораздо больше памяти: для представления 31 ключа использовано 360 слов,

вто время как бинарное дерево поиска занимает 62 слова памяти. (Однако упр. 4 показывает, что, проявив известную изобретательность, можно вместить бор табл. 1 в 49 слов.)

Picture:

Рис. 31. Бор табл. 1, преобразованный в ”лес”.

2.Удачный поиск в обоих случаях требует около 26 единиц времени. Но неудачный поиск оказывается более быстрым для бора и более медленным для бинарного дерева поиска. Для наших данных поиск чаще будет неудачным, нежели удачным, поэтому с точки зрения скорости бор более предпочтителен.

3.Если вместо 31 наиболее употребительного английского слова рассмотрим применение указателя KWIC (рис. 15), бор теряет свои преимущества вследствие характера данных. Например, бор требует 12 итераций, чтобы различить слова COMPUTATION и COMPUTATIONS. (В этом случае было бы лучше так построить бор, чтобы слова обрабатывались справа налево, а не слева направо.)

Первая публикация о боровой памяти принадлежит Рене де ла Брианде [Proc. Western Joint Computer Conf., 15 (1959), 295–298]. Он указал, что можно сэкономить память (за счет увеличения времени работы), если использовать связанный список для каждого узла-вектора, так как большинство элементов этого вектора обычно пусто. На самом деле предложенная идея ведет к замене бора табл. 1 на лес деревьев, изображенный на рис. 31. При поиске в таком лесу мы находим корень, соответствующий первой литере, затем сына этого корня, соответствующего второй литере, и т. д.

Фактически в своей статье де ла Брианде не прерывал разветвления так, как показано в табл. 1 или на рис. 31; вместо этого он представлял каждый ключ литера за литерой до достижения признака конца слова. Таким образом, в действительности де ла Брианде вместо дерева ”H” (рис. 31) использовал бы

Picture:

Рис. стр. 576

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

Если использовать обычный способ представления деревьев в бинарном виде, то (1) преобразуется в бинарное дерево (Для представления полного леса рис. 31 необходимо добавить указатель, ведущий из H направо, к соседнему корню I.) При поиске в этом бинарном дереве литеры аргумента сравниваются с литерами дерева; мы следуем по ссылкам RLINK, пока не найдем нужную литеру; затем берем ссылку LLINK и следующую литеру аргумента рассматриваем аналогичным образом.

304 Original pages: 572-600

Поиск в таком бинарном дереве в большей или меньшей степени сводится к сравнениям, причем разветвление происходит не по признаку ”меньше-больше”, а по ”равно-неравно”. Элементарная теория п. 6.2.1 гласит, что в среднем на то, чтобы различить N ключей, нужно по крайней мере log2 N сравнений; среднее число сравнений, производимых при поиске по дереву, подобному изображенному на рис. 31, не меньше числа проверок при использовании методов бинарного поиска, изложенных в x 6.2.

С другой стороны, бор табл. 1 позволяет сразу производить M-путевое разветвление; мы увидим, что при больших N поиск в среднем включает лишь около logM N = log2 N= log2 M итераций, если исходные данные—случайные числа. Мы увидим также, что применение схемы бора в ”чистом” виде (как в алгоритме T) требует порядка N= lnM узлов, если нужно различить N случайных ключей; следовательно, размер занимаемой памяти пропорционален MN= lnM.

Эти рассуждения показывают, что идея бора хороша лишь для небольшого числа верхних уровней дерева. Можно улучшить характеристики, комбинируя две стратегии: для нескольких первых литер использовать бор, а затем перейти на другую методику. Например, Э. Г. Сассенгат, мл. [Е. Н. Sassenguth, CACM, 6 (1963), 272–279] предложил использовать литерный анализ до достижения той части дерева, где остается, скажем, не более шести ключей, а затем произвести в этом коротком списке последовательный поиск. Далее мы увидим, что такая смешанная стратегия уменьшает количество узлов бора примерно в шесть раз без существенного изменения времени работы.

Приложение к английскому языку. Напрашивается много изменений основных стратегий бора и политерного поиска. Чтобы получить представление о некоторых из имеющихся возможностей, рассмотрим гипотетическое крупномасштабное приложение: предположим, что в памяти ЭВМ нужно запомнить довольно полный словарь английского языка. Для этого требуется достаточно большая внутренняя память, скажем на 50000 слов. Наша цель состоит в нахождении экономного способа представления словаря, обеспечивающего при этом достаточно быстрый поиск.

Реализация такого проекта—задача непростая. Требуется как хорошее знание содержания словаря, так и значительная программистская изобретательность. Давайте на некоторое время поставим себя в положение человека, приступающего к такому крупному проекту.

Обычный университетский словарь содержит свыше 100000 слов; наши запросы не столь велики, но рассмотрение подобного словаря позволяет наметить пути к решению. Если мы попытаемся применить боровую память, то вскоре заметим возможность важных упрощений. Предположим, что собственные имена и сокращения в расчет не принимаются. Тогда, если слово начинается на букву b, его вторая буква обязательно отлична от b, c, d, f, g, j, k, m, n, p, q, s, t, v, w, x, z. [Слово ”bdellium” (”бделлий”— род ароматической смолы) можно и не включать в словарь!] На самом деле, те же 17 букв не могут стоять на втором месте в словах, начинающихся с c, d, f, g, h, j, k, 1, m, n, p, r, t, v, w, x, z. Исключение составляют несколько слов, начинающихся с ct, cz, dw, fj, gn, mn, nt, pf, pn, pt, tc, tm, ts, tz, zw, и заметное количество слов, начинающихся с kn, ps, tw. Один из способов использовать этот факт состоит в кодировании букв (например, можно иметь таблицу из 26 слов и выполнять команду вроде ”LD1 TABLE,1”) таким образом, чтобы согласные b, c, d, : : : , z переводились в специальное представление, большее, чем числовой код оставшихся букв a, e, h, i, l, o, r, u, y. Теперь многие узлы бора могут быть укорочены до 9-путевого разветвления; специальная ячейка выхода используется для редко встречающихся букв. Такой подход позволяет сэкономить память во многих частях бора, предназначенного для английского языка, а не только при обработке второй буквы.

В обычном университетском словаре слова начинаются лишь с 309 из 262 = 676 возможных комбинаций двух букв, а из этих 309 пар 88 служат началом 15 или менее слов. (Типичные примеры таких пар: aa, ah, aj, ak, ao, aq, ay, az, bd, bh, : : : , xr, yc, yi, yp, yt, yu, yw, za, zu, zw; большинство людей может назвать максимум по одному слову из большинства этих категорий.) Если встречается одна из указанных редких категорий, можно от боровой памяти перейти к некоторой другой схеме, например к последовательному поиску.

Другим способом уменьшения требуемого для словаря объема памяти является использование приставок. Например, если ищется слово, начинающееся с re-, pre-, anti-, trans-, dis-, un- и т. д., можно отделить приставку и искать остаток слова. Таким путем можно удалить много слов типа reapply (повторно применять), recompute (снова вычислить), redecorate (заново декорировать), redesign (перепроектировать), redeposit (перекладывать) и т. д.; однако нужно оставить слова вроде remainder (остаток), requirement (требование), retain (сохранять), remove (удалять), readily (охотно) и т. д., так как их значения без приставки ”re-” покрыты тайной. Итак, сначала следует искать слово и лишь затем пытаться удалить приставку, если первый поиск оказался неудачным.

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

Original pages: 572-600 305

многим глагольным основам, получая набор родственных слов:

-e

-es

-ing

-ed

-s

-ings

-edlv

-able

-ingly

-er

-ible

-ion

-or

-ably

-ions

-ers

-ibly

-ional

-ors

-ability

-ionally

 

-abilities

 

(Многие из этих суффиксов сами составлены из суффиксов.) Если попытаться добавить эти суффиксы

к основам

computcalculatsearchsuffixtranslatinterpretconfus-

мы увидим, как много слов получается; это чрезвычайно увеличивает вместимость нашего словаря. Безусловно, при такой процедуре получается и много несуществующих слов, например ”compution”; основа computatоказывается столь же необходимой, как и comput-. Однако вреда в этом нет, поскольку такие комбинации и не будут служить аргументами поиска, а если бы некий автор придумал бы слово ”computedly”, мы имели бы для него готовый перевод. Заметим, что большинству людей понятно слово ”confusability” (смущаемость), хотя оно встречается в немногих словарях; наш словарь будет давать подходящее толкование. При правильной обработке приставок и суффиксов наш словарь сможет из одной лишь глагольной основы ”establish” вывести значение слова ”antidisestablishmentarianism”.

Разумеется, нужно заботиться о том, чтобы значение слова полностью определялось основой

исуффиксом; исключения должны быть внесены в словарь таким образом, чтобы их можно было найти до обращения к поиску суффикса. Например, сходство между словами ”socialism” (социализм)

и”socialist” (социалист) способно только сбить с толку при анализе слов ”organism” (организм) и ”organist” (органист)!

Можно использовать целый ряд приемов для уменьшения объема памяти. Но как в единой системе компактно представить разнообразие методов? Ответ состоит в том, чтобы трактовать словарь как программу, написанную на специальном машинном языке для специальной интерпретирующей системы (ср. с п. 1.43); записи в узле бора можно трактовать как инструкции. Например, в табл. 1 мы имеем два вида ”инструкций”, а программа T использует знаковый бит как код операции; знак минус означает ответвление к другому узлу и переход к следующей литере за следующей инструкцией, в то время как знак плюс вызывает сравнение аргумента с определенным ключом.

В интерпретирующей системе для нашего словаря мы могли бы иметь операции следующих типов:

Проверка n, , . ”Если следующая литера аргумента кодируется числом k n, перейти в ячейку + k за следующей инструкцией; в противном случае перейти в ячейку ”.

Сравнение n, , . ”Сравнить оставшиеся литеры аргумента с n словами, расположенными в ячейках , +1, : : : , +n −1. Если подходящее слово имеет адрес +k, поиск кончается удачно и ”значение” лежит в ячейке + k, но если подходящего слова нет, поиск кончается неудачно”.

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

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

306 Original pages: 572-600

Более полная информация об организации словаря имеется в интересных статьях Сиднея Лэмба и Уильяма Якобсена, мл. [Mechanical Translation, 6 (1961), 76–107]; Ю. С. Шварца [JACM, 10 (1963), 413–439]; Галли и Ямады [IBM Systems J., 6 (1967), 192–207].

Бинарный случай. Рассмотрим теперь частный случай M = 2, когда в каждый момент времени обрабатывается один бит аргумента поиска. Разработаны два интересных метода, особенно подходящие для данного случая.

Первый метод, который мы будем называть цифровым поиском по дереву, предложен Коффманом и Ивом [CACM, 13 (1970), 427–432, 436]. Так же как и в алгоритмах поиска по дереву из п. 6.2.2, в узлах дерева хранятся полные ключи, но для выбора левой или правой ветви используются не результат сравнения ключей, а биты аргумента. На рис. 32 изображено дерево, построенное этим методом; оно составлено из 31 наиболее употребительного английского слова, причем слова вставлялись в порядке уменьшения частот. Чтобы получить бинарные данные для этой иллюстрации, слова записывались

влитерном коде машины MIX с последующим преобразованием в двоичные числа, содержащие по 5 битов в байте. Таким образом, слово WHICH превратится в ”11010 01000 01001 00011 01000”.

Чтобы найти WHICH на рис. 32, мы сначала сравниваем его со словом THE в корне дерева. Так как они не совпадают и первый бит слова WHICH равен 1, нужно идти вправо и произвести сравнение со словом OF. Так как совпадения не произошло и второй бит WHICH равен 1, мы идем вправо и сравниваем наш аргумент со словом WITH и т. д.

Дерево рис. 32 построено способом, похожим на способ построения дерева рис. 12 из п. 6.2.2, лишь для разветвления вместо сравнений использовались биты ключей, поэтому интересно отметить разницу между ними. Если рассмотреть заданные частоты, цифровой поиск по дереву рис. 32 требует

всреднем 3:42 сравнения на удачный поиск; это несколько лучше, чем 4:04 сравнения, требующегося для рис. 12, хотя, разумеется, время самого сравнения будет различным в двух этих случаях.

 

Picture:

Рис. 32. Дерево цифрового поиска для 31 наиболее употребительного англий-

 

 

ского слова, слова вставлены в порядке уменьшения частот.

 

 

Алгоритм D. (Цифровой поиск по дереву.) Этот алгоритм позволяет найти данный аргумент K в

таблице записей, образующих описанным выше способом бинарное дерево. Если K нет в таблице, в

дерево в подходящем месте вставляется новый узел, содержащий K.

 

 

 

Как и в алгоритме 6.2.2Т, здесь предполагается, что дерево непусто, а его узлы содержат поляKEY,

LLINK и RLINK. Читатель увидит, что эти алгоритмы почти идентичны.

 

 

D1

[Начальная установка.] Установить P ROOT, K1

K.

 

 

D2

[Сравнение.] Если K = KEY(P), поиск оканчивается удачно; в противном случае занести в b самый

 

левый бит K1 и сдвинуть K1 на единицу влево (т. е. удалить этот бит и добавить справа 0). Если

 

b = 0, перейти на D3; в противном случае перейти на D4.

 

 

D3

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

LLINK(P) и вернуться на D2; в противном случае

 

перейти на D5.

 

 

 

 

 

 

D4

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

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

 

D5

[Вставка в дерево.] Выполнить Q ( AVAIL, KEY(Q)

K, LLINK(Q)

RLINK(Q)

. Если b = 0,

 

установить LLINK(P) Q; в противном случае RLINK(P) Q.

 

 

 

 

 

Алгоритм 6.2.2T по сути своей бинарный, однако приведенный алгоритм, как нетрудно заметить,

можно легко распространить на M-арный цифровой поиск для любого M 2 (см. упр. 13).

 

Picture:

Рис. 33. Пример текста и дерева Патриции.

 

 

Дональд Моррисон [JACM, 15 (1968), 514–534] придумал очень привлекательный способ формирования N-узловых деревьев поиска, основанный на бинарном представлении ключей, без запоминания ключей в узлах. Его метод, названный ”Патриция” (Практический алгоритм для выборки информации, закодированной буквами и цифрами)7, особенно подходит для работы с очень длинными ключами переменной длины, такими, как заголовки или фразы, хранящиеся в файле большого объема.

Основная идея Патриции состоит в том, чтобы строить бинарное дерево, избегая однопутевых разветвлений, для чего в каждый узел включается число, показывающее, сколько битов нужно пропустить, перед тем как делать следующую проверку. Существует несколько способов реализации этой идеи; возможно, простейший с точки зрения объяснения изображен на рис. 33. Имеется массив

7 В оригинале ”Patricia”—Practical Algorithm To Retrieve Information Coded In Alphanumeric.—

Прим. перев.

Original pages: 572-600 307

битов TEXT (обычно довольно длинный); он может храниться во внешнем файле с прямым доступом, так как при каждом поиске обращение к нему происходит лишь раз. Каждый ключ, который должен храниться в нашей таблице, определяется местом начала в тексте; можно считать, что он идет от места начала до конца текста. (Патриция не ищет точного равенства между ключом и аргументом, а определяет, существует ли ключ, начинающийся с аргумента.)

Впримере, изображенном на рис. 33, имеется семь ключей, начинающихся с каждого входящего

втекст слова, а именно ”THIS IS THE HOUSE THAT JACK BUILT.”, ”IS THE HOUSE THAT JACK BUILT.”,

: : : , ”BUILT.”. Налагается важное ограничение, состоящее в том, что один ключ не может служить началом другого; это условие выполняется, если мы оканчиваем текст специальным кодом конца текста (в данном случае ”.”), который нигде больше не появляется. То же ограничение подразумевается

всхеме бора алгоритма T, где признаком конца служил ” ”.

Дерево, используемое Патрицией для поиска, должно целиком помещаться в оперативной памяти, или его нужно организовать по страничной схеме, описанной в п. 6.2.4. Оно состоит из головы и N − 1 узлов, причем узлы содержат несколько полей:

KEY—указатель на текст. Это поле должно иметь длину не менее log2 C битов, где C—число литер в тексте. Слова, изображенные на рис. 33 внутри узлов, на самом деле должны быть представлены указателями на текст; например, вместо ”(JACK)” узел содержит число 24 (начальную точку ключа ”JACK BUILT.”).

LLINK и RLINK—ссылки внутри дерева. Они должны иметь длину не менее log2 N битов. LTAG и RTAG—однобитовые поля, по которым мы можем судить, являются ли LLINK и RLINK соответственно ссылками на сыновей или на предков. Пунктирные линии на рис. 33 соответствуют ссылкам, для которых TAG = 1.

SKIP—число битов, которые нужно пропустить при поиске, как объяснялось выше. Это поле должно быть достаточно большим для того, чтобы вместить наибольшее число k, для которого в двух различных ключах найдутся совпадающие подцепочки из k битов; обычно для практических целей достаточно считать, что k не слишком велико, и при превышении размеров поля SKIP выдавать сообщение об ошибке. На рис. 33 поля SKIP изображены внутри каждого узла числами.

Голова содержит лишь поля KEY, LLINK и LTAG.

Поиск в дереве Патриции выполняется следующим образом: предположим, мы ищем слово THAT (бинарное представление 10111 01000 00001 10111). По содержимому поля SKIP в корневом узле мы заключаем, что сначала нужно проверить первый бит аргумента. Он равен 1, поэтому надо двигаться вправо. Поле SKIP следующего узла побуждает нас проверить 1+11 = 12-й бит аргумента. Он равен 0, и мы идем влево. Изучив поле SKIP следующего узла, видим, что нужно проверить 12 + 1 = 13-й бит, который равен 0. Так как LTAG = 1, мы идем на узел γ, который отсылает нас к массиву TEXT. По тому же пути пойдет поиск для любого аргумента, имеющего бинарное представление 1xxxx xxxxx x00 : : :, поэтому нужно сравнить аргумент с найденным ключом.

Предположим, с другой стороны, что мы ищем некоторый ключ, начинающийся с TH, или все такие ключи. Поиск начинается, как и раньше, но в конце концов мы обратимся к (несуществующему) 12-му биту десятибитового аргумента. Теперь нужно сравнить аргумент с фрагментом массива TEXT, который специфицируется в текущем узле (в данном случае γ); если совпадения не произошло, то не существует ключа, началом которого служит аргумент поиска; если совпадение имело место, аргумент является началом любого ключа, на который указывают пунктирные линии, выходящие из узла γ и его потомков.

Более точно этот процесс описывается следующим образом.

Алгоритм P. (Патриция.) Алгоритм, используя массив TEXT и дерево с описанными выше полями KEY, LLINK, RLINK, LTAG, RTAG и SKIP в узлах, позволяет определить, существует ли в массиве TEXT ключ, начинающийся с аргумента K. (Если имеется r 1 таких ключей, можно за O(r) шагов определить расположение каждого из них; см. упр. 14.) Предполагается, что есть по крайней мере

один ключ.

 

P1

[Начальная установка.] Установить P HEAD и j 0 (Переменная P является указателем, кото-

 

рый будет двигаться вниз по дереву, а j—это счетчик, определяющий позиции битов аргумента.)

 

Занести в n количество битов аргумента K.

 

P2

[Шаг влево.] Установить Q P и P LLINK(Q). Если LTAG(Q) = 1, перейти на P6.

 

P3

[Пропуск битов.] (В этот момент мы знаем, что, если первые j битов аргумента соответствуют

 

некоторому ключу, они соответствуют ключу, начинающемуся в KEY(P).) Установить j

j +

 

SKIP(P). Если j > n, перейти на P6.

 

308 Original pages: 572-600

P4 [Проверка бита.] В этот момент мы знаем, что, если первые (j−1)битов соответствуют некоторому ключу, они соответствуют ключу, начинающемуся в KEY(P). Если j-й бит K равен 0, перейти на P2; в противном случае перейти на P5.

P5 [Шаг вправо.] Установить Q P и P RLINK(Q). Если RTAG(Q) = 0, перейти на P3.

P6 [Сравнение.] (В этот момент мы знаем, что, если K соответствует некоторому ключу, он соответствует ключу, начинающемуся в KEY(P).) Сравнить K с ключом, начинающимся в позиции KEY(P) массива TEXT. Если произошло совпадение n первых битов, алгоритм заканчивается удачно; в случае несовпадения он заканчивается неудачно.

В упр. 15 прежде всего показано, как можно осуществить построение дерева Патриции. Имеется также возможность добавлять к тексту и вставлять новые ключи при условии, что новый текстовой материал оканчивается определенным разделителем (например, символом конца текста, за которым следует порядковый номер).

Характер Патриции слегка причудлив, и все ее достоинства заметны лишь внимательному наблюдателю.

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

Picture: Рис. 34. Пример случайного бинарного бора.

Рассмотрим сначала бинарные боры, т. е. боры с M = 2. На рис. 34 изображен бинарный бор, образующийся, если 16 ключей из примеров сортировки гл. 5 рассматривать как десятибитовые двоичные числа. (Ключи приведены в восьмеричной записи, так что, например, 1144 представляет десятибитовое число (1001100100)2.) Как и в алгоритме T, мы используем бор для хранения информации о левых битах ключей до тех пор, пока впервые достигается точка, где ключ определяется однозначно; там он записывается полностью.

Если сравнить рис. 34 с табл. 5.2.2-3, обнаружится удивительная взаимосвязь между боровой памятью и обменной поразрядной сортировкой. (Возможно, эта взаимосвязь является очевидной.) 22 узла рис. 34 в точности соответствуют 22 стадиям расчленения в табл. 5.2.2-3 (узлы следует нумеровать в прямом порядке). Число проверяемых битов в стадии расчленения равно числу ключей в соответствующем узле-и его подборах; таким образом, можно сформулировать следующий результат.

Теорема T. Если N различных двоичных чисел помещены в бинарный бор, как описано выше, то

(i) число узлов бора равно числу стадий расчленения, нужных при обменной поразрядной сортировке этих чисел; (ii) среднее число проверок битов, требуемых для выборки ключа с помощью алгоритма T, равно 1=N, умноженному на число проверок битов при. обменной поразрядной сортировке.

Благодаря теореме мы можем использовать весь математический аппарат, развитый в п. 5.2.2 для обменной поразрядной сортировки. Предположим, например, что ключами являются случайные, равномерно распределенные между 0 и 1 вещественные числа, представленные с бесконечной точностью. Тогда количество проверок битов, необходимых для выборки информации, будет равно log2 N +γ=(ln2)+1=2+f(N)+O(N−1), а число узлов бора составит N=(ln2)+Ng(N)+O(1). Здесь f(N) и g(N)—сложные функции, которыми можно пренебречь, так как их значения всегда меньше 10−6 (см. упр. 5.2.2-38,48).

Разумеется, перед нами стоит более трудная задача: обобщить полученные результаты на случай M-арных боров. Мы опишем лишь отправную точку исследований, оставляя поучительные детали в качестве упражнений.

Пусть AN —среднее число узлов в случайном M-арном бору поиска, содержащем N ключей. Тогда A0 = A1 = 0, а для N 2 мы имеем

AN = 1 + k1

+ +kM =N

N!

M−N

(Ak1 + + AkM );

(3)

k1!: : : kM !

 

X

 

 

так как N!M−N=k1!: : : kM ! есть вероятность того, что k1 ключей содержится в первом подбору, : : : , kM —в M-м. Воспользовавшись соображениями симметрии и проведя суммирование по k2, : : : , kM , перепишем это соотношение так:

AN = 1 + M1−N k1+

+kM =N

N!

Ak1 =

 

 

k1!: : : kM!

 

 

 

X

(M − 1)N−kAk; N

 

 

 

N

2:

 

= 1 + M1−N X k

(4)

k

 

 

 

 

Original pages: 572-600 309

Аналогично, если через CN обозначить среднее суммарное количество проверок битов, нужных для

поиска в бору всех N ключей, то C0 = C1 = 0, а

 

 

 

CN = N + M1−N k

N

(M − 1)N−kCk;

N 2:

(5)

k

X

 

 

 

 

В упр. 17 показано, как работать с рекуррентными соотношениями такого вида, а в упр. 18–25 разрабатывается соответствующая теория случайных боров. [С другой точки зрения к анализу AN впервые подошли Л. Р. Джонсон и М. Мак-Эндрю [IBM J. Res. and Devel., 8 (1964), 189–193] в связи с эквивалентным аппаратно-ориентированным алгоритмом сортировки.]

Переходя теперь к изучению деревьев цифрового поиска, мы обнаруживаем, что, с одной стороны, формулы похожи, а с другой—достаточно различны, и сразу не ясно, как исследовать асимпто-

 

 

 

 

 

 

тическое поведение. Например, если CN обозначает среднее суммарное количество проверок битов,

производимых при поиске всех N ключей в бинарном дереве цифрового поиска, нетрудно вывести,

 

 

 

 

 

 

как и раньше, что C0

= C1 = 0 и

X

 

 

 

 

 

N

 

 

 

CN+1 = N + M1−N

k

k (M − 1)N−kCk;

N 0:

(6)

Это почти идентично выражению (5), но появления N + 1 вместо N в левой, части уравнения достаточно для того, чтобы изменить общий характер рекуррентности, так что методы, используемые для изучения (5), в данном случае непригодны.

Рассмотрим сначала бинарный случай. На рис. 35 изображено дерево цифрового поиска, соответствующее 16 ключам рис. 34, вставленным в порядке, использованном в примерах из гл. 5. Среднее число проверок битов при случайном удачном поиске найти легко—оно равно длине внутреннего пути дерева, деленной на N, так как нужно l проверок, чтобы найти узел, расположенный на уровне l. Заметим, однако, что среднее число проверок битов при случайном неудачном поиске не так просто связано с длиной внешнего пути дерева, ибо неудачный поиск с большей вероятностью оканчивается вблизи корня; например, вероятность достижения левой ветви узла 0075 (рис. 35) равна 1=8 (рассматриваются бесконечно точные ключи), а в левую ветвь узла 0232 мы попадаем с вероятностью, равной лишь 1=32. (По этой причине при равномерном распределении ключей деревья цифрового поиска, как правило, лучше сбалансированы, чем бинарные деревья поиска, использовавшиеся в алгоритме 6.2.2T.)

Для описания соответствующих характеристик деревьев цифрового поиска можно использовать производящую функцию. Пусть на уровне l расположено al узлов; рассмотрим производящую функцию a(z) = Palzl. Например, рис. 35 соответствует

Picture:

Рис. 35 Случайное дерево цифрового поиска, построенное с помощью алго-

 

 

ритма D.

 

 

 

 

функция a z = 1 + 2z + 4z2 + 5z3 + 4z4. Если b

 

узлов уровня l являются внешними и b(z) =

b

zl, то

в силу упр.(6.2.1)

-25 имеем

l

 

P l

 

 

b(z) = 1 + (2z − 1)a(z):

 

(7)

Например, 1 + (2z − 1)(1 + 2z + 4z2 + 5z3 + 4z4) = 3z3 + 6z4 + 8z5. Среднее число проверок битов при случайном удачном поиске равно a0(1)=a(l), так как a0(1) есть длина внутреннего пути дерева, а a(1)—

количество внутренних узлов. Среднее число проверок битов для случайного неудачного поиска рав-

P

lbl2−l = 21b0

 

21

 

 

21

 

9

но

 

 

= a

 

, так как мы достигаем данного внешнего узла уровня l с вероятностью 2−l.

При неудачном поиске число сравнений совпадает с числом проверок битов, а при ”удачном”—на единицу больше этого числа. Для дерева рис. 35 удачный поиск в среднем требует 216 проверок битов и 3169 сравнений; в процессе неудачного поиска производится 387 сравнений и проверок (в среднем).

Введем теперь ”усредненную” по всем деревьям с N узлами функцию a(z) и обозначим ее че-

P

рез gN(z). Иными словами, gN(z) = pT aT (z), где сумма берется по всем бинарным деревьям цифрового поиска T с N внутренними узлами; aT (z) есть производящая функция для внутренних узлов T,

а pT есть вероятность того, что при вставке N случайных чисел с помощью алгоритма D получается

дерево T. Таким образом, для удачного поиска среднее число проверок битов равно g0 (1)=N, а для

N

неудачного—gN 12 .

Имитируя процесс построения дерева, gN(z) можно вычислить следующим образом. Если a(z) есть производящая функция для дерева с N узлами, можно образовать N + 1 деревьев, делая вставку в позицию любого внешнего узла. Эта вставка производится в данный внешний узел уровня l с вероятностью 2−l; следовательно, сумма производящих функций для N + 1 новых деревьев, умноженных

310 Original pages: 572-600

 

 

 

 

 

 

 

 

 

 

 

 

 

на вероятность их появления, равна a z

b

1z

=

a z

 

 

z

a

 

1z

. Усредняя по всем деревьям

с N узлами, получаем

( )+

 

2

( )+ 1 + (

−1)

2

 

gN+1(z) = gN(z) + 1 + (z − 1)gN

1

z

;

g0(z) = 0:

(8)

 

 

 

2

С соответствующей производящей функцией для внешних узлов hN(z) = 1 + (2z − 1)gN(z) работать несколько легче, так как (8) эквивалентно формуле

hN+1(z) = hN(z) + (2z − 1)hN

1

;h0(z) = 1:

 

2z

(9)

Многократно применяя это правило, находим, что

hN+1(z) =

=hN−1(z) + 2(2z − 1)hN−1

=hN−2(z) + 3(2z − 1)hN−2

+ (2z − 1)(z − 1)

1

− 1

2

и т. д.; окончательно имеем

 

1

 

+ (2z − 1(z − 1)hN−1

1

 

z =

 

 

 

2

4

 

1

 

+ 3(2z − l)(z − l)hN−2

 

1

z

+

 

 

 

2

4

 

 

 

1

z

 

 

 

 

 

 

hN−2

 

 

 

 

 

 

 

 

8

 

 

 

 

 

 

 

 

 

 

 

X

 

N

 

Y

 

 

 

 

 

 

 

 

 

 

 

 

hN(z) =

k

k

 

0

 

j<k(21−jz

− 1);

 

 

 

(10)

 

 

 

 

 

X

 

 

 

N

 

 

 

Y

 

 

 

 

 

 

 

 

 

 

 

gN(z) = k 0

 

 

 

 

0 j<k(2−jz − 1):

 

 

 

 

 

k + 1

(11)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Например, g4(z) = 4 + 6(z − l) + 4(z − l)

21z − l

 

+ (z − 1) 21z − 1

41z − 1 . Эти формулы позволяют

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

 

 

 

 

 

 

 

 

 

 

 

 

X

 

 

N

 

 

 

Y

 

 

 

 

 

 

 

 

CN = gN0 (1) = k 0

k + 2

1 j k(2−j − 1);

(12)

1

 

X

N

 

 

 

Y

 

 

 

 

 

 

 

 

 

 

 

gN

 

 

= k 0 k + 1

 

1 j k(2−j 1) = CN+1 CN:

(13)

2

Совсем не очевидно, что эти формулы для CN удовлетворяют (6)!

К сожалению, найденные выражения непригодны для вычислений или получения асимптотических оценок, так как 2−j − 1 < 0; получится большое количество членов, многие из которых сократятся. Более полезные формулы для CN можно вывести, применяя тождества упр. 5.1.1-16. Имеем

 

 

j 1

 

 

 

k 0

l 0

 

 

 

 

 

 

 

 

 

 

@Y

 

 

 

AX

 

N

 

Y

 

 

 

 

 

 

 

 

 

 

CN = 0

 

 

(1 2−j)1

 

 

 

 

k + 2

( 1)k

 

(1 2−l−k−1)−1 =

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Y

 

 

 

@Y

 

 

 

AX

 

N

 

X

 

 

 

 

 

 

 

= 0

 

 

(1

2−j)1

 

 

 

 

k + 2

( 1)k

 

 

 

(2−k−1)m

 

 

 

(1

2−r)−1 =

 

 

j

 

1

 

 

 

k

 

0

 

m

 

0

 

 

 

 

1 r

 

m

(14)

 

 

 

 

 

X

 

 

 

 

 

 

 

 

 

Y

 

 

 

 

 

X

 

 

N

(−2−m)k − 1 + 2−mN!

 

 

 

 

 

=

 

 

2m

 

 

k

 

 

 

(1 − 2−j−m−1) =

 

m

0

 

 

 

k

 

 

 

 

 

X

 

 

j

0

 

 

 

 

Y

 

X

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

=

 

 

2m((1 − 2−m)N − 1 + 2−mN)

 

(−2−m−1)n2−n(n−1)=2

(1 − 2−r)−1:

 

m 0

 

 

 

 

 

 

 

 

 

 

n 0

 

 

 

 

 

 

 

 

1

r n

Сразу не ясно, чем это лучше выражения (12), но выведенная формула имеет то важное преимущество, что она быстро сходится для любого фиксированного n. В точности аналогичная ситуация

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