Опубликован: 05.01.2015 | Доступ: свободный | Студентов: 2178 / 0 | Длительность: 63:16:00
Лекция 9:

Очереди с приоритетами и пирамидальная сортировка

АТД очереди с приоритетами

В большинстве приложений, использующих очереди с приоритетами, нужно, чтобы операция извлечь наибольший не возвращала значение извлеченного ключа, а сообщала, в которой из записей находится максимальный ключ, и чтобы аналогично выполнялись и другие операции. Другими словами, приоритеты устанавливаются, а очереди с приоритетами применяются только для упорядочения доступа к другой информации. Эта организация аналогична использованию косвенной сортировки и сортировки указателей, описанных в "Элементарные методы сортировки" . В частности, данный подход необходим для придания смысла таким операциям, как изменить приоритет или извлечь. Сейчас мы подробно рассмотрим реализацию этой идеи — не только потому, что в дальнейшем будем использовать очереди с приоритетами именно так, но еще и потому, что такая ситуация характерна для задач, с которыми приходится сталкиваться при разработке интерфейсов и реализаций абстрактных типов данных.

Если из очереди с приоритетами нужно извлечь элемент, то как указать, который элемент надо извлечь? Если нужно использовать несколько очередей с приоритетами, то как следует организовать реализации, чтобы иметь возможность манипулировать очередями с приоритетами подобно другим типам данных? Подобные вопросы были рассмотрены в "Абстрактные типы данных" . Программа 9.8 представляет обобщенный интерфейс для очередей с приоритетами в том виде, который был введен в разделе 4.8 "Абстрактные типы данных" .

Программа 9.8. Полный АТД очереди с приоритетами

Данный интерфейс для АТД очереди с приоритетами дает клиентским программам возможность удалять элементы и изменять их приоритеты (с помощью дескрипторов, обеспечиваемых реализацией), а также выполнять слияние очередей.

  template <class Item>
  class PQ
    { private:
      // Программный код, зависящий от реализации
      public:
        // Определение дескриптора, зависящее от реализации
      PQ(int);
      int empty() const;
      handle insert(Item);
      Item getmax();
      void change(handle, Item);
      void remove(handle);
      void join(PQ<Item>&);
    };
        

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

Такая организация налагает ограничения как на клиентскую программу, так и на реализацию. Клиент не может получить доступ к информации никаким другим способом, кроме как через этот интерфейс. На него возлагается ответственность за правильное использование дескрипторов: например, у реализации нет возможности проверять ошибочные действия клиента, вроде использования дескриптора уже удаленного элемента. Со своей стороны, реализация не может свободно перемещать информацию, поскольку клиенты могут хранить дескрипторы, а затем их использовать. Этот момент станет более понятным при изучении деталей реализации. Как обычно, какой бы уровень детализации ни был выбран в наших реализациях, абстрактный интерфейс наподобие программы 9.8 является удобной отправной точкой для поиска компромисса между потребностями приложений и реализаций.

Примитивные реализации базовых операций над очередями с приоритетами, использующих неупорядоченное представление данных в виде двухсвязных списков, приведены в программе 9.9. Этот код просто иллюстрирует природу интерфейса; нетрудно разработать другие, столь же несложные реализации, используя другие элементарные представления.

Программа 9.9. Неупорядоченная очередь с приоритетамив виде двухсвязного списка

Данная реализация содержит операции создать, проверить на наличие и вставить, взятые из интерфейса программы 9.8 (см. программу 9.10, в которой содержатся реализации четырех других функций). В ней используется простой неупорядоченный список с головным и хвостовым узлами. Структура node (узел) описана как узел двухсвязного списка (элемент и две ссылки). Приватными полями данных являются лишь ссылки головного и хвостового узлов.

  Template <class Item>
  class PQ
    { private:
        struct node
{ Item item; node *prev, *next;
  node(Item v)
    { item = v; prev = 0; next = 0; }
};
        typedef node* link;
        link head, tail;
      public:
        typedef node* handle;
        PQ(int = 0)
{ head = new node(0); tail = new node(0);
  head->prev = tail; head->next = tail;
  tail->prev = head; tail->next = head;
}
        int empty() const
{ return head->next->next == head; }
        handle insert(Item v)
{ handle t = new node(v);
  t->next = head->next; t->next->prev = t;
  t->prev = head; head->next = t;
  return t;
}
        Item getmax();
        void change(handle, Item);
        void remove(handle);
        void join(PQ<Item>&);
     };
        

Как упоминалось в разделе 9.1, реализация, приведенная в программах 9.9 и 9.10, пригодна для приложений с небольшой очередью с приоритетами и нечастым выполнением операций извлечь наибольший или найти наибольший; в остальных случаях лучше использовать реализации на базе пирамидальной сортировки. Реализация процедур fixUp и fixDown для пирамидально упорядоченных деревьев с явными ссылками и сохранением согласованности дескрипторов — достаточно сложная задача, которая вынесена в упражнения, поскольку в разделах 9.6 и 9.7 мы подробно рассмотрим два альтернативных подхода.

Полный АТД наподобие программы 9.8 обладает массой достоинств, однако иногда полезно рассматривать другие подходы, с различными ограничениями на клиентские программы и реализации. В разделе 9.6 мы рассмотрим пример, в котором обработку записей и ключей выполняет клиентская программа, а подпрограммы обработки очередей с приоритетами обращаются к ним неявно.

Программа 9.10. Очередь с приоритетами в виде двухсвязного списка (продолжение)

