Энциклопедия Turbo Pascal. Главы 1-4 - Списки с двойной связью
ОГЛАВЛЕНИЕ
Списки с двойной связью
Каждый элемент в списке с двойной связью имеет указатель на следующий элемент списка и указатель на предыдущий элемент списка. Рис.11 иллюстрирует характер связей в таком списке. Список, который вместо одной имеет две связи, отличается двумя основными преимуществами. Во-первых, список может читаться в обоих направлениях. Это не только упрощает сортировку списка, но также позволяет пользователям базы данных просматривать данные в обоих направлениях. Во-вторых, и прямая и обратная связь позволяют прочитать список полностью и поэтому при нарушении одной из связей список может быть восстановлен по другой связи. Это свойство имеет смысл использовать при отказах оборудования, приводящих к нарушению списка.
-----------¬ -----------¬ ------------¬
¦1 info ¦ ¦1 info ¦ ¦1 info ¦
+-----T----+ +-----T----+ +-----T-----+
¦2nil ¦ ¦ ¦ ¦ ¦ ¦ ¦2 nil¦
L-----+----- L-----+----- L-----+-----
Рис.11. Список с двойной связью:
1 - информационные поля; 2 - связь с нулевым значением.
Для списка с двойной связью предусматриваются три основные операции: вставка нового первого элемента, вставка нового среднего элемента и вставка нового последнего элемента. Эти операции проиллюстрированы на рис.12.
Список с двойной связью строится подобно списку с одной связью, причем в записи должно быть предусмотрено место для двух указателей. Для примера со списком почтовых корреспонденций запись адреса можно модифицировать следующим образом:
type
str80 = string[80];
AddrPointer = ^address;
address = record
1 Inserling a New First Element
-------¬ -------¬
¦2 new ¦ ¦2 new ¦
+---T--+ +---T--+
¦ ¦ ¦ ¦ ¦ ¦
L---+--- L---+-- 4becomes
-------¬ -------¬ -------¬ -------¬ -------¬ -------¬
¦5 info¦ ¦5 info¦ ¦5 info¦ ¦5 info¦ ¦5 info¦ ¦5 info¦
+---T--+ +--T---+ +--T---+ +--T---+ +---T--+ +--T---+
3¦nil¦ ¦ ¦ ¦ ¦ ¦ ¦nil¦3 ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦nil¦3
L---+--- L--+---- L--+---- L--+---- L---+--- L--+--- 6 Inserling a New Middle Element
-------¬ -------¬
¦2 new ¦ ¦2 new ¦
+--T---+ +---T--+
¦ ¦ ¦ ¦ ¦ ¦
L--+---- L---+-- 4becomes
-------¬ -------¬ -------¬ -------¬ -------¬ -------¬
¦5 info¦ ¦5 info¦ ¦5 info¦ ¦5 info¦ ¦5 info¦ ¦5 info¦
+---T--+ +--T---+ +--T---+ +---T--+ +---T--+ +--T---+
3¦nil¦ ¦ ¦ ¦ ¦ ¦ ¦nil¦3 3¦nil¦ ¦ ¦ ¦ ¦ ¦ ¦nil¦3
L---+--- L--+---- L--+---- L---+--- L---+--- L--+--- 7 Inserling a New Last Element
-------¬ -------¬
¦2 new ¦ ¦2 new ¦
+--T---+ +--T---+
¦ ¦ ¦ ¦ ¦nil¦3
L--+---- L--+--- 4becomes
-------¬ -------¬ -------¬ -------¬ -------¬ -------¬
¦5 info¦ ¦5 info¦ ¦5 info¦ ¦5 info¦ ¦5 info¦ ¦5 info¦
+---T--+ +--T---+ +--T---+ +---T--+ +--T---+ +---T--+
3¦nil¦ ¦ ¦ ¦ ¦ ¦ ¦nil¦3 3¦nil¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
L---+--- L--+---- L--+---- L---+--- L--+---- L---+---
Рис.12. Вставка элемента в список с двойной связью:
1 - вставка нового первого элемента;
2 - новый элемент;
3 - указатель с нулевым значением;
4 - левый список преобразуется в правый;
5 - информационные поля;
6 - вставка нового среднего элемента;
7 - вставка нового последнего элемента.
name : string[30];
street: string[40];
city: string[20];
state: string[2];
zip: string[9];
next: AddrPointer; { указатель на следующую запись }
prior: AddrPointer; { указатель на предыдущую запись }
end;
DataItem = address;
DataArray = array [1..100] of AddrPointer;
Ниже приводится функция, которая позволяет строить список с двойной связью для записей адресов:
{добавление элементов в список с двойной связью }
procedure DL_Store(i: AddrPointer);
begin
if last=nil then { первый элемент списка }
begin
last:=i;
start:=i;
i^.next:=nil;
i^.prior:=nil;
end
else
begin
last^.next:=i;
i^.next:=nil;
i^.prior:=last;
last:=i;
end;
end; { конец функции добавления в список с двойной связью }
Эта функция помещает каждый новый элемент в конец списка. Следует иметь в виду, что перед первым обращением к функции переменные "start" и "last" должны устанавливаться в нулевое значение. В ходе построения списка с двойной связью каждый новый элемент может /как и для списка с одной связью/ устанавливаться не в конец, а в соответствующее место, обеспечивая упорядочность элементов в списке. Ниже приводится функция, которая создает список с двойной связью, упорядоченый по возрастанию фамилий из записей адресов:
{упорядоченная установка элементов в список с двойной связью}
function DSL_Store(info, start: AddrPointer;
var last: AddrPointer): AddrPointer;
{ вставка элементов в соответствующее место с сохранением
порядка }
var
old, top: AddrPointer;
done: boolean;
begin
top := start;
old := nil;
done := FALSE;
if start = nil then begin { первый элемент списка }
info^.next := nil;
last := info;
info^.prior :=nil;
DCL_Store := info;
end else
begin
while (start<>nil) and (not done) do
begin
if start^.name < info^.name then
begin
old := start;
start := start^.next;
end else
begin { вставка в середину }
if old <>nil then
begin
old^.next := info;
info^.next := start;
start^.prior := info;
info^.prior := old;
DSL_Store := top; { сохранение начала }
done := TRUE;
end else
begin
info^.next := start;{новый первый элемент }
info^.prior := nil;
DSL_Store := info;
done := TRUE;
end;
end;
end; { конец цикла }
if not done then begin
last^.next := info;
info^.next := nil;
info^.prior := last;
last := info;
DSL_Store := top; { сохранение начала }
end;
end;
end; { конец функции DSL_Store }
Поскольку элемент может помещаться в самое начало списка, эта функция должна выдавать указатель на начало списка, чтобы в других частях программы учесть изменения начала списка. При поиске конкретного элемента списка, как и для списка с одиночной связью, делается последовательный проход по цепочке связей пока не будет найден требуемый элемент.
При удалении элемента из списка возникает одна из трех ситуаций: удаление первого элемента, удаление среднего элемента и удаление последнего элемента. Рис.13 иллюстрирует изменение связей для этих случаев.
1 Deleting the First Item
3becomes
-------¬ -------¬ -------¬ --------¬ -------¬ -------¬
¦2 info¦ ¦2 info¦ ¦2 info¦ 4¦2 info ¦ ¦2 info¦ ¦2 info¦
+---T--+ +--T---+ +--T---+ +---T---+ +---T--+ +--T---+
5¦nil¦ ¦ ¦ ¦ ¦ ¦ ¦nil¦5 5¦nil¦nil¦55¦nil¦ ¦ ¦ ¦nil¦5
L---+--- L--+---- L--+---- L---+---- L---+--- L--+---
6 Deleting a Middle Item
3becomes
-------¬ -------¬ -------¬ -------¬ --------¬ -------¬
¦2 info¦ ¦2 info¦ ¦2 info¦ ¦2 info¦ ¦2 info ¦ ¦2 info¦
+---T--+ +--T---+ +--T---+ +---T--+ +---T---+ +--T---+
5¦nil¦ ¦ ¦ ¦ ¦ ¦ ¦nil¦5 5¦nil¦ ¦5 ¦nil¦nil¦5¦ ¦nil¦5
L---+--- L--+---- L--+---- L---+--- L---+---- L--+---
7 Deleting the Last Item
3becomes
-------¬ -------¬ -------¬ -------¬ -------¬ --------¬
¦2 info¦ ¦2 info¦ ¦2 info¦ ¦2 info¦ ¦2 info¦ ¦2 info ¦
+---T--+ +--T---+ +--T---+ +---T--+ +--T---+ +---T---+
5¦nil¦ ¦ ¦ ¦ ¦ ¦ ¦nil¦5 5¦nil¦ ¦ ¦ ¦nil¦55¦nil¦nil¦5
L---+--- L--+---- L--+---- L---+--- L--+---- L---+----
Рис.13. Удаление элемента из списка с двойной связью:
1 - удаление первого элемента;
2 - информационные поля;
3 - левый список преобразуется в правый;
4 - удаленный элемент;
5 - указатель с нулевым значением;
6 - удаление среднего элемента;
7 - удаление последнего элемента.
Ниже приводится функция, которая выполняет удаление элемента типа "address" из списка с двойной связью:
{ удаление элемента из списка с двойной связью }
function DL_Delete(start: AddrPointer;
key: str80): AddrPointer;
var
temp, temp2: AddrPointer;
done: boolean;
begin
if start^.name=key then begin { первый элемент списка}
DL_Delete:=start^.next;
if temp^.next <> nil then
begin
temp := start^.next;
temp^.prior := nil;
end;
dispose(start);
end else
begin
done := FALSE;
temp := start^.next;
temp2 := start;
while (temp<>nil) and (not done) do
begin
if temp^.name=key then
begin
temp^.next:=temp^.next;
if temp^.next<>nil then
temp^.next^.prior:=temp2;
done:=TRUE;
dispose(temp);
end else
begin
temp2:=temp;
temp:=temp^.next;
end;
end;
DL_Delete:=start; { начало не изменяется }
if not done then WriteLn('not found');
end;
end; { конец функции удаления элемента
из списка с двойной связью }
Этой функции передается на один указатель меньше, чем для соответствующей функции при использовании списка с одной связью. /Удаленный элемент уже имеет указатель на предыдущий элемент и указатель на следующий элемент/. Поскольку первый элемент в списке может измениться, функция возвращает указатель на начало списка.