Дональд Кнут. Искусство программирования. т.3 / knuth3
.pdf
Original pages: 601-650 341
таким образом алгоритм 6.2.2Т с хешированием. Проанализируйте среднее число проб, нужных этому комбинированному алгоритму и в случае удачного, и в случае неудачного поиска. [Указание: ср. с формулой (5.2.1-11).]
39.[M30] Цель этого упражнения—проанализировать среднее число проб в алгоритме C (срастающиеся цепочки). Обозначим через c(k1; k2; k3; : : :) количество хеш-последовательностей (35), заставляющих алгоритм C образовать ровно k1 списков длины 1, k2 списков длины 2 и т. д.; k1 + 2k2 + 3k3 + : : : = N. Найдите рекуррентное соотношение, определяющее эти числа, и используйте его при выводе простой формулы для суммы
SN = |
X |
kjc(k1; k2; : : :): |
j 1
k1+k2+:::=N
Как связана величина SN с числом проб во время неудачного поиска с использованием алгоритма C?
40.[M33] Найдите дисперсию числа проб, производимых в алгоритме C, если поиск был неудачным (см. (15).)
41.[М40] Проанализируйте, сколько раз в среднемR уменьшается на 1 при вставке (N+1)-го элемента с помощью алгоритма C. (Обозначим это число через TN.)
42.[М20] Выведите (17).
43.[М42] Проанализируйте модификацию алгоритма C, использующую таблицу размера M0 M. Для хеширования используются лишь первые M позиций, так что первые M0 − M свободных
узлов, найденных в шаге C5, расположатся в дополнительной части таблицы. Какой выбор M при фиксированном M0, 1 M M0, дает наилучшие характеристики?
44.[М43] (Случайное опробование с вторичным скучиванием.) Цель этого упражнения—определить среднее число проб в схеме открытой адресаций с последовательностью опробований
h(K); (h(K) + p1) mod M; (h(K) + p2) mod M; : : : ; (h(K) + pM−1) mod M;
где p1 p2 : : : pM−1—случайно-выбранная перестановка множества f1; 2; : : : ; M − 1 g, зависящая от h(K). Иными словами, у всех ключей с одинаковым значением h(K) одна и та же последовательность опробований, а (M − 1)!M возможных выборов M последовательностей проб с этим свойством равновероятны.
Можно точно смоделировать ситуацию, если над первоначально пустым линейным массивом размера m выполнить следующую процедуру. Делаем n раз такую операцию:
Свероятностью p займем крайнюю левую свободную позицию. В противном случае (т. е.
свероятностью q = 1−p) выберем любую позицию таблицы, кроме крайней левой, причем все m − 1 позиций равновероятны. Если выбранная позиция свободна, займем ее; в противном случае выберем любую свободную позицию (включая самую левую) и займем ее, считая, что все свободные позиции равновероятны.
Например, если m = 5и n = 3, то после выполнения описанной процедуры конфигурация (занято, занято, свободно, занято, свободно) получается с вероятностью
1927 qqq + 16pqq + 16qpq + 1164qqp + 13ppq + 14pqp + 14qpp:
(Эта процедура соответствует случайному опробованию с вторичным скучиванием, когда p = 1=m, ибо можно перенумеровать элементы таблицы так, что некоторая последовательность проб совпадет с 0, 1, 2, : : : , а все остальные будут случайными.)
Найдите формулу для среднего числа занятых позиций в левом конце массива (две в приведенном примере). Определите также асимптотическое значение этой величины при p = 1=m, n = (m + 1), m ! 1.
45.[М43] Решите аналог упр. 44 с третичным скучиванием, когда последовательность проб начи-
нается с h1(K), (h1(K) + h2(K)) mod M; выбор дальнейших проб случаен и зависит лишь от h1(K) и h2(K) (т. е. (M −2)!M(M−1) возможных выборов последовательностей проб с этим свойством считаются равновероятными). Является ли предлагаемая процедура асимптотически эквивалентной равномерному опробованию?
46.[M42] Найдите CN0 и CN для метода открытой адресации, использующего последовательность
проб
h(K); 0; 1; : : :; h(K − 1); h(K) + 1; : : :; M − 1:
Original pages: 651-675 343
63.[М25] Если в рассеянной таблице производятся случайные вставки и удаления, то сколько нужно в среднем независимых вставок, чтобы все M позиций побывали в занятом состоянии?
64.[M46] Проанализируйте ожидаемое поведение алгоритма R. Сколько раз в среднем будет выполняться шаг R4?
>65. [20] (Ключи переменной длины.) Во многих приложениях рассеянных таблиц ключи могут состоять из произвольного числа литер. В таком случае нельзя просто запомнить ключ в таблице, как это делалось в программах данного параграфа. Придумайте хороший способ использования рассеянных таблиц для хранения ключей переменной длины, если работа ведется на машинеMIX.
6.5. ВЫБОРКА ПО ВТОРИЧНЫМ КЛЮЧАМ
Мы завершили изучение поиска по ”первичным ключам”, т. е. по ключам, однозначно определяющим запись в файле. Но иногда необходимо вести поиск, основываясь не на первичных ключах, а на значениях других полей записи, которые часто называются ”вторичными ключами” или ”атрибутами” записи. Например, в регистрационном файле, содержащем информацию о студентах университета, может понадобиться найти всех студентов-второкурсников из Огайо, не специализирующихся по математике или статистике, или может понадобиться найти всех незамужних аспиранток, говорящих по-французски, и т. д.
Вообще предположим, что каждая запись имеет несколько атрибутов и мы хотим найти все записи с определенными значениями определенных атрибутов. Спецификация искомых записей называется запросом. Обычно допускается не более трех следующих типов запросов.
a)Простой запрос, когда определенному атрибуту задается конкретное значение, например СПЕЦИАЛЬНОСТЬ = МАТЕМАТИКА или МЕСТОЖИТЕЛЬСТВО.ШТАТ = ОГАЙО.
b)Запрос по области значений, когда для определенного атрибута задается конкретная область значений, например ЦЕНА < 18:00$; или 21 ВОЗРАСТ 23.
c)Булев запрос состоит из запросов двух первых типов, соединенных операциями И, ИЛИ, НЕ; например,
(КУРС = ВТОРОКУРСНИК) И (МЕСТОЖИТЕЛЬСТВО.ШТАТ = ОГАЙО)
И НЕ((СПЕЦИАЛЬНОСТЬ = МАТЕМАТИКА) ИЛИ (СПЕЦИАЛЬНОСТЬ = СТАТИСТИКА)):
Задача разработки эффективных методов поиска достаточно трудна уже для этих трех типов запросов, поэтому запросы более сложных типов обычно не рассматриваются. Например, железнодорожная компания могла бы иметь файл, описывающий текущее состояние всех принадлежащих ей товарных вагонов. Запрос типа ”найди все свободные вагоны-холодильники в пределах 500 миль от Сиэтла” в явном виде был бы недопустим, если бы ”расстояние от Сиэтла” было не атрибутом каждой записи, а сложной функцией нескольких атрибутов. Использование же логических кванторов в дополнение к И, ИЛИ и НЕ ведет к дальнейшим усложнениям, степень которых ограничена лишь фантазией автора запроса. Например, имея файл бейсбольной статистики, мы могли бы запросить данные о самой длинной серии удачных ударов в вечерних играх. Эти запросы сложны, но на них все же можно ответить, выполнив один проход по должным образом организованному файлу. Есть и еще более трудные запросы; например, найти все пары записей, имеющих одинаковые значения пяти или более атрибутов (без определения того, какие именно атрибуты должны совпадать). Подобные запросы можно рассматривать как общие задачи программирования, выходящие за рамки данной работы, хотя часто их можно разбить на подзадачи рассматриваемых нами типов.
Прежде чем переходить к изучению различных методов выборки по вторичным ключам, уместно рассмотреть экономическую сторону вопроса. Хотя довольно обширная область приложений попадает в жесткие рамки трех типов запросов, сложные методы, которые мы будем изучать, далеко не всегда являются удовлетворительными; иную работу можно быстрее сделать вручную! ЭВМ увеличили скорость научных вычислений приблизительно в 107–108 раз; повышение же эффективности в деле управления информацией неизмеримо меньше. При операциях с большим количеством данных современные ЭВМ работают все еще с механическими (а не электронными) скоростями, поэтому, заменяя ручную систему на ЭВМ, мы не получаем резкого улучшения характеристик на единицу затрат. Не следует ожидать от ЭВМ слишком многого только потому, что они здорово решают некоторые задачи: : :
Люди покорили Эверест ”потому, что он существует”, и потому, что было создано оборудование, сделавшее восхождение возможным; точно так же, встретившись с горой информации, люди попытались использовать ЭВМ для ответов на самые трудные мыслимые и немыслимые вопросы в оперативном режиме и реальном масштабе времени, не учитывая должным образом стоимости работы. Требуемые вычисления возможны, но слишком дороги для каждодневного использования.
344 Original pages: 651-675
Рассмотрим, например, следующий простой способ выборки по вторичным ключам: после ”буферизации” ряда запросов можно произвести последовательный поиск во всем файле, выбирая все нужные записи. (”Буферизация” означает, что мы накапливаем запросы, прежде чем начать их обработку.) Этот метод вполне удовлетворителен, если файл не слишком велик, а на запросы не надо отвечать немедленно. Он годится даже для файлов на лентах и лишь время от времени требует внимания ЭВМ, что делает его очень экономичным в смысле стоимости оборудования. Более того, предлагаемый подход применим даже для обработки вычислительных запросов типа ”расстояния от Сиэтла”.
Другой способ облегчить выборку по вторичным ключам—поручить человеку часть работы, обеспечив его должным образом оформленными указателями информации. Часто такой подход оказывается наиболее разумным и экономичным (разумеется, при том условии, что после выхода в свет нового указателя старая бумага перерабатывается).
Однако описанные простые схемы не являются удовлетворительными, если важны быстрые ответы на запросы, а файлы очень велики. Такая ситуация имеет место, например, если файл представляет собой объект непрерывных запросов от ряда одновременных пользователей или если запросы порождаются не человеком, а машиной. Цель настоящего параграфа—понять, насколько хорошо можно производить выборку по вторичным ключам, используя обычные ЭВМ, при различных предположениях о структуре файла.
Было выдвинуто немало хороших идей для решения этой задачи, но (как читатель наверняка уже догадался) эти алгоритмы оказались неизмеримо хуже имеющихся алгоритмов выборки по первичным ключам. Вследствие большого разнообразия файлов и приложений мы не сможем дать исчерпывающего обсуждения всех имеющихся возможностей, как и не сможем проанализировать поведение каждого алгоритма в типичных условиях. В настоящем параграфе содержатся лишь основные из предлагавшихся подходов; дело читателя решать, какая комбинация методов больше всего подходят в каждом конкретном случае.
Инвертированные файлы. Первый важный класс методов выборки по вторичным ключам основан на идее ”инвертированного файла”. Этот термин не означает, что файл перевернут вверх дном, он означает, что записи и атрибуты поменялись ролями. Вместо перечисления атрибутов данной записи перечисляются записи, имеющие данное значение атрибута.
В повседневной жизни мы довольно часто (правда, под другими именами) сталкиваемся с инвертированными файлами. Например, инвертированным файлом, соответствующим русско-англий- скому словарю, является англо-русский словарь. Инвертированным файлом, соответствующим этой книге, являются указатели, помещенные на последних страницах. Бухгалтеры традиционно используют ”двойную бухгалтерию”, когда все деловые соглашения регистрируются как в счете кассы, так и в счете клиента, что позволяет легко контролировать наличную сумму денег и долг клиента.
Вообще инвертированный файл обычно не существует сам по себе, его нужно использовать вместе с первоначальным, неинвертированным файлом для ускорения поиска с помощью содержащейся в нем избыточной информации. Компоненты инвертированного файла, т. е. списки всех записей, имеющих данное значение некоторого атрибута, называются инвертированными списками.
Как и все списки вообще, инвертированные списки можно представлять в памяти ЭВМ различными способами, и для разных приложений подходят разные представления. Некоторые поля вторичных ключей могут иметь лишь два значения (например, атрибут ”пол”), а соответствующие инвертированные списки очень длинны, но другие поля обычно имеют очень много значений с редкими повторениями (например, атрибут ”номер телефона”).
Представим себе, что мы хотим хранить информацию в телефонной книге таким образом, чтобы все элементы можно было выбирать либо по имени, либо по номеру телефона, либо по месту жительства. Одно из решений состоит в том, чтобы просто иметь три отдельных файла, ориентированных на каждый тип ключей. Другая идея заключается в объединении этих файлов, например, с помощью построения трех рассеянных таблиц, служащих в методе цепочек головными узлами списков. В этой последней схеме каждая запись должна быть элементом трех списков и должна поэтому содержать три поля ссылки; такой метод называется Многосписочным и обсуждается ниже. Еще одна возможность—объединить три файла в один суперфайл по аналогии с библиотечным каталогом, в котором карточки с фамилиями авторов, карточки с названиями книг и карточки с темами книг все вместе упорядочены по алфавиту.
Изучение формата, использованного в указателях к данной книге, наталкивает на другие идеи о представлении инвертированных списков. Если определенному значению атрибута соответствует около пяти элементов, можно организовать обычный последовательный список адресов записей (аналогично номерам страниц в указателе к книге), имеющих данное значение вторичного ключа. Если соответствующие записи располагаются последовательно, может быть полезным указание диапазона
Original pages: 651-675 345
(например, со страницы 200 по 214). Если известно, что записи файла довольно часто перемещаются, вместо адресов записей в инвертированном файле, пожалуй, лучше использовать первичные ключи; тогда при смене адресов записей не нужно будет производить никаких изменений. Например, при ссылках на Библию всегда указывается глава и стих, а указатели в некоторых книгах основаны не на нумерации страниц, а на номерах параграфов.
Однако предложенные идеи не очень подходят для случая атрибута с двумя возможными значениями, типа атрибута ”ПОЛ”. В такой ситуации нужен лишь один инвертированный список, ибо не мужчина есть женщина и обратно. Если каждое значение соответствует примерно половине элементов файла, инвертированные списки будут ужасно длинными, однако на двоичной ЭВМ эту трудность можно разрешить весьма изящно, используя представление в виде цепочки битов, где каждый бит определяет значение атрибута определенной записи. Так, цепочка битов 01001011101:: :могла бы означать, что первая запись файла описывает мужчину, вторая—женщину, следующие две—мужчин и т. д. (Ср. также с обсуждением представления простых чисел в конце x 6.1.)
Приведенные методы удовлетворительны для обработки запросов по конкретным значениям атрибутов. Некоторое обобщение этих методов позволит обрабатывать запросы по области значений. При этом вместо хеширования нужно использовать схему поиска, основанную на сравнениях (x 6.2).
Для булевых запросов типа ”(СПЕЦИАЛЬНОСТЬ = МАТЕМАТИКА)И(МЕСТОЖИТЕЛЬСТВО:ШТАТ = ОГАЙО)”
нужно пересечь два инвертированных списка. Это можно сделать несколькими способами; например, если оба списка упорядочены, один проход по каждому из них позволяет выявить все общие элементы. Или же можно взять кратчайший список и просматривать его элементы, проверяя значения других атрибутов; однако такой метод применим к операции И и не применим к операции ИЛИ. Кроме того, он не годится для внешних файлов, ибо ведет к большому числу обращений к записям, не удовлетворяющим запросу.
Аналогичные рассуждения показывают, что упомянутая выше Многосписочная организация неэффективна для булевых запросов, если файл внешний, поскольку она требует много ненужных обращений. Представим себе, например, что произойдет, если указатель к этой книге организовать Многосписочным образом. Каждый элемент указателя будет ссылаться лишь на последнюю из тех страниц, где располагается описываемый объект; на каждой странице для каждого объекта должна иметься ссылка на место его предыдущего появления. В таком случае придется перелистать много страниц, чтобы найти весь материал, относящийся к теме ”[Анализ алгоритмов] и [(Внешняя сортировка) или (Внутренняя сортировка)]”. С другой стороны, тот же запрос можно обработать, взглянув всего на две страницы обычного указателя и произведя несложные операции над инвертированными списками для нахождения небольшого подмножества страниц, удовлетворяющих запросу.
Если инвертированный список представлен в виде цепочки битов, не составляет большого труда выполнить булевы комбинации запросов, поскольку ЭВМ могут манипулировать цепочками битов со сравнительно высокой скоростью. В случае смешанных запросов, когда некоторые атрибуты представлены посредством последовательных списков номеров записей, а другие посредством цепочек битов, нетрудно преобразовать последовательные списки в цепочки битов и произвести над ними булевы операции.
В этом месте полезно привести количественный пример гипотетического приложения. Предположим, имеется 1000000 записей по 40 литер в каждой, и пусть файл хранится на дисках MIXTEC(см. п. 5.4.9). Сам файл занимает два дисковых пакета; инвертированные списки займут, вероятно, несколько больше места. Каждая дорожка содержит 5000 литер = 30 000 битов, поэтому инвертированный список для некоторого атрибута займет не более 34 дорожек. (Максимальное число дорожек соответствует случаю, когда представление в виде цепочек битов наиболее короткое.) Предположим, имеется довольно сложный запрос, состоящий из булевой комбинации 10 инвертированных списков; в худшем случае нам придется пересечь 340 дорожек информации из инвертированного файла. Полное время чтения составит 340 25 мс = 8:5 с. Среднее время задержки равно приблизительно половине этого количества, но если искусно составить программу, задержку можно исключить. Если хранить первые дорожки цепочек на одном цилиндре, вторые на следующем и т. п., то время поиска можно оценить величиной 34 26 мс 0:9 с (или 1.8 с, если чтение производится с двух дисков). Наконец, если запросу удовлетворяет k записей, то потребуется k (60 мс (поиск)+12:5 мс(задержка)+0:2 мс (чтение)) дополнительного времени, чтобы достать их для последующей обработки. Таким образом, оптимистическая оценка полного времени обработки этого довольно сложного запроса дает число (10+0:073kt) с. Полученный результат можно сопоставить с 210 с, нужными на чтение всего файла с максимальной скоростью.
Рассмотренный пример показывает, что для дисковой памяти оптимизация по пространству тесно связана с оптимизацией по времени; время обработки инвертированных списков приблизительно равно времени, затрачиваемому на их поиск и чтение.
346 Original pages: 651-675
До сих пор в большей или меньшей степени предполагалось, что в процессе обработки запроса файл не растет и не сокращается; но что делать, если необходимы частые изменения? Во многих приложениях достаточно буферизовать ряд требуемых изменений и позаботиться о них в свободную минуту, когда не нужно отвечать на запросы. Если же изменения файла имеют высокий приоритет, напрашивается использование B-деревьев (п. 6.2.4). Все множество инвертированных списков можно было бы поместить в одно гигантское B-дерево, причем нетерминальные узлы должны содержать значения ключей, а листья—и ключи, и списки указателей на записи.
В предыдущем обсуждении мы умолчали еще об одном трудном вопросе—о задаче булевых комбинаций запросов по области значений. Предположим, например, что записи файла описывают города Северной Америки и что запрашиваются названия всех городов с координатами
(21:49 < ШИРОТА 37:41 ) И (70:34 ДОЛГОТА 75:72 ):
Скорее всего, ни одна структура данных по-настоящему не годится для подобных ”запросов по прямоугольной области значений”. (Взглянув на карту, мы видим, что многие города удовлетворяют ограничению на широту, многие—на долготу, но едва ли найдется город, удовлетворяющий обоим ограничениям.) Пожалуй, наилучший подход состоит в довольно грубом расчленении множества всех возможных значений ШИРОТЫ и ДОЛГОТЫ, чтобы на каждый атрибут приходилось лишь несколько классов (например, можно округлять с недостатком до числа, кратного 5 ), и в последующем построении инвертированного списка для каждого комбинированного (ШИРОТА; ДОЛГОТА) класса. Это все равно, что иметь карты, где для каждого небольшого района отведена отдельная страница. При использовании интервалов в 5 к запросу имеют отношение восемь страниц, а именно (20 ; 70 ), (25 ; 70 ), : : : , (35 ; 75 ). Запрос необходимо обработать для каждой из них, либо производя более тонкое расчленение внутри страницы, либо непосредственно обращаясь к записям в зависимости от числа записей, соответствующих этой странице. По существу, получается структура дерева с двумерными разветвлениями во внутренних узлах.
Другой подход к запросам по прямоугольной области, также основанный на структуре дерева, предложил Брюс Мак-Натт. Пусть, например, нужно обработать запрос типа ”Какой город ближе всего к точке x?”, если дано значение x. Каждый узел предлагаемого Мак-Наттом бинарного дерева поиска соответствует городу y и ”контрольному радиусу” r; левое поддерево этого узла соответствует всем городам z, позднее вставленным в дерево, причем расстояние от y до z должно быть r + ; аналогично правое поддерево предназначено для расстояний r− . Здесь —данный допуск; города, отстоящие от y меньше, чем на r + , и больше, чем на r − , должны входить в оба поддерева. Поиск в таком ”почтовом дереве” позволяет выявить все города, отстоящие от данной точки менее, чем на . (См. рис. 45.)
В 1972 г. Мак-Натт и Эдвард Принг, основываясь на этой идее, провели несколько экспериментов. В качестве базы данных они использовали 231 наиболее населенный город континентальных Соединенных Штатов, взятый в случайном порядке. Контрольный радиус r уменьшался регулярным образом: при шаге влево r заменяли на 0:67r, а при шаге вправо—на 0:57r. Если же выбиралась вторая из двух последовательных правых ветвей, радиус оставался неизменным. В результате получилось, что при = 20 миль в дереве было 610 узлов, а при = 35 миль—1600 узлов. На рис. 45 изображены верхние уровни меньшего дерева. (В оставшихся уровнях Орландо (штат Флорида) появляется и ниже Джексонвила, и ниже Майами. Некоторые города встречаются довольно часто; так, 17 узлов предназначены для Броктона (штат Массачусетс)!)
Picture: |
Рис 45. Верхние уровни ”почтового дерева”. Чтобы найти все города, располо- |
|
женные вблизи данной точки x, начнем с корня: если x не далее 1800 миль от |
|
Лас-Вегаса, идем влево, в противном случае— вправо; затем повторяем этом |
|
процесс, пока не достигнем концевого узла. Метод построения дерева гаран- |
|
тирует, что в процессе поиска мы достигнем всех городов, отстоящих от x не |
|
более чем на 20 миль. |
Составные атрибуты. Можно скомбинировать два или более атрибутов в один суператрибут. Например, атрибут ”(КУРС; СПЕЦИАЛЬНОСТЬ)” в университетском регистрационном файле можно создать, комбинируя поля КУРС и СПЕЦИАЛЬНОСТЬ. При таком подходе на запрос часто можно ответить с помощью объединения нескольких коротких списков, а не с помощью пересечения более длинных списков.
Идею комбинации атрибутов развил В. Лум [CACM, 13 (1970), 660–665]. Он предложил упорядочивать инвертированные списки, соответствующие составным атрибутам, лексикографически
Original pages: 651-675 347
слева направо и изготовлять несколько копий, в которых отдельные атрибуты переставлены надлежащим образом. Предположим, например, что имеются три атрибута A, B, C; можно образовать три комбинированных атрибута
(A; B; C); (B; C; A); (C; A; B) |
(1) |
и для каждого из них построить упорядоченный инвертированный список. (Так, в первом списке записи упорядочены по значению поля A; все записи с одним и тем же значением A упорядочены по B, а затем и по C.) Подобная организация позволяет отвечать на запросы, состоящие из любой комбинации этих трех атрибутов; например, все записи, имеющие определенные значения A и C, расположены в третьем списке друг за другом.
Аналогично из четырех атрибутов A, B, C, D можно образовать шесть комбинированных атрибутов
(A; B; C; D); (B; C; D; A); (B; D; A; C);
(2)
(C; A; D; B); (C; D; A; B); (D; A; B; C);
что позволяет отвечать на все комбинации простых запросов, в которых фиксированы, значения од-
ного, двух, трех или четырех атрибутов. Существует общая процедура построения |
|
n |
комбинирован- |
||||
ных атрибутов из n атрибутов, где k |
|
1n |
|
|
|
k |
|
|
|
определенные комбинации |
|||||
|
2 |
; при этом все записи, имеющие |
|
|
|
|
|
k или n − k значений атрибутов, расположатся последовательно в одном из инвертированных списков (см. упр. 1). Или же можно обойтись меньшим числом комбинаций, если некоторые атрибуты имеют ограниченное количество значений. Например, если атрибут D может принимать лишь два значения, то три комбинации
(D; A; B; C); (D; B; C; A); (D; C; A; B); |
(3) |
полученные из (1) внесением Dв скобки, будут почти столь же хороши, как и (2), ибо для ответов на запросы, не зависящие от D, один из списков достаточно просмотреть всего в двух местах. Избыточность же информации уменьшилась вдвое.
Бинарные атрибуты. Полезно рассмотреть частный случай, когда все атрибуты могут принимать лишь два значения. По существу, мы имеем полную противоположность комбинированию атрибутов, так как можем представить любое значение в виде двоичного числа и рассматривать биты этого числа как отдельные атрибуты. В табл. 1 (см. McCall, Cook Book (New York: Random House (1963), Ch. 9) приведен типичный файл, содержащий атрибуты ”да-нет”; в данном случае записи описывают избранные рецепты печенья, а атрибуты определяют, какие ингредиенты используются. Например, для приготовления миндальных вафель с ромом нужны масло, мука, молоко, орехи и сахарный песок. Если трактовать табл. 1 как матрицу из нулей и единиц, то транспонированная матрица дает инвертированный файл в виде цепочек битов.
Picture: Таблица 1 Файл с бинарнымн атрибутами
В правом столбце табл. 1 указаны особые, редко употребляемые продукты Их можно было бы закодировать более эффективно, не отводя каждому по столбцу; то же справедливо и для столбца ”маисовый крахмал”. Можно было бы эффективнее закодировать столбец ”мука”, ибо мука входит во все блюда, кроме меренги. Сейчас, однако, давайте оставим в стороне эти соображения и просто проигнорируем столбец ”особые ингредиенты”.
Определим базовый запрос в файле с бинарными атрибутами, как запрос на все записи, имеющие нули в определенных столбцах, единицы—в некоторых других и произвольные значения—в оставшихся столбцах. Используя ” ” для обозначения произвольного значения, любой базовый запрос можно представите в виде последовательности нулей, единиц и звездочек. Представим себе человека, которому вдруг очень захотелось печенья с кокосовыми орехами, тогда как шоколад вызывает у него аллергию, анис он не терпит, а ванилина в доме нет. Он может сформулировать такой запрос:
0 0 1 0 |
(4) |
Таблица 1 покажет, что на самом деле ему хочется палочек ароматных с черносливом.
Прежде чем рассмотреть общую задачу организации файла для обработки базовых запросов, важно изучить частный случай, когда запрос состоит только из единиц и звездочек. Такой запрос можно назвать включающим, ибо он запрашивает все записи, включающие в себя некоторое множество атрибутов (естественно считать, что единица обозначает имеющийся атрибут, а нуль—отсутствующий). Например, из рецептов табл. 1 только глазированные имбирные пряники и старинное сахарное печенье содержат и пекарный порошок, и пищевую соду.
348 Original pages: 651-675 |
|
|
|
|
|
|
Таблица 2 |
Пример кодирования наложением |
|
||
Коды отдельных специй |
|
|
|
Абрикосы |
1000010000 |
Лимонный сок |
1000100000 |
Апельсины |
0100000100 |
Мед |
0000000011 |
Арахисовое масло |
0000000101 |
Меласса |
1001000000 |
Бананы |
0000100010 |
Мускатный орех |
0000010010 |
Ванилин |
0000001001 |
Мускатный ”цвет” |
0000010100 |
Душистый перец |
0000100001 |
Орехи |
0000100100 |
Засахаренная вишня |
0000101000 |
Перец |
0010000100 |
Зерна аниса |
0000011000 |
Смородиновое желе |
0010000001 |
Изюм |
0101000000 |
Финики |
1000000100 |
Имбирь |
0000110000 |
Цитрон |
0100000010 |
Кардамон |
1000000001 |
Чернослив |
0010000010 |
Кокосовые орехи |
0001010000 |
Чеснок |
0001100000 |
Корица |
1000000010 |
Шоколад |
0010001000 |
Кофе |
0001000100 |
Экстракт миндаля |
0100000001 |
Лимонная цедра |
0011000000 |
Яблочный соус |
0010010000 |
Наложенные коды |
|
|
|
Бананово-овсяное печенье |
|
|
1000111111 |
Ванильно-ореховое мороженое |
|
|
0000101101 |
Воздушное печенье |
|
|
0000001001 |
Глазированные имбирные пряники |
|
|
1001110010 |
Драгоценное печенье |
|
|
0010101101 |
Драже в шоколаде |
|
|
0010101100 |
Ириски |
|
|
0010111101 |
Малайский крендель |
|
|
1011100101 |
Медовые пряники |
|
|
1011110111 |
Меренги |
|
|
1000101100 |
Миндальное печенье с кокосами |
|
|
0001111101 |
Миндальные вафли с ромом |
|
|
0000100100 |
Моравское печенье со специями |
|
|
1001110011 |
Овсяные палочки с финиками |
|
|
1000100100 |
Палочки ароматные с черносливом |
|
|
0111110110 |
Печенье с арахисовым маслом |
|
|
0010001101 |
Печенье с орехами |
|
|
1101010110 |
Печенье с перцем |
|
|
1111111111 |
Печенье с яблочным соусом |
|
|
1111111111 |
Печенье со сливочным сыром |
|
|
0010001001 |
Полукруглый пирог с начинкой |
|
|
1011101101 |
Путаница |
|
|
1000001011 |
Райские палочки |
|
|
0001111101 |
Рождественское анисовое печенье |
|
|
0011011000 |
Старинное сахарное печенье |
|
|
0000011011 |
Фигурные песочные коржики |
|
|
0000000000 |
Финский какор |
|
|
0100100101 |
Шведский крендель |
|
|
0000000000 |
Швейцарское рассыпное печенье с корицей |
|
|
1000000010 |
Шоколадный хворост |
|
|
0010101101 |
Шотландские овсяные коржики |
|
|
0000001001 |
Юбочки |
|
|
0000001001 |
Для некоторых приложений достаточно обеспечить лишь ответы на включающие запросы. Это справедливо, например, для ручных файловых систем ”на картах с краевой перфорацией” или ”картотек признаков”. Система на картах с краевой перфорацией, соответствующая табл. 1, содержала бы по карте на каждый рецепт, где каждому ингредиенту соответствует вырез. (См. рис. 46.) Для обработки включающего запроса карты складывают в аккуратную стопку и вводят спицы в позиции, соответствующие требуемым атрибутам. Затем спицы поднимают и карты, имеющие нужные
Original pages: 651-675 349
атрибуты, выпадают.
Picture: |
Рис. 46. Карта с краевой перфорацией. |
”Картотека признаков” аналогично работает с инвертированным файлом. В этом случае имеется по карте на каждый атрибут. Для каждой записи на картах отводится определенная позиция, и если запись обладает некоторым атрибутом, то в соответствующем месте делается пробивка. Следовательно, обычная 80-колонная перфокарта может содержать информацию о 12 80 = 960 записях. Для обработки включающего запроса отбирают карты нужных атрибутов, кладут их вместе и просвечивают; лучи света пройдут через все позиции, соответствующие искомым записям. Эта операция аналогична обработке булевых запросов путем пересечения инвертированных цепочек битов.
Кодирование наложением. Причина нашего особого интереса к ручным картотекам кроется в том, что было придумано много остроумных способов экономии места на картах с краевой перфорацией; тот же подход применим к представлению файлов в памяти ЭВМ. Кодирование наложением напоминает хеширование; в действительности его изобрели за несколько лет до открытия хеширования. Идея состоит в том, чтобы отобразить атрибуты в случайные k-битовые коды в n-битовых полях и наложить коды имеющихся в записи атрибутов. Включающий запрос о некотором множестве атрибутов можно преобразовать во включающий запрос о соответствующих наложенных двоичных кодах. Этому запросу могут удовлетворять несколько лишних записей, но количество таких ”ложных выпадений” можно статистически учесть. [Ср. с Calvin N. Mooers, Amer. Chem. Soc. Meeting, 112 (September, 1947), 14E–15E; American Documentation, 2 (1951), 20–32.] В качестве примера кодирования наложением вновь рассмотрим табл. 1, но не всю, а только часть, касающуюся специй. В табл. 2 показано, что получится, если присвоить атрибутам специй случайные двухбитовые коды в десятибитовом поле и наложить их. Например, элемент ”шоколадный хворост” получается наложением кодов для шоколада, орехов и ванилина:
0010001000 _ 0000100100 _ 0000001001 = 0010101101:
Наложение кодов привносит также несколько лишних атрибутов, в данном случае это душистый перец, засахаренная вишня, смородиновое желе, кокосовое масло и перец, что приведет к ”ложным выпадениям” при ответах на некоторые запросы (кроме того, у нас появился новый рецепт—ложного печенья!) На самом деле кодирование наложением не очень хорошо работает для табл. 2, являющейся маленьким примером со многими атрибутами. Действительно, печенье с яблочным соусом будет выпадать при каждом запросе, ибо эта запись получена наложением семи кодов, покрывающих все десять позиций; еще хуже обстоит дело с печеньем с перцем, полученным наложением двенадцати кодов! С другой стороны, в некоторых случаях табл. 2 работает удивительно хорошо; например, в
ответ на запрос о ванилине зря выпадет лишь печенье с перцем. |
||
|
Более подходящим является пример, когда у нас есть, скажем, 32-битовое поле и множество |
|
32 |
|
|
из |
= 4960 различных атрибутов. Каждая запись может иметь до шести атрибутов, и каждый |
|
|
3 |
|
атрибут кодируется спецификацией 3-х из 32-х битов. Если мы предположим, что каждая запись имеет шесть случайно выбранных атрибутов, то вероятности ложного выпадения при включающем
запросе таковы: |
|
|
|
по одному атрибуту: |
0:07948358; |
|
|
по двум атрибутам: |
0:00708659; |
|
|
по трем атрибутам: |
0:00067094; |
(5) |
|
по четырем атрибутам: |
0:00006786; |
||
|
|||
по пяти атрибутам: |
0:00000728; |
|
|
по шести атрибутам: |
0:00000082: |
|
Таким образом, если есть M записей, не удовлетворяющих запросу по двум атрибутам, то приблизительно 0:007M имеют наложенные коды, покрывающие все биты этих двух атрибутов. (Вероятности подсчитаны в упр. 4.) Для представления инвертированного файла потребуется 32 (количество записей) битов, что составляет менее половины от числа битов, необходимых для описания собственно атрибутов в исходном файле.
Малькольм Ч. Харрисон [CACM, 14 (1971), 777–779] заметил, что кодирование наложением можно использовать для ускорения поиска текста. Предположим, что нам нужно определить все вхождения некоторой цепочки литер в большой текст без построения громоздкого дерева Патриции. Предположим, кроме того, что текст поделен на строки c1 c2 : : : c50 по 50 литер в каждой. Харрисон предлагает кодировать каждую из 49 пар c1 c2, c2 c3, : : : , c49 c50 путем отображения ее в число от 0 до, скажем, 127. Затем подсчитать ”сигнатуру” строки c1 c2 : : : c50—цепочку из 128 битов b0 b1 : : : b127, где bi = 1 тогда и только тогда, когда h(cj; cj+1) = i при некотором j.
350 Original pages: 651-675
Если нам нужно найти все вхождения слова ИГОЛКА в большой текстовой файл СТОГСЕНА, мы просто отыскиваем все строки, сигнатуры которых содержат 1 в позициях h(ИГ), h(ГО), h(ОЛ), h(ЛК), h(KA). При случайной хеш-функции вероятность того, что некоторая случайная строка содержит в сигнатуре все эти единицы, равна всего лишь 0.00341 (ср. с упр. 4); следовательно, пересечение пяти инвертированных списков цепочек битов позволит быстро найти все строки, содержащие слово ИГОЛКА (хотя, вероятно, будет и несколько ложных выпадений).
Вданном приложении гипотеза о случайности на самом деле несправедлива, поскольку обычные тексты несут в себе большое количество избыточной информации; распределение двухбуквенных комбинаций в словах весьма неравномерно. Например, полезно отбросить все пары cj cj+1, содержащие литеру ”пробел”, так как обычно пробелы встречаются гораздо чаще других литер.
Другое интересное применение кодирования наложением к задачам поиска нашел Бартон Блум [CACM, 13 (1970), 422–426]; на самом деле его метод предназначен для выборки по первичным ключам, но нам удобно обсудить его в этом параграфе. Представим себе, что производится поиск в большой совокупности данных, причем, если он оказался неудачным, никаких действий выполнять не нужно. Например, мы хотим проверить чей-либо номер паспорта или сумму выплаченных налогов и т. п.;
иесли в файле нет соответствующей этому лицу записи, то дальнейшего исследования не требуется. Аналогично при применении ЭВМ для типографского набора текстов можно придумать простой алгоритм, который позволит правильно делать переносы для большинства слов, но будет неприменим к 50000 словам-исключениям. Если какое-либо слово не удастся найти в файле исключений, можно использовать этот простой алгоритм.
Вподобной ситуации можно хранить во внутренней памяти таблицу битов, так что в большинстве случаев отсутствие ключа будет опознало без обращений к внешней памяти. Вот как это делается. Обозначим внутреннюю таблицу битов через b0 b1 : : : bM−1, где M весьма велико. Для каждого ключа Kj файла вычислим k независимых хеш-функций h1(Kj), : : :, hk(Kj) и установим соответствующие k битов равными 1. (Эти k значений не обязаны быть различными.) Таким образом, bi = 1 тогда и только тогда, когда hl(Kj) = i при некоторых j и l. Теперь для того, чтобы определить, содержится ли аргу-
мент поиска K во внешнем файле, нужно сначала проверить, выполняется ли соотношение bhl(K) = 1 при 1 l k: если нет, то незачем обращаться к внешней памяти, если же да, то при подходящем
выборе K и M обычными методами поиска нам скорее всего удастся найти K. Вероятность ложного выпадения для файла из N записей приближенно равна (1 − e−kN=M)k. По существу, в методе Блума весь файл рассматривается как одна запись, первичные ключи трактуются как имеющиеся атрибуты,
акодирование наложением производится в огромном M-битовом поле.
Еще один вариант кодирования наложением в своей докторской диссертации разработал Ричард Густафсон (Univ. South Carolina, 1969). Предположим, имеется N записей и каждая из них содержит 6 из 10000 возможных атрибутов. Например, записи могли бы описывать технические статьи, а атрибуты—имеющиеся в них ключевые слова. Пусть h—хеш-функция, отображающая каждый атрибут в число между 0 и 15. Если запись обладает атрибутами a1, a2, : : : , a6, то по методу Густафсона она отображается в 16-битовое число b0 b1: : : b15, где bi = 1 тогда и только тогда, когда h(aj) = i
при некотором |
j |
; далее, если лишь |
k |
битов стали единичными, |
k < |
6, то другие 6 − |
k единиц добавля- |
||||
|
|
|
|
|
|
16 |
|
||||
ются неким случайным образом (не обязательно зависящим от самих записей). Имеется |
|
9 |
= 8008 |
||||||||
16-битовых кодов, содержащих ровно шесть единиц; при известной доле везения |
приблизительно |
||||||||||
|
|
|
|
||||||||
N=8008 записей отобразятся в каждое значение. Можно хранить 8008 списков записей, с помощью подходящей функции вычисляя адрес, соответствующий коду b0 b1: : : b15. В самом деле, если единицы расположены в позициях 0 p1 < p2 < : : : < p6, функция
p11 + p22 + + p66
преобразует каждую цепочку b0 b1: : :b15 в число между 0 и 8007, причем разные цепочки порождают разные адреса. (См. упр. 1.2.6-56 и 2.2.6-7.)
Если теперь мы хотим найти все записи, имеющие три определенных атрибута A1, A2, A3, то следует вычислить h(A1), h(A2) и h(A3); если эти значения различны, нам придется просмотреть записи, хранящиеся в 133 = 286 списках, коды которых b0 b1: : :b15 содержат единицы в позициях h(A1), h(A2)и h(A3). Иными словами, исследованию подвергнутся лишь286 100=8008 3:5% записей.
Комбинаторное хеширование. Основная идея только что описанного метода Густафсона состоит в том, чтобы найти такой способ отображения записей в адреса памяти, чтобы лишь небольшое количество адресов было связано с определенным запросом. Однако этот метод применим лишь к включающим запросам, когда отдельные записи обладают малым числом атрибутов. Другой тип отображений, предназначенных для обработки произвольных базовых запросов типа (4), состоящих из нулей, единиц и звездочек, открыл в 1971 г. Рональд Райвест.
