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

Метод_материалы / Учебники / Программирование_С

.pdf
Скачиваний:
66
Добавлен:
16.03.2016
Размер:
2.31 Mб
Скачать

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

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

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

3.2.4.4. Разнородные бинарные деревья

Часто информация, содержащаяся в различных узлах бинарного дерева, имеет отличающиеся атрибуты. Например, представляя выражение с числовыми операндами, мы можем использовать бинарное дерево так, что его листы содержат числа, а узлы, не являющиеся листами, — символы, представляющие операторы. На рис. 3.11, а приведено такое бинарное дерево, представляющее выражение 3+4*(6–7)/5+3. Его особенность — то, что его узлы содержат данные двух разных типов — целые числа (операнды) и символы (операции).

Рис. 3.11. Представление выражения 3+4*(6-7)/5+3 в виде бинарного дерева

231

Одно из возможных решений для представления такого дерева в программе — создать массив символов всех арифметических операций oper {‘+’,’-‘,’*’.’/’,’^’}, а в структуре узла дерева (кроме обычных указателей) хранить два поля данных: одно — признак типа узла (например, 0 для операций и 1 для операнда), второе — индекс массива oper для операций и число для операнда. Таким образом, дерево стало однородным. Пример показан на рис. 3.11, б.

Если в качестве данных должны храниться не числа, а, например, строки символов, то можно создать дополнительный массив для строк.

Псевдокод:

{

int evbtree (ptr_tree)

{

if (ptr_tree->left == null)

{ //выражение — единственный операнд evbtree=num[ptr_tree->data)];

return evbtree;

}

else

{

//вычислить левое поддерево frsoper=evbtree(ptr_tree.left);

//вычислить правое поддерево secoper = evbtree(ptr_tree->right));

//выделить оператор osymb=oper[ptr_tree–>data];

//применить оператор и возвратить результат evbtree= apply (osymb, frsoper, secoper); return evbtree;

}f

}

}

Напишите программу, вручную заполняющую исходное разнородное дерево, строящую однородное дерево и вычисляющую результат.

3.2.4.5. Алгоритм и деревья Хаффмена

Предположим, что у нас есть алфавит из n символов и длинное сообщение, состоящее из символов этого алфавита. Мы хотим закодировать сообщение в виде длинной строки битов следующим образом. Присвоим каждому символу алфавита определенную последовательность битов (код). Затем соединим отдельные коды символов, составляющих сообщение, и получим кодировку сообщения. Например, предположим, что алфавит состоит из четырех символов — А, В, С и D — и что символам назначены следующие коды:

232

Символ

Код

A

010

B

100

C

000

D

111

Сообщение АВACCDА кодируется как 010100010000000111010. Однако такая кодировка неэффективна, поскольку, для каждого символа используются 3 бит, так что для кодировки всего сообщения требуется 21 бит. Можно улучшить кодировку (для этого примера), если каждому символу назначен 2-битовый код:

Символ

Код

A

00

B

01

C

10

D

11

Тогда кодировка сообщения была бы 00010010101100; требуется лишь 14 бит. Попытаемся найти код, который минимизирует длину закодированного сообщения — при любом количестве символов.

В нашем примере каждая из букв В и D появляется в сообщении только один раз, а буква А — три раза. Стало быть, если выбран код, в котором букве А назначена более короткая строка битов, чем буквам В и D, то длина закодированного сообщения будет меньше. Это происходит от того, что короткий код (представляющий букву А) появляется более часто, чем длинный. Коды могут быть назначены следующим образом:

Символ

Частота

Код

 

 

 

А

3

0

 

 

 

В

1

110

 

 

 

С

2

10

 

 

 

D

1

111

 

 

 

При использовании этого кода сообщение ABACCDA кодируется как 0110010101110, что требует 13 бит. В длинных сообщениях, содержащих символы, встречающиеся редко, экономия существенна. Обычно коды создаются на основе их частоты во всем множестве сообщений, например на основе известных данных о частоте появления символов алфавита в некотором языке.

233

