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

Как и для класса TtdStack, код класса TtdQueue будет разрабатываться на основе главных принципов. Аргументы за использование такой схемы мы рассматривали во время написания кода для класса стека.

Листинг 3.26. Класс TtdQueue

TtdQueue = class private PCount : longint;
FDispose : TtdDisposeProc;
FHead : PslNode;
FName : TtdNameString;
FTail : PslNode;
protected
procedure qError(aErrorCode : integer;
const aMethodName : TtdNameString);
class procedure qGetNodeManager;
public
constructor Create(aDispose : TtdDisposeProc);
destructor Destroy;
override; procedure Clear;
function Dequeue : pointer;
procedure Enqueue (altem : pointer);
function Examine : pointer;
function IsEmpty : boolean;
property Count : longint read FCount;
property Name : TtdNameString read FName write FName;
end;

Как и ранее, конструктор Create проверяет, существует ли экземпляр диспетчера узлов, а затем распределяет с его помощью фиктивный начальный узел. Затем инициализируется специальный указатель FTail, который при создании указывает на начальный узел. Его содержимое будет меняться, чтобы он всегда указывал на последний узел связного списка. Это позволит легко вставлять новые элементы после конечного узла.

Листинг 3.27. Конструктор и деструктор для класса TtdQueue

constructor TtdQueue.Create(aDispose : TtdDisposeProc);
begin inherited Create;
{сохранить процедуру удаления) FDispose :=aDispose; {получить диспетчер узлов) qGetNodeManageг;
{распределить и связать начальный и конечный узлы) FHead := PslNode(SLNodeManager.AllocNode);
FHeadA.slnNext : = nil;
FHeadA.sInData : = nil;
{установить указатель конечного узла на начальный узел) FTail := FHead;
end;
destructor TtdQueue. Destroy;
begin
{удалить все оставшиеся узлы; очистить начальный фиктивный узел) if (Count о 0) then Clear;
SLNodeManager.FreeNode(FHead);
inherited Destroy;
end;

А теперь перейдем к методу Enqueue. Он посредством диспетчера узлов распределяет новый узел и устанавливает его указатель данных на вставляемый элемент. Затем используется указатель FTail. Учитывая, что он указывает на последний узел, мы вставляем новый узел за ним, после чего перемещаем указатель на одну позицию вперед - на новый узел, который теперь стал последним.

Листинг 3.28. Метод Enqueue класса TtdQueue

procedure TtdQueue. Enqueue (altem : pointer); var
Temp : PslNode;
begin
Temp := PslNode (SLNodeManager .AllocNode); TempA. slnData := altem;
TempA.slnNext := nil;
{добавить новый узел в конец списка и переместить указатель конечного узла на только что вставленный узел) FTailА.slnNext := Temp;
FTail := Temp;
inc(FCount);
end;

Метод Dequeue ничуть не сложнее. Сначала он проверяет список на наличие в нем элементов, а затем, пользуясь алгоритмом "удалить после" фиктивного начального узла FHead, удаляет из списка первый узел. Перед освобождением узла с помощью диспетчера узлов метод Dequeue возвращает данные. После выполнения метода количество элементов в списке уменьшается на единицу. Вот здесь и начинается самое интересное. Представьте себе, что из очереди снимается один единственный имеющийся в ней элемент. До выполнения операции Dequeue указатель FTail указывал на последний узел списка, который был одновременно и первым. После снятия элемента с очереди первый узел будет отсутствовать, но указатель FTail все еще указывает на него. Нам нужно сделать так, чтобы после удаления узла в списке FTail указывал на фиктивный начальный элемент. Если же в списке до удаления присутствовало несколько элементов, указатель будет указывать на действительный последний узел.

Листинг 3.29. Метод Dequeue класса TtdQueue

function TtdQueue. Dequeue : pointer;
var
Temp : PslNode;
begin if (Count = 0) then
qError(tdeQueuelsEmpty, 1 Dequeue1); Temp := FHeadA.slnNext;
Result := TempA.slnData;
FHeadA.slnNext : = TempA.slnNext;
SLNodeManager.FreeNode(Temp);
dec(FCount);
{если после удаления элемента очередь опустела, переместить указатель последнего элемента на фиктивный начальный узел} if (Count = 0) then FTail := FHead;
end;

Остальные методы, Clear, Examine и IsEmpty, еще проще.

Листинг 3.30. Методы Clear, Examine и IsEmpty класса TtdQueue

procedure TtdQueue.Clear;
var
Temp : PslNode;
begin
{удалить все узлы за исключением начального; при возможности освободить все данные} Temp : = FHeadA.slnNext;
while (Temp <>
nil) do begin
FHeadA.slnNext : = TempA.slnNext;
if Assigned(FDispose) then FDispose(TempA.slnData);
SLNodeManager.FreeNode(Temp);
Temp : = FHeadA. slnNext;
end;
FCount := 0;
{теперь очередь пуста, установить указатель последнего элемента на начальный узел} FTail : = FHead;
end;
function TtdQueue.Examine : pointer;
begin
if (Count = 0) then qError(tdeQueuelsEmpty, 1 Examine');
Result : = FHeadA.slnNextA.slnData;
end;
function TtdQueue.IsEmptу : boolean;
begin
Result := (Count = 0) ;
end;

Полный код класса TtdQueue можно найти на Web-сайте издательства, в разделе материалов. После выгрузки материалов отыщите среди них файл TDStkQue.pas.

Очереди || Оглавление || Очереди на основе массивов


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



Новости за месяц

  • Июнь
    2019
  • Пн
  • Вт
  • Ср
  • Чт
  • Пт
  • Сб
  • Вс