Подстановка данных реализаций вместо соответствующих объявлений завершает реализацию очереди с приоритетами из программы 9.9. Операция извлечь наибольший выполняет просмотр всего списка, но все же затраты на поддержку двухсвязного списка оправданы тем, что операции изменить приоритет, извлечь и объединить реализованы с постоянным временем выполнения, и используются только элементарные операции над связными списками (более подробно о связных списках см. "Элементарные структуры данных" .

При необходимости сюда можно добавить деструктор, конструктор копирования и перегруженный оператор присваивания, чтобы еще больше усовершенствовать это приложение и получить АТД первого класса (см. раздел 4.8 "Абстрактные типы данных" ). Обратите внимание, что реализация функции join (объединить) выбирает узлы списков из параметра, который должен быть включен в результат, но при этом не копирует их.

  Item getmax( )
    { Item max; link x = head->next;
      for (link t = x; t->next != head; t = t->next)
        if (x->item < t->item) x = t;
      max = x->item;
      remove(x);
      return max;
    }
  void change (handle x, Item v)
    { x->key = v; }
  void remove(handle x)
    { x->next->prev = x->prev;
      x->prev->next = x->next;
      delete x;
    }
  void join(PQ<Item>& p)
    { tail->prev->next = p.head->next;
      p.head->next->prev = tail->prev;
      head->prev = p.tail;
      p.tail->next = head;
      delete tail; delete p.head;
      tail = p.tail;
    }
        

Могут быть удобными и небольшие изменения интерфейсов. Например, может потребоваться функция, возвращающая значение ключа с наибольшим приоритетом в очереди, а не способ обращения к этому ключу и связанной с ним информации. Кроме того, выплывают вопросы управления памятью и семантики копирования, которые были рассмотрены в разделе 4.8 "Абстрактные типы данных" . Мы не рассматриваем операции уничтожить или копировать и выбрали лишь один из нескольких возможных вариантов операции объединить (см. упражнения 9.43 и 9.44).

Такие процедуры легко добавить в интерфейс, представленный в программе 9.8, но реализацию с гарантированной логарифмической производительностью всех операций разработать гораздо труднее. В приложениях, в которых очереди с приоритетами не бывают большими, или со специальными свойствами смеси операций вставить и извлечь наибольший, может оказаться более удобным полностью гибкий интерфейс. Но в тех случаях, когда очереди могут вырастать до значительных размеров и будет заметно или желательно десятикратное, или даже стократное увеличение производительности, можно ограничиться только операциями, которые обеспечивают такую производительность. Были проведены обширные исследования, на результатах которых основаны алгоритмы обработки очередей с приоритетами для различных смесей операций. Важным примером такого рода является биномиальная очередь, описанная в разделе 9.7.

Упражнения

9.38. Какая реализация очереди с приоритетами лучше подходит для того, чтобы найти 100 наименьших чисел в наборе из 106 случайных чисел? Обоснуйте свой ответ.

9.39. Напишите реализации, подобные программам 9.9 и 9.10, в которых используются упорядоченные двухсвязные списки. Примечание: Поскольку клиент может запоминать дескрипторы элементов этой структуры данных, программы могут изменять в узлах только ссылки (но не ключи).

9.40. Напишите реализации операций вставить и извлечь наибольший (интерфейс очереди с приоритетами из программы 9.1), воспользовавшись пирамидально упорядоченными полными деревьями с явными узлами и ссылками. Совет: Поскольку у клиента нет дескрипторов элементов этой структуры данных, можно воспользоваться тем, что легче обменять значения информационных полей узлов, чем сами узлы.

9.41. Напишите реализации операций вставить, извлечь наибольший, изменить приоритет и извлечь (интерфейс очереди с приоритетами из программы 9.8), воспользовавшись пирамидально упорядоченными деревьями с явными ссылками. Примечание: Поскольку клиент может запоминать дескрипторы элементов этой структуры данных, это упражнение сложнее упражнения 9.40 — не только потому, что узлы должны быть трехсвязными, но также и потому, что программы могут изменять в узлах только ссылки (но не ключи).

9.42. Добавьте (примитивную) реализацию операции объединить в реализацию из упражнения 9.41.

9.43. Добавьте в программу 9.8 объявления деструктора, конструктора копирования и перегруженной операции присваивания, чтобы превратить ее в АТД первого класса, включите соответствующие реализации в программы 9.9 и 9.10 и напишите программу-драйвер, которая протестирует полученные интерфейс и реализацию.

9.44. Измените интерфейс и реализацию операции объединить в программах 9.9 и 9.10, чтобы она возвращала PQ (результат объединения аргументов) и уничтожала свои аргументы.

9.45. Напишите интерфейс очереди с приоритетами и реализацию, которая поддерживает операции создать и извлечь наибольший, используя для этого турнир (см. раздел 5.7 "Рекурсия и деревья" ). В качестве основы для операции создать используйте программу 5.19.

9.46. Преобразуйте решение упражнения 9.45 в АТД первого класса.

9.47. Добавьте в решение упражнения 9.45 операцию вставить.

Бактыгуль Асаинова
Бактыгуль Асаинова

Здравствуйте прошла курсы на тему Алгоритмы С++. Но не пришел сертификат и не доступен.Где и как можно его скаачат?

Александра Боброва
Александра Боброва

Я прошла все лекции на 100%.

Но в https://www.intuit.ru/intuituser/study/diplomas ничего нет.

Что делать? Как получить сертификат?