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

Алгоритмические языки высокого уровня. Курс лекций

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
940 Кб
Скачать

x^.Next:=nil; {следующего значения нет }

Присоединение нового элемента к началу (голове) списка произ- водится аналогично:

New(x); { ввод значения элемента x^.Data := … } x^.Next:=Head; Head:=x;

В этом случае последний введенный элемент окажется в списке первым, а первый последним.

Замечание: все действия по созданию элементов списка кроме создания первого (Head) элемента начала (головы) списка полно- стью аналогичны, и их естественно выполнять в цикле.

Добавление элемента в начало списка:

var head,p:ukazatel; f: text; begin

head:= nil;

while not eof(f) do begin

new(p);

read(f,p^.znach); p^.next:= head; head:= p;

end;

end.

Добавление элемента в конец списка:

var head,p,q: ukazatel; f: text; begin

if eof(f)

then head:= nil else

begin

new(head); {головной элемент созда- ется отдельно}

read(f,head^.znach); head^.next:= nil; q:= head;

while not eof(f) do begin

new(p);

read(f,p^.znach); p^.next:= nil; q^.next:= p;

q:= q^.next; end;

end;

end.

111

Просмотр элементов списка

Просмотр элементов списка осуществляется последовательно, на- чиная с его начала. Указатель р последовательно ссылается на пер- вый, второй и т.д. элементы списка до тех пор, пока весь список не будет пройден. Начальное значение р адрес первого элемента списка p^. Если р указывает на конец списка, то его значение равно Nil.

p:= head; {начать просмотр с начала (головы) списка} while p<>nil do

begin writeln(p^.znach);

p:= p^.next; {переход к следующему элементу списка} end;

Замечание: чтобы во время работы со списком не произошло вы- хода за его пределы, любой список обязательно должен оканчивать- ся «нулевым» указателем nil.

Удаление элементов списка

Удалять элементы можно из начала (головы), конца или середины.

Удаление элемента из начала списка.

List := Head; { запомним адрес первого элемента списка } Head := Head^.List; { теперь Head указывает на второй эле-

мент списка }

Dispose(List); { освободим память, занятую переменной

List^ }

Удаление из конца списка производится, когда указатель х пока- зывает на предпоследний элемент списка, а List на последний.

List := Head; x := Head; While List^.Next<>nil do

begin

x := List;

List := List^.Next; end;

x^.Next := nil; Dispose(List);

Удаление элемента из середины списка. Для этого нужно знать адреса удаляемого элемента и элемента, находящегося в списке пе- ред ним.

List := Head; {начать с головы списка}

While (List<>nil) and (List^. znach <>Digit) do {поиск} begin

x := List;

112

List := List^.Next; {удаляемый элемент} end;

x^.Next := List^.Next; {связка "через один"} Dispose(List); {освобождение памяти}

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

p:= head; {начать с головы списка}

while p^.next^.zhach<>х do p:= p^.next; {поиск} q:= p^.next; {удаляемый элемент}

p^.next:= q^.next; {связка "через один"} dispose(q); {освобождение памяти}

Перестройка списков.

Разницу между структурой статической (массив) и структурой динамической (список) очень доступно проиллюстрировал Никлаус Вирт в своей книге «Алгоритмы и структуры данных».

Представим обычную очередь у прилавка в магазине. Первый по- купатель это тот, кто в данную минуту стоит непосредственно воз- ле прилавка; следующий за ним второй, за вторым третий и т.д. Покупатели занумерованы строго в порядке следования, и вновь при- шедшие встают в хвост. В принципе, взглянув на очередь, всегда можно сказать, кто за кем стоит. А что происходит, если один из по- купателей желает покинуть очередь? Хвост тут же сдвигается: каж- дый человек делает шаг вперед, чтобы очередь не утратила целост- ности. Если же, наоборот, некто желает встроиться в середину оче- реди (невзирая на крики «А вас тут не стояло!»), то задним прихо- дится пятиться, чтобы освободить ему место. Точно так же ведут се- бя элементы линейного массива.

113

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

И вовсе не обязательно садиться рядом с последним пациентом: вновь пришедший может занять любое свободное кресло в прием- ной. А если у кого-то вдруг перестали болеть зубы и он радостно уходит из очереди, то «стоявшему» за ним достаточно спросить: «А вы за кем занимали?». При этом физического перемещения пациен- тов в пространстве не происходит. Аналогично, если вдруг появляет- ся пациент с талончиком на более раннее время, «задние» пропуска- ют его вперед, не сдвигаясь со своих кресел. Именно так ведут себя и элементы динамических списков.

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

1. Удаление всех нулей из списка (см. рис. 4.2, а).

{голову списка обрабатываем отдельно } while (head<>nil)and(head^.znach =0)do

begin

p:= head;

head:= head^.next; dispose(p);

end;

{середина и конец списка обрабатываются вместе } p:= head;

while p^.next <> nil do

if p^.next^.znach = 0 then begin

q:= p^.next;

114

p^.next:= p^.next^.next; dispose(q);

