Алгоритмические языки высокого уровня. Курс лекций
.pdf
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
