Здравствуйте прошла курсы на тему Алгоритмы С++. Но не пришел сертификат и не доступен.Где и как можно его скаачат? |
Минимальные остовные деревья
Программа 20.7. Поиск по приоритету
Функция pfs выполняет обобщенный поиск на графе с использованием накопителя в качестве очереди с приоритетами (см. "Поиск на графе" ). Приоритет P определяется так, чтобы данный класс реализовал алгоритм Прима для вычисления MST; другие определения приоритетов приведут к другим алгоритмам. Главный цикл переносит ребро с максимальным приоритетом (с наименьшим весом) из накопителя в дерево, а затем проверяет все ребра, смежные с только что занесенной в дерево вершиной, чтобы узнать, потребуются ли изменения в накопителе. Ребра, ведущие к вершинам, которые отсутствуют в накопителе и в дереве, заносятся в накопитель, при этом самые короткие ребра в вершины накопителя заменяют соответствующие ребра в накопителе.
Класс PQi представляет собой косвенный интерфейс очереди с приоритетами (см. "Очереди с приоритетами и пирамидальная сортировка" ), в котором добавлена возможность передачи в конструктор ссылки на массив приоритетов, вызов delmax заменен на getmin, а вызов change — на lower. Реализация этого интерфейса приведена в программе 20.10.
template <class Graph, class Edge> class MST { const Graph &G; vector<double> wt; vector<Edge *> fr, mst; void pfs(int s) { PQi<double> pQ(G.V(), wt); pQ.insert(s); while (!pQ.empty()) { int v = pQ.getmin(); mst[v] = fr[v]; typename Graph::adjIterator A(G, v); for (Edge* e = A.beg(); !A.end(); e = A.nxt()) { double P = e->wt(); int w = e->other(v) ; if (fr[w] == 0) { wt[w] = P; pQ.insert(w); fr[w] = e; } else if (mst[w] == 0 && Р < wt[w]) { wt[w] = P; pQ.lower(w); fr[w] = e; } } } } public: MST(Graph &G) : G(G), fr(G.V()), mst(G.V()), wt(G.V() , -1) { for (int v = 0; v < G.V(); v++) if (mst[v] == 0) pfs(v); } };
Поиск по приоритету — подходящее обобщение поиска ширину и поиска в глубину, поскольку эти методы также могут быть получены за счет соответствующей настройки приоритета. Например, мы можем (правда, не вполне естественно) использовать переменную cnt для присвоения уникального приоритета cnt++ каждой вершине при помещении ее в очередь с приоритетами. Если определить, что P эквивалентно cnt, мы получим прямую нумерацию узлов и поиск в глубину, поскольку недавно занесенные узлы имеют наивысший приоритет. Если мы определим, что P эквивалентно V-cnt, мы получим поиск в ширину, поскольку в этом случае наивысший приоритет имеют старые узлы. Такие назначения приоритетов приводят к тому, что очередь с приоритетами ведет себя, соответственно, как стек и как обычная очередь. Подобная эквивалентность представляет чисто академический интерес, поскольку для поиска в глубину и для поиска в ширину операции очереди с приоритетами не обязательны. Кроме того, как было сказано в "Поиск на графе" , формальное доказательство эквивалентности требует особого внимания к правилам замены, чтобы получить ту же последовательность вершин, что и при работе классических алгоритмов.
Как мы увидим, поиск по приоритетам охватывает не только поиск в глубину, поиск в ширину и алгоритм Прима для вычисления MST-дерева, но и несколько других классических алгоритмов. Все эти алгоритмы отличаются только функциями вычисления приоритетов. Поэтому время их выполнения зависит от производительности АТД очереди с приоритетами. Известен общий результат, который охватывает не только две рассмотренные нами реализации алгоритма Прима, но также и широкий класс фундаментальных алгоритмов обработки графов.
Рис. 20.10. Реализация поиска по приоритету для построения MST алгоритмом Прима Используя поиск по приоритету, алгоритм Прима обрабатывает только вершины и ребра, наиболее близкие к MST-дереву (показаны серым цветом)
Лемма 20.8. Для всех графов и функций вычисления приоритетов метод поиска по приоритету позволяет вычислить остовное дерево за линейное время плюс время, пропорциональное времени выполнения V операций вставить, V операций извлечь минимальное и E операций уменьшить ключ в очереди с приоритетами, размер которой не превышает V.
Доказательство. Из доказательства леммы 20.7 следует и этот более общий результат. Нам нужно просмотреть все ребра графа; отсюда следует часть с линейным временем. Алгоритм никогда не увеличивает приоритет (поскольку изменяет приоритет лишь в сторону уменьшения). Более точно указав, что нам нужно от АТД очереди с приоритетами (уменьшить ключ, а не обязательно изменить приоритет), мы уточняем описание производительности алгоритма.
В частности, использование реализации очереди с приоритетами с помощью неупорядоченного массива дает оптимальное решение для насыщенных графов с такой же производительностью в худшем случае, что и классическая реализация алгоритма Прима (программа 20.6). То есть леммы 20.6 и 20.7 представляют собой частные случаи леммы 20.8; на протяжении этой книги нам встретятся и другие многочисленные алгоритмы, которые отличаются друг от друга только выбором функций вычисления приоритетов и реализациями очередей с приоритетами.
Свойство 20.7 представляет собой важный общий результат: устанавливаемая им временная граница есть верхняя граница для худшего случая, которая для широкого класса задач обработки графов гарантирует производительность, отличающуюся от оптимальной (линейное время) не более чем в lgV раз. Однако этот результат несколько пессимистичен для многих видов графов, с которыми нам приходится сталкиваться на практике — по двум причинам. Во-первых, граница lgV для операций с очередями с приоритетами справедлива только тогда, когда количество вершин в накопителе пропорционально V, и даже в таком случае это лишь верхняя граница. В случае реального графа из практического приложения накопитель может быть весьма небольшим (см. рис. 20.10 и рис. 20.11), а некоторые операции для очередей с приоритетами требуют гораздо менее lgV шагов.
Этот эффект заметен, но обычно увеличивает время выполнения лишь на небольшой постоянный коэффициент. Например, доказательство того, что накопитель не сможет содержать более вершин, улучшит граничное значение только в два раза. Но более важно то, что обычно количество операций уменьшить ключ намного меньше E, поскольку эти операции выполняются только при обнаружении ребра в узел, содержащийся в накопителе, которое короче кратчайшего до этого ребра такого же типа. Это происходит довольно редко: большая часть ребер никак не влияет на очередь с приоритетами (см. упражнение 20.40). Поиск по приоритету вполне можно считать линейным — кроме случаев, когда V lgV значительно больше E.
Нижний график показывает размеры накопителя при работе PFS для примера с рис. 20.10. Выше для сравнения приведены графики для поиска в глубину, рандомизированного поиска и поиска с рис. 18.28.
АТД очереди с приоритетами и абстракции обобщенного поиска на графах позволяют понять взаимосвязь между различными алгоритмами. Поскольку эти абстракции (и программные механизмы, обеспечивающие поддержку их использования) были разработаны спустя много лет после базовых методов, соответствие алгоритмов их классическим описаниям может интересовать разве что историков. Но все же знание всех основных исторических фактов полезно, когда мы сталкиваемся с описаниями алгоритмов построения MST в исследовательских работах или в других текстах, а понимание того, как эти немногочисленные простые абстракции связывают работы многих исследователей, разделенные многими десятилетиями, убедительно доказывает их ценность и мощь. Поэтому мы кратко рассмотрим происхождение этих алгоритмов.
Реализация MST для насыщенных графов, которая фактически эквивалентна программе 20.6, была впервые опубликована Примом (Prim) в 1961 г. и, несколько позже и независимо от него, Дейкстрой (Dijkstra). Обычно она называется алгоритмом Прима, хотя формулировка Дейкстры была более общей — поэтому некоторые ученые считают алгоритм вычисления MST специальным случаем алгоритма Дейкстры. Однако основная идея была высказана Ярником (Jarnik) еще в 1939 г., так что некоторые авторы называют этот метод алгоритмом Ярника, считая, что Прим (а также Дейкстра) просто разработал эффективную реализацию алгоритма для насыщенных графов. После распространения АТД очередей с приоритетами в начале 1970-х годов применение этого алгоритма для вычисления MST на разреженных графах уже не представляло трудности. Широко известный факт, что MST для разреженных графов можно вычислить за время, пропорциональное E lgV, не связан с именем какого-либо исследователя. Как будет показано в разделе 20.6, с тех пор многие исследователи направили свои усилия на поиск эффективных реализаций как ключевого момента отыскания эффективных алгоритмов построения MST для разреженных графов.
Упражнения
20.35. Оцените производительность примитивной реализации алгоритма Прима, описанной в начале данного раздела, для графа с Vвершинами. Указание. При решении этой задачи может пригодиться комбинаторная сумма:
20.36. Выполните упражнение 20.35 для графов, у которых все вершины имеют одну и ту же фиксированную степень t.
20.37. Выполните упражнение 20.35 для разреженных графов общего вида с V вершинами и E ребрами. Поскольку время выполнения зависит от весов ребер и степеней вершин, проведите анализ для худшего случая. Приведите семейство графов, для которого действительна ваша оценка для худшего случая.
20.38. Представьте в стиле рис. 20.8 результаты вычисления MST-дерева с помощью алгоритма Прима для сети, определенной в упражнении 20.26.
20.39. Опишите семейство графов c Vвершинами и E ребрами, для которого достигается оценка времени выполнения алгоритма Прима с поиском по приоритетам в худшем случае.
20.40. Разработайте походящий генератор случайных графов с V вершинами и E ребрами, чтобы время выполнения алгоритма Прима с поиском по приоритетам (программа 20.7) было нелинейным.
20.41. Измените программу 20.7, чтобы она работала так же, как и программа 18.8, то есть хранила в накопителе все ребра, инцидентные древесным вершинам. Эмпирически сравните полученную реализацию с программой 20.7 для различных взвешенных графов (см. упражнения 20.9-20.14).
20.42. Выведите из интерфейса, определенного в программе 9.12, реализацию очереди с приоритетами для использования в программе 20.7 (чтобы было возможно использование любой реализации этого интерфейса).
20.43. Воспользуйтесь контейнером priority_queue из библиотеки STL для реализации интерфейса очереди с приоритетами, который применяется в программе 20.7.
20.44. Предположим, что вы используете реализацию очереди с приоритетами, в которой применяется сортированный список. Каким будет время выполнения в худшем случае для графа с V вершинами и с E ребрами (с точностью до постоянного множителя)? В каких случаях этот метод можно будет применять, и можно ли вообще? Обоснуйте свой ответ.
20.45. Ребро MST, удаление которого из графа приводит к увеличению веса MST, называется критическим ребром. Покажите, как найти все критические ребра в графе за время, пропорциональное E lgV.
20.46. Эмпирически сравните производительность программы 20.6 с производительностью программы 20.7, используя реализацию очереди с приоритетами в виде неупорядоченного массива для различных взвешенных графов (см. упражнения 20.9-20.14).
20.47. Эмпирически определите эффект использования в программе 20.7 реализации очереди с приоритетами на основе турнира индексного пирамидального дерева (см. упражнение 9.53) вместо программы 9.12 для различных взвешенных графов (см. упражнения 20.9-20.14).
20.48. Эмпирически проанализируйте веса деревьев (см. упражнение 20.23) как функции от V для различных взвешенных графов (см. упражнения 20.9-20.14).
20.49. Эмпирически проанализируйте максимальный размер накопителя как функции от V для различных взвешенных графов (см. упражнения 20.9-20.14).
20.50. Эмпирически проанализируйте высоту дерева как функции от V для различных взвешенных графов (см. упражнения 20.9-20.14).
20.51. Эмпирически определите зависимость результатов выполнения упражнений 20.49 и 20.50 от выбора исходной вершины. Будет ли лучше, если выбирать ее случайно?
20.52. Напишите клиентскую программу, которая выполняет динамическую графическую анимацию алгоритма Прима. Программа должна строить изображения наподобие рис. 20.10 рис. 20.10 (см. упражнения 17.56-17.60). Проверьте работу программы на случайных евклидовых графах с соседними связями и на решетчатых графах (см. упражнения 20.17 и 20.19), используя столько точек, сколько можно обработать за приемлемое время.