end else

p:= p^.next;

в

б

а

Рис 4.2. Примеры перестройки списков

2. Вставка в список, хранящий все нечетные числа от 1 до 11, трех новых элементов 0, 8 и 12 с сохранением его упорядоченности (см.

рис. 4.2, б).

115

3. Обмен второго и третьего элементов списка (см. рис. 4.2, в).

p:= head^.next; head^.next:= p^.next; p^.next:= p^.next^.next; head^.next^.next:= p;

Бинарное дерево это иерархическая динамическая структура данных, состоящая из узлов и соединяющих их дуг. Описание би- нарного дерева: структура типа Record, содержащая два поля ука- затели на левое и правое поддеревья, и поле данных Integer:

Type

PTree = ^TTree;

TTree = Record

Data : Integer; Left, Right : PTree; end;

Корень дерева описывается ссылочной переменной: Var Tree : PTree;

К основным операциям над деревьями относятся:

занесение элемента в дерево;

обход дерева;

удаление элемента из дерева.

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

Описание процедуры вставки в дерево новой вершины. При встав-

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

Procedure InsTree(var ANode : PTree; n : lnteger); Begin

if ANode = nil then Begin

116

new(ANode); With ANode^ do Begin

Left := nil; Right := nil; Data := n; end;

end

else if n< ANode^.Data then InsTree(ANode^.Left, n)

else

InsTree(ANode^.Right, n);

End;

Вывод элементов дерева. (описание процедуры вывода значений элементов двоичного дерева на экран). Для этого необходимо вы-

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

вывод числа, хранящегося в узле;

обход левого поддерева;

обход правого поддерева.

Порядок выполнения этих действий определяет способ обхода де- рева. Способы обхода:

прямой обход (сверху вниз);

симметричный обход (слева направо);

обратный обход (снизу вверх).

Процедура симметричного вывода дерева имеет следующий вид:

Procedure PrintTree(ANode : PTree);

Begin

if ANode <> nil then

Begin

PrintTree(ANode^.Left);

WriteLn(ANode^.Data);

PrintTree(ANode^.Right)

End;

End;

Основная программа осуществляет ввод чисел с клавиатуры. Ис- пользуются: переменная Tree типа PTree – значение указателя на корень дерева; переменная Digit типа Integer для хране- ния очередного введенного числа.

Var

Tree : PTree;

117

Digit : Integer;

BEGIN {основная программа} Writeln(‘Окончание ввода – 0’); Tree := nil;

Read(Digit); While Digit<>0 Do Begin

InsTree(Tree,Digit);

Write(‘Введите очередное число: ‘); ReadLn(Digit);

End;

PrintTree(Tree);

end.

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

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

Такие элементы не могут иметь более одного потомка. Задана следующая структура:

Type

PTree = ^TTree;

TTree = Record

Data : Integer; Left, Right : PTree; end;

Var

Tree : PTree;

118

Необходимо удалить из дерева Tree узел со значением поля

Data=x.

В процедуре DeleteNode различаются три случая:

1)узла со значением, равным х, нет;

2)узел со значением х имеет не более одного потомка;

3)узел со значением х имеет двух потомков.

Procedure DeleteNode(x : Integer; var ANode : PTree); Var q : PTree;

Procedure Del(var R : PTree); Begin

if R^.Right <> nil then Del(R.^Right) else

begin

q^.Data := R^.Data; q := R;

R := R^.Left; Dispose(q); end;

End; { Del }

Вegin { DeleteTree }

if ANode = nil then

Writeln(‘Элемента с ключом ’,x,’ в дереве нет’)

else

if x < ANode^.Data then DeleteNode(x,ANode^.Left)

else if x > ANode^.Data then DeleteNode(x,ANode^.Right) else

begin

q := ANode;

if q^.Right = nil then Begin

ANode := q^.Left; Dispose(q);

end else

if q^.Left = nil then begin

ANode := q^.Right; Dispose(q);

end

else Del(q^.Left); end;

End;

Вспомогательная процедура Del вызывается только в третьем случае. Она «спускается» вдоль самой правой ветви левого поддере- ва удаляемого узла q^ и затем заменяет данные (поле Data) в q^

119

соответствующими значениями самого правого узла R^ этого левого поддерева, после чего от R^ можно освободиться. На рис. 4.3 зада- но начальное дерево (см. рис. 4.3, а), из которого последовательно удаляются узлы со значениями 13, 15, 5, 10. Полученные деревья по- казаны на рис. 4.3 b e.

Рис. 4.3. Удаление узлов из дерева

Пример программы

Описание: в программе создается линейный список, добавляются и удаляются элементы, осуществляется просмотр элементов списка.

program Spisok; Uses crt;

Type Uk=^Uk_; Uk_=record

Next: Uk; Val:integer;

End;

Var

Fin:boolean; K,c:integer; ch:char; P1,

P_beg,

P_end, P_pr:Uk;

Procedure Vivod_Spisok(p1:Uk); Var I,j:byte;

120

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]