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

Иванова Г.С. - Основы программирования

.pdf
Скачиваний:
2771
Добавлен:
02.04.2015
Размер:
13.53 Mб
Скачать

7. Программирование с использованием динамической памяти

Комбинируя эти фрагменты, можно организовать построение списка с

любой дисциплиной добавления элементов.

Пример 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

{создаем элемент} {заносим значение}
then {если элемент меньше первого элемента списка, то}

Часть 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