
информатика_2 / Программирование_С
.pdf
2.2.9. Упорядоченный двусвязный список
Упорядоченный двусвязный список отличается тем, что вместо функции добавления элемента (справа или слева) используется функция вставки элемента.
Основные функции:
•вставить элемент,
•удалить указанный элемент.
Рис. 2.13. Вставка элемента в двусвязный список
Для вставки элемента необходимо провести следующие манипуляции (рис. 2.13):
•найти подходящее место для вставки, причем следует остановить буферный указатель pt_buf на элементе, после которого будет вставлен новый элемент,
•получить динамическую память под новый элемент — указатель pt_new,
•записать вставляемое значение в поле данных нового элемента,
•перебросить указатели (полагаем, что в предложении struct указатель влево назван left, а указатель вправо — right).
На уровне кода это будет выглядеть так: pt_new–>right = pt_buf–>right; pt_buf–>right = pt_new; pt_new–>right–>left = pt_new; pt_new–>left = pt_buf;
Реализовать функцию удаления элемента с указанным значением предоставляем читателю.
151
2.2.9.1. Задачи на упорядоченный двусвязный список
2.2.9.1.1. Изменение порядка элементов списка (целые числа) на обратный Реализовать упорядоченный двусвязный список, тип данных — целые числа. Прикладная задача:
исходный список заполняется из входного потока, затем программа меняет порядок элементов списка на обратный.
2.2.9.1.2. Изменение порядка элементов списка (строки) на обратный Реализовать упорядоченный двусвязный список, тип данных — строки
символов. Прикладная задача:
исходный список заполняется из входного потока, затем программа меняет порядок элементов списка на обратный.
2.2.9.1.3. Смена местами половин списка (целые числа)
Реализовать упорядоченный двусвязный список, тип данных — целые числа. Прикладная задача:
исходный список заполняется из входного потока, затем программа меняет местами первую и вторую половины списка.
2.2.9.1.4. Смена местами половин списка (строки)
Реализовать упорядоченный двусвязный список, тип данных — строки символов.
Прикладная задача:
исходный список заполняется из входного потока, затем программа меняет местами первую и вторую половины списка.
2.2.9.1.5. Удаление элементов списка, больших заданного Реализовать упорядоченный двусвязный список, тип данных — целые числа. Прикладная задача:
исходный список заполняется из входного потока, затем программа удаляет все элементы, большие заданного.
2.2.9.1.6. Удаление элементов списка, больших по длине заданной Реализовать упорядоченный двусвязный список, тип данных — строки
символов. Прикладная задача:
исходный список заполняется из входного потока, затем программа удаляет все элементы, большие по длине заданной.
2.2.10. Двусторонняя очередь (DEQ) на основе списка
Двусторонняя очередь на основе списка имеет своей базой такую же структуру, как и двусвязный список, от которого отличается только системой функций.
Основные функции:
152
•добавить элемент слева,
•добавить элемент справа,
•удалить элемент слева,
•удалить элемент справа.
Как и для двусторонней очереди на основе массива, можно получить функциональность любой из более простых структур (стека, очереди, списка), комбинируя по-разному основные функции.
2.2.10.1. Задачи на двустороннюю очередь
2.2.10.1.1. Изменение порядка элементов очереди (целые числа) на обратный Реализовать двустороннюю очередь на основе списка, тип данных — целые
числа.
Прикладная задача:
исходная очередь заполняется из входного потока, затем программа меняет порядок элементов очереди на обратный.
2.2.10.1.2. Изменение порядка элементов очереди (строки) на обратный Реализовать двустороннюю очередь на основе списка, тип данных — строки
символов. Прикладная задача:
исходная очередь заполняется из входного потока, затем программа меняет порядок элементов очереди на обратный.
2.2.10.1.3. Смена местами половин очереди (целые числа)
Реализовать двустороннюю очередь на основе списка, тип данных — целые числа.
Прикладная задача:
исходная очередь заполняется из входного потока, затем программа меняет местами первую и вторую половины очереди.
2.2.10.1.4. Смена местами половин очереди (строки)
Реализовать двустороннюю очередь на основе списка, тип данных — строки символов.
Прикладная задача:
исходная очередь заполняется из входного потока, затем программа меняет местами первую и вторую половины очереди.
2.2.10.1.5. Удаление элементов очереди, больших заданного Реализовать двустороннюю очередь на основе списка, тип данных — целые
числа.
Прикладная задача:
исходная очередь заполняется из входного потока, затем программа удаляет все элементы, большие заданного.
2.2.10.1.6. Удаление элементов очереди, больших по длине заданной Реализовать двустороннюю очередь на основе списка, тип данных — строки
символов. Прикладная задача:
153