При использовании кодов переменной длины код одного символа не должен совпадать с началом кода другого символа. В нашем случае битовая строка сообщения просматривается слева направо. Если первый бит равен 0, то это символ А; в противном случае это В, С или D и проверяется следующий бит. Если второй бит равен 0, то это символ С, иначе это В или D и надо проверить третий бит. Если он равен 0, значит это В, а если 1, то D. Как только распознан первый символ, процесс повторяется для нахождения второго символа, начиная со следующего бита.

Следующая схема кодирования основана на известных частотах появления каждого символа в сообщении. Находим два символа, появляющихся наименее часто. В нашем примере это В и D. Будем различать их по последнему биту кодов: 0 — В, 1 — D. Соединим эти символы в единый символ BD, появление которого означает, что это либо символ В, либо символ D. Частота появления этого нового символа равна сумме частот двух составляющих символов, то есть частота BD — 2. Теперь у нас есть три символа: А (частота 3), С (частота 2) и BD (частота 2). Снова выберем два символа с наименьшей частотой, т. е. С и BD. Будем различать их по последнему биту кодов: 0 — С, 1 — BD. Затем два символа объединяются в один символ CBD с частотой 4. К этому времени осталось только два символа — А и CBD. Они объединяются в один символ ACBD с частотой 7. Будем различать их по последнему биту кодов: 0 — А, 1 — CBD.

Продолжим процесс кодирования. Символ ACBD содержит весь алфавит, ему присваивается в качестве кода пустая строка битов нулевой длины. Двум символам, составляющим ACBD (А и CBD), присваиваются соответственно коды 0 и 1: если встречается 0, значит закодирован символ А, а если 1, то это С, В или D. Аналогично двум символам, составляющим CBD (С и BD), назначаются соответственно коды 10 и 11. Первый бит указывает, что символ входит в группу CBD, а второй позволяет отличить С и BD. Символам, составляющим BD (В и D), назначаются соответственно коды 110 и 111.

Операция объединения двух символов в один предполагает использование структуры бинарного дерева. Каждый лист представляет символ исходного алфавита. Каждый узел, не являющийся листом, представляет соединение символов из листов и/или узлов — потомков данного узла. На рис. 3.12 приведено бинарное дерево, построенное с использованием нашего примера. Каждый узел содержит символ и его частоту. Такие деревья называют деревьями Хаффмена, по имени изобретателя этого метода кодирования.

Рис. 3.12. Дерево Хаффмена для сообщения АВACCDА

234

Как только дерево Хаффмена построено, код любого символа алфавита может быть определен просмотром дерева снизу вверх, начиная с листа, представляющего этот символ. Начальное значение кода — пустая строка. Каждый раз, когда мы поднимаемся по левой ветви, к коду слева приписывается 0; каждый раз при подъеме по правой ветви к коду слева приписывается 1. Например, если начать с листа для символа B, мы получим последовательно 0 – 10 – 110.

Поле данных узла дерева содержит частоту появления символа, представленного этим узлом.

Входы алгоритма: число символов исходного алфавита n и frqncy — массив размера не меньше n, такой что frqncy[i] — относительная частота i-го символа. В алгоритме должны присваиваться значения массиву code размером не меньше n; code[i] содержит код, присвоенный i-му символу.

В алгоритме также строится массив указателей pstn размера не меньше n; pstn[i] указывает на узел, представляющий i-й символ. Этот массив необходим для идентификации точки дерева, с которой надо начинать составление кода для конкретного символа алфавита. Как только дерево построено, для определения того, какой бит (0 или 1) должен подставляться слева в текущее значение кода при «подъеме» по дереву, может использоваться функция IsLeft.

Ниже приведен псевдокод для алгоритма Хаффмена (множество rootnodes содержит указатели на корни частичных бинарных деревьев, которые не являются еще левыми или правыми поддеревьями):

