Читаем Фундаментальные алгоритмы и структуры данных в Delphi полностью

Если при этом коэффициент загрузки хеш-таблицы достигает максимального значения, мы расширяем хеш-таблицу.

Как легко догадаться, метод Delete работает аналогично.

Листинг 7.15. Удаление элемента из хеш-таблицы со связыванием

procedure TtdHashTableChained.Delete(const aKey : string);

var

Inx : integer;

Parent : pointer;

Temp : PHashedItem;

begin

{поиск ключа}

if not htcFindPrim(aKey, Inx, Parent) then

htcError(tdeHashTblKeyNotFound, 'Delete');

{удалить элемент и ключ из данного узла}

Temp := PHashedItem(Parent)^.hiNext;

if Assigned(FDispose) then

FDispose(Temp^.hiItem);

{$IFDEF Delphi1}

DisposeStr(Temp^.hiKey);

{$ELSE}

Temp^.hiKey := '';

{$ENDIF}

{разорвать связь с узлом и освободить его}

PHashedItem(Parent)^.hiNext := Temp^.hiNext;

FNodeMgr.FreeNode(Temp);

dec(FCount);

end;

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

Метод Clear очень похож на метод Delete, за исключением того, что мы просто стандартным образом удаляем все узлы из каждого связного списка (естественно, за исключением заглавных узлов).

Листинг 7.16. Очистка хеш-таблицы TtdHashTableChained

procedure TtdHashTableChained.Clear;

var

Inx : integer;

Temp, Walker : PHashedItem;

begin

for Inx := 0 to pred(FTable.Count) do

begin

Walker := PHashedItem(FTable.List^[Inx])^.hiNext;

while (Walker <> nil) do

begin

if Assigned(FDispose) then

FDispose(Walker^.hiItem);

{$IFDEF Delphi1}

DisposeStr(Walker^.hiKey);

{$ELSE}

Walker^.hiKey := '';

{$ENDIF}

Temp := Walker;

Walker := Walker^.hiNext;

FNodeMgr.FreeNode(Temp);

end;

PHashedItem(FTable.List^[Inx])^.hiNext := nil;

end;

FCount := 0;

end;

Метод Find прост, поскольку основная часть работы выполняется вездесущим методом htcFindPrim.

Листинг 7.17. Поиск элемента в хеш-таблице со связыванием

function TtdHashTableChained.Find(const aKey : string; var aItem : pointer): boolean;

var

Inx : integer;

Parent : pointer;

begin

if htcFindPrim(aKey, Inx, Parent) then begin

Result := true;

aItem := PHashedItem(Parent)^.hiNext^.hiItem;

end

else begin

Result := false;

aItem := nil;

end;

end;

Единственная небольшая сложность состоит в том, что метод htcFindPrim возвращает родительский узел действительно интересующего нас узла.

Увеличение хеш-таблицы - вовсе не то, что требуется, поскольку при этом необходимо выполнить очень много операций по перемещению данных. Однако класс содержит автоматическую операцию увеличения таблицы. Свойство MaxLoadFactor управляет тем, когда это происходит, вызывая метод htcGrowTable в случае вставки слишком большого количества элементов.

Листинг 7.18. Увеличение хеш-таблицы со связыванием

procedure TtdHashTableChained.htcGrowTable;

begin

{увеличить размер таблицы примерно в два раза по сравнению с предыдущим размером}

htcAlterTableSize(TDGetClosestPrime(succ(FTable.Count * 2)));

end;

procedure TtdHashTableChained.htcAlterTableSize(aNewTableSize : integer);

var

Inx : integer;

OldTable : TList;

Walker, Temp : PHashedItem;

begin

{сохранить старую таблицу}

OldTable := FTable;

{распределить новую таблицу}

FTable := TList.Create;

try

FTable.Count := aNewTableSize;

htcAllocHeads(FTable);

{считывать старую таблицу и перенести ключи и элементы в новую таблицу посредством их вставки}