исходная очередь заполняется из входного потока, затем программа удаляет все элементы, большие по длине заданной.
2.2.11. Мультисписок
Наконец, мультисписок — это структура с произвольным количеством указателей на другие (не обязательно соседние) элементы. Эта структура используется для хранения подсписков, каждый из которых удовлетворяет некоторому признаку — а каждому признаку соответствует свой указатель.
Основные функции:
•добавить элемент,
•удалить указанный элемент.
Рис. 2.14. Мультисписок из пяти элементов
На рис. 2.14 изображен мультисписок из пяти элементов с тремя указателями и одним полем данных. Если, например, это список клиентов страховой компании, то один из указателей может быть использован для выделения подсписка людей с избыточным весом, другой — курящих и так далее. Для получения полного списка злоупотребляющих курением будет достаточно встать на соответствующий указатель и пройти до NULL.
2.2.11.1. Задачи на мультисписок
2.2.11.1.1. Удаление элементов, по одному из целых полей больших заданного Реализовать мультисписок, не менее 5-ти полей указателей, не менее 3-х
полей данных — целые числа и строки символов. Прикладная задача:
154

исходный мультисписок заполняется из входного потока, затем программа удаляет все элементы, по одному из целых полей большие заданного.
2.2.11.1.2. Удаление элементов, по одному из целых полей больших заданного Реализовать мультисписок, не менее 5-ти полей указателей, не менее 3-х
полей данных — целые числа и строки символов. Прикладная задача:
исходный мультисписок заполняется из входного потока, затем программа удаляет все элементы, по одному из строковых полей большие по длине заданной.
2.2.12. Двоичные деревья
Деревом в теории графов называют структуру, подобную изображенной на рис. 2.15,а (не строгое определение!). Дерево состоит из множества узлов (на рис. 2.15,а — кружки) и множества соединяющих пары узлов ребер (линии). При
Рис. 2.15. Дерево и двоичное дерево этом движение по ребрам — одностороннее; на нашем рисунке — сверху вниз.
Узлы, достижимые из текущего за один шаг, называют его потомками; текущий узел является их предком. Корень дерева не имеет предков. Узлы, не имеющие потомков, называют листьями. На нижнем уровне могут располагаться только листья.
Для произвольного дерева количество потомков каждого узла может быть любым. Если это количество для всех узлов не превышает двух, дерево называют двоичным (бинарным) (рис. 2.15, б); в данном примере в каждом узле дерева лежат целые числа. В программе идентификация дерева производится через указатель на его корень (pt_tree).
155
Двоичные деревья являются весьма эффективными структурами для хранения и быстрого поиска данных (см. следующий раздел).
Основные функции:
•добавить элемент,
•просмотреть дерево.
Вэтом и в следующем разделах приводятся тексты программ, использующие так называемую рекурсию (см. раздел 3.1.1). К сожалению, для эффективной реализации двоичных деревьев нам приходится «забегать вперед».
Структура данных для реализации двоичных деревьев очень похожа на структуру двунаправленного списка; каждый узел дерева содержит поле данных и два указателя — на левого и правого потомков:
Ниже приводится программа формирования двоичного дерева произвольного вида, элементы — целые числа. Функция формирования дерева FormTree рекурсивна, условием отсечки является ввод значения 0, что говорит о создании листа. Формирование дерева заканчивается, когда все листы введены. Функция просмотра дерева в симметричном порядке LookTreeInOrder также рекурсивна.
Формирование ДБП может быть проведено с помощью такой функции добавления листа к дереву (псевдокод):
// получить новую ячейку; if ( //дерево не пусто)
{
//пройти по дереву до нужного узла
//добавить лист слева или справа
}
else
//новое дерево — формируем корень
//потомки нового листа — в NULL
Реализация программы на языке выглядит так:
# include <stdio.h>
# include <conio.h>
struct NODE{ |
// определение узла дерева |
|
int |
data; |
// поле данных |
NODE |
* l_heir, // указатели на потомков |
|
}; |
|
* r_heir; |
|
|
156
typedef NODE * TREE; |
|
// определение указателя на узел |
TREE IniTree ( TREE pt_tree ); |
// инициация дерева |
|
TREE FormTree( TREE pt_tree ); |
// рекурсивное формирование |
|
|
|
// дерева |
void LookTreeInOrder ( TREE pt_tree ); // просмотр дерева в |
||
|
|
// симметричном порядке |
main() |
|
|
{ |
|
|
TREE tree; |
// создаем указатель на дерево |
|
int tree_el; |
// целое число — элемент дерева |
|
clrscr(); |
|
|
tree = IniTree( tree ); |
// не возвращает данных об |
|
|
// успехе/неуспехе попытки, |
|
|
// поскольку указатель на |
|
|
// дерево может быть |
|
|
// модифицирован и его нужно |
|
|
// вернуть |
printf( "При вводе 0 предыдущий злемент входного потока — лист): \n"); tree = FormTree( tree );
printf("\nПросмотр дерева в симметричном порядке:\n");
LookTreeInOrder( tree); |
|
return 0; |
|
} |
|
TREE IniTree ( TREE pt_tree ) |
// некорректный способ |
|
// инициации, надо бы вернуть |
{ |
// память в кучу |
|
|
pt_tree = NULL; |
|
return pt_tree; |
|
|
157 |
}
TREE FormTree( TREE pt_tree )
{
int new_el; TREE pt_buff;
scanf("%d", &new_el);
if ( new_el ) |
// != 0 |
// рекурсивное |
|
|
// формирование ветви дерева |
|
|
// завершается вводом нуля — |
|
|
// построен лист; далее рекурсия |
|
|
// возвращается на узел выше и |
{ |
|
// ждет очередного нуля и т.д. |
|
|
pt_tree = new NODE; pt_tree–>data = new_el;
pt_tree–>l_heir = FormTree( pt_tree–>l_heir ); pt_tree–>r_heir = FormTree( pt_tree–>r_heir );
}
else
pt_tree = NULL;
return pt_tree;
}
void LookTreeInOrder ( TREE pt_tree )
{
if ( pt_tree != NULL )
{
LookTreeInOrder( pt_tree–>l_heir ); printf( " %d ", pt_tree–>data ); LookTreeInOrder( pt_tree–>r_heir );
//описание рекурсивных
//процедур просмотра см.
//в разделе 2.2.13 «Деревья
//бинарного поиска»
}
}
2.2.12.1.Задачи на двоичные деревья
2.2.12.1.1.Удвоение элементов двоичного дерева, больших заданного Реализовать двоичное дерево, тип данных — целые числа.
158

