Здравствуйте прошла курсы на тему Алгоритмы С++. Но не пришел сертификат и не доступен.Где и как можно его скаачат? |
Потоки в сетях
Программа 22.2 определяет класс EDGE, который мы используем для реализации абстракции остаточной сети. При такой реализации мы все так же работаем исключительно с указателями на клиентские ребра. Наши алгоритмы работают с остаточной сетью, но на самом деле они (через указатели на ребра) проверяют пропускные способности и изменяют потоки ребер в клиентских ребрах. Функции-члены from и other позволяют производить обработку ребер с любой ориентацией: функция e.other(v) возвращает конечную вершину е, отличную от v. Функции-члены capRto и addflowRto(v) реализуют остаточную сеть: если е - указатель на ребро v-w с пропускной способностью с и потоком f, то e->capRto(w) содержит значение c-f, а e->capRto(v) содержит f, e->addflowRto(w, d) увеличивает поток на величину d, а e->addflowRto(v, d) уменьшает его на величину d.
Программа 22.2. Ребра транспортной сети
Для реализации транспортной сети мы используем класс GRAPH неориентированного графа из "Минимальные остовные деревья" , который позволяет оперировать указателями на ребра, реализованные в данном интерфейсе. Ребра ориентированы, но функции-члены реализуют абстракцию остаточной сети, которая охватывает оба направления каждого ребра (см. текст).
class EDGE { int pv, pw, pcap, pflow; public: EDGE(int v, int w, int cap) : pv(v), pw(w), pcap(cap), pflow(0) { } int v() const { return pv; } int w() const { return pw; } int cap() const { return pcap; } int flow() const { return pflow; } bool from (int v) const { return pv == v; } int other(int v) const { return from(v) ? pw : pv; } int capRto(int v) const { return from(v) ? pflow : pcap - pflow; } void addflowRto(int v, int d) { pflow += from(v) ? -d : d; } };
Остаточные сети позволяют использовать для поиска расширяющего пути любой обобщенный поиск на графе (см. "Поиск на графе" ), поскольку любой путь из истока в сток в остаточной сети непосредственно соответствует расширяющему пути в исходной сети. Увеличение потока вдоль пути приводит к изменениям в остаточной сети: например, по меньшей мере одно ребро в остаточной сети меняет направление или исчезает (однако использование абстрактной остаточной сети означает, что мы проверяем наличие положительной пропускной способности, но нам не нужно фактически вставлять и удалять ребра). На рис. 22.16 показан пример последовательности расширяющих путей и соответствующие остаточные сети.
Программа 22.3 представляет собой реализацию на основе очереди с приоритетами, которая охватывает все эти возможности, используя слегка измененную версию реализации поиска по приоритету на графах из программы 21.1, которая приведена в программе 22.4. Эта реализация позволяет выбирать одну из нескольких различных классических реализаций алгоритма Форда-Фалкерсона, просто указывая различные приоритеты, которые дают различные структуры данных для накопителя.
Программа 22.3. Реализация вычисления максимального потока расширением путей
Данный класс реализует общий алгоритм вычисления максимального потока расширением путей (алгоритм Форда-Фалкерсона). Он использует поиск по приоритету для нахождения пути из истока в сток в остаточной сети (см. программу 22.4), затем добавляет в этот путь максимально возможный поток и повторяет этот процесс, пока не останется ни одного такого пути. Построение объекта этого класса устанавливает в ребрах заданной сети такие величины потоков, при которых поток из истока в сток максимален. Вектор st содержит остовное дерево PFS: в st[v] содержится указатель на ребро, которое соединяет вершину v с ее родителем. Функция ST возвращает родителя для вершины, заданной в ее аргументе. Функция augment использует ST для обхода пути, чтобы вычислить его пропускную способность и затем увеличить поток.
template <class Graph, class Edge> class MAXFLOW { const Graph &G; int s, t; vector<int> wt; vector<Edge *> st; int ST(int v) const { return st[v]->other(v); } void augment(int s, int t) { int d = st[t]->capRto(t); for (int v = ST(t); v != s; v = ST(v)) if (st[v]->capRto(v) < d) d = st[v]->capRto(v); st[t]->addflowRto(t, d); for (int v = ST(t); v != s; v = ST(v)) st[v]->addflowRto(v, d); } bool pfs(); public: MAXFLOW(const Graph &G, int s, int t) : G(G), s(s), t(t), st(G.V()), wt(G.V()) { while (pfs()) augment(s, t); } };
Вычисление расширяющих путей в транспортной сети эквивалентно поиску ориентированных путей в остаточной сети, определяемой потоком. Для каждого ребра исходной сети мы создаем в остаточной сети по ребру в каждом направлении: одно в направлении потока с весом, равным незадействованной пропускной способности, а другое - в обратном направлении с весом, равным потоку. Ребра с нулевыми весами не включаются. Первоначально (вверху) остаточная сеть совпадает с транспортной сетью, но веса ребер равны их пропускным способностям.
При расширении потока вдоль пути 0-1-3-5 (второйряд) мы заполняем ребра 0-1 и 3-5 до их пропускной способности, чтобы они поменяли направление в остаточной сети, уменьшаем вес ребра 1-3, чтобы он соответствовал оставшемуся потоку, и добавляем ребро 3-1 с весом 2. Аналогично, при расширении вдоль пути 0-2-4-5 мы заполняем ребро 2-4 до его пропускной способности, чтобы оно сменило направление на обратное, и получаем разнонаправленные ребра между вершинами 0 и 2 и между вершинами 4 и 5, представляющие поток и неиспользованную пропускной способность. После расширения потока вдоль пути 0-2-3-1-4-5 (внизу) в остаточной сети не остается ни одного ориентированного пути из истока в сток, поэтому нет расширяющих путей.
Как было показано в "Кратчайшие пути" , использование очереди с приоритетами для реализации стека, очереди или рандомизированной очереди в качестве накопителя увеличивает трудоемкость операций с накопителем на множитель lgV Этого можно избежать, используя в реализациях АТД обобщенной очереди наподобие программы 18.10 прямые реализации - поэтому при анализе алгоритмов мы полагаем, что трудоемкость операций с накопителем в рассматриваемых случаях постоянна. Используя единственную реализацию в программе 22.3, мы демонстрируем наличие непосредственных связей между различными реализациями алгоритма Форда-Фалкерсона.
Несмотря на обобщенный характер, программа 22.3 не охватывает всех реализаций алгоритма Форда-Фалкерсона (см. например, упражнения 22.36 и 22.38). Исследователи продолжают разрабатывать новые способы реализации этого алгоритма. Однако семейство алгоритмов, охватываемых программой 22.3, получило широкое распространение, помогает понять процесс вычисления максимальных потоков и знакомит с несложными реализациями, которые хорошо работают на обычно встречающихся сетях.
Программа 22.4. Реализация поиска по приоритетам для нахождения расширяющих путей
Данная реализация поиска по приоритетам получена из реализации, которую мы использовали для алгоритма Дейкстры (программа 21.1), но теперь в ней веса принимают целочисленные значения, обрабатываются ребра остаточной сети, и выполнение прекращается при достижении стока или возврата false, если не существует пути из истока в сток. Заданное здесь определение приоритета P позволяет получить расширяющий путь до максимальной пропускной способности (отрицательные величины сохраняются в очереди с приоритетами в соответствии с интерфейсом программы 20.10). Другие определения P приводят к различным алгоритмам вычисления максимального потока.
template <class Graph, class Edge> bool MAXFLOW<Graph, Edge>::pfs() { PQi<int> pQ(G.V(), wt); for (int v = 0; v < G.V(); v++) { wt[v] = 0; st[v] = 0; pQ.insert(v); } wt[s] = -M; pQ.lower(s); while (!pQ.empty()) { int v = pQ.getmin(); wt[v] = -M; if (v == t || (v != s && st[v] == 0)) break; typename Graph::adjIterator A(G, v); for (Edge* e = A.beg(); !A.end(); e = A.nxt()) { int w = e->other(v); int cap = e->capRto(w); int P = cap < -wt[v] ? cap : -wt[v]; if (cap > 0 && -P < wt[w]) { wt[w] = -P; pQ.lower(w); st[w] = e; } } } return st[t] != 0; }
Как мы вскоре убедимся, эти базовые алгоритмические средства дают простые (и, как правило, полезные) решения задачи о транспортных потоках. Однако исчерпывающий анализ для выбора наилучшего метода сам является сложной задачей, поскольку время выполнения этих методов зависит от:
- Количества расширяющих путей, необходимых для отыскания максимального пути.
- Времени, необходимого для отыскания каждого расширяющего пути.
Эти величины могут изменяться в широких пределах, в зависимости от вида обрабатываемой сети и стратегии поиска на графе (структура данных накопителя).
Видимо, простейшая реализация алгоритма Форда-Фалкерсона использует кратчайший расширяющий путь (кратчайший по количеству ребер в этом пути, а не по потоку или по пропускной способности). Этот метод был предложен Эдмондсом (Edmonds) и Карпом (Karp) в 1972 г. В его реализации в качестве накопителя используется очередь - либо с помощью кольцевого счетчика для P, либо на основе АТД очереди вместо АТД очереди с приоритетами из программы 22.3. В этом случае поиск расширяющего пути превращается в поиск в ширину (BFS) в остаточной сети, в точности как описано в разделах 18.8 "Поиск на графе" и рис. 22.2. На рис. 22.17 показан пример работы этой реализации метода Форда-Фалкерсона. Мы будем называть этот метод алгоритмом вычисления максимального потока с помощью кратчайших расширяющих путей (shortest-augumenting-path). Как показано на этом рисунке, длины расширяющих путей образуют неубывающую последовательность. Анализ этого метода в лемме 22.7 показывает, что это свойство верно всегда.
Здесь показан процесс поиска максимального потока с помощью реализации метода Форда-Фалкерсона на основе кратчайших расширяющих путей. По мере работы алгоритма длины путей увеличиваются: первые четыре пути в верхнем ряду имеют длину 3, последний путь в верхнем ряду и все пути во втором ряду имеют длину 4; первые два пути в нижнем ряду имеют длину 5, и в конце находятся два пути длиной 7, каждый из которых содержит обратное ребро.
В другой реализация алгоритма Форда-Фалкерсона, предложенной Эдмондсоном и Карпом, выполняется расширение пути, который увеличивает поток на наибольшую величину. Реализацию этого метода обеспечивает значение приоритета P, использованное в программе 22.3. При этом алгоритм выбирает из накопителя такие ребра, чтобы мощность потока, который будет добавлен в прямое ребро или удален из обратного ребра, была максимальной. Этот метод мы будем называть алгоритмом вычисления максимального потока с помощью расширяющий путей с максимальной пропускной способностью (maximum-capacity-augmenting-path). На рис. 22.18 показана работа этого алгоритма на той же транспортной сети, что и на рис. 22.17.
Это всего лишь два примера (которые мы можем проанализировать!) реализаций алгоритма Форда-Фалкерсона. В конце этого раздела мы познакомимся и с другими реализации. Но сначала мы рассмотрим задачу анализа методов на основе расширения путей с тем, чтобы исследовать их свойства и затем определить метод, который обеспечивает наивысшую производительность.
Пытаясь выбрать один из семейства алгоритмов, представленных программой 22.3, мы опять попадаем в знакомую ситуацию. Следует ли обращать внимание на производительность, гарантированную в худшем случае, или это лишь математическая абстракция, не имеющая отношения к сетям, которые встречаются на практике? Этот вопрос особо актуален в данном контексте, поскольку классические границы производительности, которые мы можем определить для худшего случая, намного выше реальной производительности при работе с типичными графами.
Ситуацию осложняют и многие другие факторы. Например, время выполнения в худшем случае для некоторых версий зависит не только от V и E, но и от значений пропускных способностей ребер в сети. Разработка алгоритма вычисления максимального потока с высокой гарантированной производительностью почти полвека привлекала к себе внимание многих исследователей, которые предложили множество методов. Оценка всех этих методов для различных видов реально встречающихся сетей с достаточной степенью точности позволяет сделать правильный выбор, однако она более расплывчата, чем оценки в других ситуациях, которые мы изучали ранее - например, для алгоритмов сортировки или поиска.
Здесь показан процесс поиска максимального потока с помощью реализации метода Форда-Фалкерсона на основе расширяющих путей с максимальной пропускной способностью. По мере работы алгоритма пропускные способности путей уменьшаются, но их длина может как увеличиваться, так и уменьшаться. Этому методу потребовалось лишь девять расширяющих путей, чтобы вычислить тот же максимальный поток, что и на рис. 22.17.
Учитывая эти трудности, рассмотрим теперь классические результаты, касающиеся производительности метода Форда-Фалкерсона в худшем случае: одну общую границу и две границы для частных случаев - для каждого из рассмотренных нами алгоритмов расширения пути. Эти результаты позволят нам скорее понять характеристики алгоритмов, чем точно прогнозировать производительность этих алгоритмов для осмысленного сравнения. Эмпирическое сравнение этих методов будет приведено в конце этого раздела.
Лемма 22.6. Пусть M - максимальная пропускная способность ребер в сети. Количество расширяющих путей, используемых любой реализацией алгоритма Форда-Фалкерсона, не превышает VM.
Доказательство. Любое сечение содержит максимум Vребер с пропускной способностью M, что дает общую пропускную способность VM. Каждый расширяющий путь увеличивает поток через каждое сечение по меньшей мере на 1, следовательно, выполнение алгоритма прекратится после VM проходов, т.к. после стольких расширений все сечения точно будут заполнены до их пропускной способности.
Как уже было сказано, в обычных ситуациях от такой границы мало проку, т.к. M может быть очень большим числом. Что еще хуже, легко описать ситуации, в которых количество итераций пропорционально максимальной пропускной способности ребра. Например, предположим, что мы используем алгоритм использования самого длинного расширяющего пути (возможно, основанного на соображении, что чем длиннее путь, тем больший поток можно добавить в ребра сети). Поскольку мы ведем подсчет итераций, мы на время игнорируем затраты на вычисление такого пути. Классический пример, представленный на рис. 22.19, показывает сеть, для которой количество итераций алгоритма с использованием самого длинного расширяющего пути равно максимальной пропускной способности ребер. Этот пример показывает, что необходимы более подробные исследования, чтобы выяснить, требуют ли другие конкретные реализации существенное меньше итераций по сравнению с оценкой из леммы 22.6.
Данная сеть демонстрирует, что количество итераций, выполняемых алгоритмом Форда-Фалкер-сона, зависит от пропускных способностей ребер и от последовательности путей, выбираемых реализацией. Сеть состоит из четырех ребер с пропускной способностью X и одного ребра с пропускной способностью 1. В верхней части рисунка показано, что реализация, которая попеременно использует цепочки 0-1-2-3 и 0-2-1-3 в качестве расширяющих путей (например, та, где выбираются длинные пути), потребует X пар итераций - наподобие двух пар, показанных на рисунке, и каждая такая пара увеличивает общих поток на 2. В нижней части рисунка показано, что реализация, которая выбирает в качестве расширяющих путей цепочку 0-1-3, а затем 0-2-3 (например, та, где выбираются короткие пути), вычисляет максимальный поток всего за две итерации. Если пропускные способности ребер выражены, скажем, 32-разрядными целыми числами, то быстродействие верхней реализации будет в миллиарды раз меньше быстродействия нижней реализации.
Для разреженных сетей и сетей с небольшими целочисленными значениями пропускных способностей лемма 22.6 на самом деле дает полезную верхнюю границу времени выполнения любой реализации алгоритма Форда-Фалкерсона.
Следствие. Время, необходимое для вычисления максимального потока, равно O(VEM), то есть O(V2M) для разреженных сетей.
Доказательство. Это утверждение непосредственно следует из фундаментального утверждения, что трудоемкость обобщенного поиска на графе линейно зависит от размера представления графа (лемма 18.12). Как уже было сказано, при реализации накопителя в виде очереди с приоритетами трудоемкость увеличивается на коэффициент lgV
Это доказательство фактически утверждает, что множитель M можно заменить отношением наибольшей и наименьшей пропускной способности в сети (см. упражнение 22.35). Если это отношение мало, любая реализация алгоритма Форда-Фалкерсона находит максимальный поток за время, пропорциональное худшему времени решения (к примеру) задачи поиска всех кратчайших путей. Существует множество ситуаций, когда пропускные способности весьма небольшие, и множитель M можно не учитывать. Соответствующий пример будет приведен в разделе 22.4.
Если M велико, граница VEM для худшего случая также велика; но она слишком завышена, поскольку получена перемножением границ для худших случаев, которые получены из придуманных примеров. Фактические затраты для сетей, встречающихся на практике, обычно намного ниже.
С теоретической точки зрения наша первая цель - определить с помощью грубой субъективной классификации из "Виды графов и их свойства" , разрешима ли задача о максимальном потоке в сетях с большими целочисленными весами (т.е. существует ли алгоритм с полиномиальным временем выполнения). Только что найденные границы не дают ответа на этот вопрос, т.к. максимальный вес M = 2m может возрастать экспоненциально с увеличением V и E. Для практических целей нужны более точные гарантии производительности. Возьмем типичный практический пример: пусть для представления весов используются 32-разрядные целые числа (m = 32). В графе с сотнями вершин и тысячами ребер следствие из леммы 22.6 утверждает, что алгоритму на базе расширения пути может понадобиться выполнить сотни триллионов операций. Если сеть содержит миллионы вершин, то ничего сказать вообще невозможно, поскольку не только невозможно выразить веса с величинами порядка 21000000, но и значения V3 и VE настолько велики, что любые границы при этом теряют всякий смысл. А нам нужна полиномиальная граница для ответа на вопрос о разрешимости и более точные границы для ситуаций, с которыми мы можем столкнуться на практике.
Лемма 22.6. носит общий характер и поэтому применима к любой реализации алгоритма Форда-Фалкерсона. Универсальная природа этого алгоритма позволяет рассматривать целый ряд простых реализаций, позволяющих улучшить рабочие характеристики алгоритма. Мы надеемся, что конкретные реализации могут иметь более низкие границы для худших случаев. Вообще-то это одна из главных причин, чтобы сразу же приступить к их рассмотрению! Теперь, как мы могли убедиться, реализовать и использовать большой класс таких реализаций совсем не сложно: для этого нужно просто подставлять в программу 22.3 различные реализации обобщенной очереди или определения приоритетов. Анализ различий поведения в худшем случае представляет собой еще более трудную задачу, что подтверждается классическими результатами для двух простых реализаций алгоритма расширения путей, которые мы сейчас рассмотрим.
Сначала мы проанализируем алгоритм с использованием кратчайшего расширяющего пути. Этот метод не относится к задаче, представленной на рис. 22.19. Его можно использовать для замены множителя M для времени выполнения в худшем случае на выражение VE/2, тем самым заявив, что задача вычисления сетевых потоков является разрешимой. Вообще-то ее можно считать даже легкой (решаемой в реальных ситуациях за полиномиальное время с помощью небольшой, но хитроумной реализации).