Фундаментальные алгоритмы и структуры данных в Delphi
Шрифт:
Однако вместо этого мы создадим так называемое косвенное сортирующее дерево (indirect heap). При добавлении элемента в очередь по приоритету, управление этим элементом передается очереди. Взамен мы получаем дескриптор (handle). Дескриптор - это значение, по которому очередь "узнает" о добавлении элемента. Если хотите, дескриптор является косвенной ссылкой на реальный элемент в сортирующем дереве.
Итак, чтобы удалить элемент из очереди по приоритету, мы передаем очереди дескриптор этого элемента. Очередь использует этот дескриптор для выяснения позиции элемента в сортирующем
Для изменения приоритета элемента мы просто изменяем значение приоритета элемента и сообщаем очереди о том, что произошло, передавая ей дескриптор элемента. Затем очередь может восстановить свойство пирамидальное™. Операция исключения из очереди работает так же, как и ранее (дескриптор элемента не нужно передавать, поскольку очередь сама определит наибольший элемент). Однако очередь уничтожит дескриптор возвращенного элемента, поскольку он больше не присутствует в очереди. Если элементы являются записями или объектами, дескриптор данного элемента можно хранить внутри самого элемента наряду с приоритетом и другими полями.
В рамках операционной системы дескриптор, который, как правило, представляет собой своего рода замаскированный указатель, обычно имеет тип длинного целого. В рассматриваемой реализации мы используем всего лишь нетипизированный указатель.
Реализация расширенной очереди по приоритету
С точки зрения пользователя очереди по приоритету новый интерфейс лишь немногим сложнее рассмотренного ранее. Код интерфейса класса расширенной очереди по приоритету TtdPriorityQueueEx приведен в листинге 9.9.
Листинг 9.9. Интерфейс класса TtdPriorityQueueEx
type
TtdPQHandle = pointer;
TtdPriorityQueueEx = class private
FCompare : TtdCompareFunc;
FHandles : pointer;
FList : TList;
FName : TtdNameString;
protected
function pqGetCount : integer;
procedure pqError(aErrorCode : integer;
const aMethodName : TtdNameString);
procedure pqBubbleUp(aHandle : TtdPQHandle);
procedure pqTrickleDown(aHandle : TtdPQHandle);
public
constructor Create(aCompare : TtdCompareFunc);
destructor Destroy; override;
procedure ChangePriority(aHandle : TtdPQHandle);
procedure Clear;
function Dequeue : pointer;
function Enqueue(alt em : pointer): TtdPQHandle;
function Examine : pointer;
function IsEmpty : boolean;
function Remove(aHandle : TtdPQHandle): pointer;
property Count : integer read pqGetCount;
property Name : TtdNameString read FName write FName;
end;
Как видите, единственное реальное различие между этим классом и классом TtdPriorityQueue состоит в наличии методов Remove и ChangePriority и в том, что метод Enqueue возвращает дескриптор.
Так как же реализован этот интерфейс? Внутренне очередь, как обычно, содержит сортирующее дерево, но на этот раз она должна поддерживать
Поэтому мы сделаем следующее: когда пользователь будет ставить элемент в очередь, мы будем добавлять элемент в связный список. Это будет сопряжено с определением и использованием узла и, по меньшей мере, двух указателей: указателя самого этого элемента и указателя следующего элемента, хотя по причинам, которые станут понятны несколько позже, мы будем использовать двухсвязный список и поэтому нам потребуется также и предыдущий указатель. Передаваемый нами обратно дескриптор элемента будет адресом узла. Теперь наступает важный момент. Узел хранит также целочисленное значение - позицию элемента в массиве, посредством которого реализовано сортирующее дерево. Сортирующее дерево не хранит сами элементы, а только их дескрипторы (т.е. узлы связного списка). Каждый раз, когда для выполнения сравнения ему нужно будет обратиться к самому элементу, оно будет разыменовывать дескриптор.
К сожалению, мы не можем использовать описанный в главе 3 класс связного списка, поскольку нам требуется доступ к узлам, а этот класс был разработан с целью сокрытия структуры узлов. Это один из случаев, когда нельзя использовать заранее стандартные классы и требуется выполнить кодирование от начала до конца. В случае применения двухсвязного списка это не так страшно, поскольку эта структура достаточно проста. Мы создадим связный список с явными начальным и конечным узлами. В результате удаление обычного узла превращается в исключительно простую задачу. Удаление узлов будет выполняться с применением обоих методов Dequeue и Remove класса расширенной очереди по приоритету.
Операции постановки в очередь и пузырькового подъема лишь немногим более сложны. Вначале мы создаем дескриптор, выделяя узел для элемента, и добавляем его в связный список узлов. Поскольку мы добавляем дескрипторы к сортирующему дереву, для доступа к элементам потребуется выполнять разыменование дескрипторов, а при перемещении элемента по сортирующему дереву индекс его новой позиции необходимо сохранять внутри узла. Код реализации методов постановки в очередь и пузырькового подъема приведен в листинге 9.10.
Листинг 9.10. Постановка в очередь и пузырьковый подъем в расширенной очереди по приоритету
procedure TtdPriorityQueueEx.pqBubbleUp(aHandle : pointer);
var
FromInx : integer;
ParentInx : integer;
ParentHandle : PpqexNode;
Handle : PpqexNode absolute aHandle;
begin
{если анализируемый дескриптор больше дескриптора родительского элемента, нужно их нужно поменять местами и продолжить процесс с новой позиции}
{Примечание: родительский узел дочернего узла, имеющего индекс n, имеет индекс (n-1)/2}