FCount := 0;

for Inx := 0 to pred(OldTable.Count) do

begin

Walker := PHashedItem(OldTable.List^[Inx])^.hiNext;

while (Walker <> nil) do

begin

{$IFDEF Delphi1}

Insert(Walker^.hiKey^, Walker^.hiItem);

{$ELSE}

Insert(Walker^.hiKey, Walker^.hiItem);

{$ENDIF}

Walker := Walker^.hiNext;

end;

end;

except

{предпринять попытку очистки и сохранения хеш-таблицы в непротиворечивом состоянии в случае возникновения исключения}

Clear;

htcFreeHeads(FTable);

FTable.Free;

FTable := OldTable;

raise;

end;

{теперь новая таблица полностью заполнена всеми элементами и их ключами, поэтому необходимо уничтожить старую таблицу и ее связные списки}

for Inx := 0 to pred(01dTable.Count) do

begin

Walker := PHashedItem(OldTable.List^[Inx])^.hiNext;

while (Walker <> nil) do

begin

{$IFDEF Delphi1}

DisposeStr(Walker^.hiKey);

{$ELSE}

Walker^.hiKey := '';

{$ENDIF}

Temp := Walker;

Walker := Walker^.hiNext;

FNodeMgr.FreeNode(Temp);

end;

PHashedItem(OldTable.List^[Inx])^.hiNext := nil;

end;

htcFreeHeads(OldTable);

OldTable.Free;

end;

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

В заключение рассмотрим основной метод, используемый многими методами класса хеш-таблицы - htcFindPrim (листинг 7.19).

Перейти на страницу:

Похожие книги

C++
C++

С++ – это универсальный язык программирования, задуманный так, чтобы сделать программирование более приятным для серьезного программиста. За исключением второстепенных деталей С++ является надмножеством языка программирования C. Помимо возможностей, которые дает C, С++ предоставляет гибкие и эффективные средства определения новых типов. Используя определения новых типов, точно отвечающих концепциям приложения, программист может разделять разрабатываемую программу на легко поддающиеся контролю части. Такой метод построения программ часто называют абстракцией данных. Информация о типах содержится в некоторых объектах типов, определенных пользователем. Такие объекты просты и надежны в использовании в тех ситуациях, когда их тип нельзя установить на стадии компиляции. Программирование с применением таких объектов часто называют объектно-ориентированным. При правильном использовании этот метод дает более короткие, проще понимаемые и легче контролируемые программы. Ключевым понятием С++ является класс. Класс – это тип, определяемый пользователем. Классы обеспечивают сокрытие данных, гарантированную инициализацию данных, неявное преобразование типов для типов, определенных пользователем, динамическое задание типа, контролируемое пользователем управление памятью и механизмы перегрузки операций. С++ предоставляет гораздо лучшие, чем в C, средства выражения модульности программы и проверки типов. В языке есть также усовершенствования, не связанные непосредственно с классами, включающие в себя символические константы, inline-подстановку функций, параметры функции по умолчанию, перегруженные имена функций, операции управления свободной памятью и ссылочный тип. В С++ сохранены возможности языка C по работе с основными объектами аппаратного обеспечения (биты, байты, слова, адреса и т.п.). Это позволяет весьма эффективно реализовывать типы, определяемые пользователем. С++ и его стандартные библиотеки спроектированы так, чтобы обеспечивать переносимость. Имеющаяся на текущий момент реализация языка будет идти в большинстве систем, поддерживающих C. Из С++ программ можно использовать C библиотеки, и с С++ можно использовать большую часть инструментальных средств, поддерживающих программирование на C. Эта книга предназначена главным образом для того, чтобы помочь серьезным программистам изучить язык и применять его в нетривиальных проектах. В ней дано полное описание С++, много примеров и еще больше фрагментов программ.

Бьёрн Страуструп , Бьярн Страустрап , Мюррей Хилл

Программирование, программы, базы данных / Программирование / Книги по IT