Джулиан Бакнелл - Фундаментальные алгоритмы и структуры данных в Delphi Страница 22
- Категория: Компьютеры и Интернет / Программирование
- Автор: Джулиан Бакнелл
- Год выпуска: -
- ISBN: -
- Издательство: -
- Страниц: 119
- Добавлено: 2019-05-29 11:02:54
Джулиан Бакнелл - Фундаментальные алгоритмы и структуры данных в Delphi краткое содержание
Прочтите описание перед тем, как прочитать онлайн книгу «Джулиан Бакнелл - Фундаментальные алгоритмы и структуры данных в Delphi» бесплатно полную версию:Книга "Фундаментальные алгоритмы и структуры данных в Delphi" представляет собой уникальное учебное и справочное пособие по наиболее распространенным алгоритмам манипулирования данными, которые зарекомендовали себя как надежные и проверенные многими поколениями программистов. По данным журнала "Delphi Informant" за 2002 год, эта книга была признана сообществом разработчиков прикладных приложений на Delphi как «самая лучшая книга по практическому применению всех версий Delphi».В книге подробно рассматриваются базовые понятия алгоритмов и основополагающие структуры данных, алгоритмы сортировки, поиска, хеширования, синтаксического разбора, сжатия данных, а также многие другие темы, тесно связанные с прикладным программированием. Изобилие тщательно проверенных примеров кода существенно ускоряет не только освоение фундаментальных алгоритмов, но также и способствует более квалифицированному подходу к повседневному программированию.Несмотря на то что книга рассчитана в первую очередь на профессиональных разработчиков приложений на Delphi, она окажет несомненную пользу и начинающим программистам, демонстрируя им приемы и трюки, которые столь популярны у истинных «профи». Все коды примеров, упомянутые в книге, доступны для выгрузки на Web-сайте издательства.
Джулиан Бакнелл - Фундаментальные алгоритмы и структуры данных в Delphi читать онлайн бесплатно
-------
Полный код класса TtdNodeManager можно найти на Web-сайте издательства, в разделе материалов. После выгрузки материалов отыщите среди них файл TDNdeMgr.pas.
Перед тем как вернуться к дальнейшим исследованиям односвязных списков, с которых мы начали наши рассуждения, отметим несколько недостатков класса TtdNodeManager. Первое, что следует отметить - это то, что метод FreeNode не проверяет, принадлежит ли освобождаемый узел к требуемому классу, т.е. находится ли он на странице, контролируемой классом. Это основной вопрос правильного функционирования класса. Если у класса есть узел, который не принадлежит к данному классу, он может иметь неверную длину (что в итоге может привести к перезаписи памяти) или может принадлежать к другому классу, который, возможно, очистит страницу, содержащую узел, и т.д. При отладке имеет смысл проводить проверку принадлежности к классу всех освобождаемых узлов. Реализация, содержащаяся в рамках сопровождающих книгу материалов, включает специальный код проверки при условии, что модель будет компилироваться с использованием утверждений.
Вторая проблема вызвана тем, что мы легко можем удалить экземпляр диспетчера узлов до удаления объектов, которые эти узлы используют. Это приведет к возникновению неизвестных ошибок. Только достаточная внимательность во время программирования может нас избавить от такого рода ошибок.
(Кстати, в качестве простого доказательства того, что мы не зря потеряли время на реализацию диспетчера узлов, можно сказать, что тесты по распределению и освобождению одного миллиона узлов показали, что диспетчер узлов работает в 3-4 раза быстрее, чем диспетчер кучи Delphi.)
Класс односвязного списка
Перед тем как приступить к реализации класса TtdSingleLinkList для представления односвязного списка, рассмотрим несколько вводных замечаний.
Начнем с самого начала. Как уже упоминалось, было бы очень удобно использовать связный список, не беспокоясь о его узлах. Хотелось бы, чтобы класс связного списка мог работать с любыми типами указателей, подобно классу TList. Для получения доступа к элементам связного списка было бы желательно использовать индекс (несмотря на то что это может негативно сказаться на быстродействии), но еще лучше было бы использовать терминологию баз данных. Так, в связном списке можно использовать курсор, который указывает на "текущий" элемент списка. Тогда можно было бы написать методы для позиционирования курсора перед любым элементом списка, перемещения курсора на следующий элемент, вставки и удаления элемента в позиции курсора и т.д. Поскольку мы создаем связный список в виде класса, мы можем работать с родительским объектом текущего элемента, что позволит запрограммировать метод Insert так, как он реализован в TList (т.е. за счет перемещения текущего элемента и всех последующих элементов на одну позицию и вставки в освободившееся место нового элемента). Аналогично можно реализовать и метод Delete.
Интерфейс класса TtdSingleLinkList выглядит следующим образом:
Листинг 3.7. Класс TtdSingleLinkList
TtdSingleLinkList = class private
FCount : longint;
FCursor : PslNode;
FCursorIx: longint;
FDispose : TtdDisposeProc;
FHead : PslNode;
FNanie : TtdNameString;
FParent : PslNode;
protected
function sllGetItem(aIndex : longint): pointer;
procedure sllSetItem(aIndex : longint; aItem : pointer);
procedure sllError(aErrorCode : integer;
const aMethodName : TtdNameString);
class procedure sllGetNodeManager;
procedure sllPositionAtNth(aIndex : longint);
public
constructor Create(aDispose : TtdDisposeProc);
destructor Destroy; override;
function Add(aItem : pointer): longint;
procedure Clear;
procedure Delete(aIndex : longint);
procedure DeleteAtCursor;
function Examine : pointer;
function First : pointer;
function IndexOf(aItem : pointer): longint;
procedure Insert(aIndex : longint; aItem : pointer);
procedure InsertAtCursor(aItem : pointer);
function IsAfterLast : boolean;
function IsBeforeFirst : boolean;
function IsEmpty : boolean;
function Last : pointer;
procedure MoveBeforeFirst;
procedure MoveNext;
procedure Remove(aItem : pointer);
procedure Sort(aCompare : TtdCompareFunc);
property Count : longint read FCount;
property Items[aIndex : longint] : pointer read sllGetItem write sllSetItem; default;
property Name : TtdNameString read FName write FName;
end;
Несмотря на то что названия методов соответствуют стандарту TList, появилось несколько новых методов. Метод MoveBeforeFirst помещает курсор перед всеми элементами связного списка. IsBeforeFirst и IsAfterLast возвращают True, если курсор находится, соответственно, перед всеми элементами или после всех элементов списка. Метод MoveNext перемещает курсор на следующий элемент списка. Свойство Items аналогично соответствующему свойству списка TList: элементы нумеруются от 0 до Count-1.
Конструктор Create проверяет, создан ли экземпляр диспетчера узлов, а затем распределяет память для узла, который будет фиктивным начальным узлом. Затем курсор помещается перед всеми узлами (поскольку в списке еще нет узлов, это совсем несложно). Деструктор Destroy очищает связный список и освобождает фиктивный начальный узел, выделенный конструктором Create.
Листинг 3.8. Конструктор и деструктор класса TtdSingleLinkList
constructor TtdSingleLinkList.Create(aDispose : TtdDisposeProc);
begin
inherited Create;
{сохранить процедуру удаления}
FDispose :=aDispose;
{получить диспетчер узлов}
s 11 GetNodeManager;
{распределить память под начальный узел}
FHead := PslNode (SLNodeManager.AllocNode);
FHead^.slnNext := nil;
FHead^.slnData := nil;
{установить курсор}
MoveBeforeFirst;
end;
destructor TtdSingleLinkList.Destroy;
begin
{удалить все узлы, включая начальный фиктивный узел}
Clear;
SLNodeManager.FreeNode(FHead);
inherited Destroy;
end;
Особый интерес здесь представляет тот факт, что связный список организован таким образом, что для всех экземпляров класса TtdSingleLinkList создается только один диспетчер узлов. Все экземпляры пользуются одним и тем же диспетчером. Можно было бы запрограммировать, чтобы каждый класс создавал свой диспетчер, но это бы означало использование большого дополнительного объема для экземпляра класса. Таким образом, учитывая то, что в приложении, в котором имеется один связный список, как правило, есть несколько списков, было решено ввести переменную класса. Но во всех этих рассуждениях присутствует один недостаток: Delphi не поддерживает переменные класса. Поэтому в коде мы имитируем такую переменную, объявив ее как глобальную в разделе implementation модуля. Если вы просмотрите содержимое файла TDLnkLst.pas, то найдете следующее объявление:
var
SLNodeManager : TtdNodeManager;
Все методы класса односвязного списка можно разбить на две категории: методы, действующие по последовательной схеме (MoveBeforeFirst, InsertAtCursor и т.д.), и методы, которые работают со списком как с массивом (свойство Items, методы Delete, IndexOf и т.д.). Рассмотрим сначала методы первой группы, поскольку мы уже говорили о принципе их работы в начале главы при описании связных списков. Для упрощения реализации мы не только храним курсор (т.е. указатель на текущий узел) в объекте, но и родительский объект курсора (т.е. указатель на родительский объект текущего курсора). Такая схема позволяет упростить методы вставки и удаления элементов.
Листинг 3.9. Стандартные операции со связным списком для класса TtdSingleLinkList
procedure TtdSingleLinkList.Clear;
var
Temp : PslNode;
begin
{удалить все узлы, за исключением начального; при возможности освободить все данные}
Temp := FHead^.slnNext;
while (Temp <> nil) do
begin
FHead^.slnNext := Temp^.slnNext;
if Assigned(FDispose) then
FDispose(Temp^.slnData);
SLNodeManager.FreeNode(Temp);
Temp := FHead^.slnNext;
end;
FCount := 0;
MoveBeforeFirst;
end;
procedure TtdSingleLinkList.DeleteAtCursor;
begin
if (FCursor = nil) or (FCursor = FHead) then
sllError(tdeListCannotDelete, 'Delete');
{удалить все элементы}
if Assigned(FDispose) then
FDispose(FCursor^.slnData);
{удалить ссылки на узел и удалить сам узел}
FParent^.slnNext := FCursor^.slnNext;
SLNodeManager.FreeNode(FCursor);
FCursor := FParent^.slnNext;
dec(FCount);
end;
function TtdSingleLinkList.Examine : pointer;
begin
if (FCursor = nil) or (FCursor = FHead) then
sllError(tdeListCannotExamine, 'Examine');
{вернуть данные с позиции курсора}
Result := FCursor^.slnData;
end;
procedure TtdSingleLinkList.InsertAtCursor(aItem : pointer);
var
NewNode : PslNode;
begin
{убедиться, что вставка производится не перед первой позицией; если курсор находится перед первой позицией, переместить его на одну позицию вперед}
if (FCursor = FHead) then
MoveNext;
{распределить новый узел и вставить его в позицию курсора}
NewNode := PslNode (SLNodeManager.AllocNode);
NewNode^.slnData := aItem;
NewNode^.slnNext := FCursor;
FParent^.slnNext := NewNode;
FCursor := NewNode;
inc(FCount);
end;
function TtdSingleLinkList.IsAfterLast : boolean;
begin
Result := FCursor;
nil;
end;
function TtdSingleLinkList.IsBeforeFirst : boolean;
begin
Result := FCursor = FHead;
end;
function TtdSingleLinkList.IsEmpty : boolean;
begin
Result := (Count = 0);
end;
procedure TtdSingleLinkList.MoveBeforeFirst;
begin
{установить курсор на начальный узел}
FCursor := FHead;
FParent := nil;
FCursorIx := -1;
end;
procedure TtdSingleLinkList.MoveNext;
begin
{переместить курсор по его указателю Next, игнорировать попытку выхода за конечный узел списка}
if (FCursor <> nil) then begin
FParent := FCursor;
FCursor := FCursor^.slnNext;
inc(FCursorIx);
end;
end;
Вы, возможно, обратили внимание, что некоторые из приведенных методов пользуются полем объекта FCursorIx. Именно это поле позволяет обеспечить высокую эффективность методов, основанных на использовании индекса, поскольку в нем хранится индекс курсора (при этом первый узел имеет индекс 0, точно так же как в TList). Значение поля используется методом ellPositionAtNth, который оптимальным образом перемещает курсор в позицию с указанным индексом.
Жалоба
Напишите нам, и мы в срочном порядке примем меры.