Прикладная задача:
исходное дерево формируется из входного потока, затем программа удваивает все элементы, большие заданного.
2.2.12.1.2. Удаление символа для элементов двоичного дерева, больших по длине заданной
Реализовать двоичное дерево, тип данных — строки символов. Прикладная задача:
исходное дерево формируется из входного потока, затем программа удаляет последний символ для всех элементов, больших по длине заданной.
2.2.13. Деревья бинарного поиска
Деревом бинарного поиска (далее ДБП) по определению является двоичное дерево, для каждого узла которого данные левого потомка меньше, а для правого — больше данных текущего узла. Отношения меньше/больше могут быть различным образом определены для данных различного типа, однако в любом случае эти типы данных должны допускать некоторое упорядочивание. Далее мы будем везде говорить о простейшем случае — данных типа int. Пример ДБП приведен на рис. 2.16.
Рис. 2.16. Дерево бинарного поиска
Формирование ДБП (псевдокод):
{
// получить новую ячейку
if (// дерево не пусто )
{
//пройти по дереву до нужного узла
//добавить лист слева или справа
159
}; else
//новое дерево — формируем корень
//потомки нового листа — в NULL
};
Уровень 2 разработки на псевдокоде:
{
new ptr_new ; // получить новую ячейку ptr_new–>data = new_el;
if ( //дерево не пусто )
{
// потомок и предок — в корень дерева
while (// потомок не NULL and new_el != данные_предка )
{
// потомок становится предком
if (// new_el < данные_предка )
//шаг вниз влево;
else
//шаг вниз вправо;
};
if (// new_el != данные_предка ) if (// new_el < данные_предка )
//добавить лист слева;
else
//добавить лист справа
};
else
// лист — в NULL
};
Ниже приводится текст программы на языке C, реализующий формирование дерева двоичного поиска.
#include <stdio.h>
#include <conio.h>
#define TRUE –1
160