{

rootnodes= //пустое множество //построить узел для каждого символа for ( i = 1 to n )

{

p=IniTree();

p–> data = frqncy[i]; pstn[i]=p;

//добавить p к rootnodes;

}

//построить дерево

while ( // rootnodes содержит больше одного элемента )

{

р1= //элемент в rootnodes с наименьшим значением data; //удалить р1 из rootnodes

р2= //элемент в rootnodes с наименьшим значением data; //удалить р2 из rootnodes

//объединить р1 и р2 как ветви одного дерева;

р=IniTree();

p–>data = р1–>data + р2–>data; //установить р1 и р2 в качестве сыновей p; //добавить р к rootnodes;

235

}

//дерево создано; использовать его для нахождения кодов гооt =// единственный элемент rootnodes;

for (i= 1 to n)

{

code[i]=” “; p = pstn[i];

//пройти вверх по дереву; while ( p != root )

{

if IsLeft(p)

code [i] =”0”+code[1]; else

code[i] =”1”+code[i]; p=Father(p);

}

}

}

Напишите программу для кодирования символов сообщения, использующую алгоритм Хаффмена. Вход программы — это число n, являющееся числом символов алфавита, за которым следует множество из n пар; каждая пара состоит из символа и его относительной частоты. Вначале программа строит строку str, состоящую из всех символов алфавита, и массив code, в котором code[i] равно коду, назначенному i-му символу в str. Затем программа печатает каждый символ, его относительную частоту и код.

Вам будет необходимо написать функции IsLeft и Father, роль которых ясна из названий. Заметим, что для путешествия по дереву вверх следует добавить в структуру узла дополнительный указатель.

Включите в программу функцию encode, которая получает строку str и массив code, сообщение msg и возвращает кодировку сообщения в битовой строке.

Исходное сообщение (при наличии битовой кодировки сообщения и дерева Хаффмена) может быть восстановлено следующим способом. Для каждого символа начинаем с корня дерева. Каждый раз, когда встречается 0, двигаемся по левой ветви, и каждый раз, когда встречается 1, двигаемся по правой ветви. Повторяем этот процесс до тех пор, пока не дойдем до листа. Новый символ исходного сообщения есть символ, соответствующий этому листу. Повторяем процесс, начиная снова с корня дерева и с бита кодировки, следующего за последним использованным. Для нашего примера кодировка 0110010101110, поэтому первый символ сообщения — A — получаем при первом же шаге по дереву влево, следующий символ — B — получим, шагнув от корня вправо (1), еще раз вправо (1), и наконец влево (0), и так далее.

Включите в программу функцию decode, которая получает кодировку сообщения в битовой строке, дерево Хаффмена и возвращает сообщение msg.

236

3.2.4.6. Удаление из дерева бинарного поиска

Приведем теперь алгоритм, который удаляет узел с ключом key из дерева бинарного поиска, после чего это дерево остается опять же деревом бинарного поиска. Всего возможны три случая. Если удаляемый узел не имеет потомков, то он может быть удален без перестройки дерева (рис. 3.13, а)

Рис. 3.13. Удаление узлов из дерева бинарного поиска а — удаление узла с ключом 15; б — удаление узла с ключом 5; в — удаление

узла с ключом 11.

237

Если удаляемый узел имеет только одно поддерево, то его единственный потомок может быть перемещен вверх, чтобы занять его место (рис. 3.13, б). Если же удаляемый узел имеет два поддерева, задача становится более сложной, так как нельзя привязать две ссылки вверх на место одной. Однако есть достаточно простой рецепт: удаляемый узел заменяется либо на самого левого потомка его правого поддерева (узел 12 в нашем примере, рис. 3.13, в), либо на самого правого потомка его левого поддерева (узел 10 в нашем примере; сделать рисунок предоставляем читателю).

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

{

р = tree; // указатель на вершину дерева

//поиск узла с ключом key: установить р так, чтобы оно указывало на

данный

//узел, a q — на его отца, если такой существует

while (р != null) and (p->data != key)

{

p;

if (key< p->data p = p->left;

else

p = p->right;

};

if (p == NULL)

//этот ключ не существует в данном дереве,

//дерево надо оставить неизменным

return;

//установить в переменную v узел, который заменит p. Первые два

//случая — узел, который должен быть удален, имеет максимум одного

//потомка if (p–>Ieft == NULL)

v= p->right;

else

if (p.right == NULL)

v= p->Ieft;

else

{

//третий случай — узел р имеет двух потомков. Установить в переменную

//v преемника элемента р в симметричном порядке, а в переменную t —

//отца переменной v

t= p;

v = p–>right;

 

s = v–>left;

// s является левым потомком v

 

238

while (s != NULL)

{

t = v; v = s;

s = v–>left;

}

// в этот момент переменная v является преемником узла p в

//симметричном порядке if (t !=p)

{

//р не является отцом переменной v, и v = t–>left. Удалить узел v из его

//текущей позиции и заменить его на правого сына узла v

t–>left = v–>right;

//Настроить сыновей v так, чтобы они были сыновьями p v–>right = p–>right;

}

v–>left = p–>left;

}

// вставить узел v в позицию, которую первоначально занимал узел p if (q == NULL)

//Узел p был корнем дерева tree=v;

else

if (p == q–>left) q–>left = v;

else

q–>right = v; delete p;

return tree;

}

3.2.4.7. Другие задачи на бинарные деревья

3.2.4.7.1. Корень бинарного дерева Покажите, что корень бинарного дерева является предком любого узла дерева,

за исключением себя. Программа просматривает все узлы дерева. 3.2.4.7.2. Узел бинарного дерева

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

3.2.4.7.3. Предки узла бинарного дерева

Сколько предков имеет узел уровня n бинарного дерева? Программа просматривает все узлы дерева, начиная с корня.

3.2.4.7.4. Максимальное число узлов бинарного дерева

Чему равно максимальное число узлов уровня n в бинарном дереве? Программа просматривает все узлы дерева, начиная с корня.

239

3.2.4.7.5. Строго бинарное дерево Строго бинарное дерево — это дерево, каждый узел которого имеет ровно

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

3.2.4.7.6. Полное бинарное дерево

это дерево, в котором каждый узел уровня n является листом и каждый узел уровня меньше n имеет непустые левое и правое поддеревья

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

3.2.4.7.7. Почти полное бинарное дерево

Это дерево такое, дя которого существует неотрицательное k такое, что

1.каждый лист в дереве имеет уровень k или k+1,

2.если узел дерева имеет правого потомка уровня k+1, тогда все его левые

потомки , являющиеся листами, также имеют уровень k+1

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

3.2.4.7.8. Строго полное бинарное дерево Составьте алгоритм и программу для определения того, что строго бинарное

дерево с n листами содержит 2n–1 узлов (см п. 3.2.4.7.5 и 3.2.4.7.6). 3.2.4.7.9. Подобные бинарные деревья

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

3.2.4.7.10. Зеркально подобные бинарные деревья Два бинарных дерева зеркально подобны, если они оба пусты, либо они оба

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

3.2.4.7.11. Выражение в инфиксной записи Составьте алгоритм и программу, получающую указатель на бинарное

дерево, представляющее выражение, и возвращающий выражение в инфиксной записи, содержащей только необходимые скобки (см. п 3.2.2.1).

3.2.4.7.12. Бинарное дерево Фибоначчи

Определите бинарное дерево Фибоначчи порядка n следующим образом. Если n=0 или n=1, дерево состоит из единственного узла. .Если n>1, .дерево состоит из корня с бинарным деревом Фибоначчи порядка n–1 в качестве левого поддерева и бинарным деревом Фибоначчи порядка n–2 в качестве правого поддерева.

Напишите программу, возвращающую указатель на бинарное дерево Фибоначчи порядка n (см. 3.1.1.1.6).

3.2.4.7.13. Расширение бинарного дерева

Дано бинарное дерево t. Его расширение определяется как бинарное дерево e(t), полученное из t добавлением нового узла к каждому пустому левому и правому указателю в t. Эти новые узлы называются внешними, а исходные узлы —внутренними; e(t) называется расширенным бинарным деревом.

240

Соседние файлы в папке Учебники