Павел Разбойников дата публикации 27-12-2009 12:12 Реализация двусвязного списка
Доброго времени суток, жители Королевства!
Когда нам в университете читали лекции по спискам, очередям и деревьям, всё было как-то сложно и непонятно. И в памяти отложилось, что нечто такое существует, а как и зачем этим пользоваться, было ещё не ясно.
Недавно я принялся писать то, что, наверно, каждый программист однажды писал, — это телефонно-адресный справочник. Тут я осознал мощь и плюсы двусвязных списков относительно простых массивов! Но, увы, лекции оказались не достаточно информативными для программирования, а поисковики отказывались мне показывать то, что нужно. Но, благодаря книге Рода Стивенса "Delphi, готовые алгоритмы" я понял, как это можно красиво и понятно реализовать.
Пишу всё это для тех, кому не понятна тема, чтобы готовым примером показать, как это может быть реализовано и как это работает, т.к. достойного материала по этой теме, кроме выше указанной книги, я так и не нашёл. Повсюду либо очень поверхностно, либо простое описание без кода.
Ниже достоинства и недостатки списков из Википедии
Достоинства:
- лёгкость добавления и удаления элементов
- размер ограничен только объёмом памяти компьютера и разрядностью указателей
- динамическое добавление и удаление элементов
Недостатки:
- сложность определения адреса элемента по его индексу (номеру) в списке
- на поля-указатели (указатели на следующий и предыдущий элемент) расходуется дополнительная память (в массивах, например, указатели не нужны)
- работа со списком медленнее, чем с массивами, так как к любому элементу списка можно обратиться, только пройдя все предшествующие ему элементы
- элементы списка могут быть расположены в памяти разреженно, что окажет негативный эффект на кэширование процессора
- над связными списками гораздо труднее (хотя и в принципе возможно) производить параллельные векторные операции, такие как вычисление суммы
К достоинствам необходимо добавить ещё несколько важных вещей.
Когда вы удаляете что-либо из массива, то вы либо делаете метку на элементе, что он удалён, либо каждый раз меняете размер массива. А представьте, что меток накопилось слишком много. Приходится их чистить. К тому же придётся считать количество удалённых элементов, если вы хотите получить общее число существующих записей. А каждый раз, проходя по элементам массива, вам надо будет пропускать удалённые элементы. Если же вы решите каждый раз менять размер массива, то программа будет медленно работать. Достаточно представить, что количество записей исчисляется тысячами, десятками тысяч...
Итак, что у нас имеется.
type
PRDListEl = ^TRDListEl;
TRDListEl = record
Value: PByte;
Prev: PRDListEl;
Next: PRDListEl;
end;
TFilterEvent = function: Boolean;
TRazDList = class
private
FFirst, FLast: PRDListEl;
FCount: Cardinal;
FFilter: PRDListEl;
FOn_Term: TFilterEvent;
FSearch: Byte;
FLastFound: PRDListEl;
FElem1, FElem2: PRDListEl;
FInd1, FInd2: Cardinal;
function GetItem(Index: Cardinal): PRDListEl;
function GetNumber(Element: PRDListEl): Int64;
procedure GetTwoItems(Index1, Index2: Cardinal);
procedure GetTwoNumbers(Element1, Element2: PRDListEl);
public
property Count: Cardinal read FCount default 0;
property First: PRDListEl read FFirst default nil;
property Last: PRDListEl read FLast default nil;
property Item[Index: Cardinal]: PRDListEl read GetItem;
property Number[Element: PRDListEl]: Int64 read GetNumber;
property Filter: PRDListEl read FFilter default nil;
property OnTerm: TFilterEvent read FOn_Term write FOn_Term;
constructor Create;
destructor Destroy; override;
function Insert(Index: Cardinal): PRDListEl; overload;
function Insert(Index: Cardinal; Value: Pointer): PRDListEl; overload;
function Add: PRDListEl; overload;
function Add(Value: Pointer): PRDListEl; overload;
function AddLeft(Index: Cardinal): PRDListEl; overload;
function AddLeft(Index: Cardinal; Value: Pointer): PRDListEl; overload;
function AddRight(Index: Cardinal): PRDListEl; overload;
function AddRight(Index: Cardinal; Value: Pointer): PRDListEl; overload;
function Delete(Element: PRDListEl): Boolean; overload;
function Delete(Index: Cardinal): Boolean; overload;
function DeleteLeft(Index: Cardinal): Boolean;
function DeleteRight(Index: Cardinal): Boolean;
function Replace(SourEl, DestEl: PRDListEl): PRDListEl; overload;
function Replace(SourIndex, DestIndex: Cardinal): Cardinal; overload;
function Copy(SourEl, DestEl: PRDListEl): Boolean; overload;
function Copy(SourIndex, DestIndex: Cardinal): Boolean; overload;
function Change(El1, El2: PRDListEl): Boolean; overload;
function Change(Index1, Index2: Cardinal): Boolean; overload;
function FindFirst: PRDListEl;
function FindLast: PRDListEl;
function FindNext: PRDListEl;
procedure Clear;
function ReCalc: Cardinal;
end;
|
|
Поиск реализован следующим образом:
- Создайте функцию по схеме:
function <имя вашей функции>: Boolean;
- Запишите туда условия выполнения поиска, например:
Result:=(DList.Filter^.Next=nil); //будет найден последний элемент списка,
где DList — экземпляр класса TRazDList.
- До вызова поиска назначьте условие поиска на свою функцию, например:
DList.OnTerm:=<имя вашей функции>;,
где DList — экземпляр класса TRazDList.
В классе я реализовал самые необходимые, на мой взгляд, поля и методы, которые могут пригодиться. Думаю, объяснять здесь нечего. Всё предельно просто. Без проблем сможет разобраться даже начинающий. К тому же в исходниках много комментариев.
Из приведённого мною кода легко сделать односвязный список, циклические списки или даже дерево. Если у вас будет желание увидеть реализацию дерева, то с удовольствием поделюсь с вами.
Исходный код модуля здесь.
Обсуждение материала [ 01-02-2010 12:51 ] 24 сообщения |