Из механизма FIFO следует, что в очереди доступны два элемента √ первый и последний простая очередь (см. рис. 5).
Структура данных, представляющая очередь, могла бы выглядеть следующим образом:
type
TypeOfElem= {};
Assoc= ^ElementOfQueue;
ElementOfQueue= record
Elem: TypeOfElem;
NextElem: Pointer
end;
Queue= Assoc;
3.2 Создание (очистка) очереди
Для создания новой пустой или очистки существующей очереди достаточно присвоить указателям на первый и последний элементы значение nil.
procedure CreateQueue ( var FirstElem, LastElem: Queue);
begin
FirstElem:= nil;
LastElem:= nil
end;
3.3 Проверка очереди на пустоту
Условием пустоты очереди является значения указателей на первый и последний элементы, равные nil.
function QueueIsClear( var FirstElem, LastElem: Queue ): Boolean;
begin
QueueIsClear:= ( FirstElem= nil ) and ( LastElem= nil )
end;
3.4 Включение элемента в очередь
Для включения элемента в очередь, необходимо создать новый элемент типа очередь, затем инициализировать его информационное поле. В заключение изменить его указатель и указатель на последний элемент очереди так, чтобы последним стал новый элемент.
procedure IncludeInQueue( var FirstElem, LastElem: Queue; NewElem: TypeOfElem);
var
ServiceVar: Queue;
begin
{созданиеновогоэлемента}
new( ServiceVar );
ServiceVar^.Elem:= NewElem;
ServiceVar^.NextElem:= nil;
if ( FirstElem= nil ) and ( LastElem= nil ) then begin
{создать очередь из одного элемента}
FirstElem:= ServiceVar;
LastElem:= ServiceVar
end
else begin
{созданный элемент поместить в конец очереди}
LastElem^.NextElem:= ServiceVar;
LastElem:= ServiceVar
end
end;
При выборе элемента из очереди информационное поле первого ее элемента должно быть присвоено результирующей переменной, а сам элемент должен быть исключен из очереди и удален. Здесь необходима также проверка на то, являлся ли этот элемент в очереди единственным, и если да, то необходимо соответствующим образом изменить указатель на последний элемент.
procedure SelectFromQueue( var FirstElem, LastElem: Queue; var Result: TypeOfElem);
var
ServiceVar: Queue;
begin
if not ( ( FirstElem= nil ) and ( LastElem= nil ) ) then begin
Result:= FirstElem^.Elem;
ServiceVar:= FirstElem;
{убираем 1-ый элемент из очереди}
FirstElem:= FirstElem^.NextElem;
{быллиэтопоследнийэлемент}
if FirstElem= nil then
LastElem:= nil;
dispose( ServiceVar )
end
end;
Из механизма LIFO следует, что в стеке доступен только последний занесенный его элемент √ так называемая вершина стека. Главный элемент, представляющий весь список как единый объект, в случае стека оказывается лишним, его роль выполняет вершина стека. Элемент, занесенный в стек раньше других имеет ссылку nil (см. рис. 6).
Структура данных, представляющая стек, могла бы выглядеть следующим образом:
type
TypeOfElem= {};
Assoc= ^ElementOfStack;
ElementOfStack= record
Elem: TypeOfElem;
NextElem: Pointer
end;
Stack= Assoc;
Рассмотрим реализацию основных операций над стеком.
Для создания нового пустого или очистки существующего стека достаточно присвоить указателю на первый его элемент (вершину) значение nil.
procedure CreateStack ( var StackHead: Stack);
begin
StackHead:= nil
end;
Условием пустоты стека является значение его вершины, равное nil.
function StackIsClear( var StackHead: Stack ): Boolean;
begin
StackIsClear:= ( StackHead= nil )
end;
Для включения элемента в стек, необходимо создать новый элемент типа стек, затем инициализировать его информационное поле. В заключение изменить его указатель и указатель на первый элемент стека так, чтобы первым стал новый элемент.
procedure IncludeInStack( var StackHead: Stack; NewElem: TypeOfElem );
var
ServiceVar: Stack;
begin
{созданиеновогоэлемента}
 new( ServiceVar );
