Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Основы программирования. Борисенко

.pdf
Скачиваний:
1534
Добавлен:
09.04.2015
Размер:
9.31 Mб
Скачать

4.6.3. Реализации множества на базе деревьев

303

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

h < 2 l o g 2 ( n + 1).

Из этого следует, что поиск в красно-черном дереве т а к ж е выполня¬ ется за логарифмическое время.

Новая вершина добавляется в красно-черное дерево как терми¬ нальная после процедуры поиска (этим RB-дерево ничем не отли¬ чается от других упорядоченных деревьев). Новая вершина окраши¬ вается в красный цвет. При этом пункт 3) в определении красночерного дерева может нарушиться. Поэтому после добавления, а так¬ ж е удаления вершины выполняется процедура восстановления струк¬ туры красно-черного дерева, играющая ту ж е роль, что и восстанов¬ ление балансировки AVL-дерева. Преимущество красно-черных де¬ ревьев состоит в том, что процедура восстановления более простая. Во многих случаях она ограничивается перекрашиванием вершин. В ней т а к ж е могут выполняться операции вращения вершины влево и

вправо (см. с. 301), но число вращений может

быть не больше двух

при добавлении

элемента и не больше четырех

при удалении. Всего

число операций

при восстановлении структуры

RB-дерева оценива¬

ется сверху через высоту дерева:

число операций < K • h,

где h — высота дерева, K — константа. Поскольку дл я высоты R B - дерева справедлива приведенная выше логарифмическая оценка от числа вершин n, получаем оценку

число операций < C log 2 n,

где C — константа. Таким образом, добавление и удаление элементов выполняется в случае красно-черных деревьев за логарифмическое время в зависимости от числа вершин дерева.

304

4.6. Множество

4.6.4.Хеширование

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

Идея хеш - реализации состоит в том, что мы сводим работу с од¬ ним большим множеством к работе с массивом небольших множеств. Рассмотрим, к примеру, записную книжку. Она содержит список фа¬ милий людей с их телефонами (телефоны — это нагрузка элементов множества). Страницы записной к н и ж к и помечены буквами алфа¬ вита; страница, помеченная некоторой буквой, содержит только фа¬ милии, начинающиеся с этой буквы. Таким образом, все множество фамилий разбито на 28 подмножеств, соответствующих буквам рус¬

ского алфавита. При поиске фамилии мы сразу открываем

записную

к н и ж к у на странице, помеченной

первой буквой фамилии, и в ре¬

зультате поиск значительно убыстряется.

 

Разбиение множества на подмножества осуществляется с помо¬

щью так называемой хеш-функции.

Хеш - функция определена на эле¬

ментах множества и принимает

целые неотрицательные

значения.

Она должна быть подобрана таким образом, чтобы 1) ее можно было легко вычислять; 2) она должна принимать всевозможные различ¬ ные значения приблизительно с равной вероятностью; 3) желатель¬

но, чтобы на близких значениях аргумента

она принимала

далекие

друг от друга значения (свойство, противоположное

математическо¬

му понятию непрерывности). В приведенном

примере

значение хеш -

функции

дл я данной фамилии равно номеру

первой буквы

фамилии

в русском

алфавите.

 

 

 

Параметром реализации является число подмножеств, которое также называют размером хеш-таблицы. Пусть он равен N . Тогда хеш - функция должна принимать значения 0, 1, 2, ... , N — 1. Ес­ ли изначально у нас есть некоторая функция H ( x ) , принимающая произвольные целые неотрицательные значения, то в качестве хеш - функции можно использовать функцию h(x), равную остатку от де¬ ления H ( x ) на N .

Множество M разбивается на N непересекающихся подмно­ жеств, занумерованных индексами от 0 до N — 1. Подмножество Mi

хеш-таблица

4.6.4. Хеширование

 

305

с индексом i содержит все элементы

x из

M , значения хеш - функции

для которых равняются i :

 

 

M i = { x G M

: h(x)

= i }

При поиске элемента x сначала вычисляется значение его хешфункции: t = h(x). Затем мы ищем x в подмножестве M t . Поскольку это подмножество небольшое, то поиск осуществляется быстро. Д л я эффективной реализации каждое подмножество должно в большин¬

стве случаев

содержать не больше

одного элемента. Следователь¬

но, размер хеш-таблицы N должен

быть больше, чем среднее чис¬

ло элементов

множества. Обычно N

выбирают превышающим число

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

М ы привели

только идею хеш - реализации

множества.

Различ¬

ные конкретные

схемы по-разному

реализуют

массив подмножеств

и борются с коллизиями. Приведем

наиболее универсальную

схему,

использующую ссылочную реализацию. Каждое подмножество пред¬ ставляется в виде линейного однонаправленного списка, содержа¬ щего элементы подмножества. Таким образом, имеемся N списков. Головы всех списков хранятся в одном массиве размера N , кото¬ рый называется хеш-таблицей. Элемент хеш-таблицы с индексом i представляет собой голову i-го списка. Голова содержит ссылку на первый элемент списка, первый элемент — на второй и так далее. Последний элемент в цепочке содержит нулевую ссылку, т.е. ссылку «в никуда». Если список пустой, то элемент хеш-таблицы с индексом i содержит нулевую ссылку.

голова i-го списка

|

306

4.6. Множество

4.6.5.Циклы «для каждого» и итераторы

Часто бывает необходимо перебрать все элементы структуры дан¬ ных и для каждого элемента выполнить некоторое действие. Обычно при записи алгоритмов на неформальном языке используется кон¬ струкция «цикл для каждого»; например, в случае множества M можно записать следующий фрагмент программы:

цикл для каждого элемента x из множества M | действие(х); конец цикла

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

установить указатель в начало списка; цикл пока указатель не в конце списка | действие(элемент за указателем); | передвинуть указатель вперед; конец цикла

Множество отличается от всех других структур данных тем, что цикл «для каждого» невозможно смоделировать, пользуясь други¬ ми предписаниями множества. Поэтому выполнение такого цикла должно быть обеспечено реализацией множества. Метод построения цикла «для каждого» в сильной степени зависит от используемого языка программирования и от конкретной библиотеки классов или подпрограмм. Как правило, создается некоторый объект, позволяю¬ щий последовательно перебирать элементы структуры. Внутреннее устройство такого объекта зависит от структуры данных и скрыто от программиста. Объект такого типа обычно называют «итератором» или «энумератором». С итератором возможны два действия:

1) проверить, есть ли

еще элементы

структуры данных, которые

не были перебраны

на предыдущих

шагах;

2) получить следующий элемент структуры данных.

4.7 Задачи по теме «Структуры данных»

307

Реализация структуры данных должна обеспечить реализацию пред¬ писания, которое создает и инициализирует объект типа итератор, предназначенный для перебора элементов структуры.

В случае множества M цикл «для каждого» записывается с по¬ мощью итератора примерно так:

итератор := начать перебор элементов множества M;

цикл пока итератор.есть еще элементы

| x := итератор.получить

следующий элемент множества;

| действие^);

 

конец цикла

 

М ы рассмотрели наиболее

важные структуры данных, использу¬

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

4.7.Задачи по теме «Структуры данных»

1.Выписать на языке Си реализацию очереди на базе "кольцево­ го" массива.

2.Реализовать двунаправленный список на базе двух стеков (вы¬ писать реализацию на псевдокоде или на Си).

3.Выписать на Си непрерывную реализацию нагруженного мно¬ жества с помощью бинарного поиска. Элементами множества являются текстовые строки, нагрузками элементов — целые числа. Используя эту реализацию, написать программу, кото¬ рая находит множество всевозможных слов в заданном тексто-

308

4.7. Задачи по теме «Структуры данных»

