4.3. Рекурсивные данные и однонаправленные списки

Массивы являются одним из самых важных инструментов программирования. Они позволяют быстро и достаточно просто управлять большими группами объектов. Однако, как мы уже видели, при организации работы со списками они не обладают достаточной гибкостью. Например, при добавлении или удалении элементов в список требуется перестроить массив, сдвигая на одну позицию все элементы расположенные правее места вставки (удаления).

А можно ли организовать список таким образом, чтобы при добавлении и удалении из него элемента не требовалось дополнительных затрат на перестройку и при этом память использовалась рационально? Оказывается, такую организацию списка можно осуществить на основе рекурсивных типов данных, которые поддерживает Delphi.

Мы уже знакомы с понятием рекурсивной процедуры, которая при своем описании имеет обращение к самой себе. Подобно этому рекурсивный тип данных при своем описании допускает обращение к самому себе.

Наиболее широко используется следующая конструкция рекурсивного типа данных:

Листинг 4.3

Type

Tinf=record; // конкретизация типа данных,

... // содержащихся в ячейках списка

Key:Tkey;

end;

Psel=^sel; //рекурсивный тип

sel=Record

Inf:TInf;// информация об элементе списка

A:Psel;//Адрес ячейки такого же типа

end;

Var

sp,sp1,spk:Psel;//указатели на ячейки списка

Как видим, описание типа Psel содержит внутри обращение к самому себе (A:Psel), т.е. оно рекурсивно. При этом А является указателем на ячейку памяти точно такой же структуры. В переменной Inf размещаются данные о некотором элементе списка, причем по крайней мере в одном из полей Inf, а иногда и в отдельном поле записи расположены сведения по которым производится поиск требуемой информации. Это поле будем обозначать key:Tkey, а сведения называть ключом.

С помощью такого рекурсивного типа организуются однонаправленные (линейные) связанные списки следующим образом: элементы списка размещаются в ячейках памяти типа Tsel, причем в указателе А каждой

ячейки помещается адрес следующей за ней ячейки:

В

се ячейки динамически размещаются в куче по адресамSp1, Sp2 ... Spk. Адрес начальной ячейки запоминается в указателе (например, в Sp1), в адресной части последней ячейки записывается Nil (отсутствие адреса). Такой список с одной точкой входа sp1 используется при организации стека. В некоторых ситуациях возможно запоминание адреса последней ячейки в указателе (например в spk). Список с 2-мя точками входами sp1 и spk используется для моделирования очереди.

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

sp:=sp1; //текущий указатель установили в начало списка

sp:=sp^.A; //теперь в sp находится sp2

sp:=sp^.A^.A; //теперь в sp находится sp4

Write(sp^.Inf);//вывод информации Inf4

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

Напишем класс для работы со списком с косвенной адресацией.

Листинг 4.4

Type Tlist=class(Tobject)

sp1,spk,sp:psel;

constructor create;

procedure Addk(Inf:Tinf);

procedure Add1(Inf:Tinf);

. . . . . . . . . . . . .

procedure Print;

end;

constructor Tlist.create;

begin