Здравствуйте прошла курсы на тему Алгоритмы С++. Но не пришел сертификат и не доступен.Где и как можно его скаачат? |
Очереди с приоритетами и пирамидальная сортировка
АТД очереди с приоритетами
В большинстве приложений, использующих очереди с приоритетами, нужно, чтобы операция извлечь наибольший не возвращала значение извлеченного ключа, а сообщала, в которой из записей находится максимальный ключ, и чтобы аналогично выполнялись и другие операции. Другими словами, приоритеты устанавливаются, а очереди с приоритетами применяются только для упорядочения доступа к другой информации. Эта организация аналогична использованию косвенной сортировки и сортировки указателей, описанных в "Элементарные методы сортировки" . В частности, данный подход необходим для придания смысла таким операциям, как изменить приоритет или извлечь. Сейчас мы подробно рассмотрим реализацию этой идеи — не только потому, что в дальнейшем будем использовать очереди с приоритетами именно так, но еще и потому, что такая ситуация характерна для задач, с которыми приходится сталкиваться при разработке интерфейсов и реализаций абстрактных типов данных.
Если из очереди с приоритетами нужно извлечь элемент, то как указать, который элемент надо извлечь? Если нужно использовать несколько очередей с приоритетами, то как следует организовать реализации, чтобы иметь возможность манипулировать очередями с приоритетами подобно другим типам данных? Подобные вопросы были рассмотрены в "Абстрактные типы данных" . Программа 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 операцию вставить.