вом файле, а также количество вхождений каждого слова в текст.

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

(i)поиск минимальной вершины дерева;

(ii)поиск максимальной вершины дерева;

(iii)для заданной вершины найти следующую по порядку;

(iv)напечатать по порядку все значения в вершинах;

(v)поиск вершины с заданным значением. Если такой верши¬ ны нет, то найти родительскую вершину;

(vi)добавление к дереву вершины с заданным значением с со¬ хранением упорядоченности;

5. Та ж е

задача, что

и в пункте

3, для случая реализации множе¬

ства на базе хеш - функции и

массива списков (эта реализация

была

рассмотрена

в разделе

4.6.4).

6.Реализовать множество на Си или псевдокоде с помощью хешфункции, используя следующий механизм разрешения колли¬ зий:

• хеш-таблица представляет собой обычный массив элемен¬ тов того ж е типа, что и элементы множества;

• помимо хеш-таблицы, используется целочисленный массив "мощность слоя" такого ж е размера. В его ячейке с индек¬ сом i хранится общее число элементов множества, значе­ ние хеш - функции на которых равно i ;

используется также массив "остаток" достаточно большой длины, тип его элементов совпадает с типом множества;

если в множестве есть только один элемент со значением хеш -функции, равным i , то он хранится в i-й ячейке хештаблицы. В противном случае, т.е. при коллизии, первый элемент помещается в i-ю ячейку хеш-таблицы, а осталь¬ ные элементы со значением хеш - функции, равным i , запи¬ сываются в массив "остаток" в произвольном порядке.

Литература

[1]

Д . Кнут.

Искусство программирования для Э В М . Т.

1-3. —

 

Пер. с англ. — М . : М и р , 1976-1978.

 

[2]

Д . Кнут.

Все про TgX. — Пер. с англ. — Протвино,

Р Д Т Х ,

 

1993.

 

 

[3]А. Г. Кушниренко, Г. В. Лебедев. Программирование дл я ма­ тематиков: Учебное пособие для вузов. — М . , Наука, 1988. — 384 с.

[4]А. П. Ершов, А. Г. Кушниренко, Г. В. Лебедев, А. Л . Семенов, А. Х. Шень. Основы информатики и вычислительной техники.

— М . : Просвещение, 1988.

[5]А. Г. Кушниренко, Г. В. Лебедев, Р. А. Сворень. Основы инфор¬ матики и вычислительной техники. — М . : Просвещение, 1990, 1991, 1993, 1996.

[6]Б. Керниган, Д . Ритчи. Язык программирования Си. — Пер. с англ. — М . : Финансы и статистика, 1992.

[7]

Б.

Страуструп.

Язык

программирования

С и + + . — Пер. с

 

англ. — М . : «Радио и связь», 1991. — 352 с.

 

[8]

Б.

Страуструп.

Язык

программирования

C + + (третье

изда¬

 

ние). — Пер. с

англ. — СПб., М . : «Невский диалект».

Изда¬

 

тельство «Бином», 1999.

 

 

[9]П. Нотон. Java. Справочное руководство. — Пер. с англ. — М . : Восточная К н и ж н а я Компания, 1996. — 448 с.

310

ЛИТЕРАТУРА

[10] Б. Э. Смит, М . Т. Джонсон . Архитектура и программирование микропроцессора I N T E L 80386. — Пер. с англ. — М . : Конкорд, 1992. — 334 с.

[11]

Ф. Доймлинг, Д .

Силлеску.

Язык

программирования

 

PostScript. — Пер. с

нем. — М . :

Физматлит, 1993. — 136

 

с.

 

 

 

[12]

Н. Вирт. Алгоритмы +

структуры данных =

программы. — Пер.

с англ. — М . : М и р , 1985.

[13] Н. Вирт. Программирование на языке Модула-2. — Пер. с англ. — М . : М и р , 1987. — 224 с.