ServiceVar^.Elem:= NewElem;
{созданный элемент сделать вершиной стека}
ServiceVar^.NextElem:= StackHead;
StackHead:= ServiceVar
end;
При выполнении этой операции информационное поле элемента, находящегося в вершине стека, должно быть присвоено в качестве значения некоторой переменой, а сам элемент должен быть исключен из стека и уничтожен.
procedure SelectFromStack( var StackHead: Stack; var Result: TypeOfElem);
var
ServiceVar: Assoc;
begin
if StackHead <> nil then begin
{выборэлементаизвершины}
Result:= StackHead^.Elem;
{запоминание ссылки на старую вершину}
ServiceVar:= StackHead;
{исключение из стека и уничтожение элемента}
StackHead:= StackHead^.NextElem;
dispose( ServiceVar )
end
end;
Необходимо обратить внимание на введение вспомогательной переменной ссылочного типа ServiceVar для осуществления удаления элемента. Типична ошибка, связанная с попыткой решить эту задачу через dispose( StackHead ).
Разберем решение типичной задачи, связанной с обработкой стека.
Текст задания
Используя стек (считать уже описанными тип Stack с информационным элементом типа Char, функцию StackIsClear (проверка пустоты стека) и процедуры CreateStack (очистка стека), IncludeInStack (вставка элемента в стек), SelectFromStack (выборка элемента из стека)) решить следующую задачу: в текстовом файле f записана без ошибок формула следующего вида:
<формула>::= <цифра>|M(<формула>,<формула>)|m(<формула>,<формула>)
цифра::= 0|1|2|3|4|5|6|7|8|9
где M обозначает функцию max, а m √ min. Вычислить (как целое число) значение данной формулы (например, M( 5, m( 6, 8)): 6).
Решение
program StackSample;
type
FileType= File of Char;
var
Source: FileType;
function formula( var t: FileType ): integer;
type
TypeOfElem= Char;
Assoc= ^ElementOfStack;
ElementOfStack= record
Elem: TypeOfElem;
NextElem: Pointer
end;
Stack= Assoc;
var
S: Stack;
c, op, x, y: char;
procedure CreateStack ( var StackHead: Stack);
begin
StackHead:= nil
end;
function StackIsClear( var StackHead: Stack ): Boolean;
begin
StackIsClear:= ( StackHead= nil )
end;
procedure IncludeInStack( var StackHead: Stack; NewElem: TypeOfElem );
var
ServiceVar: Stack;
begin
{созданиеновогоэлемента}
new( ServiceVar );
ServiceVar^.Elem:= NewElem;
{созданный элемент сделать вершиной стека}
ServiceVar^.NextElem:= StackHead;
StackHead:= ServiceVar
end;
procedure SelectFromStack( var StackHead: Stack; var Result: TypeOfElem );
var
ServiceVar: Assoc;
begin
if StackHead <> nil then begin
{выборэлементаизвершины}
Result:= StackHead^.Elem;
{запоминание ссылки на старую вершину}
ServiceVar:= StackHead;
{исключение из стека и уничтожение элемента}
StackHead:= StackHead^.NextElem;
dispose( ServiceVar )
end
end;
begin
reset( t );
CreateStack( S );
while not eof( t ) do begin
read(t, c);
{обработка очередной литеры текста (литеры ╚(╩ и ╚,╩ игнорируются)}
if c in ['0'..'9','M','m'] then IncludeInStack( S, c)
else
if c= ')' then begin {конецформулывида p(x, y)}
{в конце стека находится тройка op x y, она удаляется
из стека, выполняется операция op и результат
записывается в стек}
SelectFromStack( S, y );
SelectFromStack( S, x );
SelectFromStack( S, op );
case op of
'M'{max}: if x > y then c:= x else c:= y;
'm'{min}: if x < y then c:= x else c:= y
end;
IncludeInStack( S, c )
end
end; {of while}
{в стеке осталась одна цифра √ значение всей формулы; цифра переводится в целое число}
SelectFromStack( S, c );
formula:= ord( c ) - ord( '0' )
end;
begin
assign( Source, 'c:\temp\source.txt' );
writeln( Formula( Source ) );
end.
4. Двоичныедеревья
Элементы двоичного дерева помимо информативной части имеют две ссылки √ на нижний левый и на нижний правый элементы. Принцип построения двоичного дерева заключается в том, что очередной элемент в зависимости от значения информативной части должен попасть в правое (если информационная часть включаемого элемента больше информационной части корня) или левое (в противном случае) поддерево. При этом в рамках выбранного поддерева определение местоположения нового элемента производится аналогично (см. рис. 7).
Данную структуру целесообразно описать следующим образом:
type
TypeOfElem= {};
Assoc= ^ElemOfTree;
ElemOfTree= record
Elem: TypeOfElem;
Left, Right: Pointer
end;
4.1 Поискэлементавдереве
function FoundInTree( Elem: TypeOfElem; var Tree, Result: Pointer ): Boolean;
var
ServiceVar: Assoc;
b: Boolean;
begin
b:= False;
ServiceVar:= Tree;
if Tree <> nil then
repeat
if ServiceVar^.Elem= Elem then b:= True
else
if Elem < ServiceVar^.Elem then ServiceVar:= ServiceVar^.Left
else ServiceVar:= ServiceVar^.Right
until b or ( ServiceVar= nil );
FoundInTree:= b;
Result:= ServiceVar
end;
4.2 Включение элемента в дерево
Включение элемента в дерево реализуется путем во-первых, поиска вершины √ предка нового элемента, во-вторых, непосредственным включением элемента в дерево по найденной позиции. Опишем процедуру поиска предка для нового элемента.
function SearchNode( Elem: TypeOfElem; var Tree, Result: Assoc): Boolean;
var
ServiceVar1, ServiceVar2: Assoc;
b: Boolean;
begin
b:= False;
ServiceVar1:= Tree;
if Tree <> nil then
repeat
ServiceVar2:= ServiceVar1;
if ServiceVar1^.Elem= Elem then {элементнайден} b:= True
else begin
{запоминание обрабатываемой вершины}
ServiceVar2:= ServiceVar1;
if Elem < ServiceVar1^.Elem then ServiceVar1:=
ServiceVar1^.Left
else ServiceVar1:= ServiceVar1^.Right
end
until b or ( ServiceVar1= nil );
SearchNode:= b;
Result:= ServiceVar2
end;
Как видно из описания, эта функция подобна ранее рассмотренной функции поиска элемента дерева (FoundInTree), но в качестве побочного эффекта фиксируется ссылка на вершину, в которой был найден заданный элемент (в случае успешного поиска), или ссылка на вершину, после обработки которой поиск прекращен (в случае неуспешного поиска). Сама процедура включения элемента в дерево будет иметь следующее описание.
procedure IncludeInTree( Elem: TypeOfElem; var Tree: Assoc );
var
Result, Node: Assoc;
begin
if not SearchNode( Elem, Tree, Result ) then begin
{формирование новой вершины в дереве}
new( Node );
Node^.Elem:= Elem;
Node^.Left:= nil;
Node^.Right:= nil;
if Tree= nil then
{если дерево пусто, то созданный элемент сделать вершиной дерева}
Tree:= Node
else
{подсоединить новую вершину к дереву}
if Elem < Result^.Elem then Result^.Left:= Node
else Result^.Right:= Node
end
end;
Двоичное дерево можно рассматривать как рекурсивную структуру данных, состоящую из корневой записи, указывающей на левое и правое поддерево. Оба поддерева имеют такую же структуру: корень поддерева и правое и левое поддеревья. При этом, для представления дерева рекурсивной динамической структурой целесообразно модифицировать описание типа дерева, данное выше. А именно, удобнее изменить тип ссылок на левое и правое поддеревья с нетипизированного (Pointer) на типизированный: