![](/user_photo/2706_HbeT2.jpg)
Иванова Г.С. - Основы программирования
.pdf7. Программирование с использованием динамической памяти
Комбинируя эти фрагменты, можно организовать построение списка с
любой дисциплиной добавления элементов.
Пример 7.2, Разработать программу, которая строит список по типу сте ка из целых чисел, вводимых с клавиатуры. Количество чисел не известно, но отлично от нуля. Конец ввода - по комбинации клавиш CTRL-Z (конец файла на устройстве ввода).
Обычно построение списка по типу стека выполняется в два этапа: в список заносится первый элемент, а затем организуется цикл добавления элементов перед первым:
ReadLn(a); |
|
|
new(first); |
{запрашиваем память под элемент} |
|
first |
\пит:=а; |
{заносим число в информационное поле} |
first |
\р:=пИ; |
{записываем nil в поле, «адрес следующего»} |
while not EOF do |
||
begin |
|
|
|
ReadLn(a); |
|
|
new(q); |
{запрашиваем память под элемент} |
|
q\num:-a; |
{заносим число в информационное поле} |
|
q^.p:-first; |
{в поле «адрес следующего» переписываем адрес |
|
first:=q; |
первого элемента} |
|
{в указатель списка заносим адрес нового элемента} |
|
end; ... |
|
Пример 7.3. Разработать программу, которая строит список по типу оче реди из целых чисел, вводимых с клавиатуры. Количество чисел не извест но, но отлично от нуля. Конец ввода - по комбинации CTRL-Z (конец файла на устройстве ввода).
При построении списка по типу очереди сначала мы заносим в стек пер вый элемент, а затем организуем цикл добавления элементов после послед него, приняв во внимание, что nil необходимо разместить в адресном поле только последнего элемента:
ReadLn(a); |
|
new(first); |
{запрашиваем память под элемент} |
first \пит:=а; |
{заносим число в информационное поле} |
f:=first; |
{f - текущий элемент, после которого добавляется |
|
следующий} |
while not EOF do begin
ReadLn(a);
new(0; {запрашиваем память под элемент} q\num:-a; {заносим число в информационное поле}
231
Часть 1. Основы алгоритмизации и процедурное программирование
f\p:=q; {в поле «адрес следующего» предыдущего элемента заносим адрес нового элемента}
f:-f\p; {теперь новый элемент стал последним} end;
q\p:=nil; (в поле «адрес следующего» последнего элемента запи сываем nil}
Этот фрагмент можно упростить, если заметить, что новый элемент к списку можно добавлять сразу при запросе памяти:
ReadLn(a); |
|
new(first); |
{запрашиваем память под элемент} |
first ^.пит:=а; |
{заносим число в информационное поле} |
f:-first; |
{f- текущий элемент, после которого добавляется |
|
следующий} |
while not EOF do |
|
begin |
|
ReadLn(a); |
|
new(f\p); |
{запрашиваем память под элемент} |
f:-f^'P; |
{теперь новый элемент стал последним} |
f\num:=a; |
{заносим число в информационное поле} |
end; |
|
f\p:=nil; ..• |
{в поле «адрес следующего» последнего элемента за |
|
писываем nil} |
Пример 7.4. Разработать программу, которая строит список, сортиро ванный по возрастанию элементов, из целых чисел, вводимых с клавиатуры. Количество чисел не известно, но отлично от нуля. Конец ввода ~ по комби нации CTRL-Z (конец файла на устройстве ввода).
Список сортирован, соответственно, добавляя элемент, необходимо со хранить закон сортировки: элемент должен вставляться перед первым эле ментом, который больше, чем добавляемый. В зависимости от конкретных данных он может вставляться в начало, середину и конец списка.
new(first); {запрашиваем память под первый элемент} ReadLn(first \nuin); {заносим число в информационное поле} first\p:=nil;
while not EOF do begin
new(q);
ReadLn(q\num); if q\num<first\num
232
7. Программирование с использованием динамической памяти
begin |
{вставляем перед первым} |
||
q\p:=first; |
|
|
|
firsU-q; |
|
|
|
end |
|
|
|
else |
|
(иначе вставляем в середину или конец} |
|
begin |
|
|
|
n:-first; |
|
{указатель на текущий элемент} |
|
f:-first; |
|
{указатель на предыдущий элемент} |
|
flag:^false; |
|
{"элемент не вставлен"} |
|
{цикл поиска места вставки} |
|
||
while (n\ponil) |
and (notflag) |
do |
|
begin |
|
|
|
n:=n\p; |
{переходим к следующему элементу} |
||
if q^,num<n\num then |
{место найдено} |
||
begin |
{вставляем в середину} |
||
|
q\p:^f\p; |
|
|
|
f''>p:^q; |
|
|
end |
y7ag;=/rwe; {"элемент вставлен"} |
||
|
|
|
|
elsef:-n; |
{сохраняем адрес текущего элемента} |
||
end; |
|
|
|
if notflag |
then {если элемент не вставлен, то} |
||
begin |
|
{вставляем после последнего} |
|
q\p:-nil; |
|
||
f\p:^q; |
|
||
end; |
|
|
|
end; |
|
|
|
end; |
|
|
|
Просмотр и обработка элементов списка. Просмотр и обработка эле ментов списка выполняется последовательно с использованием дополни тельного указателя:
f:-=first;
whilefonil do begin
<обработка элемента по адресу f>
end; ...
В качестве примера рассмотрим вывод на экран элементов списка:
233
Часть I. Основы алгоритмизации и процедурное программирование
f:=first; whilefonil do
begin
WriteLn(f\num, * *);
end; ...
Поиск элемента в списке. Поиск элементов в списке также выполня ется последовательно, но при этом, как это и положено в поисковом цикле, обычно организуют выход из цикла, если нужный элемент найден, и осуще ствляют проверку после цикла, был ли найден элемент:
fi^flrst; flag:--false;
while (fonil) and (notflag) do begin
iff \num=k then flag:^notflag elsef:^f\p;
end;
ifflag then <элемент найден>
else <элемент не найден>; ...
Удаление элемента из списка. При выполнении операции удаления также возможно четыре случая:
•удаление единственного элемента;
•удаление первого (не единственного) элемента списка;
•удаление элемента, следующего за данным;
•удаление последнего элемента.
Удаление единственного элемента. После удаления единственного эле мента список становится пустым, следовательно при выполнении этой опе рации необходимо не только освободить память, выделенную для размеще ния элемента, но и занести nil в указатель списка first (рис. 7.14):
first |
|
first |
first |
|
Dispose(first); |
|
|
|
|
Щ |
[1] |
|
first:^nil; ... |
|
|
^ |
' ' |
* |
|
|
Удаление первого (не единст- |
||
а |
|
Disposefflrst); |
first:^пН; венного) элемента списка. |
Удале- |
|||
|
б |
в |
"^® первого элемента состоит из со |
||||
|
Рис. 7.14. Удаление |
|
хранения адреса следующего эле- |
||||
|
|
мента в рабочей переменной |
f, ос- |
||||
единственного элемента списка: |
вобождения памяти элемента |
и за |
|||||
л-исходное состояние; б-освобождение |
писи |
В указатель списка сохранен- |
|||||
памяти; в - занесение константы nil в |
ного |
адреса следующего |
элемента |
||||
|
|
указатель списка |
|
(рис. 7.15): |
|
|
234
7. Программирование с использованием динамической памяти
Ей |
Л |
first |
rJL |
^ |
^ |
||
|
|
f:^firsi\p; |
|
Д - , |
|
first |
|
firstM г — 1 |
|
|
|
TT0l |
|
|
пТТ0 |
Dispose(first): |
|
first: =/• |
|
e |
|
|
г |
Рис. 7.15. Удаление первого (не единственного) элемента списка:
а - исходное состояние; б - сохранение адреса следующего элемента в специальном указателе; в - освобождение памяти; г - запись в указа тель списка адреса следующего элемента
f:='first\p; |
(сохраняем адрес следующего элемента} |
Dispose(first); |
{освобождаем память} |
first:"=/; ... |
(заносим в указатель списка адрес следующего |
|
элемента} |
Удаление единственного элемента и первого элемента в программе мож но объединить:
q—first; first :=first\p; Dispose(q);...
Удаление элемента, следующего за данным (не последнего). Удаление элемента, следующего за данным, требует запоминания адреса удаляемого элемента, изменения адресной части данного элемента и освобождения па мяти (рис. 7.16).
n:=f^,p; (сохраняем адрес удаляемого элемента}
f^,p:=n\p; (заносим в адресное поле предыдущего элемента ад рес следующего элемента}
Dispose(n); ... (освобождаем память}
235
Часть L Основы алгоритмизации и процедурное программирование
first |
I |
|
|
Удаление последнего элемен |
|||||||||
|
изчшэчхш |
та. Удаление последнего элемен |
|||||||||||
|
та отличается только тем, что в |
||||||||||||
|
поле «адрес следующего» задан |
||||||||||||
|
|
|
|
ного элемента записывается кон |
|||||||||
|
|
|
|
станта nil: |
|
|
|
|
|
|
|||
first |
f |
n |
|
n:=f^p; |
|
|
|
|
|
|
|||
|
сиэчзпзч ЕГЭЧХШ |
|
|
|
|
|
|
||||||
|
f\p:=nil; |
|
|
|
|
|
|
||||||
|
Dispose(п); ... |
|
|
|
|
||||||||
|
n-.^f'^.p; |
|
|
Удаление последнего |
эле |
||||||||
|
|
|
|
||||||||||
|
|
|
|
мента можно свести к удалению |
|||||||||
first |
f |
n |
|
элемента, |
следующего за |
дан |
|||||||
|
ным, так как адресная часть уда |
||||||||||||
|
|
|
|
||||||||||
|
|
|
9"ТЯ;?Г8Т0] |
ляемого элемента равна nil. |
|
|
|||||||
|
|
|
Комбинируя |
приемы удале |
|||||||||
|
f\p:=n\p; |
|
|
ния, мы также можем организо |
|||||||||
|
|
|
вать |
любую |
дисциплину удале |
||||||||
|
|
|
|
||||||||||
|
|
|
|
ния. |
|
|
|
|
|
|
|
|
|
|
|
|
|
Пример |
7.5. |
Разработать |
|||||||
first |
f |
|
|
программу, |
которая |
удаляет |
из |
||||||
|
пизч7TT] |
Д"8Т01 |
списка все элементы меньше за |
||||||||||
|
данного значения к. |
|
|
могут |
|||||||||
|
Dispose(n); |
|
|
Удаляемые |
значения |
||||||||
|
|
|
располагаться в списке на любом |
||||||||||
|
|
|
|
||||||||||
|
|
|
|
месте, следовательно, |
возможны |
||||||||
Рис. 7.16. Удаление элемента, следую |
все четыре |
варианта |
удаления |
||||||||||
элемента, |
которые |
сводятся |
к |
||||||||||
|
щего за данным (не последнего): |
||||||||||||
|
двум случаям: |
|
|
|
|
|
|||||||
а - исходное состояние; б - сохранение адреса |
|
|
|
|
|
||||||||
• |
удаление |
единственного |
|||||||||||
удаляемого элемента; в - исключение удаляемого |
|||||||||||||
элемента |
и удаление записей |
из |
|||||||||||
элемента из списка; г - освобождение памяти |
|||||||||||||
начала списка-удаление из нача |
|||||||||||||
|
|
|
|
||||||||||
|
|
|
|
ла списка; |
|
|
|
|
|
|
|||
• |
удаление средних и последнего элементов - |
удаление не из начала |
списка. Для разделения этих двух случаев введем специальный признак «удаление из начала», который в начале установим равным true, а затем, как только в списке будет оставлен хотя бы один элемент - изменим на false.
n.-'^Jirst;
nft:=true; {признак «удаление из начала списка»} repeat
ifn^.num<kthen begin
236
7. Программирование с использованием динамической памяти
ifnft |
then |
{если «удаление из начала списка»} |
|
begin |
{удаление из начала списка} |
||
|
q:^firsi; |
||
|
first:=first^.p; |
||
|
Dispose(q); |
||
|
n:-first; {переходим к следующему элементу} |
||
end |
|
|
|
else |
{иначе} |
||
begin |
|
{удаление из середины и конца} |
|
|
q:^n; |
|
|
|
п:-п\р; |
{переходим к следующему элементу} |
|
|
В18ро8е(ф; |
||
|
f\p:^n; |
|
|
end |
|
|
|
end |
|
|
|
else {оставляем элемент в списке} |
|||
begin |
|
|
|
f:-n; |
|
|
{устанавливаем адрес предыдущего элемента} |
п:-п\р; |
|
{переходим к следующему элементу} |
|
nft:='not nft |
{«удаление не из начала списка»} |
||
end; |
|
|
|
until n-nil;... |
|
{до завершения списка} |
Задания для самопроверки
Задание 1. Разработайте профамму, которая вводит с клавиатуры последова тельность чисел до символа «#», а затем удаляет из нее все числа, превышающие среднее арифметическое чисел введенной последовательности. Оставшиеся значе ния выведите в обратном порядке.
Задание 2. Разработайте программу, которая вводит с клавиатуры последова тельность чисел до символа «#», а затем определяет следующие суммы:
XI + х^; Х2 + Xj,.,; хз + Хп.2; ... х^ + х,. Указание, Используйте двусвязный список.
Задание 3. Разработайте профамму, которая определяет «водящего» в детской ифе. Водящий определяется с помощью «считалки» следующим образом. Все ифающие встают в круг и начинают «считаться». Каждый раз тот, на ком закончилась считалка, выбывает из круга. Водит оставшийся. Исходное количество ифающих п. Количество слов считалки т .
Указание, Используйте кольцевой список.
237
Часть L Основы алгоритмизации и процедурное программирование
7.5.Бинарные деревья
Вматематике бинарным (двоичным) деревом называют конечное множе ство вершин, которое либо пусто, либо состоит из корня и не более чем двух непересекающихся бинарных деревьев, называемых левым и правым подде ревьями данного корня.
Таким образом, каждая вершина бинарного дерева может включать одно или два поддерева или не включать поддеревьев вовсе. Первое поддерево обычно называют левым, а второе - правым. Соответственно ветвь, исходя щую из вершины и ведущую в корень левого поддерева, называют левой, а ветвь, ведущую в корень правого поддерева - правой. Вершины, из которых не выходит ни одной ветви, называют листьями (рис. 7.17).
Впрограммах для реализации бинарных деревьев используют п-связные списки. С вершинами бинарного дерева обычно связывают записи, хранящие некоторую информацию.
Построение дерева выполняется следующим образом. Если дерево пус то, то первая же вершина становится корнем дерева. Добавление остальных вершин регламентируется в зависимости от условия задачи: в соответствии с заданной дисциплиной построения дерева отыскивается подходящая верши на, к которой й подсоединяется новая вершина.
Достаточно часто используют регулярные бинарные деревья с разными законами построения. Примером могут служить сортированные бинарные деревья, построение которых осуществляется по правилу: ключевое поле ле вого поддерева всегда долэюно содерэюать значение меньше, чем в корне, а ключевое поле правого поддерева - значение больше или равное значению в корне.
Рассмотрим основные операции с сортированными бинарными деревь
ями.
Корень дерева |
|
Левая ветвь |
Правая ветвь |
Корень левого |
Корень правого |
поддерева |
поддерева |
Рис. 7.17. Пример бинарного дерева
238
7. Программирование с использованием динамической памяти
Исходные установки. В начале программы необходимо описать эле мент и его тип:
Туре topjptr-^top; |
{тип «указатель на вершину»} |
|
top=recorci |
|
|
value:integer; |
{число} |
|
left, |
|
{указатель на левое поддерево} |
right:top_ptr; |
{указатель на правое поддерево} |
|
end; |
|
|
В статической памяти описываем указатель корня дерева и несколько указателей, используемых при выполнении операций со списком:
Var root, {указатель структуры - адрес корня дерева} pass, next, q:topjptr; {вспомогательные указатели}
Исходное состояние - «пустое дерево»:
root:=nil;
Построение дерева. Дерево строится в соответствии с главным прави лом. Например, пусть дана последовательность целых чисел {5, 2, 8, 7, 2, 9, 1, 5}. Первое число 5 будет записано в корень дерева (рис. 7.18, а). Второе число 2 меньше значения в корне дерева, следовательно, оно будет записано в левое поддерево (рис. 7.18, б). Следующее число 8 больше значения в кор не, соответственно оно будет записано в правое поддерево (рис. 7.18, в). Сле дующее число 7 больше, чем значение в корне дерева, значит, оно должно быть записано в правое поддерево, но правое поддерево уже построено. Сравниваем 7 со значением в корне правого поддерева - числом 8. Так как добавляемое значение меньше значения в корне правого поддерева, то добав-
©®
© © 0 i2)©5©
д
Рис. 7.18. Построение сортированного бинарного дерева:
первые шаги (ЙГ - г) и окончательный вариант (д)
239
Часть I. Основы алгоритмизации и процедурное программирование
ляем левое поддерево уже к этому корню (рис. 7.18, г). Полностью сформи рованное бинарное дерево представлено на рис. 7.18, д.
Фрагмент программы, реализующий добавление вершины к дереву, со стоит из трех частей: создания вершины, поиска корня, к которому можно до бавить поддерево, придерживаясь основного правила, и, непосредственно, добавления вершины:
{создание новой вершины} |
|
||
new(q); {выделяем память для нового элемента} |
|
||
with q^ do |
{заносим значения} |
|
|
begin value:=п; |
|
||
|
left:=nil; |
|
|
end; |
right:=nil; |
|
|
|
|
|
|
{поиск корня для добавляемой вершины} |
|
||
pass:=root; |
|
{начинаем с корня бинарного дерева} |
|
while passonil |
do |
{пока не найдено свободное место} |
|
begin next:=pass; |
{сохраняем адрес корня-кандидата} |
||
if q\value<pass^.value then pass: =pass\left |
{влево} |
elsepass:^pass^,right; {вправо}
end;
{добавление вершины}
if q\value<next\value then {если значение меньше корня} next\left:=q {добавляем левое поддерево}
else next^.right:=q; {добавляем правое поддерево}
Используя рекурсивь^ость определения дерева, можно построить рекур сивную процедуру добавления вершин к дереву. В качестве параметров эта процедура будет получать указатель на корень дерева и указатель на добав ляемый элемент.
Procedure Add(Var r:top_ptr; pass:top_ptr); begin
ifr=nil then r:=pass {если место свободно, то добавляем} else {иначе идем налево или направо}
if (pass\ value<r\value) then Add(r\ left,pass) else Add(r^.right,pass);
end;...
Поиск вершины в сортированном бинарном дереве. Поиск в сорти рованном бинарном дереве осуществляется следующим образом: вначале значение ключа поиска сравнивается со значением в корне. Если значение ключа в искомой вершине меньше, чем в корневой, то поиск переходит в ле вую ветвь. Если больше или равно - то в правую ветвь. И так в каждой сле дующей вершине до тех пор, пока не отыщется искомая. Так, для предыду-
240