Текст
                    «П I	gW	л
I	ailn	I	Ч₽


Jeremy G. Siek, Lie-Quan Lee, Andrew Lumsdaine The Boost Graph Library User Guide and Reference Manual Addison-Wesley Boston • San Francisco • New York «Toronto Montreal • London • Munich • Paris • Madrid • Capetown Sydney «Tokyo • Singapore • Mexico City
БИБЛИОТЕКА ПРОГРАММИСТА Джереми Сик, Лай-Кван Ли, Эндрю Ламсдэйн C++ Baost Graph Library C^nnrtP' Москва * Санкт-Петербург Нижний Новгород • Воронеж Ростов-на-Дону • Екатеринбург * Самара - Новосибирск Киев • Харьков • Минск 2006
Джереми Сик, Лай-Кван Ли, Эндрю Ламсдэйн C++ Boost Graph Library. Библиотека программиста Перевел с английского Р.Сузи Главный редактор Е. Строганова Заведующий редакцией А. Кривцов Руководитель проекта А. Адаменко Научный редактор В. Лаптев Иллюстрации В. Демидова. Л. Родионова Литературный редактор Е. Яковлева Художник Л. Адуевская Корректоры Н. Викторова, И, Смирнова Верстка Л. Родионова ББК 32.973-018.1 УДК 004.43 Дж. Сик, Л. Ли, Э. Ламсдэйн С35 C++ Boost Graph Library. Библиотека программиста I Пер. с английского Сузи Р. — СПб.: Питер, 2006. — 304 с.: ил. ISBN 5-469-00352-3 Издание, являющееся переводом одной из книг серии «C++ in Depths, посвящено описанию Boost Graph Library (BGL) — библиотеки да построения структур данных и алгоритмов вычисле- ний иа графах, предназначенных для решения самых разнообразных задач: от оптимизации интернет- маршрутиэацни и планирования телефонных сетей до задач молекулярной биологии. Содержит развернутое описание BGL. демонстрирует примеры приложений к реальным задачам. Первая часть является полным руководством пользователя, начинается с введения понятий теории графов, терми- нологии и описания обобщенных алгоритмов на графах, знакомит пользователя со всеми основными возможностями библиотеки BGL. Вторая часть — полное справочное руководство, содержит доку- ментацию ко всем концепциям BGL, ее алгоритмам и классам. © Pearson Education Inc., 2002 © Перевод иа русский язык, ЗАО Издательский дом «Питер», 2006 © Издание на русском языке, оформление, ЗАО Издательский дом «Питер», 2006 Права ня издание получены по соглашению с Addison-Westey Longman. Все права защищены Никакая часть данной книги не может быть воспроизведена а какой бы те ни было форме без письменного разрешения владельцев авторских прав. Информация, содержащаяся в данной книге, получена из источников. рассматриваемых издательством как надежные. Тем не менее, имея а виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведения и не несет ответе!веииость за возможные ошибки, связанные с использованием кижги ISBN 5-469-00352-3 ISBN 0-201-72914-8 (англ.) ООО «Питер Принта. 194044, Санкт-Петербург, Б. Сампсоннеаскмй пр., д. 29» Лицензия ИД № 05784 от 07.09,01. Налоговая льгота — общероссийский классификатор продукта ОК 005-93, том 2; 95 3005 — литература учебная. Подписано к печати 28.09.05. Формат 70*100-16. Уел. п. п. 24,51, Тираж 2000.3акк>375 Отпечатано с готовых диапозитивов в ОАО «Техническая книга» 190003, Санкт-Петербург, Измайловский пр , 29
Краткое содержание Предисловие................................................ 11 Введение................................................... 15 Часть I. Руководство пользователя....................... .23 Глава 1. Введение.................................... 24 Глава 2. Обобщенное программирование в C++.................39 Глава 3. Изучаем BGL..................................... 59 Глава 4. Основные алгоритмы на графах......................77 Глава 5. Задачи нахождения кратчайших путей................89 Глава б. Задача минимального остовного дерева..............102 Глава 7. Компоненты связности........................... 109 Глава 8. Максимальный поток............................. 117 Глава 9. Неявные графы: обход конем..................... 124 Глава 10. Взаимодействие с другими графовыми библиотеками ....... 130 Глава 11. Руководство по производительности...............137 Часть П. Справочное руководство........................... 145 Глава 12. Концепции BGL................................. 146 Глава 13. Алгоритмы BGL............................. 170 Глава 14. Классы BGI—.....................................215 Глава 15. Библиотека отображений свойств..................274 Глава 16. Вспомогательные концепции, классы и функции..... 284 Библиография.............................................. 294 Дополнение к библиографии............................... 297 Алфавитный указатель........................................299
Содержание Предисловие...................................................................11 Введение......................................................................15 Обобщенное программирование................................................16 Немного из истории BG1................................................... 17 что такое Boost?...........................................................18 Получение и установка программного обеспечения BGL.........................18 Как пользоваться книгой.................................................... 19 Грамотное программирование.................................................20 Благодарности.............................................................. 21 Лицензия.................................................................. 22 От издательства.......................................................... 22 Часть I. Руководство пользователя........................................... 23 Глава 1. Введение.............................................................24 1.1. Немного терминологии из теории графов................................. 24 1.2. Графовые концепции...................,................................26 1.2.1. Описатели вершин и ребер......................................... 26 1.2.2. Отображение свойств......................... ..................... 27 1,2.3. Обход графа.................................................... 28 1.2.4. Создание и модификация графа.....................................29 1.2.5. Посетители алгоритмов............................................30 1.3. Классы и адаптеры графов.............................................. 32 1.3.1. Классы графов.............................................. 32 1.3.2. Адаптеры графов.................................................. 33 1.4. Обобщенные алгоритмы на графах.................................... 34 1.4.1. Обобщенный алгоритм топологической сортировки.................... 34 1.4.2. Обобщенный алгоритм поиска в глубину.............................38 Глава 2. Обобщенное программирование в C++....................................39 2.1. Введение..............................................................39 2.1.1. Полиморфизм в объектно-ориентированном программировании........... 40 2.1.2. Полиморфизм в обобщенном программировании........................41 2.1.3. Сравнение ОП и ООП...............................................41 2.2. Обобщенное программирование и STL.....................................44 2.3. Концепции и модели.................................................... 47 2.3.1. Наборы требований................................................47 2.3.2. Пример: Inputiterator............................................48
Содержание 7 2.4. Ассоциированные типы и классы свойств..............................49 2.4.1. Ассоциированные типы в шаблонах функций.......................49 2.4.2. Определители типов, вложенные в классах..................... 49 2.4.3. Определение класса свойств.................................. 50 2.4.4. Частичная специализация................................... 51 2.4.5. Диспетчеризация тегов.........................................52 2.5. Проверка концепции.................................................53 2.5.1. Классы для проверки концепций.................................54 2.5.2. Прототипы концепций...........................................55 2.6. Пространство имен .................................................56 2.6.1. Классы.................................................. 56 2.6.2. Поиск Кенига.............................................. 56 2.7. Именованные параметры функций......................................58 Глава 3. Изучаем BGL..................................................... 59 3.1. Зависимости нежа* файлами........................................ 59 3.2. Подготовка графа...................................................60 3.2.1. Решаем, какой графовый класс использовать.....................61 3.2.2. Строим граф с помощью итераторов ребер........................61 3.3. Порядок компиляции.................................................62 3.3.1. Топологическая сортировка через поиск в глубину........... 62 3.3.2. Маркировка вершин с использованием внешних свойств............64 3.3.3. Доступ к смежным вершинам................................... 64 3.3.4. Обход всех вершин.............................................65 3.4. Циклические зависимости............................................66 3.5. «На пути» к обобщенному поиску в глубину: посетители...............67 3.6. Подготовка графа: внутренние свойства..............................69 3.7. Время компиляции...................................................71 3.8. Обобщенная топологическая сортировка и поиск в глубину.............72 3.9. Время параллельной компиляции.................................... 74 3.10. Итоги............................................................ 76 Глава 4. Основные алгоритмы на графах......................................77 4.1. Поиск в ширину................................................. 77 4.1.1. Определения...................................................77 4.1.2. Шесть степеней Кевина Бэкона..................................78 4.2. Поиск в глубину.................................................. 82 4.2.1. Определения............................................... 83 4.2.2. Нахождение циклов в графах потоков управления программы.......84 Глава 5. Задачи нахождения кратчайших путей................................89 5.1. Определения...................................................... 89 5.2. Маршрутизация в Интернете...................................... 90 5.3. Алгоритм Беллмана-Форда и маршрутизация с помощью вектора расстояний.......................................... 91 5.4. Маршрутизация с учетом состояния линии и алгоритм Дейкстры.........95 Глава 6. Задача минимального остовного дерева......................... 102 6.1. Определения.......................................................102 6.2. Планирование телефонной сети.................................... 102 6.3. Алгоритм Краскала............................................. 104 6.4. Алгоритм Прима....................................................106
8 Содержание Глава 7. Компоненты связности............................................. 109 7.1. Определения...................................................... 109 7.2. Связные компоненты и связность Интернета......................... 110 7.3. Сильные компоненты связности и ссылки веб-страниц....................114 Глава 8. Максимальный поток...................................................117 8.1. Определения..........................................................117 8.2. Реберная связность............................................... 118 Глава 9. Неявные графы: обход конем...........................................124 9.1. Ходы конем как граф................................................ 125 9.2. Поиск с возвратом на графе......................................... 127 9.3. Эвристика Варнедорфа.................................................128 Глава 10. Взаимодействие с другими графовыми библиотеками....................130 10.1. Использование топологической сортировки из BGL с графом из LEDA.....131 10.2. Использование топологической сортировки из BGL с графом из SGB......132 10.3. Реализация адаптеров графов.........................................134 Глава 11. Руководство по производительности................................. 137 11.1. Сравнения графовых классов..........................................137 11.1.1. Результаты и обсуждение...................................... 138 11.2. Итоги главы................................................. 144 Часть II. Справочное руководство..............................................145 Глава 12. Концепции BGL.......................................................146 12.1. Концепции обхода графов......................................... 146 12.1.1. Неориентированные графы........................................148 12.1.2. Graph..........................................................151 12.1.3. IncidenceGraph............................................... 152 12.1.4. BidirectionalGraph........................................... 153 12.1.5. AdjacencyGraph.................................................154 12.1.6. VertexUstGraph.................................................155 12.1.7. EdgeListGraph................................................ 156 12.1.8. AdjacencyMatrix.............................................. 157 12.2. Концепции для изменения графов......................................157 12.2.1. VertexMutableGraph........................................... 159 12.2.2. EdgeMutableGraph...............................................160 12.2.3. MutablelnddenceGraph...........................................161 12.2.4. MutabteBidirectionalGraph......................................161 12.2.5. MutableEdgeUstGraph............................................162 12.2.6. PropertyGraph..................................................162 12.2.7. VertexMutablePropertyGraph.....................................163 12.2.8. EdgeMutablePropertyGaph........................................164 12.3. Концепции посетителей...............................................164 12.3.1. BFSVisitor.....................................................165 12.3.2. DFSVisitor.....................................................166 12.3.3. DijkstraVisitor.............................................. 167 12.3.4. BellmanFordVisitor....................................... 168 Глава 13. Алгоритмы BGL.......................................................170 13.1. Обзор...............................................................170 13.1.1. Информация об алгоритме...................................... 171
Содержание 9 13.2, Базовые алгоритмы........................................................172 13.2.1. breadth_first_search ...............................................172 13.2.2. breadthJirst_visit..................................................176 13.2.3 depth_flrst_search............................................. 177 13.2.4. depth_first_visit...................................................• 181 13.2.5. topological-sort.................................................. 182 13.3. Алгоритмы кратчайших путей...............................................183 13.3.1. dijkstra_shortesLpaths............................................ 183 13.3.2. bellman_ford_shortest_paths.........................................188 13.33. johnson_all_jMrtrs_shortest_paths...................................191 13.4. Алгоритмы минимальных остовных деревьев..................................194 13.4.1. kruskal_minimum_spanning_tree.......................................194 13.4.2. prim_minimum_spanning_tree..........................................197 13.5. Статические компоненты связности....................................... 200 13.5.1. axmected_components............................................. 200 13.5.2. strong_components...................................................202 13.6. Растущие компоненты связности.......................................... 205 13.6.1. InttiallzeJncrementaLcomponents.....................................207 13.6.2. frKremental.coniponents........................................... 207 13.6.3. same_component......................................................207 13.6.4. componentjndex.................................................... 208 13.7. Алгоритмы максимального потока...........................................209 13.7.1. edmunds_karp_max_flow...............................................209 13.7.2. push_relabel_max_flow.............................................. 212 Глава 14. Классы BGL............................................................. 215 14.1. Классы графов............................................................215 14.1.1. adjacencyjst........................................................215 14.1.2. adjacency_matrrx....................................................235 14.2. Вспомогательные классы...................................................243 14.2.1. graph_trait5........................................................243 14.2.2. adjacencyJist_traits................................................246 14.2.3. adjacency_matrix_traits.............................................247 14.2.4. property_map........................................................248 14.2.5. property............................................................249 14.3. Графовые адаптеры........................................................250 14.3.1. edgejst.............................................................250 14.3.2. reverse_graph.......................................................252 14.3.3. filtered_graph......................................................256 14.3.4. Указатель на SGB Graph............................................ 261 14J.5. GRAPH<V,E> из библиотеки LEDA........................................265 14.3.6. std::vector<EdgeList>..........;....................................271 Глава 15. Библиотека отображений свойств...........................................274 15.1. Концепции отображений свойств............................................275 15.1.1. ReadablePropertyMap.................................................276 15.1.2. WritabJePropertyMap.................................................277 15.1.3. ReadWrltePropertyMap................................................277 15.1.4. LvaJuePropertyMap...................................................278 15.2. Классы отображений свойств...............................................278 15.2.1. propertyjraits......................................................278
10 Содержание 15,2.2. iterator_property_map.......................................... 280 15.2.3. Теги свойств....................................................281 15.3. Создание пользовательских отображений свойств.........................282 15.3,1. Отображения свойств для Stanford GraphBase......................282 15.3.2. Отображение свойств из std::map................................ 283 Глава 16, Вспомогательные концепции, классы и функции..........................284 16.1. Buffer.............................................................. 284 16.2. Colorvalue............................................................285 16.3. MultiPassinputiterator............................................ 285 16.4. Monoid................................................................286 16.5. mutable_queue.........................................................286 16.6, Непересекающиеся множества............................................288 16.6.1. disioint_sets...................................................288 16.6.2. find_with_path_halving........................................ 290 16.6.3. find„with_full_path_compression............................... 290 16.7. tie...................................................................290 16.8. graph_property_iter_range..................................... 291 Библиография .............................................................. 294 Дополнение к библиографии......................................................297 Теория графов...............................................................297 C++ и STL...................................................................297 Алфавитный указатель.......................................................... 299
Ричарбу и Элизабет Дж. Г. С. Юн L-Q.L. Ванди, Бену, Эмили и Бетани А. Л. Предисловие Когда я впервые увидел эту книгу, я почувствовал зависть. В конце концов желание создать библиотеку, подобную Boost Graph Library (BGL), привело меня к открытию обобщенного программирования. В 1984 году я вошел в профессорско-преподава- тельский состав Политехнического университета в Бруклине, имея некоторые, еще довольно смутные, идеи построения библиотек программных компонентов. По прав- де говоря, они были на втором плане: в то время мои истинные интересы касались формальных обоснований естественного языка, кое в чем напоминающего Органона Аристотеля, но более полного и формального. Я был, наверное, единственным доцен- том на кафедрах информатики и электротехники, кто собирался получить должность, внимательно изучая категории Аристотеля. Интересно заметить, что дизайн Standard Template Library (STL), в частности лежащий в основе онтологии объектов, базиру- ется на моем понимании того, что отношение «целое-часть» является основополага- ющим отношением, которое описывает реальный мир, и что оно вообще не похоже на отношение «элемент-множество», известное нам из теории множеств. Реальные объекты не имеют общих частей: моя нога не является еще чьей-то ногой. То же самое можно сказать и о контейнерах STL. Например операции std:; 11 st;: spl ice переме- щают части из одного контейнера в другой подобно операции по пересадке органов: моя почка — это моя почка, до тех пор, пока ее не пересадят кому-нибудь другому. В любом случае, я был твердо убежден, что программные компоненты должны быть функциональными и основываться на системе функционального програм- мирования Джона Бэкуса ( John Backus’s FP system). Единственной новой идеей было то, что функции могут быть связаны с некоторыми аксиомами. Например, «алгоритм русского крестьянина», позволяющий вычислить п-ю степень за O(log п) шагов, подходит для любого объекта, для которого определена бинарная ассо- циативная операция. Другими словами, я верил, что алгоритмы должны быть связаны с тем, что сейчас мы называем концепциями (см. раздел 2.3 этой книги), которые я обозначал как структурные типы (structure types), а теоретики называют многосортными алгебрами (multi-sorted algebras). Удачей для меня было то, что в Политехническом yi шверситете был замечатель- ный человек — Аарон Кершенбаум, обладавший глубокими знаниями в области
12 Предисловие алгоритмов на графах и стремлением их реализовать. Аарон заинтересовался моими попытками расчленить программы на простые примитивы и провел много времени, обучая меня графовым алгоритмам и {шботая со мной над их реализацией. Он также показал мне, что есть фундаментальные вещи, которые нельзя сделать в функциональном стиле без высоких изменений в сложности. Хотя я часто мог реализовать алгоритмы, требующие линейного времени, в функциональном стиле без изменения асимптотической сложности, реализовать на практике алгоритмы с логарифмическим временем без изменения сложности на линейную я не мог. В частности, Аарон объяснял мне, почему очередь по шноритетам столь важна для алгоритмов на графах. Он был хорошо <х ведомлеи в этом вопросе: Кнут в его книге по Stanford GraphBa.se [22 j приписывает авторство Аарону в применении частично упорядоченных бинарных деревьев (binary heaps) к алгоритмам Прима и Дейкс.тры. Мы очень обрадовались, когда смогли полущить алгоритмы Прима и Дейкст- ры как два случая одного обобщенного алгоритма (высокоуровневого). Это про- сто замечательно, что код BGL схож с нашим кодом (см. например сноску в разде- ле 13.4.2). Следующий пример на языке Scheme показывает, как два алгоритма были реализованы в терминах одного высокоуровневого алгоритма. Единствен- ная разница заключается в том, как комбинируются значения расстояний: сложе- нием у Дейкстры и выбором второго операнда у Прима: (define dijkitra (make-seen-based-algorithm-with-mart make-neap-wlth-membershlp-and-vaiues + < )> (define prim (make-scan-based-algcrithm-with-mart rMke-heao-with-mefflbership-and-va'ues (lambda (x >• i y) < )) На поиск подходящего языка программирования для эффективной реализа- ции подобного стиля у меня ушло почти десять лет. Наконец я нашел C++, кото- рый помог мне создавать программы, полезные лля общества. Более того, C++ серьезно повлиял на мой проект, основанный на модели машины для С. Шабло- ны и перегрузка функций вот особенности С+ -\ позволившие создать STL. Я часто слышу от людей выражения недовольства по поводу перегрузки в С+-, но, как это обычно бывает для большинства полезных механизмов, перегрузка может быть использована неправильно. Для разработки полезных абстракций перегрузка незаменима. Если мы обратимся к математике, то многие ее идеи получили разви- тие именно благодаря перегрузке. В качестве примера можно привести расширение понятия чисел от натуральных к целым, к рациональным, к гауссовым, к р-адическим числам и т. д. Можно легко догадаться о некоторых вещах без знания точных опреде- лений. Если я вижу выражение, в котором используются операции сложения и умно- жения, я предполагаю наличие дистрибутивности. Если я вижу знак «меньше» и сло- жение. то предполагаю, что если а < Ь, то а + с < b + с (я редко складываю несчетные количества). Перегрузка позволяет нам переносить знание от одного типа к другому. Важно понять, что можно писать обобщенные алгоритмы, просто используя перегрузку, без шаблонов, но это требует много нажатий на клавиши. То есть для каждого класса, например удовлетворяющего требованиям итератора произволь- ного доступа, нужно вручную определять все относящиеся к нему алгоритмы. Это утомительно, но осуществимо (нужно определять только сигнатуры: тела будут те же). Нужно заметить, что настраиваемые модули (generics) в языке Ада требу-
Предисловие 13 ют ручной реализации и, поэтому, не так полезны, поскольку каждый алгоритм нужно реализовывать вручную. Шаблоны С+ + решают эту проблему, позволяя определять такие вещи единожды. Еще есть вещи, нужные в обобщенном программировании, но непредставимые в C++. Обобщенные алгоритмы — это алгоритмы, которые работают с объектами, обладающими похожими интерфейсами. Не идентичными, как в объектно-ориен- тированном программировании, а именно похожими. Использование не только бинарных методов вызывает проблему (см. раздел 2.1.3), интерфейсы фактически описываются с помощью одного типа (односортная алгебра). Если мы вниматель- но посмотрим на объекты вроде итераторов, то увидим, что они могут быть описа- ны только а терминах нескольких типов: тип самого итератора, тип значения и тип расстояния. Другими словами, необходимо три типа, чтобы определить интерфей - сы для одного типа. В C++ для этого нет нужного аппарата. В результате мы не можем определить итераторы и, следовательно, откомпилировать обобщенные алгоритмы. Например, если мы определим алгоритм reduce таким образом: template «class Inputiterator, class ainaryOperationWithldentity-- typename tterator_trait$<Inputlterator>::value_type reduceCInputIterator first. Input Iterator last? BinaryOperationWithldentity op) typedef typename 1terator_traits<InputIterator*::value_type 1: if (first last) return identity_element(op): T result • ‘first: while (♦♦first !- last) result - optresult. ‘first): return result: ) но вместо ++first !- last напишем++flrst < last, никакой компилятор нс смо- жет обнаружить ошибку в месте определения. Хотя стандарт ясно декларирует, что operator не нужен для итераторов ввода, у компилятора нет возможности знать об этом. Требования к итераторам сформулированы только на словах. Мы пыта- емся программировать с концепциями (многосортными алгебрами) на языке, в ко- тором для них нет поддержки. Насколько сложно расширить C++, чтобы действительно позволить этот стиль программирования? Во-лервщ, нам нужно ввести концепции как новое средство интерфейса. Например, мы можем определить следующие концепции: concept SemiRegular : Assignable. DefaultConstructWle {}; concept Regular : SemiRegular. EqualityCompacable {}; concept Inputiterator : Regular. Incrementable { SemiRegular value_type: Integral distance_type; const value type&~operator*0; val ue_type( Inputiterator} reduceUnputlterator first. Inputiterator last, BinaryOperationWithldentity op) (value type(Inputlterator) ~ argument_type(BinaryOperationWithIdent'ty)j { if (first •” last) return 1 dentity_e1ement(op): value_type(lnputlterator) result - ‘first: while (++first I- lest) result - optresult. ‘first): return result: }
14 Предисловие Обобщенные функции (generic functions) — это функции, которые принимают концепции как аргументы и в добавление к списку аргументов имеют список ог- раничений типов. Теперь полная проверка типов может быть выполнена в месте определения, без рассмотрения мест вызова или в местах вызова без рассмотре- ния тела алгоритма. Иногда требуется несколько реализаций одной и той же концепции. Напри- мер, для слияния возможна следующая запись: Outputiterator rnergecInputlteratorQ] firstl, Inputlterator[l] lastl. Inputlterator[23 first?. Inputlterator[2) last?. OutputIterator result) (bool operatorsvalue_type(Inputlterator[l]), value_type£InputIterator[2J)). value_type(lnputlterator[l]) — value_type(lnputlterator[2]). output_type(Output!terator) — value_type(lnputlterator[2])); Заметим, сто это слияние не такое эффективное, как в STL. Невозможно соеди- нить список чисел с плавающей запятой и вектор чисел двойной точности в дек це- лых. чисел. Но алгоритмы STL часто производят неожиданные и, по моему мнению, нежелательные преобразования типов. Если нужно слить числа с двойной точностью и числа с плавающей запятой в целые, лучше использовать явный функциональ- ный объект для сравнения и специальный итератор вывода для преобразования. В языке C++ поддерживается два различных механизма абстракции: объектно- ориентированный подход и шаблоны. Использование объектно-ориентированного подхода позволяет точно определить интерфейс и провести диспетчеризацию време- ни исполнения. Вместе с этим диспетчеризация бинарных или мультиметодов неосу- ществима, а связывание времени исполнения часто неэффективно. Шаблоны пред- назначены для более сложных интерфейсов и разрешены во время компиляции. Серьезным препятствием д ля их использования разработчиками программного обес- печения является тот факт, что в шаблонах отсутствует разделение между интерфей- сами и реализацией. Например, недавно я пытался откомпилировать пример STL- программы из десяти строк, используя один из наиболее популярных компиляторов C++... Я был в шоке, увидев несколько страниц неразборчивых сообщений об ошиб- ках. Можно предположить, что введение концепций объединит возможности обоих подходов и уберет ограничения, накладываемые ими. Кроме того, можно представить концепции в виде виртуал ьных таблиц, распространяющихся на указатели к описате- лям типов: виртуальная таблица для итератора ввода содержит не только указатели на operator* и operator-»*, но и указатели на актуальный тип итератора, тип его значе- ния и тип расстояния. А затем можно ввести указатели и ссылки на концепции! Обобщенное программирование — это сравнительно молодое направление в программировании. Я счастлив наблюдать, что небольшая попытка, начатая двадцать лет назад Дэйвом Массером, Дипаком Капуром, Аароном Кершенбау- мом и мной, привела к появлению библиотек нового поколения, таких как BGL и MTL. Я должен поздравить У ниверситет Индианы с лучшей командой по обоб- щенному программированию в мире. Я уверен, что они сотворят и другие чудеса! Александр Степанов. Пало-Альто, Калифорния. Сентябрь, 20011 1 Я бы хотел поблагодарить Джона Вилкинсона. Марка Манасо, Марка Наджорка и Джереми Сика за многие пенные предложения
Введение Графовая абстракция — это мощный инструмент решения задач, используемый для описания отношений между дискретными объектами. Практические задачи могут быть смоделированы в виде графов для различных областей, например та- ких, как маршрутизация пакетов в Интернете, проектирование телефонной сети, системы сборки программного обеспечения, поисковые машины WWW, молеку- лярная биология, системы автоматизированного планирования дорожною мар- шрута, научные вычисления и т. п. Достоинством графовой абстракции является тот факт, что найденное решение проблемы теории графов может быть использо- вано для решения проблем в широком диапазоне областей. Например, задача на- хождения выхода из лабиринта и задача нахождения групп взаимно достижимых веб-страниц могут быть решены с помощью поиска в глубину — важнейшего по- ложения из теории графов. При сосредоточении на сути этих задач, а именно на графовой модели, описывающей дискретные объекты и отношения между ними, специалисты по теории графов нашли решения не просто для «горстки» отдель- ных проблем, а для целых семейств задач. Сразу же возникает вопрос. Если теория графов всеобще и широко применяет- ся для произвольных сфер задач, не должно ли программное обеспечение, реализу- ющее графовые алгоритмы, быть таким же универсальным в применении? Может показаться, что теория графов — это идеальная область для повторного использо- вания программного кода. Одиако до сих пор потенциальное повторное использо- вание было далеко от реальности. Графовые задачи редко встречаются в чистой теоретико-графовой форме, они чаще включены в более крупные проблемы, за- висящие от области применения. В результате данные, которые можно смодели- ровать как граф, зачастую явно не представлены как граф, и тогда они заклады- ваются в некоторую структуру данных, специфичную для приложения. Даже в случае, когда данные приложения явно представлены в виде графа, конкретное представление, выбираемое программистом, может не совпадать с представлением, ожидаемым библиотекой. Более того, различные приложения могут накладывать разные ограничения к временной и пространственной сложности графовых струк- тур данных.
16 Введение Эти особенности являются серьезной проблемой для разработчика библиотеки алгоритмов па графах, который хочет предоставить повторно используемое про- граммное обеспечение. Невозможно предусмотреть все возможные структуры данных, которые могут потребоваться, и написать различные версии графового алгоритма специально для каждой из них. В настоящее время алгоритмы на гра- фах пишутся и терминах той структуры данных, которая наиболее удобна для ал- горитма, и пользователи должны преобразовывать их структуры данных к такому формату, чтобы применить алгоритм. Это неэффективное решение, поглощаю- щее время программиста и вычислительные ресурсы. Часто затраты на преобра- зования оказываются слишком высокими, и прог|>аммист переписывает алгоритм в терминах своей собственной структуры данных. Этот подход отнимает время и способствует появлению ошибок, а также имст-т тенденцию приводить к недо- статочно эффективным решениям, поскольку программист приложения может не быть экспертом в области графовых алгоритмов. Обобщенное программирование Стандартная библиотека шаблонов (Standard Template Library. STL) [40] по- явилась в 1994 году и была сразу принята в стандарт C++. STL — библиотека взаимозаменяемых компонентов для решения многих фундаментальных задач на последовательностях элементов. Отличие библиотеки STLot предлагаемых ранее библиотек состоит в том, что каждый STL-алгоритм может работать с ши- роким набором последовательных структур данных: связные списки, массивы, множества и т. п. Абстракция итератора обеспечила интерфейс между контей- нерами и алгоритмами, и шаблонный механизм С+- предоставил нужную гиб- кость в реализации без потери эффективности. Каждый алгоритм в STL являет- ся шаблоном функции, параметризованным по типам итераторов, с которыми он работает. Любой итератор, который удовлетворяет минимальному набору требований, может быть использован независимо от структуры данных, обходи- мой итератором. Системный подход, использованный в STL для построения абстракций и взаимозаменяемых компонентов, называегся обобщенным програм- мированием. Обобщенное программирование хорошо зарекомендовало себя при решении проблемы повторного использования кода для библиотек алгоритмов на гра- фах. В рамках обобщенного программирования алгоритмы на графах могут быть сделаны более гибкими и легко используемыми в большом наборе приложений. Каждый графовый алгоритм пишется не в терминах специфической структуры данных, а для графовой абстракции, которая может быть реализована многими различными структурами данных. Написание обобщенных графовых алгорит- мов имеет дополнительное преимущество, являясь более естественным. Абст- ракция, свойственная псевдокоду описания алгоритма, сохраняется в обобщен- ной функции. Библиотека Boost Graph Library (BGL) - это первая библиотека графов С+-, применяющая понятия обобщенного программирования при создании алгорит- мов на графах.
Немного из истории BGL 17 Немного из истории BGL Boost Graph Library появилась как библиотека обобщенных графовых компонен- тов (Generic Graph Component Library, GCCL) в лаборатории научных вычисле- ний (Lab for Scientific Computing, LSC). Эта лаборатория под руководством про- фессора Эндрю Ламсдэйна охватывала различные сферы деятельности, зани- малась исследованиями алгоритмов, программного обеспечения, инструментов и систем времени выполнения для вычислительной науки и техники1. Особое вни- мание было уделено разработке промышленного, высокопроизводительного про- граммной! обеспечения с использованием современных языков программирова- ния и методов, в том числе обобщенного программирования. Вскоре после того как была выпущена STL, в LSC началась работа по приме- нению обобщенного программирования к научным расчетам. Библиотека матрич- ных шаблонов (Matrix Template Library. MTL) была одним из первых проектов. Многие уроки, усвоенные во время создания MTL, были учтены при проектиро- вании и реализации GGCL. Одним из важных классов вычислений линейной алгебры в научных расчетах является класс вычислений с разреженными матрицами, в котором графовые ал- горитмы играют большую роль. Когда лаборатория LSC разрабатывала методы обработки разреженных матриц для MTL, необходимость в высокопроизводитель- ных повторно используемых (и обобщенных) графовых алгоритмах стала очевид- на. Однако ни в одной из графовых библиотек, доступ пых в то время (LEDA, GTL, Stanford GraphBase), не использовался обобщенный стиль программирования в отличие от STL или MTL. Таким образом, данные библиотеки не удовлетво- ряли требованиям LSC в гибкости и производительности. Другие исследователи также были заинтересованы в создании обобщенной библиотеки алгоритмов на графах для C++. Во время встречи с Бьерном Страуструпом мы познакомились с некоторыми людьми из «AT&T», тоже нуждавшимися в такой библиотеке Дру- гая ранняя работа в области графовых алгоритмов включала отдельные примеры кодов, написанных Александром Степановым, а также Дитмаром Кюлем вето ма- гистерской диссертации. Джереми Сик, вдохновленный домашними упражнениями по алгоритмам для своего курса, начал создавать прототипы интерфейса и некоторых графовых классов ватой 1998 года с учетом более ранних разработок. Затем Лай-Кван Ли разработал первую версию GGCL, которая стала его магистерским диссертационным проектом. В следующем году авторы начали сотрудничать с Александром Степановым и Мэ- тыо Остерном. В это время 1>еалйзация Степанова для компонент связности на ос- нове неперссекающихся множеств была добавлена к GGCL, и началась работа по документированию концепций для GGCL подобно документации Остсрнадля STL. В том же году авторам стало известно о Boost, и они были обрадованы тем, что нашли организацию, заинтересованную в создании высококачественных библиотек ’ С тех гор LSC была преобразована в лабораторию открытых систем (Open Systems Laboratory, OSL). Хотя название н местопшюжешю изменились, программа работы остается прежней. Дополнительная информация находится на веб-сайте OSL http://www.osl.tu.edu.
18 Введение C++ с открытыми исходными кодами. В Boost было несколько человек, уделяв- ших внимание обобщенным графовым алгоритмам, и одним из этих людей был Дитмар Кюль. Некоторые обсуждения обобщенных интерфейсов для графовых структур привели к пересмотру GCCL и появлению в ней новых интерфейсов, очень похожих на те, что есть в Boost Graph Library сейчас. 4 сентября 2000 года GGCL была формально рецензирована под руководством Дэвида Абрахамса и стала Boost Graph Library. Первый выпуск BGL состоялся 27 сентября 2000 года. BGL не является «замороженной» библиотекой. Она про- должает расти и развиваться для наибольшего удовлетворения потребностей сво- их пользователей. Мы приглашаем читателей присоединиться к группе Boost для работы над расширением BGL Что такое Boost? Boost — сетевое сообщество, которое поддерживает разработку и проводит кол- легиальную оценку свободных библиотек для C++. Особое внимание уделяется переносимым и высококачественным библиотекам, которые хорошо работают совместно (и «в том же духе») со стандартной библиотекой C++. Члены сообще- ства предоставляют предложения (проекты и реализации библиотек) для объек- тивной оценки. Сообщество Boost (под управлением менеджера по рецензирова- нию) рассматривает библиотеку, обеспечивает обратную связь с участниками и принимает решение о включении библиотеки в набор Boost-бнблиотек. Биб- лиотеки доступны с веб-сайта http://www.boost.org. Кроме того, список рассылки Boost является важным местом для обсуждения планов и организации сотрудни- чества. Получение и установка программного обеспечения BGL Библиотека алгоритмов на графах Boost Graph Library доступна как часть коллек- ции библиотек Boost. Свежий выпуск библиотек Boost можно загрузить с помо- щью браузера последующим адресам: http://www.boost.org/boost_all.zip (zip-архив для Windows), http://www.boost.org/boost_all.tar.gz (для Unix), а также по FTP из каталога ftp://boostsourceforge.net/pub/boost/release/. Zip-архив коллекции библиотек Boost можно разархивировать программой WinZip или ее аналогом. Таг-архив для Unix можно разархивировать с помощью такой команды: gunzip -cd boost_all.tcr.gz j tar xvf - В результате создается каталог, имя которого состоит из слова boost и номера версии: например, разархивирование версии 1.31.0 создает каталог boost_l_31_0. В этом каталоге находятся два важных подкаталога: boost и libs. Подкаталог boost содержит заголовочные файлы для всех библиотек коллекции. Подкаталог libs имеет отдельные подкаталоги для каждой библиотеки в коллекции. Эти подката- логи содержат файлы исходных кодов и документации к данным библиотекам.
Как пользоваться книгой 19 Сориентироваться в архиве можно, открыв в браузере веб-страницу boost_l_31_0/ index.htm. Заголовочные файлы BGL находятся в каталоге boost/graph/. Однако для BGL нужны и другие заголовочные файлы, так как в библиотеке используются различ- ные компоненты Boost. Гипертекстовая документация находится в каталоге libs/ graph/doc/, а исходные коды примеров — в libs/graph/example/. В каталоге libs/ gгарh/test/ содержатся тестовые наборы для BGL Исходные файлы для реализации анализаторов Graph viz-файлов и программ печати расположены в libs/graph/src/. Кроме того, что описано далее, для использования BGL не требуется компи- ляция и сборка. Все, что необходимо, — это добавить каталог заголовочных фай- лов Boost к пути поиска заголовочных файлов. Например, если в Windows 2000 библиотека версии t.31.0 разархивирована в корень диска С, для компиляторов Borland, GCC и Metrowerks добавьте -Ic:/boost_l_31_0 к командной строке ком- пилятора, а для Microsoft Visual C++ — /I "c:/boost_l_31_0". Для интегрированных сред разработки (IDE) укажите с: /boost_l_31_0 (или то, во что вы переименовали этот каталог) к пути поиска заголовочных файлов, используя соответствующий диалог. Перед применением BGL-интерфейсов к LEDA или Stanford GraphBase последние должны быть установлены согласно инструкциям к ним. Для ис- пользования функции read graphvizO (для чтения Graphviz-файлов «AT&T») необходимо собрать и скомпоновать дополнительную библиотеку из каталога boost_l_31_0/lib$/graph/src. Библиотека Boost Graph Library написана на ISO/IEC Standard C+ + и компи- лируется большинством компиляторов C++. Чтобы получить последнюю инфор- мацию о поддержке различных компиляторов, на веб-сайте Boost загрузите стра- ницу «Compiler Status» по адресу http://www.boostorg/status/compiler_status.htmL Как пользоваться книгой Эта книга является одновременно руководством пользователя и справочным по- собием но BGL. Она предназначена для того, чтобы читатель смог использовать BGL для решения задач на графах, встречающихся в реальной жизни. Книга до- лжна представлять интерес для программистов, желающих более углубленно изу- чить обобщенное программирование. Хотя сейчас уже достаточно много книг о том, как использовать обобщенные библиотеки (что почти всегда означает при- менение стандартной библиотеки STL), в очень немногих действительно описыва- ется, как создать обобщенное программное обеспечение. Тем не менее обобщенное программирование — это жизненно важная, новая парадигма разработки програм- много обеспечения. Мы надеемся, что с помощью примеров из этой книги читатель научится программировать обобщенно (а не только использовать обобщенные библиотеки), применять и расширять принципы обобщенного программирования за пределы контейнерных типов и алгоритмов STL. В качестве третьего помощника к руководству пользователя и справочному пособию выступит сам исходный код библиотеки BGL. Код BGL предназначен не только для обучения и образования, но и для реального использования. Для студентов, изучающих графовые алгоритмы и структуры данных, BGL предоставляет обширный набор алгоритмов. Студент может сконцентрироваться
20 Введение на изучении важной теории, на которой основаны графовые алгоритмы, без рис- ка «увязнуть» и отвлечься на слишком большое количество деталей реализа- ции. Для профессиональных программистов BGL предлагает высококачественные реализации структур данных и алгоритмов. Программисты смогут значительно сократить время разработки за счет надежности библиотеки. Время, которое было бы затрачено на разработку (и отладку) сложных графовых структур данных и ал- горитмов, может быть использовано для других дел. Более того, гибкий интер- фейс BGL позволит программистам применять графовые алгоритмы в ситуаци- ях, когда граф может существовать лить неявно. Для теоретиков эта книга дает стимул к использованию обобщенного програм- мирования для реализации теоретико-графовых алгоритмов. Алгоритмы, напи- санные с применением интерфейса BGL, смогут широко применяться и иметь возможность повторного использования в различных областях. Мы предполагаем, что читатель хорошо понимает C++. Мы не пытаемся на- учить читателя C++ в этой книге, так как для этого есть много хороших источни- ков (мы особенно рекомендуем [42] и [25]). Мы также подразумеваем некоторое знакомство с STL (см, [34] и [3]). Однако мы представляем наиболее передовые возможности C++, использованные для реализации обобщенных библиотек в це- лом и BGL в частности. В книге также вводятся необходимые понятия теории графов, но без особо- го рассмотрения. Для детального знакомства с элементарной теорией графов см. [10]. Грамотное программирование Примеры программ в этой книге представлены с использованием стиля грамот- ного программирования (literate programming style), разработанного Дональдом Кнутом. Стиль грамотного программирования состоит в написании исходного кода и документации в одном и том же файле. Затем специальная программа пре- образует файл в «чистый» файл исходного кода и в файл документации с красиво напечатанным исходным кодом. При грамотном программировании легче гаран- тировать, что примеры кодов в книге действительно компилируются и запуска- ются и что они соответствуют тексту. Исходный код для каждого примера разбит на части. Части могут содержать ссылки на другие части. Например, ниже приведена часть, названная «Определе- ние функции сортировки слияниями», которая ссылается на другие части, назван- ные «Разделить массив пополам и отсортировать каждую половину» и «Слить обе половины». Примеры часто могут начинаться с части, которая является схе- мой всего вычисления, после чего следуют части, заполняющие детали. Напри- мер, следующий шаблон функции является обобщенной реализацией алгорит- ма сортировки слияниями [ 10]. В алгоритме два шага: сортировка каждой части и слияние частей: ( Определение функции сортировки слияниями > - template «typename RandomAccessIterator. typename Compare* void merge_sort(RandomAccessIterator first. RandomAccessIterator last. Compare cmp)
Благодарности 21 I if (first + 1 < last) ( ( Разделить массив пополам и отсортировать н.мдуо половину ) ( Слить обе половины ) } } Обычно размер каждой части ограничен несколькими строками кода, которые выполняют определенную задачу. Имена для частей выбраны так, чтобы переда- вать суть этой задачи. ( Разделить пассив пополам и отсортировать камдус половину ) « RandomAccessIterator mid - first + (last first'^S: merge_sort(first. mid. crop): merge_sorViMd. last, crop'; Функция std:: 1 npl ace_merge() осуществляет основную работу этого алгорит- ма, создавая единый отсортированный массив из двух массивов: ( Сли-ь обе половины ) « std. ;inplacejnerge( first. mid. last, or-p;: Иногда для названия используется имя файла. Это означает, что часть записа- на в отдельный 4«1йл. Многие примеры в этой книге записаны в файлах и могут быть найдены в каталоге libs/graph/example/ в поставке Boost. В следующем при- мере название функции merge_sort() записано в заголовочном файле: < merge-sort.hpo ) ж fifndef M£RGE_SORr_HPP Idefine M£RGE_SORT_HPP ( Определение функции сортировки слияниями ) #er.dif П MERGE_SORi_HPP Благодарности Мы обязаны поблагодарить множество людей, которые вдохновляли и поддер- живали нас в разработке BGL и в написании этой книга. Особую признательность хотелось бы выразить Александру Степанову и Дэви- ду Массеру за их первые таги в деле обобщенного программирования и постоян- ную поддержку нашей работы, а также за вклад в библиотеку BGL. Мы особенно благодарим Дэвида Массера за чтение корректуры к этой книге. Работа Мэттью Остерна по документированию концепций STL обеспечила фундамент для со- здания концепций BGL. Мы благодарим Дитмара Кюля за его работу над обоб- щенными гра^ювыми алгоритмами и паттернами проектирования, особенно за абстракцию ассоциативного свойства. Нужно заметить, что работа была бы не- возможна без выразительной мощи языка C++ Бьерна Страуструпа. Дэвид Абрахамс. Джене Mojiep. Дитмар Июль, Бемав Дэйвс, Гари Пауэл, Грег Колвин и другие участники Boost сделали ценный вклад в интерфейс BGL, вне- сли много'гасленные предложения по улучшению, подробно вычитывали эту кни- гу. Мы также благодарим следующих пользователей BGL, чьи вопросы помогли усовершенствовать BGL (и эту книгу'): Гордон Вудхал, Дэйв Лонгхорн, Джоэль Филипс, Эдвард Люк и Стефен Норт.
22 Введение Мы также благодарим следующих людей за просмотр этой книги во время ее написания: Жана Кристиана ван Винкеля, Дэвид Массера, Бемана Дэйвса и Джеф- фри Сквайреса. Большое спасибо нашему редактору Деборе Лафферти и координаторам вы- пуска Киму Арни Малкахи, Черли Ферпоссону и Мерси Барнс, а также осталь- ной части команды издательства «Addison-Wesley» — работать с вами было одно удовольствие. В начале работа над BGL была поддержана гратом NSF ACI-9982205. Части BGL были завершены, когда третий автор был в годичном отпуске в Американ- ской Национальной лаборатории Лоуренса Беркли (где первые два автора были случайными гостями). Все рисунки графов в этой книге были сделаны с исполь- зованием программы dot из пакета Graphviz. Лицензия Библиотека BGL выпушена под «художественной» лицензией («artistic» license) программного обеспечения с открытыми исходными кодами. Копия лицензии на BGL приложена к исходному коду в файле LICENSE. BGL может свободно использоваться как для коммерческого, так и не коммер четкого использования. Основным ограничением на BGL является то, ’гго моди- фицированный исходный код может распространяться, только если он отмечен как нестандартная версия BGL. Для распространения BGL и внесения измене- ний предпочтительным является использование веб-сайта Boost. От издательства Ваши замечания, предложения и вопросы отправляйте по адресу электронной почты comp@piter.com (издательство «Питер», компьютерная редакция). Мы будем рады узнать ваше мнение! Подробную информацию о наших книгах вы найдете на веб-сайте издатель- ства http://www.piter.com.
Руководство пользователя
Введение I В этой главе будут приведены общие положения о некоторых интерфейсах и ком понентах библиотеки для работы с графами Boost Graph Library (BGL). Мы на- чнем с краткого обзора терминологии теории графов. В качестве примера хорошо моделируемой графом системы будет выступать сеть интернет-маршрутизаторов. Обобщенные интерфейсы, определяемые в BGL, описываются в разделе 1.2. Кон- кретные графовые классы, реализующие эти интерфейсы, рассматриваются в раз- деле 1.3. Некоторые сведения об обобщенных алгоритмах на графах приведены в разделе 1.4 данной главы. 1.1. Немного терминологии из теории графов Графовая модель сети интернет-маршрутизаторов показана на рис. 1.1. Маршру- тизаторы помечены кружками с буквами, а соединения между ними подписаны средними величинами задержек передачи сигнала (в миллисекундах). Если придерживаться терминологии теории графов, каждый маршрутизатор в примере сети является вершиной (vertex) (ее еще называют узлом), а каждое со-
1.1. Немного терминологии из теории графов 25 единение — ребром (edge) (или дугой). Граф G состоит из множества вершин V и множества ребер Е, что можно записать как G - (V, Е). Количество элементов множества вершин (число вершин графа) обозначается |У|, а число элементов множества ребер — \Е |. Ребро записывается как пара, состоящая из вершин, кото- рые соединяет ребро. Пара (и, е) обозначает ребро, соединяющее вершину и с вер- шиной V. Сеть маршрутизаторов (рис. 1.1) может быть выражена в нотации теории мно- жеств следующим образом: У — {а, Ь, с, d, в} Е - {(а, b), (а, d), (A. d), (с, а), (с, е), (d, с), (rf, е)} G = ([/,£) Граф может быть ориентированным пли неориентированным, в зависимости от того, ориентированы или нет ребра из множества ребер. Ребра ориентиро- ванного графа обычно называются дугами (хотя далее мы будем пользоваться термином ребро). Ребро ориентированного графа (орграфа) обозначается как упорядоченная пара (u, v), где и — начальная вершина, a v — конечная вершина ребра. Ребра (и, о) и (о, и) являются различными. В неориентированном графе ребро соединяет вершины в обоих направлениях, поэтому порядок вершин в реб- ре не имеет значения: (а,») и (о, и) обозначают одно и то же ребро. Ребро, нача- ло и конец которого совпадают, называется петлей. Такие ребра не допускаются в неориентированном графе. Два или более ребра, соединяющие одни и те же вершины, называются параллельными, а граф, имеющий такие ребра, — муль- тиграфом. Если граф содержит ребро (и, о), то говорят, что вершина и смежна с верши- ной и. Для ориентированного графа ребро (и, г) является исходящим для верши- ны и и входящим для вершины о. В неориентированном графе ребро («, о) инци- дентно вершине и (и вершине v). Множества смежности вершин графа на рис. 1Л описываются следующим образом: Adjacent[a] - {А, $ Adjacent[b] - {</} Adjacent[c] “ {а, е} Adjacent[d\ - {с, е} Adjacent[e] - {} Далее записаны исходящие ребра для каждой вершины: O«tE4ges[a] - {(а, Ь), (а </)} Out£flfe«s[b] “ {(A, d)} Ou£E4ges[c] - {(с, а), (с, е)} OutEdges{d\ - {{d, с), (</, е)} OutEdges[e] - {} Входящие ребра представлены аналогично: InEdges[a] - {(с, а)} InEdges[b]- {(а, Ь)} InEdges[c] - {(</, с)} InEdges[dl - {(а, d), (b, d)} /AEc/gfes[e] - {(с, е), (d, е)}
26 Глава 1 • Введение 1.2. Графовые концепции Одна из основных задач обобщенной библиотеки — определить интерфейсы, которые позволят писать алгоритмы, не зависящие от конкретной структуры данных. Заметим, что под интерфейсом подразумевается не только набор про- тотипов функций, но и наборы синтаксических условий, таких как имена функ- ций и их аргументов, семантических условий (вызываемые функции должны иметь определенные эффекты), гарантии той или иной сложности по времени и памяти. Пользуясь терминологией из книга «Обобщенное программирование и STL* (см. главу «Дополнение к библиографии), мы используем слово концепция (con- cept) для обозначения этого более богатого определения интерфейса. Стандарт- ная библиотека шаблонов (Standard Template Library, STL) определяет набор концепций итераторов, которые обеспечивают обобщенный механизм обхода и до- ступа к последовательностям объектов. Аналогично, рассматриваемая в этой книге BGL определяет набор концепций для изучения графов и манипулирования ими. В данном разделе мы рассмотрим эти концепции. Примеры раздела не относятся к конкретным типам графов. Они написаны как шаблоны функций, где граф яв- ляется параметром шаблона. Обобщенная функция, написанная с помощью BGL, может применяться к любому типу графа из BGL или даже к типам графов, опре- деленным пользователем. В разделе 1.3 мы обсудим конкретные графовые клас- сы, поставляемые вместе с BGL. 1.2.1. Описатели вершин и ребер В BGL вершинами и ребрами можно управлять при помощи «непрозрачных» ма- нипуляторов, называемых описателями вершин (vertex descriptors) и описателя- ми ребер (edge descriptors,). Графовые типы могут использовать различные типы для своих описателей. Например, некоторые графовые типы применяют целые числа, а другие — указатели. Типы дескрипторов’ для графового типа всегда дос- тупны через класс graph traits. Более подробная информация по использованию классов свойств приведена в разделе 2.4, а класс graph traits обсуждается, в част- ности, в разделе 14.2.1. Описатели вершин выполняют очень примитивные функции. Их можно со- здать только при помощи конструктора, со значениями но умолчанию, затем ско- пировать при необходимости или сравнить на равенство. Описатели ребер подоб- ны описателям вершин, но предоставляют доступ к связанным с шши вершинам. Следующий шаблон функции демонстрирует2 реализацию обобщенной функции is sel f_loop(), определяющей, является ли ребро петлей: template <typename Graph» bool is_self_loop(typename graph_traits<Graph>::edce_descriptor e. const Graphs g) ( 1 Термины «описатель» и «дескриптор» означают одно и то же. — Примеч. рвд. 1 Из эстетических соображений для определения параметрон шаблона мы используем typename вместо его аналога class.
1.2. Графовые концепции 27 typename graph_tгаHs«Graph>::vertex_descriptor u. v; u - source(e. g): v - target(e, g): return u •=- v: I 1.2.2. Отображение свойств Графы становятся полезными в виде моделей для задач из специфических областей при помощи ассоциирования объектов и величин с вершинами и ребрами. Напри- мер, для графа на рис. 1.1 каждая вершина имеет имя, состоящее из одного симво- ла, а каждое ребро — величину задержки передачи сигнала. В BGL прикрепленные объекты или величины называются свойствами. Существует много возможностей для реализации, которые могут быть использованы для ассоциирования свойства с. вершиной или ребром: члены структуры (struct); отдельные массивы, индекси- рованные номером вершины или ребра; хэш-таблицы и т. п. Однако для написа- ния обобщенных алгоритмов на графах нам нужен универсальный синтаксис для доступа к свойствам, независимый от того, как они хранятся. Этот универсаль- ный синтаксис определяется концепциями отображения свойства (property map). Отображение свойства — это объект, обеспечивающий отображение из мно- жества объектов-ключей во множество объектов-значений. Концепции отобра- жений свойств определяют только три функции: • get (p_map, key) — возвращает объект-значение для ключ a key; • putCpjnap. key. value) — присваивает значение value объекту-значению, со- ответствующему ключу key; • pjnap[key] — возвращает ссылку на объект-значение. В следующем примере приведена обобщенная функция print vertex namef), которая выводит имя вершины v для данного отображения свойства namejnap: template «typename vertexCescriptor. typename VertexNameMap> void print_vertex_name(VertexDescriptor v. VertexNameHap namejnap) stc::cout <« get(name jnap. v): Аналогично задержка передачи сигнала для ребра может быть напечатана с по- мощью функции pri nt_trans_del ау( ): template «typename Graph, typename TransOelayMap. typename VentexNameMao> void print_trans_delay(typename graph_traits«Graph>::edge-descriptor e. const Graphs g. TransDelayMap delay_map. VertexNameHap nare_map) $td::cout « "trans-de1ay(" « get(name_map. sourcefe. g)) « «< gettname map. targetCe, g)T « ’) - * « gettdelay map. e); } Функции prlnt_vertex_name() и pnnt_trans_delay() будут использованы в сле- дующем разделе. Отображения свойств обсуждаются более подробно в главе 15, включая приемы создания отображений свойств, определяемых пользователем. Способы добавления свойств в граф и получения соответствующих отображений описаны в разделе 3.6.
28 Глава 1 • Введение 1.2.3. Обход графа Абстракция «граф» состоит из нескольких видов наборов (collections): вершины и реб- ра графа, исходящие и входящие ребра, смежные вершины для каждой вершины. По аналогии с STL, BGL использует итераторы для обеспечения доступа к каждому из этих наборов. Имеется пять видов итераторов для графов, один для каждого набора: 1. Итератор вершин используется для обхода всех вершин графа. Тип значе- ния итератора вершин — описатель вершины. 2. Итератор ребер используется для обхода всех ребер i-рафа. Тип значения итератора ребер — описатель ребра. 3. Итератор исходящих ребер применяется для доступа ко всем исходящим ребрам данной вершины и. Тип значения этого итератора — описатель реб- ра. Каждый описатель ребра, выдаваемый этим итератором, имеет и в каче- стве начальной вершины и смежную с и вершину в качестве конечной. 4. Итератор входящих ребер применяется для доступа ко веем входящим реб- рам вершины р. Тип значения — описатель ребра. Каждый описатель ребра, выдаваемый этим итератором, имеет v в качестве конечной вершины и вер- шину, к которой v является смежной, в качестве начальной вершины. 5. Итератор смежности делает доступными вершины, смежные данной. Тип значения этого итератора — описатель вершины. Как и описатели, каждый граф имеет свои собственные типы итераторов, до- ступные из класса graph_trait$. Для каждого из только что описанных итераторов BGL интерфейс определяет функцию, возвращающую std:: pai г объектов-итера- торов: первый итератор указывает на первый объект последовательности, а вто- рой итератор указывает за ее конец. Функция pnnt_vert.ex_name(выводящая на печать имена всех вершин в графе, приведена в листинге 1.1. Листинг 1.1. Функция для печати имен вершин графа template «typename Graph, typenane VertexNanwHap> void print vertex_names(const Graphs g. Vertex.NameNap rare map) { std::cout « "vertices(g) - | typedef typename graph_trait$<Graph>-:vert₽x_ ireraro'* for (std::pair<tter_t. iter_t> p - verticesig); p.first !- p.second: +*p first) ( print vertex nanei*p.first. name_map). std :cout « ' '. } std::cout « "I” std::endl. I Применение этой функции к обьекту-графу, моделирующему сеть маршрут и- заторов (см. рис. 1.1), приводит к следующему результату: vertices(g) - { а Ь с d е ) Функция print_trais_delayO, которая печатает задержки передачи сигнала, прикрепленные к каждому ребру графа, приведена в листинге 1.2. В функции ис- пользуется функция 11 е() (из boost/tupte/tuple.hpp) для осуществления прямого присваивания из std::paiг в скалярные переменные first и last.
1.2, Графовые концепции 29 Листинг 1.2. Печать задержек template «typename Graph, typename TransDeiayMap, typename VertextaneMap» void print_trans_delays(const Graphs g. TransDelayMap trans_delay_map. VertexNameMap name map) { typename graph_traits«Graph>::edgejterator first, last: for (tie(first, last) - edges(g): first I- last: ++first) ( print_trans_delay(*fir$t, g. trans_oelay_map. namejnap): std::cout << std::endl: } ) Вывод этой функции для графа с рис, 1.1 следующий: trans-delay(a.b) « 1.2 trans-delay(a.d) - 4.5 trans-delay(D.d) - 1.8 trans-delay(с,а) - 2.6 trans-delay(c.e) - 5.2 trans-delay(d.c) - 0.4 trans-delay(d.e) - 3.3 В дополнение к функциям verti ces (1 и edges () имеются out edges (). 1n_edges() и adjacerrt_vertices(), которые используют в качестве аргументов описатель вер- шины и объект-граф и возвращают пару итераторов. Многие алгоритмы не нуждаются во всем разнообразии доступных итерато- ров, иногда графовые типы не могут предоставить эффективные реализации для всех типов итераторов. Осторожнее применяйте в алгоритмах конкретные типы графов, нельзя требовать от них исполнения неподдерживаемых операций. Если вы попытаетесь использовать графовый тип, который не предоставляет требуе- мую алгоритмом операцию, возникнет ошибка компиляции. Сопровождающая такую ошибку информация поможет понять, какая операция не реализована. В разделе 2.5 этот материал описан более детально. Операции, доступные для данного графового типа, приведены в документации к этому типу. Раздел -«Модель для» из справочных глав 12-14 содержит инфор- мацию о предоставляемых операциях, в нем перечислены концепции, поддержи- ваемые данным графовым типом. Операции, требуемые некоторым алгоритмом, даны в документации к алгоритму, где перечислены концепции, требуемые каж- дым параметром алгоритма. 1.2.4. Создание и модификация графа Библиотека BGL позволяет определить интерфейсы для добавления или удале- ния вершин и ребер графа. В этом разделе мы рассмотрим небольшой пример со- здания графа, моделирующего сеть маршрутизаторов, изображенных на рис. 1.1. Сначала используется функция add_vertex() для добавления к графу пяти узлов, представляющих маршрутизаторы. Функция add_edge() применяется для того, чтобы добавить соединения между маршрутизаторами. template «typename Graph, typename VertexNameMap. typename TransDeiayMap» void build_router_network(Graph& g. VertexNameMap namejnap, TransDeiayMap delayjnap)
30 Глава 1 • Введение { ( Добавить маршрутизаторы к сети ) ( Добавить соединения сети ) 1 Функция add_vertex() возвращает описатель вершины для новой вершины. Мы используем этот описатель для присваивания вершине имени в отображении свойства имен: ( Добавить маршрутизаторы к сети ) typename graph_traits«Graph>::vertex_descriptor a, b. с. d. е: а - add_vertex(g); паге_аар[а] - 'а': b - adcTvertex(g): nanejnaplb] “ 'b'; с - add_vertex(g); папе_яар[с] - 'С: d - add_vertex(g): nairejnapid] - ’d’; е - add_vertex(g); name_map[e] - 'е': Функция add_edge( ) возвращает std::раз г, где первый член пары — описатель ребра для нового ребра, а второй — логический флаг, показывающий, добавлено ли ребро (в некоторых графовых типах невозможно добавить ребро, если ребро с тем же началом и концом уже есть в графе). Код добавления соединений сети приведен в листинге 1.3. Листинг 1.3. Добавление соединений сети ' Добавить соединения сети ) typename graph_traits<Graph>: :edge_descriptor ed: bool inserted; tie(ed. inserted) - acd_edge(a. b. g): delay_map(ed] - 1.2: tieied. inserted) - acd^edgefa. d. g); delayjwied] -4.5; tieced. inserted) - ac<Tedge(t). d. g): delay_map[ed] - 1.8: tle(ed. inserted) - acd_edge(c. a. g); delay~aap[ed] • 2.6: tieted. inserted) - acd_edge(c. e. g): delay_map[ed] • 5.2; tie(ed. inserted) - add_edge(d, c. g); delayjrapted] - 0.4,- tie(ed. inserted) « addjadgeid. e, g); delay_map[ed] - 3.3: В некоторых случаях более эффективно добавлять или удалять несколько вер- шин или ребер одновременно, а не по одной. Интерфейс BGL имеет функцию и для этого. 1.2.5. Посетители алгоритмов Многие из алгоритмов STL имеют параметр объект-функцию, предоставляющий механизм для настройки поведения алгоритма в конкретном приложении. Хоро- шим примером является функция std: :sort(), имеющая параметр conpa re для за- дания сравнения: template «typename RandomAccessIterator. typename B’naryPredicate> void sortiRandomAccessIterator first. RandonAccessIterator last. BinaryPredicate compare) Параметр compare является объектом-функцией (иногда используют название ♦функтор»). Его применение проиллюстрировано в следующем примере. Рассмотрим код программы для адресной книги. Сортировка массива адресов по фамилии контактного лица может быть осуществлена вызовом функции std. :sort() с соответствующим объектом-функцией. Пример такой функции приведен далее:
1.2. Графовые концепции 31 struct compare/! ast_name { bool operator0(const adcress_info& x. const adaress_info& y) const { return x.lastjrame < y.last name; 1 ): Сортировка массива адресов производится вызовом функции std :; sort() с пе- редачей специализированной функции сравнения. std;:vector<adrfress info» addresses; И ... compare J astjiame compare; std;:sort(addresses.begin(). addresses.endО, compare): Библиотека BGL предоставляет механизм, подобный функциональным объек- там, для специализации алгоритмов на графах. Эти объекты называются посети- телями алгоритмов (algorithm visitors). Посетитель BGL — объект из нескольких функций. Вместо одного operator) для функционального объекта посетитель BGL определяет несколько функций, вызываемых в определенных событийных точ- ках алгоритма (событийные точки изменяются для каждого алгоритма). Несмот- ря на название, посетители в BGL несколько отличны от паттерна «посетитель*, описанного в книге «Паттерны проектирования* [14] авторов Гамма Э., Хелм Р.. Джонсон Р., Влиссидес Дж. («банда четырех*). Посетитель «банды четырех», пре- доставляет механизм, выполняющий новые операции над объектной структурой без модификации классов. Подобно посетителю «банды четырех» назначение по- сетителя BGL — обеспечить механизм для расширения. Однако разница заключа- ется в том, что посетитель в BGL расширяет алгоритмы, а не структуры объекта. В листинге 1.4 приведен код вывода на экран интернет-маршрутизаторов (см. рис. 1.1) путем расширения функции поиска в ширину brcadth f 1 r$t_search () с помощью посетителя. Посетитель печатает имя вершины при наступлении со- бытия посещения вершины (с.м. раздел 4.1.1, где описана процедура поиска в ши- рину). Класс посетителя определяется в соответствии с интерфейсом, описанным в концепции BFSV1 si tor. Листинг 1.4. Пример класса посетителя template «typenane VertexNaneHap> class bfs_name_printer : pybHc default_bfs_vtsftor { // Наследовать действия в событийных точках по умолчание (пустые) public: pfs_name_pnnter(VertexNameMap n_map) : iri_name_map(n_mapi { } template <typename Vertex, typename Graph» void discover_vertext Vertex u. const Graphs ) const { std: :cout « <jet(iii_namejMp. u) « ' } private: VertexNameMap m__n«e_«p: }; Далее был создан объект-посетитель типа bfs_name_pn nter и передан функции breadth_f1rst_search(). Функция visitorO, использованная здесь, является час- тью процедуры именованного параметра, которая описана в разделе 2.7. bfs_nare_printer<Vert.exNameMap> visfnamejnap): std::cout « "Порядок посеиения вершин; breadth_first_search(g, a, visitor(visl): std:;сой « std: :endl:
32 Глава 1 • Введение Это дает на выходе следующую запись: Порядок посещения вершин: a odce Ребра дерева поиска в ширину изображены на рис. 1.2. черными стрелками. 1.3. Классы и адаптеры графов Графовые типы, предоставляемые BGL, можно отнести к одной из двух категорий. К первой относятся графовые классы, которые служат для хранения графов в памяти, ко второй — графовые адаптеры (adapters), которые создают измененные представле- ния графов (views) или интерфейс к BGL графу, основанному на другом типе. 1.3.1. Классы графов Библиотека BGL содержит два первичных гра<|ювых класса: список смежности adjacencyj 1st и матрица смежности adjacencyjw.trix. Главный компонент для представления графов — dd.jacenc.y_l 1st. Этот класс обобщает традиционное представление графа в виде списка смежности. Граф представляется набором вершин, каждая из которых хранится со своим набором исходящих ребер. Фактическая реализация набора вершин и ребер может под страиваться под определенные нужды приложения. Класс adjacencyjist имеет несколько параметров шаблона; EdgeList, VertexL 1 st, Directed, Vertexproperties, EdgeProperties и GraphP-operties. • EdgeLi st и Vertex.i st предназначены для к. taccoB, используемых для хране- ния списка вершин и списка ребер графа. Эти параметры позволяют найти компромисс между ско|кхчью обхода и скоростью вставкн/удаления, а так- же выбрать уровень потребления памяти. Кроме того, параметр EdgeList. определяет, могут ли добавляться к графу параллельные вершины. • Di rected определяет, является ли граф ориентированным, неориентирован- ным или двунаправленным (bidirectional graph). Двунаправленный граф предоставляет доступ не только к исходящим ребрам, но и к входящим. • VertexProperti es, EdgePrope.”t i es и GraphProperti es определяют типы свойств, закрепленных за вершинами, ребрами и самим графом соответственно.
1.3. Классы и адаптеры графов 33 Полная документация по классу adjacencyjist находится в разделе 14.1.1. Для представления плотных графов (таких графов, у которых |Е| « W '?) больше подходит класс adjacencyjnatn х. В adjecency_mat.ri х доступ к произволь- ному ребру (и, ») очень эффективен (это доступ за постоянное время). Этот класс может служить для представления как ориентированных, так и неориен- тированных графов и обеспечивать механизм для закрепления свойств за вер- шинами и ребрами. Полная документация по классу adjacencyJist находится в разделе 14.1.2. Стоит заметить, что хотя все приводимые в этой книге примеры относительно невелики (чтобы графическое изображение фафа могло поместиться на одной странице), графовые классы BGL являются эффективными по алгоритмам и тре- буемой памяти. Они могут использоваться для представления графов с миллио- нами вершин. 1.3.2. Адаптеры графов Библиотека BGL включает в себя большое количество адаптеров графов. Первая группа классов адаптирует любой BGL-граф для реализации нового поведения. Для этого применяются следующие адаптеры фафов: • reverse_graph — адаптер, обращающий направления ребер ориентированного графа таким образом, что входящие вершины ведут себя как исходящие и на- оборот; • fl 1 tered_graph — адаптер, создающий представление графа, где два объек- та-функции выступают предикатами, контролирующими видимость вершин и ребер в новом фафе. Также BGL обеспечивает поддержку объектов и структур данных, не являю- щихся графовыми классами BGL. Эта поддержка осуществляется через классы- адаптеры и перегруженныефункции (overloaded functions). Ниже приведено опи- сание этих интерфейсов. • edgej 1 st — адаптер, который создает BGL-граф из выхода итератора ре- бер. • Пакет программ Stanford GraphBase поддерживается перегруженными фун к- циями из заголовочного файла boost/graph/stanford_graph.hpp. В результате GraphBase-тип Graph* адаптируется к графовому интерфейсу BGL. • LEDA (популярный объектно-ориентированный пакет) включает структу- ры данных и алгоритмы для фафов. Тип GRAPH<vtype. etype> из LEDA адап- тируется к фафовому интерфейсу BGL с помощью перегруженных функ- ций из файла boost/graph/leda_graph.hpp. • Тип std: :vector<std::1ist<int>> из STL преобразуется в фафблагодаря пе- рефуженным функциям из файла boost/graph/vector_a$_graph.hpp. Интерфейс BGL более детально описан в справочнике ио концепциям в гла- ве 12. Каждый графовый класс реализует некоторые (пли все) из этих концеп- ций. Класс adjacencyjist можно считать канонической реализацией (моделью) BGL-графа, поскольку он иллюстрирует все базовые идеи и интерфейсы BGL- графов.
34 Глава 1 • Введение 1.4. Обобщенные алгоритмы на графах Алгоритмы на графах из BGL являются обобщенными. Они чрезвычайно гибкие по типам структур данных графов, к которым они могут применяться, и по возмож- ностям специализации для решения широкого диапазона задач. Сначала мы рас- смотрим функцию topological__sort(} для двух различных типов графов, а затем продемонстрируем работу обобщенного алгоритма функции 6epth_fi rst_searcb( > применительно к реализации topologic<51_sor’ (). 1.4.1. Обобщенный алгоритм топологической сортировки Топологическое упорядочение ориентированного 1рафа — это такое упорядочение его вершин, при котором если в графе присутствует ребро (и, о), то вершина и появляется до вершины г в упорядочении. Шаблон функции topoiogical_sorr.() имеет два аргумента: граф для упорядочения и итератор вывода. Алгоритм запи- сывает вершины в итератор вывода в обратном топологическом порядке. Топологические упорядочения, например, применяются в задачах планирова- ния. На рис. 1.3 изображен граф, вершинами которого являются подлежащие вы- полнению задания, а ребра показывают завиенмтхти между заданиями (напри- мер, получить деньги в банкомате нужно перед покупкой продуктов). Рис. 1.3. Граф зависимостей между заданиями
1.4. Обобщенные алгоритмы на графах 35 В следующих двух разделах будет показано, как применить алгоритм тополо- гической сортировки к этой задаче. В каждом разделе для иллюстрации обобщен- ной природы алгоритмов BGL использован свой тип графа. Использование топологической сортировки с вектором списков Вначале мы применяем топологическую сортировку к графу, построенному с по- мощью std::vector* std: :1 ist<int> >. Код программы топологической сортиров- ки графа приведен в листинге 1.5. Листинг 1.5. Топологическая сортировка графа. Файл topo-sortl.cpp ( topo-sortl ерр ) ^include «deque» // для хранения упорядоченных вераич # 1 Delude «vector* finclude <11 st» ^include «iostream» findude «boast/graphivector_as_graph,hpp> include <boost/graph/topological_sort ,npp> int main!) using namespace boost: ( Создать метки для каждого задания ) ( Создать граф > ( Выполнить топологическую сортировку и вывести реэупь:<и ) return EX n_SUCCESS: } Вершины графа представлены целыми числами от нуля до шести, поэтому удобно хранить метки вершин в массиве. В листинге 1.6 приведен код для созда- ния соответствующих меток. Листинг 1.6. Создание меток для заданий ( Создать метки для каждого задания ) « const char* tasksf ] - { "получить деньги в банкомате". "забрать детей из школы". "купить продукты”. "привести детей на тренировку". "забрать дегей с тренировки", ‘приготовить ужин", "съесть ужин", }. const int n_tasks - slzeof (tasks) i sizeof (char*): Граф реализован в виде вектора списков. Каждая вершина графа связана ин- дексом вектора. Таким образом, размер вектора определяется количеством вер- шин в графе. Список по этому же индексу используется для представления ребер, исходящих от данной вершины к другим вершинам графа. Каждое ребро (w, v) добавляется к графу помещением числа для v в список и. Используемая структу- ра данных проиллюстрирована на рис. 1.4. Благодаря функциям из boost/graph/vector_as_graph.hpp вектор списков соот- ветствует BGL-концепции Vertex!. istGraph, а значит, может быть использован в функции topolog1cal_sort(). Код создания графа приведен в листинге 1.7
36 Глава 1 • Введение Рис. 1.4. Представление графа зависимостей заданий в виде вектора списков Листинг 1.7. Создание графа ( Создать граф ) std.:vector< std;:1ist<int> > gtn_tasks): g[0].push_back(3): gnj.push_back(3). g[l] push_tack(4); g[2]_push_back(l); g[3] push_back(5;: g[4].push_back(6); q[5].push_back(6): Чтобы применить topo1ogica1_sort(), необходимо подготовить место для хране- ния результатов. В BGL-алгоритме топологической сортировки вывод записыва- ется в обратном топологическом порядке (так как это можно реализовать эффек- тивнее). Восстановление топологического порядка требует обращения упоря- дочения, вычисленного алгоритмом. В следующем примере (листинг 1.8) исполь- зуется std:: dequeue в качестве структуры данных для вывода результатов, так как std: dequeue может делать вставку в начало за постоянное время, что нужно для получения обратного порядка. Кроме того, вызов topologica1_sort() требует од- ного из двух: 1) задания отображения свойства для цветовой окраски вершин; 2) задания отображения из вершин в целые числа, чтобы алгоритм мог создать свое собственное отображение свойства для отметки этапов прохождения по гра- фу. Поскольку в нашем примере вершины уже являются целыми числами, мы просто задаем тождественное отображение свойства i denti ty_property_map как ото- бражение индексов вершин. Функция vertex_index_map() используется для зада- ния именованных параметров (см. раздел 2.7). Код топологической сортировки и вывода результата приведен в листинге 1.8. Листинг 1.8. Топологическая сортировка и вывод результата ( Выполнить топологическую сортировку и вывести результат ) « std' 4leque<int> topo_order: topologiesI_sort(g. std::front_inserterttopo_order). vertex JndexjMpt i denti t.y_propertyjMp())): int П = 1;
1.4. Обобщенные алгоритмы на графах 37 for (std::deque«int>: iterator i •> topojjrder.begino. 1 !“ topo_order.end(): ’+1. +-»n) { Std::cout «-tasks[*i] « stdi.endl: } В результате показан порядок, при котором задания могли бы быть выполне- ны с учетом заданных зависимостей: подучить деньги в банкомате купить продукты приготовить ужин забрать детей из школы привести детей на тренировку забрать детей с тренировки съесть ужин Использование топологической сортировки с классом adjacencyjlst Для демонстрации гибкости обобщенного алгоритма topological_sort() будем ис- пользовать совершенно другой тип графа: шаблон класса adjacencyjlst. Так как функция topol ogi са 1 _sort () является шаблоном, в нем MOiyr быть использованы гра- фовые структуры произвольного типа. Все, что необходимо. — это чтобы тип удовле- творял концепции, требуемой алгоритмом. Первые два параметра шаблона класса adjacencyjlst определяют конкретную используемую внутреннюю структуру. Пер- вый аргумент lists указывает, что std; :list используется для каждого списка ис- ходящих ребер. Второй аргумент vecS свидетельствует о том, что std: -.vecior ис- пользуется как основа списка смежности. Эта версия класса adjacencyJ1 st аналогична по характеру вектору списков, которым мы пользовались в предыдущем разделе. < Создать объект для списка смежности ) » adjacency 1 ist<listS. vecS. dlrectedS» g(n_tasks): Функция add_edge() предоставляет интерфейс для вставки ребер в граф класса adjacencyjlst (и в любые другие графы, поддерживающие концепцию EdgeMu- tableGraph). Если std: -.vector используется в качестве основы списка смежности, тип описателя вершин для adjacency ! ist будет целым числом. Но использовать целые числа для задания вершин возможно не во всех графовых типах. ( Добавить ребра к списку смежности ) add_edge(0. 3. g): add_edge(l. 3. g): addjdgefl. 4, g): adcTedge(2. 1. g): add^edgeO. 5. g): addjdge(4. 6. g): addjsdge(5. 6. g). В остальном программа аналогична предыдущему примеру (см. листинг 1.5), за исключением того, что вместо заголовочного файла vector_.as_graph.hpp используется файл adjacencyjlst.hpp. В листинге 1.9 приведен код программы топологиче-ской сортировки графа (две части из предыдущего раздела использованы повторно). Листинг 1.9. Топологическая сортировка графа. Файл topo-sort2.cpp ( topo-sort2,cpp 18 ) ♦include «vector* продолжением
38 Глава 1 • Введение Листинг 1.9 (продолжение) nclude «'deque* # ind tide <KiostZgraph/tflpo1ocicai_sort.hpfp fi rd ude <bdost/graph/adjac«icy_ list. hpp* int: main*.) । i using namespace boost: { Создать метки для тамдию задания ) ( Создать объект для списка снежности } ( Добавить ребра к списку снежности > < Выполнить топопогическу» соотировку и вывести результат ) return EXn_SUCCESS: } 1.4.2. Обобщенный алгоритм поиска в глубину Реализация topological__sort() в BCL состоит всего из нескольких строк, так как этот алгоритм может использовать функцию dep"n_ftrst_search(), как это обычно и делается в учебниках. Реализация состоит из комбинации cepth_f1rst_search() и посетителя, устанавливающего порядок, в котором вершины проходят через событийнуюточку «закончить вершину» поиска в глубину. Объяснение того, по- чему таким образом действительно вычисляется топологическое упорядочение, дано в разделе 3.3. В листинге 1.10 описывается алгоритмический посетитель, который записы- вает вершины при их прохождении через соответствующую событийную точку поиска в глубину. Чтобы сделать этот класс более общим, упорядочение вершин записывается в итераторе вывода, так что пользователь может выбрать метод вы- вода сам. Листинг 1.10. Посетитель для записи посещаемых вершин template dypename OutputIterator* class topo_sort visitor ; puohc default._dfs,yi!>ii3r { II наследовать действия по уиопчанис (пустые) риЫ 1с: topv_sort_visitor(Outpi.tIterator iter) . m_iter(iter) { } template <typename Vertex, typename Graph*” void fimsh_vertex(Vertex u, const Graphs,) { *m_item - u. ) private. Out put Iterator mJ ter: }• Итак, topologica’i_sort() реализуется запуском алгоритма поиска в глубину depth_first_search() ct.opo_sort_visil.or() в качестве параметра. template <typenarne Graph, typenarae Output!Leraior* void topdogical_sort(Graph& g. Output Iterator result, iter) { topo sort_visitor<Outputiterator> vistresult ’ter), depth_first_srarch(o. visitor-vis)). )
Обобщенное программирование в Си—h 2.1. Введение Обобщенное программирование (ОП, generic programming, GP) - методология проектирования и реализации программ, которая разделяет структуры данных и алгоритмы через использование абстрактных спецификаций требований. В С*+ для обобщенного программирования характерно использование параметрического полиморфизма шаблонов (templates) с акцентом на производительность. Методо- логия обобщенного программирования была использована нами при создании библиотеки BGL. Для понимания устройства и структуры BGL читателю необхо- димо иметь хорошие знания в области обобщенного программирования. В силу своей относительной новизны (по крайней мере, в сообществе пользователей C++) мы решили дать в этой главе введение в обобщенное программирование. Также будут обсуждаться основные приемы ОН в C++, базирующиеся на шаблонах. Эти приемы — не просто набор «фокусов*: совместно они образуют новый подъязык в рамках C++. Абстрактные спецификации требований в обобщенном программировании подобны прежнему понятию абстрактных типов данных. Абстрактный тип дан- ных — это спецификация типа. Она состоит из описания подходящих операций и задает семантику этих операций, зачастую включая пред- и постусловия и акси- омы (или инварианты) [30]. Классический пример абстрактного типа данных - стек с методами записи («push») и извлечения («рор») данных. Есть много спосо- бов реализации стека: массив переменного размера, связный список и другие, но детали реализации не важны для пользователя стека до тех пор, пока реализация удовлетворяет спецификации абстрактного типа данных. В обобщенном программировании понятие абстрактного типа данных рас- ширяется. Вместо определения спецификации для отдельного типа мы описыва- ем семейство типов, которые имеют общи й интерфейс и семантическое поведение (semantic behavior). Набор требований, описывающий интерфейс и семантическое поведение, называется концепцией (concept). Алгоритмы, сконструированные
40 Глава 2 • Обобщенное программирование в C++ в обобщенном стиле, можно применить к любым типам, удовлетворяющим тре- бованиям этого алгоритма. Эта способность использовать различные типы с одной и той же переменной (или параметром функции) называется полиморфизмом. 2.1.1. Полиморфизм в объектно-ориентированном программировании В объектно-ориентированном программировании (ООП) полиморфизм реализу- ется посредством виртуальных функций и наследования, что называется поли- морфизмом подтипов. Требования концепции к интерфейсу могут быть записаны как виртуальные функции в абстрактном базовом классе. Предусловия и инвари- анты соответствуют утверждениям (assertions), когда это возможно. Конкретные классы нас ледуют из абстрактного базового класса и определяют реализации этих функций. Говорят, что конкретные классы являются подтипами (или производ- ными классами) базового класса. Обобщенные функции пишутся в терминах аб- страктного класса, а во время выполнения вызовы функций осуществляются для конкретного типа объекта (в C++ через таблицы виртуальных функций). Любой подтип абстрактного базовой} класса взаимозаменяем и может быть использован в обобщенной функции. Классическим примером концепции из математики является аддитивная абелева группа — это множество элементов с ассоциативной операцией сложения, имею- щее обратный элемент и нуль [45]. Мы можем представить эту' концепцию в C++, определив абстрактный базовый класс, следующим образом: // Концепция AdditlveAbelianGroup как абстрактный базовый класс class AdditlveAbelianGroup { public: virtual void add(AdditiveAbelianGroup* y) • 0: virtual Addin veAbelianGroup* inversed - 0: virtual AdditlveAbelianGroup* zerod - 0: } Используя этот абстрактный базовый класс, мы можем написать универсаль- ную функцию $um(); AdditlveAbelianGroup* sum(array<Add'tiveAbelianGroup*» v) { AdditlveAbelianGroup* total - v[0]-'-zerod. for (mt i - 0: i < v sized: •••н) total->add(v[_W: return total; Функция sum(} будет работать с любым массивом, тип элемента которого про- изводен от AdditlveAbelianGroup. Примерами таких типов могут служить числа и векторы. class Real : public AdditlveAbelianGroup { // ... }: class Vector .- public AdditlveAbelianGroup { // ... i
2.1. Введение 41 2.1.2. Полиморфизм в обобщенном программировании В обобщенном программировании полиморфизм реализуется с помощью шабло- нов классов или функций. Шаблоны обеспечивают параметрический полиморфизм. Ниже функция sum() записана как шаблон. Наличие базового класса Additive- Abel 1 anGroup уже не нужно, хотя мы оставили его в качестве имени параметра шаб- лона для удобства и документирования. template «typename AdditiveAbelianGroup> AdditiveAbeHanGroup sum(array<AddltiveAbelianGroun> v) { AdditiveAbellanGroup total - v[0j.zero(): for (int 1 - 0; t < v.sized: +-»i) total .add(v[i]k return total: } В C+ + концепция выражается в наборе требований к аргументу шаблона, что- бы шаблон класса или функции мог быть успешно скомпилирован и исполнен. Хотя концепции присутствуют в обобщенном программировании неявно, они очень важны и должны быть тщательно документированы. В настоящее время такая документация обычно появляется в комментариях к коду млн в книгах, та- ких как «Обобщенное программирование и STL» (см. главу «Дополнение к биб- лиографии»), Вернемся к примеру с Addi ti veAbel lanGroup, но уже в качестве кон- цепции AdditiveAbelianGroup. // концепция AdditiveAbelianGroup (аддитивная абелева группа) и правильные выражения: И x.add(y) Н добавить И у - х.inversed И обратить н у - x.zerod И ноль и сенаитика: // Конкретные типы, удовлетворяющие требованиям AdditiveAbelianGroup, не обя- зательно должны наследовать от некоторого базового класса. Типы аргументов шаблона подставляются в шаблон функции при инстанцировании (во время ком- пиляции). Для описания отношения между конкретными типами и концепция- ми, которым они удовлетворяют, используется термин модель. Например, Real и Vecto- являются моделями концепции AdditiveAbelianGroup. struct Real { // нет наследования И .. Ь struct Vector { П нет наследования и ... ): 2.1.3. Сравнение ОП и ООП До сих пор мы не совсем точно описывали обобщенное программирование как «про- граммирование с шаблонами», а объектно-ориен тированное — как «программиро- вание с наследованием». Это может быть несколько неправильно истолковано, так как суть этих методологий только косвенно связана с шаблонами и наследованием.
42 Глава 2 • Обобщенное программирование в C++ Если говорить более формально, обобщенное программирование основано на па- раметрическом полиморфизме, тогда как объекто-ориентированное — на поли- морфизме подтипов. В C++ эти полиморфизмы реализованы в виде шаблонов и наследования, но другие языки могут предлагать для этого иные механизмы Например, расширение сигнатур (signatures extension) в GNU C++ (4] обеспечи- вает альтернативную форму полиморфизма подтипов. Мулътиметоды (в таких языках, как CLOS [21]) предлагают семантику, более близкую к параметрическо- му полиморфизму, но с диспетчеризацией вызовов времени выполнения (в отли- чие от диспетчеризации во время компиляции шаблонов). Тем нс мепсс, поскольку мы выбрали стандартный C++, было бы полезно со- поставить ОП и ООП, сравнив наследование (и виртуальные функции) с шабло- нами в контексте C++. Виртуальные функции медленнее шаблонных функций Вызов виртуальной функции выполняется медленнее, чем вызов шаблонной функ- ции (последний столь же быстр, как и вызов обычной функции), так как вызов виртуальной функции включает в себя лишнее разыменование указателя для на- хождения метода в таблице виртуальных функций. Сами по себе эти «накладные расходы» могут быть незначительными, но они способны косвенно повлиять на скомпилированный код: например, не позволить оптимизирующему компилято- ру встроить функцию в код (какжэт/ммележую функцию, inline-функцию) и при- менить дальнейшие оптимизации полученного кода. Конечно, размер издержек на виртуальные функции всецело зависит от объ- ема производимых в функции вычислений. Для компонентов уровня итераторов и контейнеров STL или итераторов графов BGL потери при вызове функции мо- гут оказаться значительными. Производительность на этом уровне сильно зави- сит оттого, являются ли функции вроде operator++() встраиваемыми. По этой причине шаблоны являются единственным выбором для реализации эффектив- ных, низкоуровневых, повторно используемых компонентов, подобных тем, что можно найти в STL или BGL. Диспетчеризация времени выполнения в сравнении с диспетчеризацией во время компиляции Диспетчеризация времени выполнения виртуальных функций и наследование являются, вне всякого сомнения, одними из лучших свойств объектно-ориен- тированного программирования. Для некоторых видов компонентов диспет- черизация времени выполнения является безусловным требованием, так как решения могут приниматься на основании информации, доступной только во время выполнения. В этом случае виртуальные функции и наследование необ- ходимы. Шаблоны не могут предложить диспетчеризацию времени выполнения, но они обеспечивают значительную гибкость во время компиляции. Фактически, если диспетчеризация может быть выполнена во время компиляции, шаблоны обеспе- чивают большую гибкость, чем наследование, так как не ограничивают типы ар- гументов шаблона родством с определенным базовым классом (но об этом мы поговорим позже).
2.1. Введение 43 Размер кода: виртуальные функции малы, шаблоны велики Распространенной проблемой в шаблонном программировании является «раз- бухание» кода из-за неправильного использования шаблонов. Тщательно спро- ектированные компоненты па основе шаблонов нс увеличивают объем кода по сравнению с их аналогами, построенными па наследовании. Основной способ управлять размером кода — отделить функциональность, зависящую от типов шаблона, от функциональности, от них не зависящей. Примером этого может слу- жить реализация std:: 1 i st в библиотеке STL от фирмы SGI. Проблема бинарного метода При использовании подтипов (наследования и виртуальных функций) для реа- лизации операций, работающих с двумя или более объектами, возникает серьезная проблема. Эта проблема известна как «проблема бинарного .метода» [8]. Класси- ческим примером (приводен далее) этой проблемы является интерфейс класса Point (точка, заданная координатами на плоскости), имеющий функцию-метод класса equal (). Эта проблема особенно актуальна для BGL, поскольку многие из определяемых в BGL типов (дескрипторы вершин, ребер и итераторы) требуют наличия операции operator—() подобно equal () для класса Рот nt Следующий абстрактный базовый класс описывает интерфейс класса Point. class Point { public; victual bool equaliconst Point* pi n?ns* - 0. }: Используя этот интерфейс, разработчик библиотеки может написать «обоб- щенную» функцию, которая подучает аргументы любого производного от Point типа и выводит, являются ли они равными. voia print_equa1(const Point* a. const Point* b> { std:'tout « std-.bool aloha << << std -end': } Рассмотрим теперь реализацию некоторого «точечною* типа, скажем, класса цветных точек ColorPoint. Предположим, что в программе это будет единствен- ный «точечный» класс, который мы будем использовать. В этом случае достаточ- но определить равенство между двумя объектами ColorPo i nt, ле определяя равен- ства между цветными точками и точками других видов. class ColorPoint : publ4C Point I public" ColorPoint (float л. floaty, std:string c) . x(x). yty). colcrCc» { } virtual bool equaHconst ColorPoint* p> const return color =— p->colt>r S& >. -« p->x fA у =“ p-’y. } protected: float x. y: std‘:string color При попытке не пользовать этот класс выясняется, что функция ColorPoint: :«]ий; (1 не заместила Point •: equal (), При создании экземпляра ColorPoint возникает следую- щая ошибка:
44 Глава 2 • Обобщенное программирование в С++ error; object of abstract class type "ColorPoirrt’’ is not allowed: pure virtual function ’Point;:equal" has no overrider ошибка: объект абстрактного гипа класса "ColorPoint" не разрешается создавать: чистая виртуальная функция "Point: equal" не заменена Получается, что по правилу типизации тип параметра функции-метода класса в производных классах должен быть либо тот же, либо базовым классом такого же типа, как у параметра в базовом классе. В случае с классом Col orPoi nt параметр для equal () должен быть Point, а не ColorPoint. Однако это изменение приводит к другой проблеме. Внутри функции equal () аргумент класса Point должен быть приведен к типу ColorPoint для выполнения сравнения. Добавление этого приве- дения означает, что на этапе компиляции остается неизвестным, является ли про- грамма, использующая ColorPoint, типобезопасной. Методу equal С) по ошибке может быть передан объект другого «точечного» класса, вызвав исключение вре- мени выполнения. Ниже описан класс ColorPoint2, в котором параметр equal'! изменен на Point, а также добавлено приведение к нужному типу: class ColorPoint? : public Point { public: ColorPoint2(f1oat x. float y. std::string s) : x(x). y(y). color(s) { ) virtual bool equal(const Point* p) const { const ColorPoint?* cp - dynam*c_cast<const ColorPoint2*>(p); return color — cp->color x «- cp->x && у cp->y: 1 protected: float x. y: std::string color: Теперь предположим, что вместо виртуальных функций мы бы использовали шаблоны функций для выражения полиморфизма. Тогда функция print_equal 1) .могла бы быть написана так: template Чурепаве PointType* void print_equa12(const PointType* a. const PointType* b) { std: tout « sto:;boolalpna « a->equal(b) « std::endl; I Для использования этой функции класс цветных точек не нуждается в насле- довании из класса точек, и проблемы с приведением типов не возникает. При вы- зове рпnt_equal2() с двумя объектами типа ColorPoint параметр PointType прини- мает значение ColorPoint. что приводит только к вызову ColorPoint::equal О. Тем самым поддерживается безопасность типов. ColorPoint* а - new ColorPointtO 0. 0.0. "blue'',': ColorPoint* b - new Co1orPoint(0 0. 0.0. "green”); prv:t_equa!2(a. b): Так как BGL реализована на шаблонах функций, проблема бинарного метода нам не угрожает. И наоборот, эта проблема возникала бы на каждом шаге, если BGL была основана на виртуальных функциях. 2.2. Обобщенное программирование и STL Предметная область STL затрагивает основные алгоритмы из информатики (на- пример, структуры массивов и списков, алгоритмы поиска и сортировки). Было
2.2. Обобщенное программирование и STL 45 много «фундаментальных* библиотек, которые пытались предоставить всеобъ- емлющий набор структур данных и алгоритмов. От них STL отличается примене- нием обобщенного программирования (процессом и практикой). Как объясняют в своей книге |35] Массер и Степанов, процесс ОП в примене- нии к некоторой задаче состоит из следующих основных шагов: 1. Обнаружение полезных и эффективных алгоритмов. 2. Определение их обобщенного представления (то есть параметризация каж- дого алгоритма таким образом, чтобы требований к обрабатываемым дан- ным было как можно меньше). 3. Описание набора (минимальных) требований, при удовлетворении кото- рых эти алгоритмы могут быть выполнены эффективно. 4. Создание среды разработки (framework), основан ной на классификации тре- бований. Этот процесс отражен в структуре и устройстве компонентов STL. На практике процесс минимизации и проектирования среды разработки подра- зумевает такую структуру, где алгоритмы не зависят от конкретных типов данных, с которыми работают. Вернее, алгоритмы пишутся для обобщенных специфика- ций, которые были установлены в ходе анализа потребностей этих алгоритмов. В алгоритмах обычно требуется выполнять обход структуры данных и получать доступ к ее элементам. Если структуры данных предоставляют стандартный интер- фейс для обхода и доступа, обобщенные алгоритмы могут быть смело смешаны и согласованы со структурами данных (в терминологии STL ~ контейнералш). Главным помощником в задаче разделения алгоритмов и контейнеров в STL является итератор (иногда его называют обобщенным указателем). Итераторы предоставляют механизм для обхода контейнеров и доступа к элементам. Интер- фейс между алгоритмом и контейнером строится на основе требований к итера- тору, которым должен соответствовать тип итераторов данного контейнера. Обоб- щенные алгоритмы наиболее гибки, когда они написаны в терминах итераторов и не полагаются на конкретный вид контейнера. Итераторы подразделяются на несколько категорий (видов), например: Input- Iterator, Forwarditerator, RandomAccessIterator. Взаимосвязь между контейнерами, алгоритмами и итераторами представлена на рис. 2.1. Рис. 2.1. Отделение контейнеров и алгоритмов с помощью итераторов Библиотека STL определяет набор требований для каждого вида итераторов. В требованиях говорится, какие операции (допустимые выражения) определены для данного итератора и каков смысл каждой операции. Для п римера рассмотрим некоторые требования к итератору произвольного доступа из STL (он включает в себя требования к Fbrwardlterator), приведенные в табл. 2.1, Тип X означает
46 Глава 2 • Обобщенное программирование в C++ итераторный тип, Т — указываемый тип, LI — тип члена Т. Объекты а, Ь, и г — ите- раторы, m - член Т, п — целое число. Таблица 2.1. Некоторые требования к итератору произвольного доступа из STL Выражение Тип результата Примечание а »’ Ь bool *а — *Ь « i’ll bool !(а — М а < b bool b - а > 0 *а U Разыменование а а->т US + +Г XS Из — s следует ++г -- +♦$ XA Из s следует —г — -s Г п X& Такой же, как п от ♦♦г <3 * П X {too » a: return top ♦- n:} Ь - в Расстояние (а < bl ? distanced. b) : - distancefb. al a [nJ Приводимый к T ♦fa ♦ n) Пример вычислений с накоплением В качестве конкретного примера рассмотрим алгоритм accumulate!), который по- следовательно применяет бинарную операцию к начальному значению и каждо- му элементу контейнера. Произведем суммирование элементов контейнера, ис- пользуя операцию сложения. Следующий код покатывает, как можно реализовать accumulated на C++. Аргументы first и last — это итераторы, отмечающие на- чальный и запредельный элементы последовательности. Все аргументы функции параметризованы по типу, таким образом, алгоритм может быть использован с лю- бым контейнером, который моделирует концепцию Inputiterator. При прохожде- нии последовательности итератор применяет тог же синтаксис, что и указатели; в частности. ooerator++() переводит итератор на следующую позицию в последо- вательности. В табл. 2.1 перечислено несколько других способов продвижения итераторов (главным образом для итератора произвольного доступа). Для доступа к элементу контейнера, на который указывает итератор, можно использовать опе- рацию разыменования operator*!) или операцию индексирования opera tor [ 1 () — для доступа по смещению от текущей позиции итератора. template «typename Inputiterator, typename I. typenere Binaryoperator* I accumuiateilnputlterator first. Inputllerator last, T Init, B.naryOperator binary cp) { for c first !" last: ♦♦first) init - bfnary_op(init. 'first). return irrt: ) Для демонстрации гибкости интерфейса, предоставляемого итератором, мы используем шаблон функции accumulate!) с вектором и со связным списком (и то и другое из STL) (листинг 2.1).
2.3. Концепции и модели 47 Листинг 2.1. Демонстрация интерфейса итератора // использование accumulated с зеюором std:.vector<douMe> х(10. 1.0;.- double suffll: sunt - std - accumulate!«.begin! i. x.endO. O.Q. std. plus<double»()'. // использование accumulate!) co связный списком std::list<double> y; dcub’e sum?; i! копирование значения вектора а список std. copy<x.begm(). >..end(), std:.back mertertyH: sum?. - std: accumulate(y. begin!), у enat). 0 0. std-:plus<doiible>()): asserttswnl — sum2). П сии должны быть равны 2.3. Концепции и модели В предыдущем разделе был приведен пример требований к RandomAccessIterator. Мы также наблюдали. как Inputiterator был применен и качестве требования для функции accuiwl ate!) и как были использованы этой функцией std:. 11 st:; 1 tera tor и std;: vector:: 1 teratcr. В этом разделе будут определены термины для описания отношений между наборами требований, функций и типов. В контексте обобщенного программирования термин «концепция» употреб- ляется для описания набора требований, которым должен удовлетворять аргу- мент шаблона, чтобы функция или класс работали правильно. В тексте использо- ван специальный шрифт для того, чтобы отличать имена концепций. Примеры определений концепций можно найти в стандарте C++. Многие из них относятся к итераторам. Кроме того, полную документацию по концепциям, использованным в STL, можно найти в книге Мэттью Остерна «Обобщенное про- граммирование и STL» (см. главу «Дополнение к библиографии») и на веб-сайте, посвященном SGI STL http://www.sgi.com/tech/stl/. Эти концепции часто приме- няются в определениях концепций BGL. 2.3.1. Наборы требований Требования к концепциям состоят из набора допустимых выражений, ассоции- рованных типов, инвариантов и гарантии сложности. Тип моделирует концепцию, если он удовлетворяет набору ее требований. Концепция может дополнять требо- вания другой концепции. Это называется уточнением (refinement) концепции. • Допустимые выражения — это выражения C++, которые должны успешно компилироваться для типов, заданных в выражении, чтобы они ечнтмнеь модел ирующими концепци ю. • Ассоциированные типы — вспомогательные типы, имеющие некоторое оз но- шение к типу Т, моделирующему концепцию. Требования концепции обычно содержат утверждения об ассоциированных типах. Например, требования к итератору обычно включают ассоциированный тип va ’ uej уре и необходи- мость того, чтобы объекты, возвращаемыеоперапией разыменования итера- тора, были именно данного типа. В С++ принято использовать класс свойств (traits class) для отображения типа Т в ассоциированные типы концепции.
48 Глава 2 • Обобщенное программирование в С++ • Инварианты — такие характеристики типов, которые должны быть посто- янно верны на этапе выполнения программы. Инварианты часто принимают форму пред- и постусловий. Когда предусловие не выполняется, поведение операции не определено и может приводить к ошибкам. Такая ситуация возникает в библиотеке BGL. Некоторые библиотеки предоставляют отла- дочные версии, которые используют утверждения или генерируют исклю- чения при нарушении предусловия. Будущие версии BGL, возможно, бу- дут делать так же. • Гарантии сложности — это максимальные пределы того, как долго может происходить выполнение одного из допустимых выражений или сколько различных ресурсов может потребовать это вычисление. 2.3.2. Пример: Inputiterator В этом разделе мы рассмотрим Inputiterator как пример концепции. Во-первых, концепция Inputiterator является уточнением Triviallterator, которая, в свою оче- редь уточняет Assignable и EqualityComparable. Таким образом, Inputiterator удов- летворяет всем требованиям Triviallterator (который удовлетворяет требованиям Assignable и EqualityComparable). Таким образом лш, моделирующий Inputiterator, будет иметь оператор разы- менования, он может быть скопирован, присвоен, значения этого типа можно срав- нивать с другими объектами-итераторами с помощью операций — и !-> Концепция Inputiterator требует наличия операций преинкремента и пост- инкремента. Эти требования обозначены следующими допустимыми выраже- ниями. Объекты 1 и j являются экземплярами типа Т, моделирующего Input- Iterator. 1 и j // присваивание (из Assignable) Т i(j); // копирование (из Assignable) 1 “ л // проверка на равенство (из EqualityComparable) I I" з •'/ проверке на неравенство (из Equal'.t,/Comparable) *i 11 разыменование (из Trivial Iterator} *+t // преинкрекенг Н‘ 11 постмнкремент Класс std: :Herator_trait$ предоставляет доступ к ассоциированным типам итераторного типа. Тип объекта, па который указывает итератор (назовем его X), может быть определен через value type класса свойств. Другими ассоциирован- ными типами являются reference, pointer, difference_type Hiterator_category. Ассоциированные типы и классы свойств обсуждаются более детально в разде- ле 2.4. В следующем шаблоне функции мы используем класс iterator_traits для получения va I uo type итератора и разыменовываем итератор: template «typename Iterator» void dereference example(Iterator i) { typename iterator trans<Iterator>: value type t: t • *i. I Что касается гарантий сложности, все действия Inputiterator должны выпол- няться за постоянное время. Примерами типов, моделирующих Inputiterator, яв- ляются std :11st<int>::iterator,double* и std: -i stream 1terator<char>.
2.4. Ассоциированные типы и классы свойств 49 Цель определения концепций становится яснее при рассмот{х.*нии реализаций обобщенных алгоритмов. Ниже следует реализация функции std::for_each(). Внутри функции к объектам-итераторам f - г:-л ц last применяются ровно четыре операции: сравнение с помощью operatorМ), инкремент operators (), разымено- вывайте operator*;) и создание копии. Чтобы эта функция успешно откомиили- ровалась и правильно работала, аргументы-итераторы должны поддерживать как минимум эти четыре операции. Концепция Inputiterator включает данные опера- ции (и еще некоторые), так что это разумный выбор для лаконичного описания требований к for_each(). template «typename Inputiterator, typename Functsci» Function for_each(Input Iterator first. Input Iterator last. Function f ) { for ( : first !> last: -»+first) * (*first); return f ; } 2.4. Ассоциированные типы и классы свойств Одним из наиболее важных механизмов, используемых в обобщенном програм- мировании, является класс свойств (traits class), который был предложен Ната- ном Майерсом [361. Механизм класса свойств может показаться немного неесте- ственным при первом знакомстве (из-за синтаксиса), но суть этой идеи проста. Очень зажно научиться использовать классы свойств, так как они постоянно при- меняются в обобщенных библиотеках вроде STL и BGL. 2.4.1. Ассоциированные типы в шаблонах функций Класс свойств — это просто способ определения информации о типе, без которого мы бы о нем вообще ничего не узнали. Например, рассмотрим обобщенную функцию sum(): template «typename Array» X sumlconst Ar--ayS v. nnt n! ( X total * 0: for (int 1'0; i < n; ++l) total v[i]: return total: } С точки зрения этой шаблонной функции о шаблонном типе Аг*'ау известно не- многое. Например, тип элементов этого массива не задал. Однако эта информация необходима для объявления локальной переменной total, котщгая должна быть того же типа, что и элементы Array. Поэтому имя X здесь - только макропеременная, ко- торую нужно заменить на что-то другое, чтобы получить корректную функцнкг Sum{). 2.4.2. Определители типов, вложенные в классах Для того чтобы получить информацию о типе, можно применить операцию раз- решения области видимости :: для доступа к операторам t.ypecef, вложенным внутрь класса. Например, класс массива может выглядеть так:
50 Глава 2 • Обобщенное программирование в C++ с'ass ту_атау { public. typedef rouble value_type: Н тип элемента массива doubles operated ](int 11 ( return m_data[i]; ): private: double” m data: h Тип элемента массива можно получить через array: :value_type. Обобщенная функция sum() реализуется с использованием этого приема следующим образом (заметьте, что X были заменены па typename Array.. value_type‘): template <typeneme Array> typename Array::value_type sumrcorst Arrays v. int n) { typename Array::value_type total - 0: for Ont i • 0; 1 < n; ++1) total v[<]: return total: } В этой функции sum() применение вложенного typedef целесообразно, пока Array является классом, имеющим внутри себя typedef. Однако существуют случаи, при которых иметь вложенный typedef непрактично или просто невозможно. Допус- тим, нам захочется использовать обобщенную функцию sum() с классом сторон- них производителей, который не предоставляет требуемый typedef. Или понадо- бится функция sum() со встроенным типом вроде double*: int n - 100: double* x - new double[n], suw. x. n): В обоих случаях это вполне возможно, так как функциональные требования для использования выполнены: оператор operated ] () работает и с doubl е*, и с во- ображаемым классом сторонних производителей. Ограничением для применения является необходимость передачи информации от классов, которые мы хотим использовать, в функцию sum(). 2.4.3. Определение класса свойств Решением этой задачи является класс свойств — шаблон класса, единственое на- значение которого — обеспечение отображения из одного типа в другие типы, фун- кции, константы. Механизм языка C++, позволяющий шаблону класса создавать такое отображение, называется специализацией шаблона (template specialization). Отображение достигается созданием различных версий класса свойств для опреде- ленных параметров-типов. Мы покажем, как это работает, создав класс array trai для использования функции $um(}. Шаблонный класс arraytraits строится на основе типа Array и позволяет оп- ределить value type (тип элемента) массива. По умолчанию (полностью ни- когда тип слева от операции разрешения области видимости:: каким-либо образом зависит от аргумента шаблона, используйте ключевое слово typename перед типом.
2,4. Ассоциированные типы и классы свойств 51 блонный) предполагается, что массив — тип со вложенным typedef. такой как ту_аггау: template «typename Array» struct array_traits { typedef typename Arrayvalue type value_type; Теперь возможно создать специализацию шаблона crray_trai ts для обработки ситуации, когда аргумент шаблона является встроенным типом, например doutl е*: template «> struct array_tr3lts«double*> { typedef double value type: }• Классы сторонних производителей, скажем, ir/ins_int_array, могут быть при- способлены к нашим нуждам аналогично: template <> struct array_traits«johns_int_array> [ typedef mt value type; Функция sumO, написанная с использованием класса array traits, показана ниже. Чтобы осуществить доступ к типу для переменной total, мы извлекаем val ue_type из array_traits. template «typename Array» typename array traits«Array->: .value type sumCconst Arrays v. int n) ( typename array_traits«Array>.:value_type total - 0; for (int i “0: i < n.- ♦-!) total +" v[i]; return total; } 2.4.4. Частичная специализация Писать отдельный класс свойств для каждого указательного типа непрактично и нежелательно. Ниже показано, как использовать частичную специализацию для организации array traits для всех типов указателей. Компилятор C++ сопоста- вит аргумент шаблона, заданный при инстанцировании trans_c7ass, и все опре- деленные специализации для Г* и выберет наиболее подходящую специализацию. Частичная специализация для Т* подойдет для любого типа-указателя. Для doubl е* компилятором будет выбрана полная специализация (приведенная в разделе 2.4.3.) как более подходящая для конкретного типа-указателя. template «typename Т> struct array_traits«T*> { typedef I value type; Частичная специализация может также быть использована для создания вер- сии array_traits для шаблона класса стороннего производителя. template «typename 1» struct array_traits<jonns_array«T> » j typedef T value_type: }
52 Глава 2 • Обобщенное программирование в C++ Наиболее известным применяемым классом из класса свойств является itera - r.or tre i Ls для STL. Библиотека BGL также использует классы свойств, такие как graph_traits и propert.y_traits. Обычно класс свойств применяется с конкретной концепцией или семейством концепций. Класс iterator_traits работает с семей- ством итераторных концепций, класс graph traits работаете семейством графо- вых концепций BGL. 2.4.5. Диспетчеризация тегов Диспетчеризация тегов (tag dispatching) — это прием, часто работающий «рука об руку» с классами свойств. В нем используется перегрузка функций для дис- петчеризации, основанной на свойствах типа. Хорошим примером является реа- лизация функции std: :advance() в STL. которая по умолчанию осуществляет ин- кремент итератора п раз. В зависимости от вида итератора могут применяться различные оптимизации в реализации данной функции. Если итератор обеспечи- вает произвольный доступ, функция advancei) может быть легко и очень эффек- тивно реализована с помощью i *= л. Если итератор двунаправленный, то возмо- жен вариант с отрицательным п, поэтому необходимо осуществить декремент итератора п раз. Отношение между внешним полиморфизмом и классами свойств состоит в том, что свойство, которое будет использовано для диспетчеризации (в нашем случае i terator-Category), доступно через traits_class. В следующем примере (листинг 2.2) функция advarcef' использует класс 1 tera- tor trai ts для ощюделения > terator ca tegory. Затем она выполняет вызов перегру- женной функции advance_dispatch(). Подходящая функция advance_dispatch() вы- бирается компилятором на основе того, к какому типу (теговому классу в листинге ниже) будет отнесен iterator_catsgory. Тег - это простой класс, основным назначе- нием которого является передача определенного свойства для использования в те- говой диспетчеризации. Принято давать теговым классам имена, оканчивающиеся на _tag. Мы не определяем перегруженную функцию для forward_iterator_tag, так как этот случай обрабатывается перегруженной функцией из input iterator tag. Листинг 2,2. Пример диспетчеризации тегов street input_iterator_tag П- struct output-1terator_tag {}: struct forward-iterator_tag : public input-iterator-tag {]: struct bidirectionaijteratof-tag , public forwardjterator_tag (): struct random_access_iterator-tag public bnlirectional_iterator_tag {): template «typename InputTterator. typenare Distance* void advance-dispatchdnputlterator& i. Distance n. input_iteratcr_tag) •s while (n 1 i; } template «typename Bidirectic-nallterator. typename Distance* void advance-dispatchiBidirectlonatIterators i. Distance n. bi di reeliona1_iterator tag) if cn >- 0) while (n- I -м-t: else while ;
2.5. Проверка концепции 53 template «typename RandonAccesslterator. typename Distance* void advance_dispatch(Ra«jcmAccessIterator& 1, Distance n. random_eccessj terator_tag) ( i +- n; | template «typename Input Iterator, typename Distance* void advanceUnput!terator& i. Distance n) { typedef typename iterat(r_tra1ts<lnputlterator>:: iterator_category Cat: advance_dlspawn(1. n. CatO): } Класс graph_tra1ts из BGL включает три категории: di rected_c=teqory, edge_pa- ral lei_category и traversal-Category. Теги для этих категорий могут быть исполь- зованы в диспетчеризации аналогично 1terator_categcry. 2.5. Проверка концепции Важным аспектом использования обобщенной библиотеки является применение под- ходящих классов в качестве аргументов шаблона к алгоритмам. То есть необходимо, чтобы применяемые классы моделировали концепции, указанные в требованиях ал- горитма. Если используется неподходящий класс, компилятор выдаст сообщения обошибке, но расшифровать эти сообщения пользователю библиотеки шаблонов мо- жет быть достаточно сложно [2,41]. Компилятор может выдать буквально страни- цы трудночитаемых сообщений, даже если допущена совсем небольшая ошибка. Ниже, в листинге 2.3, допущена типичная ошибка, где функция std::sortO применяется к массиву объектов. В этом случае operators) не реализован для типа foo — это означает, что foo нарушает требования LessThanComparable (которые даны в документации к std: :scrtO). Листинг 2.3. Ошибка: невыполнение требований концепции ^include «algorithm* class foo { }; int naln(int. char*[ ]) { foo array_of_foo[10]: std;:sortTarray_of_foo. array_of_foo 101; return 0; 1 Сообщение об ошибке, возникающее в результате, нелегко понять, и все, за ис- ключением наиболее опытных программистов C++, попадут в очень непростое положение, пытаясь найти источник возникновения ошибки изданного сообще- ния. В сообщении не упоминается концепция LessThanComparable, требования ко- торой были нарушены, но показаны многие внутренние функции, вызываемые из std:: sort (). Помимо этого, нет указания на строку, в которой произошла ошиб- ка. В нашем случае это вызов std:: sertf). Вот как выглядит сообщение об ошибке: stl_heap.h: in function vo*d _____adjust_heap<foo*.int.foc>(foo*.1nt.int.foo). stl_heap.tv.214-. instantiated from ________make_heap<foo*.foo.ptrditf_t*<too*. foo*.foo*.ptrd:ff~t*) st 1 _heap.h:225. instantiated from make_heap<foo*>(ftx>*,foo*) stl2algo.h;1562: instantiated from __рата1_зог1<Гоо*.Гоо>(жоо*Лоо*. foo*.foo*)
54 Глава 2 • Обобщенное программирование в C++ St1_a1go.h.l574- instantiated from partialjsort<foo*>(foo*.foo*.foo*) stCalgo.h:1279: instantiated from _introsort_loop<foo*.foo,int>(foo*. foo*.foo*.int) $t1_algo.h:1320: instantiated from here stl heap.h:H5: no match for foo. & < foo & 2.5.1. Классы для проверки концепций Для преодоления этой проблемы мы придумали идиому C++ для принудитель- ной проверки на соответствие концепции, которую мы назвали проверкой концеп- ции 139]. Соответствующий код находится в библиотеке проверки концепций BCCL (Boost Concept Checking Library) [6], Для каждой концепции BCCL пре- доставляет класс проверки концепции, такой как, например, следующий класс для LessThanComparable. Требуемые допустимые выражения для концепции проверя- ются в функции-методе класса ccnstraintst). template <typename Т> struct LessThanCcmparableCcncept { void constraintsO { (boolHa < b): та. b: По заданным пользователем аргументам шаблона в начале обобщенного алго- ритма с использованием funct 1 on requl res() из BCCL инстанцируется класс про- верки концепции. #1 пс1ude <boost/concept_check.hpp> template <typename Iterator» void wfe_sort( Iterator first. Iterator last) ( typedef typename std::iterator_traits«Iterator>:.«luejtype T: function_requires< LessThanConparableConcept<T> >(); // другие требования ... SU1::sort!first. last): Теперь, если sa fe_sort () будет использован неправильно, сообщение об ошиб- ке (приведенное далее) будет намного понятнее : оно короче, обозначено место ошибки, приведено имя нарушенной концепции, внутренние функции алгоритма не фигурируют в сообщении. boost/concept_check.hpp: In method void boost::LessThanComparableCcncept<foo>::constraints:): boost/concept_check hpp:31: instantiated fram boost: :function_requires<boost: -.LessThanComparabl ;Concept<foo> >() sort_eg.cpp:Il: instantiated from safe_sort<foo*>(foo*. foo*) sort_eg.cpp:21: instantiated from here boost/concept_check.hpp:260: no match for foo 8 < foo & В библиотеке BGL применяются проверки концепций для обеспечения пользо- вателей более правильными сообщениями об ошибках. Для каждой графовой концепии создан соответствующий класс проверки концепции, определенный в заголовочном файле boost/graph/graph_concepts.hpp. В начале каждого алгорит- ма з BGL проверяются концепции каждого аргумента. Сообщения об ошибках из graph_concepts.hpp, скорее всего, свидетельствуют о том, что какой-нибудь из
2.5. Проверка концепции 55 типов одного из аргументов некоторого алгоритма нарушает требования этого ал- горитма к концепции. 2.5.2. Прототипы концепций Дополнительной к проверке концепций задачей является верификация того, что за документированные требования обобщенного алгоритма действительно реализу- ются алгоритмом. Эту задачу мы называем покрытием концепции (concept covering). Обычно авторы библиотек проверяют покрытие вручную, что приводит к ошиб- кам. Мы же разработали идиому для C++, которая использует проверку топов ком- пилятора C++ [39| для автоматизации этой задачи. Код для покрытия концепций также находится в BCCL. Библиотека BCCL имеет прототип-класс для каждой концепции, применяемой в стандартной библиотеке. Прототип-класс представля- ет собой минимальную реализацию концепции. Для проверки того, покрывает ли концепция алгоритм, создается объект прототип-класса и передается алгоритму. В следующем примере программа пытается проверить, покрываются ли требова- ния std.:sort() итератором, моделирующим RandomAccessIterator, который имеет тип значения LessThanComparable. ^include «algorithm* ^include «boost/concept._archetype.ripp> int marnO I using namespace boost: typedef less_than_comparable_archetype<> T. rardom_access_iterator_archetype<T> ri. std::sorttri. ri); } На самом деле эта программа не будет успешно скомпилирована, так как те концепции не покрывают требования std::sort() к аргументам своего шаблона. Результирующее сообщение об ошибке показывает, что алгоритм также требует, чтобы тип значения был CopyConstructible. null_archetype(const nu’.l_arc<ietype<*.nt> 8) is private Алгоритму требуется не только конструктор копирования, но и операция при- сваивания. Эти требования объединяются в концепции Assignable, Следующий код показывает реализацию прототип-класса для Assignable. Прототип-класс снабжен параметром шаблона Base для того, чтобы можно было комбинировать прототи- пы. Для проверки std:: sort О нам нужно соединить прототип-классы для концеп- ций Assignable и LessThanComparable. template «typename Base - nuri_archetype<> > class assignab1e_archetype : public Base { typedef a$signable_archetype self : public: ass1gnab1e_archetype(const self &) I } self 8 operator-tconst self 8) { return *rhis. ) b Библиотека ВСЬсодержит прототип-классы для каждой графовой концепции в заголовочном файле boo$t/graph/graph_archetypes.hpp. Тестовые программы ве- рификации спецификаций для всех алгоритмов BGL с использованием графо- вых прототипов находятся в каталоге libs/graph/test/,
56 Глава 2 • Обобщенное программирование в C++ 2.6. Пространство имен Как и в других Boost-библиотеках, каждый компонент Boost Graph Library опре- делен в пространстве имен boost для того, чтобы избежать конфликта имен с дру- гими библиотеками и приложениями. В этом разделе мы покажем, как получить доступ к классам и функциям BGL в пространстве имен boost. 2.6.1. Классы Получить доступ к классам BGLmojkho несколькими способами. В следующем коде показаны три способа доступа к классу adjacency_l i st из пространства имен boost. ( ч Применить префикс пространства имен tawst:: adjacency_ 11 st*> g; } { И Внести класс в текущую область видимости операторов using using boost::adjacencyJtst. ad latency_1 ist<> g. ) { H Внести все компоненты Boost в текущую область видимости using namespace boos:; adjacency^ 1 g: Для краткости и ясности представления в примерах кода в этой книге опу- шен префикс boost:как если бы оператор using namespace boast: был задан в объемлющей области видимости. Для кода, в котором применяются Boost-биб- лиотеки, мы рекомендуем использовать префикс boost:: в заголовочных файлах и либо указывать его же в исходных файлах, либо применять там операторы using с явным указанием классов. Старайтесь не использовать просто using namespace boost:, гак как это может привести к конфликтам имен. Однако внутри функ- ций (то есть в ограниченной области видимости) опасность появления конф- ликтов мала и using namespace можно использовать. 2.6.2. Поиск Кенига Операции на графах Интерфейс BGL состоит из перегруженных функций, определенных для каждого графового типа. Например, функция num_vert.i се; () имеет один аргумент, объект- граф, и возвращает количество вершин. Эта функция перегружается для каждого графового класса BGL Интересно (и, как мы увидим, к счастью), перегруженные функции могут вызываться без уточнения имени функции пространством имен. Используя процесс, называемый поиском Кенига, компилятор C++ рассматрива- ет тип аргумента для перегруженных функций и ищет перегруженные функции в пространстве имен аргумента'. Поиск Кенига назван так в честь его первооткрывателя Эндрю Кенига (Andrew Koenig). Иногда его называют поиском, зависимым от аргумента.
2.6. Пространство имен 57 В следующем примере приведена демонстрация поиска Кенига. Рассмотрим случай, когда используются графовые классы из двух различных графовых биб- лиотек. Каждая библиотека имеет свое собственное пространство имен, внутри которого определен графовый класс и функция num verticesi). namespace libjack { class graph { /* ... */ }: int num_vertices(const Q-apnS) {/*...'/} 1 namespace Ubjill { class graph { /* ... */ ): int num vertices(const g-aphi) {/*..*/} } Предположим, что пользователь хочет применить некоторый обобщенный гра- фовый алгоритм, скажем, boost::pall О, к обоим графовым типам. int twin!) ( libjack: igraph gl: boost:: pai Hgl): libjlll::graph g2. boost: :parl(g2): } Внутри boost:: pa 11 () присутствует вызов num_verti ces('. В этой ситуации было бы желательно, чтобы при использовании графа из lib Jack вызывалась функция 11 bjack;: numjerti ces (). а если граф из 11 bj 111 — то 11 b J i 11:; num_vertices (). Поиск Кенига, как свойство языка C++, позволяет это сделать. Если вызов функ- ции не уточнен указанием пространства имен, компилятор C++ будет осуществ- лять поиск в пространстве имен аргументов с целью определения правильной функции для вызова. namespace boost { tenplate <typename Graph» vo’d paiKGraohS g> { typename graph_traits<Graph>; .-verticesjizejype N - num_vertTces(g); // Разрешится в"поиске Кенига И . . } // namespace boost Графовые алгоритмы Графовые алгоритмы из BGL отличаются от операций над графами тем, что они являются шаблонами функций, а не перегруженными функциями. Тем самым поиск Кенига неприменим к графовым алгоритмам BGL и получить к ним доступ можно только посредством префикса пространства имен boost:: или с использо- ванием других методов, описанных в разделе 2.6.1. Например, для вызова алго- ритма breadth_first_search() префикс boost:: необходим: boost::breedth_first_search(g. start, visitor(vis)):
58 Глава 2 • Обобщенное программирование в C++ 2.7. Именованные параметры функций Многие алгоритмы BGL имеют длинный список параметров, чтобы обеспечить максимальную гибкость. Однако часто такая гибкость не требуется и было бы достаточным использовать значения по умолчанию для большинства парамет- ров. Например, рассмотрим следующий шаблон функции, имеющий три аргу- мента. template «typename X. typenatne Y. typename Z> void f (X x. Y y. 2 z): Пользователь должен иметь возможность передачи любого числа аргументов (и даже ни одного), а неуказанные параметры должны получить значения по умол- чанию. Также необходима возможность передачи аргумента яля параметра у. но не для х или z. Некоторые языки программирования имеют прямую под- держку этих возможностей, называемых именованными параметрами (named parameters или keyword parameters). При применении именованных параметров в качестве признака для привязки параметров аргументам используется непо- рядок параметров (как это принято), а имя, свое у каждого аргумента. // Если бы О* поддерживал иненовдиные лараиегры. иы ио?пи бы написать: mt а. int Ь; f (z-b. х-а): // связать b с параметрон г. а с параметром х П у подучает значение аргумента по унолчанис Конечно. C++ не поддерживает именованные параметры, но это свойство может быть реализовано с помощью небольшой хитрости. Библиотека BGL включает класс, называемый bgl_named_params, который имитирует именованные параметры, позволяя строить списки парамет}юв*. В следующем коде приве- ден пример вызова функции bel lman_ford_shortest_path() с использованием тех- ники именованных параметров. Каждый из аргументов передается функции, чье имя показывает, с каким параметром связать аргумент. Заметьте, что име- нованные параметры разделены не запятой, а точкой. На класс bgl_naffled_paran,s нс ссылаются явно — он создается неявно при вызове weighljnap(). а затем спи- сок аргументов расширяется путем вызовов к di ttance_map() и predecessor_map(). Ьоо1 г - boost: :bellnian_ford_shortest_paths(g. inttb). boost;,weight_map(welgbt). distance_nBp(&distance[O]). predecessor jnap(&parent[O])): Порядок, в котором следуют аргументы, неважен, пока каждому аргументу соответствует правильная функция. Приведенный ниже вызов функции bel lman_ ford_shortest_paths() эквивалентен приведенному выше. bool г - boost::bellman_ford_shortest_parhs(c. 1пШ). boost::predecessor jnap(8parent[Q]) distancejnap(&distance[O]). weightjnap(weight)); 1 Это обобщение идиомы, onисаиной в |41 j.
Изучаем BGL Как уже было сказано, концепции играют центральную роль в обобщенном про- граммировании. Концепции — эго определения интерфейсов, позволяющие ал- горитму использовать много различных компонентов. Библиотека алгоритмов на графах (BGL, Boost Graph Library) определяет большой набор концепций, затрагивающий различные аспекты работы с. графом, такие как обход графа или изменение его структуры. В данной главе мы познакомим читателя с эти- ми концепциями, а также дадим мотивацию к выбору той или иной концепции BGL. Из описания процесса обобщенного программирования (см. главу 2 в начале) мы знаем, что концепции выявляются при анализе алгоритмов, используемых для решения проблем в определенных областях. В данной главе мы изучим проблему отслеживания зависимостей файлов в системе сборки. Для каждой подзадачи бу- дут рассмотрены обобщенные, универсальные решения. В результате мы полу- чим обобщенный алгоритм на графе и его приложение к задаче нахождения зави- симостей между файлами. «По пути» мы также рассмотрим более «земные», ио необходимые темы, такие как создание графового объекта и наполнение его вершинами и ребрами. 3.1. Зависимости между файлами Обычным применением для графов является представление зависимостей. Про- граммисты сталкиваются с зависимостями между файлами каждый день при ком- пиляции программ. Информация об этих зависимостях используется такими про- граммами, как make, или средами разработки вроде Visual C++ для определения того, какие файлы должны быть перекомпилированы при генерации новой вер- сии программы (или, в общем случае, для выполнения некоторой цели) после изменений в файлах с исходным кодом. На рис. 3.1 изображен граф, у которого имеется отдельная вершина для каж- дого файла с исходным кодом, объектного файла и библиотеки, использованных
60 Глава 3 • Изучаем BGL в программе killerapp. Ребро этого графа показывает, что целевой объект некото- рым образом зависит от другого (например, заголовочный файл включается в Ис- ходный файл, а объектный файл компилируется из исходного). Рис. 3.1. Граф зависимостей между файлами Ответы на многие вощюсы, возникающие при разработке системы сборки, та- кой как make, могут быть сформулированы в терминах графа зависимостей. Нас может интересовать следующее: 1. Если необходимо собрать все цели, а каком порядке это должно быть сде- лано? 2. Присутствуют ли в зависимостях циклы? Цикл в зависимостях являет- ся ошибкой, и должно быть выдано соответствующее сообщение об ошибке. 3. Сколько шагов нужно сделать для сборки всех целей? Сколько шагов необ- ходимо осуществить для сборки всех целей, если независимые цели соби- рать параллельно (например, на сети рабочих станций или на многопроцес- сорном компьютере)? В следующих разделах эти вопросы ставятся в терминах графов и разрабаты- ваются графовые алгоритмы для их решения. Граф, представленный на рис. 3.1. используется во всех примерах. 3.2. Подготовка графа Перед тем как мы сможем решать поставленные выше вопросы, нам необходимо иметь представление графа зависимостей между файлами в памяти. То есть нам нужно, используя BGL, построить графовый объект.
3.2. Подготовка графа 61 3.2.1. Решаем, какой графовый класс использовать В BGL имеется несколько графовых классов, из которых можно выбирать. Хотя в силу обобщенного характера алгоритмов BGL можно использовать любой оп- ределенный пользователем графовый класс, мы ограничимся только классами из BGL. Основными классами в BGL являются adjacencyJ isl и adjacency_matri г. Первый целесообразно использовать во многих случаях, особенно для представле- ния разреженных графов. Граф файловых зависимостей содержит очень малое число ребер для одной вершины, то есть он является разреженным. Класс adj асе’ । cyjnatr х подходит для представления плотных графов, но очень невыгоден для разреженных. В этой главе используется исключительно ad jacency_h st. хотя многое из пред- ставленного можно без изменений использовать с классом adjacency matm, так какой имеет почти идентичный интерфейсе adjacency_1 ist. Здесь мы используем тот же вариант adjacency ! 1st, как и в разделе 1.4.1. typedef adjacency lists. H Хранить исходящие ребра каждой вершины в std. list vecS. // Хранить набор вершин в std::vector directedS Н Граф файловых зависимостей ориентированный > f11e_dep_graph: 3.2.2. Строим граф с помощью итераторов ребер В разделе 1.2.4 мы показали, как можно использовать функции add_vertex() и add_edgeC) для создания графа. Эти функции добавляют вершины и ребра по од- ному за вызов, ново многих случаях необходимо добавить несколько ребер за один вызов. Для этого класс adjacency ! ist имеет конструктор, который принимает на входе два итератора, определяющих блок ребер. Итератором ребер может быть любой Inputiterator, который разыменовывается в std:;pair целых чисел (i,y), представляющих ребро графа. Целые числа i и j представляют вершины, где 0£1<|У| и 0Sj<|V|. Параметры пит показывают соответственно количество вершин и ребер в графе. Эти параметры необязательны, по их применение увели- чивает скорость создания графа. Параметр свойств графа р присоединяется к гра- фовому объекту. Прототип конструктора, использующего итераторы ребер, запи- сывается следующим образом: template <typename EdgeIterator» adjacencyJistCEdgelterator first. Edgeiterator last. vertices_size_type n - Q. edges_size_type m - 0. const GraphPropertfes& p - GraptProperties01 В следующем коде показано применение «реберного» конструктора для созда- ния графа. Итератор std:: 1 st reafflj terator служит основой итератора ввода, чита- ющего данные о ребрах из файла. В файле задано число вершин графа, после чего приведены пары чисел, обозначающие ребра. Второй итератор ввода (создавае- мый со значениями по умолчанию) является «заглушкой» для конца ввода. Ите- ратор std: :istream_1terator передается напрямую конструктору графа. std:: 1 f st ream f 11 ej n ("maXef 11 e- dependent! es. da t"): typedef graph_traits<file_dep_graph>::vertice$_size_type size_type: s i ze_tyре n_ve r t i ces;
62 Глава 3 • Изучаем BGL file in » n_vertices; H прочесть чисто вершин std: :istreainjterator<std: :pair<sue_type. size_t.ype> > input_beg4<n(fiTejn). input_end; *1le_dep_graph g(input_begin, input_end. n_vertices': Поскольку тип значения std:. istreamyteratcr — это std: :pai г, итератор ввода нужно определить для std:: pal г. namespace std { template «typename T> std:-1 streams operator»(std: astreamS in. std::pair<T.T>& p) I in » p.first » p.second: return In. } } 3.3. Порядок компиляции Первый вопрос касался определения порядка сборки всех целевых объектов. Ос- новной заботой здесь является обеспечение того, чтобы до сборки данной цели были построены все цели, от которых она зависит. Это та же самая задача, кото- рую мы рассматривали в разделе 1.4.1, когда планировали выполнение заданий. 3.3.1. Топологическая сортировка через поиск в глубину Как уже упоминалось в разделе 1,4.2, топологическое упорядочение может быть получено с использованием алгоритма поиска в глубину (depth-first search). На- помним, что при поиске в глубину посещаются все вершины в графе, начиная с любой вершины, и затем выбирается новое ребро. В следующей вершине выби- рается другое ребро для нового шага. Этот процесс продолжается, пока не захо- дит в тупик (вершина без исходящих ребер до еще не посещенных вершин). За- тем алгоритм возвращается на последнюю посещенную вершину, у которой есть другая, еще не пройденная, смежная вершина. После того как будут посещены все вершины, которых можно достичь из выбранной в начале вершины, выбирается еще одна из непройденных вершин, и поиск продолжается. Ребра, которые алго- ритм обошел в каждом из этих поисков, образуют дерево поиска в глубину, а все такие поиски дают лес поиска в глубину. Лес. поиска в глубину не уникален для данного графа. Обычно можно найти несколько допустимых лесов для данного графа, так как порядок посещения вершин не фиксирован. Каждое уникальное упорядочение имеет свое дерево поиска в глубину. Существуют две полезные метрики при поиске в глубину — порядок посеще- ния и порядок окончания обработки вершины. Представим целочисленный счет- чик, значение которого в начале равно нулю. Каждый раз при первом посещении вершины значение счетчика записывается как момент посещения для данной вер- шины и увеличивается на единицу. Аналогично, когда произведен обход всех вер- шин, достижимых из данной, значение счетчика записывается как момент окон- чания данной вершины. Значение счетчика посещения родительской вершины всегда меньше, чем у дочерней. И наоборот, значение счетчика окончания обра- ботки меньше у дочерней вершины, чем у родительской. Поиск в глубину на гра- фе файловых зависимостей показан на рис. 3.2. Ребра деревьев поиска в глубину
3.3. Порядок компиляции 63 отмечены черными стрелками, а у вершин подписаны значения их счетчиков по- сещения и окончания обработки (через косую черту). Связь между топологическим упорядочением и поиском в глубину можно объяс- нить, рассмотрев три различных случая в некоторой точке алгоритма поиска в глу- бину, где проверяется ребро (и, г). В каждом случаезначение счетчика в момент окон- чания обработки v всегда меньше того же значения для и. Таким образом, моменты окончания обработки задают топологическое упорядочение (в обратном порядке). 1. Вершина v еще не посещена. Это означает, что v будет потомком и и значе- ние счетчика окончания обработки вершины будет меньше, чем у и, так как при поиске в глубину потомки и обрабатываются прежде, че.м и. 2. Вершина г была посещена ранее в другом дереве поиска в глубину. Значит, значение счетчика окончания обработки о меньше, чем и. 3. Вершина s была посещена ранее в этом же дереве поиска в глубину. Если это случилось, граф содержит цикл и его топологическое упорядочение не- возможно. Цикл — это путь из ребер, такой, что первая п последняя верши- на пути — одна и та же. Главной частью поиска в глубину является рекурсивный алгоритм, который вызывает самого себя для каждой смежной вершины. Мы создадим функцию topo_sort_dfs(), которая реализует поиск в глубину, модифицированный для вы- числения топологического упорядочения. Первая версия этой функции будет не- обобщенной функцией, решающей задачу «в лоб». В следующих разделах мы вне- сем изменения, которые позволят создать обобщенный алгоритм. Параметры topo_sort_dfs О включают в себя граф, стартовую вершину, указа- тель на массив для записи топологического порядка, а также массив для записи посещенных вершин. Указатель 1оро_огйегустанавливается на конец массива и за- тем уменьшается для получения прямого топологического порядка из обратного.
64 Глава 3 • Изучаем BGL Заметьте, что topo order передается по ссылке, поэтому его декремент модифици- рует оригинал объекта при каждом рекурсивном вызове (если бы topo_order пере- давался по значению, декремент влиял бы только па копию исходного объекта). В листинге 3.1 приведена реализация функции T.opo_sort_dFs(). Листинг 3,1. Функция topo_sort_dfs() void topo_sort_dfs(const file_depjjr<iph& g. vertexjt u. vertex_t*& topo order, mt* mark) { mark[u] - Г. // 1 означает ’посещенная". О “еще не посещенная" ( Для каждой снежной вераинм сделать рекурсивный вызов ) *--topo order « u.- I Типы vertex t и edge_t являются соответственно дескрипторами вершин и ре- бер для file_dep_graph. typedef graph_traits<filei_dep_grap)i>: ;vertex_descriptor vertex_t; typedef graph_traits<f le_dep_graph.-- wigpjiescriptdr edge_t:~ 3.3.2. Маркировка вершин с использованием внешних свойств Каждая вершина во время поиска должна быть посещена только один раз. Для записи факта посещения вершины мы можем пометить ее в массиве, содержащем пометки для всех вершин. В общем случае будет использоваться термин внешнее хранилище свойств (external property storage) в качестве названия при сохранении свойств вершин или ребер в структуре данных вроде массива или хэш-таблицы, отдельной от графового объекта. Пометки — только одно из свойств, которые можно хранить в структурах данных, внешних по отношению к графу. Значения свойств ищутся по ключу, который может быть легко получен из дескриптора вершины или ребра. В нашем примере .мы используем версию adjacency ! 1st, где дескрипторы вершин — петые числа от нуля до num_vert i ces (g) • 1. Поэтому дескрипторы вер- шин сами могут быть использованы в качестве индексов к массиву пометок. 3.3.3. Доступ к смежным вершинам В функции topc_sort_dfs() нам необходимо полу чшгь доступ к вершинам, смежным с вершиной и. Концепция AdjacencyGraph определяет иите|>фейс для доступа к смеж- ным вершинам. Функция ad,;acent_verti ces() получает вершину и граф в качестве аргументов и возвращает пару итераторов, чей тип значения - дескриптор вершин. Первый итератор указывает на первую смежную вершину, а второй -• за конец последовательности смежных вершин. Смежные вершины выдаются итераторами в произвольном порядке. Оба этих итератора имеют тип adjacencyj terator из клас- са graph_tra1ts. В справочном разделе по adjacency_l 1st (раздел 14.1.1) написано, что тип adjacencyJ 1st моделирует концепцию AdjacencyGraph, а значит, мы можем корректно использовать функцию adjacent verticesO с нашим графом файловых зависимостей. Код для обхода смежных вершин в topo sort_dfs() показан ниже: < Дпя каждой снежной вершины сделан. рекурсивный аызгв .* » grapn_traits<file_dep_graph>-:adjacencyjterator r>. vi_end;
3.3. Порядок компиляции 65 for . viend) - adjacent_vertues(u, g); vl !•» vi_enc: ++vi) 1f (mark[*vi] — 0} topo_sort_dfs(g. *vi. topojirde-r. mark) . 3.3.4. Обход всех вершин Один из способов гарантировать, что упорядочение будет получено для каждой вершины графа (а не только для вершины, достигаемой из некоторой стартовой вершины), — поместить вызов topo_sort_dfs() в цикл по всем вершинам графа. Интерфейс для обхода всех вершин графа определен в концепции VertexlistGraph. Функция vertt ces О получает графовый объект и возвращает пару итераторов вер- шин. Цикл по всем вершинам и создание массива пометок приведены в функции topo_sort() (листинг 3.2). Листинг 3.2. Функция topo„sort() void topo sort (const file_dep graph& g. vert.ex._t* topo_order) { ’ std::vector<1nt> mark(num_vertices(g). 0): graph_tra1ts<file_dep_graph>: rvertexjterator vi. vi_end: for (tiefvi. vi_end) - verfices(g): vi !- vi_end: +»vi) if (mark[*vi] — 0) topo sort dfs(g. *vi. topo order. taark[0J). } Для удобства нам нужно преобразовать вершины-числа в ассоциированные с ними имена целей. Список имен целей (в порядке, соответствующем номеру вер- шины) хранится в файле, так что возможно прочитать этот файл и сохранить имена в массиве, который затем будет использован для печати имен вершин. std::vector<std:rstr1ng> name(n»_vertices(g}).- std: :ifstream naffle_inCmakeftle-ferget-names.dat"): graph_traits<fi‘e_dep_graph>::vertex_iterator vi. vi_end: for (tietvi. vi_end) - vertices(g): vi !» vi_end: **v1) name_in » narre[*vi]: Теперь мы создадим массив order для хранения результатов и применим функ- цию топологической сортировки. std.:vector<verte*_t> order(num_vertices(g)): topo_sort(g. &order[0] + nun_vertices(g)); for Tint i - 0; 1 < num_vertices(g>: >-+rt stakeout <*" name[order[ij] « std.;endl: В результате получится следующий список: zag.cpp zig.ерр foo.cpp Par.cpp zow.h boz.h zig.o yow.h dax.h zag о foo.o bar.o .1 3«. 375
66 Глава 3 • Изучаем BGI. Hbfoobar.a 1ibzigzag. a killerapp 3.4. Циклические зависимости Одним из важных предположений в предыдущем разделе было то, что граф файло- вых зависимостей не имеет циклов. Как установлено в разделе 3.3.1, граф с цик- лами не имеет топологического упорядочения. Формально верный Makefile не бу- дет иметь циклов, но наша система сборки должна обнаруживать такие ошибки и сообщать о них. Поиск в глубину может быть использован и для задачи выявления циклов. Если он будет применен к графу с циклом, одна из ветвей дерева поиска замкнется на себе, то есть, найдется ребро из вершины к одному из ее предков по дереву. Назо- вем такое ребро черным. Появление черного ребра можно обнаружить, если мы изменим схему отметки вершин. Вместо того чтобы отмечать каждую вершину как посещенную или не посещенную, мы будем использовать трехцветную схему: белый цвет будет обозначать не посещенную вершину, серый — посещенную, но не обработанную (в процессе поиска потомков), и черный — вершину, посещенную вместе со всеми потомками. Трехцветная схема полезна для некоторых графовых алгоритмов, поэтому заголовочный файл boost/graph/properties.hpp определяет следующий перечислимый тип: enum defau1t_color_type ( white_color. gray „color. black_color }; Цикл в графе идентифицируется по серой смежной вершине — это означает, что ребро замыкается на предке. Код в листинге 3.3 представляет версию поиска в глубину, модифицированную для обнаружения циклов. Листинг 3.3. Поиск в глубину с обнаружением циклов bool has_cycle_dfs(corst file_dep_graph& g. vertex_l u. default_color type* color) ( colorful - gray„color; grapn_traits<file_dep_graph>:adjacency „iterator vi. vi_end: for (tie(vi. vi_end) - adgacent_vertices(u. g): vi !- vT_end; -r+vi) if (co'or[*vi] wnite_color) if (has_cycle_dfs(g, *vi. color)? return true: и если обнаружен цикл, то немедленный возврат else if (color[*vi] — gray„coior) // *vi является предком return true; colorful = black_co!or; return false: i Как и в топологической сортировке, в функции has cyclef) (листинг 3.4) ре- курсивный вызов помещен внутрь цикла по всем вершинам, так что возможно охватить при проверке все деревья поиска в графе. Листинг 3.4. Функция has_cyde() bool has_cycle(const file_dep_graph& g) std: .vecror<defau1t„color_t.ype> color(num__vertkes(g). whitejxlor);
3.5. «На пути» к обобщенному поиску в глубину: посетители 67 greph_traits<file_dep_graph>::vertex_1terator vi. vi__end; for (tieCvi. vi_end) - verfices(g). vi !- vi_end; ++vi) if (color[*vi] — white_color) if (has_cycle_dfs(g, *vi. 8color[0])) return false: } 3.5. «На пути» к обобщенному поиску в глубину: посетители На данный момент мы имеем две законченные функции: topo sort () и has_cycl е О. каждая из которых реализована в виде поиска в глубину, хотя и немного по-разно- му. Однако их существенная схожесть дает прекрасную возможность для повтор- ного использования кода. Было бы намного лучше, если бы мы имели один алго- ритм для поиска в глубину, который являлся обобщением topo_sort() и has cycl е() и использовал параметры для специализации поиска в глубину в каждой из задач. Дизайн библиотеки STL подсказывает нам, как можно создать подходящим образом параметризованный алгоритм поиска в глубину. Многие из алгоритмов STL могут быть специализированы параметром в виде определенного пользова- телем объекта-функции. Нам хотелось бы параметризовать поиск таким же спо- собом, реализовав в topo_sprt() и has_cycle() передачу объекта-функции. К сожалению, в нашем случае ситуация несколько более сложная, чем в типич- ном алгоритме STL В частности, есть несколько разл ич пых мест, где должны проис- ходить специализированные действия. Например, функция topo_sort() записы- вает упорядочение в самом конце рекурсивной функции topo_sort_dfs (), тогда как has_cycle() требует включения действия внутри цикла по смежным вершинам. Решением этой проблемы является применение объекта-функции с более чем одним членом для обратного вызова. Вместо единственной функции operatorO мы используем класс с несколькими функциями-методами класса, вызываемыми из различных мест (событийных точек). Такой объект-функция называется посе- тителем алгоритма (algorithm visitor). Посетитель для алгоритма поиска в глу- бину будет иметь пять функций: discover_vertex(), tree_edge(), back_edge(), forward_or_cross_edge() и f ini sh_vertex(). Также вместо итерации по смежным вер- шинам мы будем обходить исходящие ребра. Это позволит передавать дескрипто- ры ребер функциям посетителя и тем самым предоставлять больше информации посетителю, определенному пользователем. Следующий код функции поиска в глубину, приведенный в листинге 3.5, имеет параметр шаблона для посетителя. Листинг 3.5. Функция поиска в глубину с посетителем template «typename Visitor* void dfs_vl(const file_dep_graph& g, vertex_t u. default_coTor_type* color. Visitor vis) ( colortu] - gray_color: vi$.discover_vertex(u. g): graph_traits<file_dep_graph>:;out_edge_fterator e>. ei_end: for <tie(ei. ei_end) - out_edges(u. g)~ ei !« el_end: *+ei) { if (color[target(*ei, g)] — wnite_color) { vis.tree_edge(*ei. g):
68 Глава 3 • Изучаем BGL Листинг 3.5 (лдадолжв^да) dfs_vl(g. target(*ei. g). color, vis): } else if (color[target(*ei. g)] — gray_color) vis.back_edge(*ei. g): else vis.forward_or_cross_edgef*ei, g): } colorEu] - black_color; vis.finish_vertex(u. g): 1 1 template «typename Visitors void generic_dfs vltconst file_dep_graph& g. Visitor vis) { std::vector<default.color, type* color(num. vertlces(g). wh1te_color): graph_traits«f т1e_dep_graph>::vertexjterator vi, vi _end; for (tie(vi. vi_erid) - vertices(g): vi !- vi_end: ++v1) ( if (color[*vi] “ wh1te_co1or) dfs_vl(g. *vi. fcolorfOJ. vis): } I Пять функций-методов класса посетителя обеспечивают необходимую гиб- кость, но пользователь, которому нужно, скажем, только одно действие, не дол- жен писать четыре пустые функции-метода класса. Эта проблема может быть лег- ко решена созданием посетителя по умолчанию, из которого могут быть созданы посетители, определяемые пользователем (листинг 3.6). Листинг 3.6. Структура посетителя со значениями по умолчанию struct default_dfs_visitor { // посещение вершины template «typename V. typename О void discover_vertex(V, const G&) { } // встретилось ребро дерева template «typename E. typename G> void tree_edge(E. const G8) { ) // встретилось обратное ребро template «typename E. typename G> void back_edge<E. const G4) { } // встретилось пряное или поперечное ребро template «typename Е. typename G> void forward_or_cross_edge(E. const G&) { } // окончание обработки template «typename V. typename G> void finish vertexfV. const G&) { } b Для демонстрации того, что обобщенный алгоритм поиска в глубину может ре- шить наши проблемы, мы переделаем функции topo_sort() и has_cycle(). Во-пер- вых, нам нужно создать посетителя, записывающего топологическое упорядоче- ние в событийной точке «окончание обработки вершины». Код этого посетителя выглядит так: struct topo_visitor : public defau1t_dfs_visitor { topo_visitor(vertex_t*& order) topo_order(order) { } void finish_vertex(vertex_t u. const file_dep_graph8) (
3.6. Подготовка графа: внутренние свойства 69 ’--topo order » u: } vertex_t*A topo order; Г. Только две строки содержатся в коде функции toposort () при ее реализации с ис- пользованием обобщенного поиска в глубину. В первой строке создается объект-по- сетитель, а во второй происходит вызов обобщенного поиска. void topo_sort(const file dep graph® g. vertex t* topo_order) { “ - - topo_visitor vis(topo_order): generic dfs vltg. vis): } Для реализации функции has__cycle() мы используем посетителя, который за- писывает наличие цикла, когда встречается обратное ребро (back edge), struct cycle_detector : public default_dfs_visitor j cycle_detector(bool& cycle) ; has_cyclet cycle) { } void back_edge(edge t. const file_dep_graph&) { has_cycTe - true: } bool8 has cycle: }: Новая функция has cyclet) создает объект обнаружения цикла и передает его обобщенному алгоритму поиска в глубину. bool nas_cycle(const file_dep graph® g) { bool hasjryde « false.- cyde_detector vis(has_cycle): geieric_dfs_vl(g. vis)” return has cycle: } 3.6. Подготовка графа: внутренние свойства Прежде чем перейти к следующему вопросу по файловым зависимостям, уделим немного внимания другому типу графа. В предыдущих разделах мы использова- ли массивы для хранения такой информации, как имена вершин. Когда свойства вершин и ребер имеют то же время жизни, что и сам граф, может быть более удоб- ным включить их непосредственно в графовый объект (назовем такие свойства внутренними). При написании собственного графового класса можно включить поля-методы класса для этих свойств в структуру вершины или ребра. Класс adjacencyjlst имеет параметры шаблона VertexProperties и EdgeProperties, позволяющие присоединять (приписывать) произвольные свойства к вершинам и ребрам. Эти параметры шаблона предполагают класс property<Tag. Т>в качестве типа аргумента. Здесь Тад — тин, задающий свойство, а Т устанавливает тип объекта- свойства. Существует некоторое количество предопределенных свойств (см. раз- дел 15.2.3), таких как vertex name t и edge_weight_t. Например, для присоедине- ния std:: stri ng к каждой вершине можно использовать следующий тип-свойство: property<vertex_name_t. std: -.string*
70 Глава 3 • Изучаем BGL Если предопределенных тегов свойств недостаточно, можно создать новый тег. Один из способов — определить тип-перечисление с именем vertex_xxx_t или edge_xxx_t, который содержит перечисление с тем же именем (без _t). Затем можно использовать 0OOST_1NSTALL_PROPERTY для специализации классов свойст в propertyjd nd и propertyjw. Зададим свойство периода компиляции в виде стоимости (cost), которое мы будем использовать в следующем разделе для вычисления полного времени компиляции. namespace boost { enum vertex_compile_cost_t { vertex_compile_cost - 111 ); // уникальный номер BOOST_INSTALL PROPERTY(vertex, compile^cost): } Класс property имеет необязательный третий параметр. Он нужен для вложе- ния нескольких классов property с добавлением множества свойств каждой вер- шине или ребру. В листинге 3.7 приведен код создания нового typedef для графа, при этом одновременно добавляются свойства вершин и ребер. Листинг 3.7. Определение типа для графа со свойствами typedef adj scencyJtst« UstS. и Хранить исходящие ребра a sta::hst lists. Н Хранить набор вершин в std; :11st directedS, ll Граф файловых зависимостей ориентированный И свойства вершин property<vertex_name_t, std::string. property<vertex_compi1e_cost_t. float. property<vertex_distance_t. float. prcperty<vertex_color_t. default_color_type> > > >. is свойства ребер pronerty<edge_weight_t. float» > file_depjjrap(i2; Мы также изменили второй аргумент шаблона adjacencyjist с vecS на lists. Это приводит к важным последствиям. Удаление вершины из графа теперь вы- полняется за постоянное время (с vecS удаление вершины линейно по числу вер- шин и ребер). С другой стороны, тип дескриптора вершины больше не является целым числом, поэтому хранить свойства в массивах и использовать вершину как смещение больше нельзя. Однако отдельное хранилище теперь не требуется, так как мы сохраняем свойства вершин в графе. В разделе 1,2.2 было введено понятие отображения свойств. Напомним, что ото- бражение свойств — это объект, который может быть использован для преобразо- вания ключа (например, вершины) в значение (например, название вершины). Когда были заданы свойства для adjacency ! 1 st (как мы только что сделали), ото- бражения для этих свойств могут быть получены с применением интерфейса PropertyGraph. Следующий код показывает пример получения двух отображений свойств: одного — для названий вершин и другого — для времени компиляции Класс свойств propertyjnap предоставляет тип отображения свойств. typedef property_map<ftle_dep_gra₽h2. vertex_nare_t>: :type name_map_t; typedef property2wp<fi1e~dep_graph2. vertex__cwi>_cost_t>::type compi1e_cost jnap_t; typedef propertyjMp<fi 1 e_dep_graph2. vertex_distance_t>; :type Определение новых тегов свойств было бы значительно праще, если бы больше компиляторов C++ удовлетворяли стандартам.
3.7. Время компиляции 71 distance_map_t: typedef property_map<f1le_dep_graph2, vertex_color_t>: :type color_map_t: Функция get О возвращает объект отображения свойств: name_«ap_t name_nap «• get<vertex_n». g). compT’eJcostjnapJ; comp1le_cost_map - get(vertex_compile_co$t. g): distance_map_t c'Tstance_map - get(vertex_distance, g): color_map_t color jnap - get(vertex_color.-g); Данные по оценке времени компиляции для каждой цели в make-файле будут храниться в отдельном файле. Файл читается при помощи std:: т fst ream, и свойства записываются в граф с использованием отображения свойств namejnap и com- pi le_cost_map. Эти отображения свойств моделируют IvaluePropertyMap, а значит, имеют операцию operator[](), которая отображает из дескрипторов вершин в ссыл- ки на соответствующие объекты свойств вершин. std::1fstream name_in("ma k ef11e-ta rget-names.dat"): std: :ifstream cwpile_cost_inC"target-coapi1e-costs.dat”); graph_traits<fi1e_dep_graph2>::vertex_iterator vi. vi_end: for (tietvi. vi_end) - vertices(g): vi 1- vi_end: »+vi) { nantejn » name_map[*v1]; compile_cost in » compile cost map[*v1]: } В следующих разделах мы изменим функции топологической сортировки и поис- ка в глубину, чтобы использова ть интерфейс отображений свойств для доступа к свой- ствам вершин вместо жесткого прописывания доступа через указатель на массив. 3.7. Время компиляции Следующие вопросы, на которые нам нужно ответить: «Сколько времени займет компиляция?» и «Сколько времени займет компиляция на параллельном компью- тере?» На первый вопрос ответить легко. Мы просто суммируем время компиля- ции для всех вершин графа. Ради интереса выполним это вычисление с помощью функции std::accumulate. Чтобы использовать данную функцию, нужны итера- торы, которые при разыменовании дают стоимость компиляции для вершины. Итераторы вершин графа не предоставляют подобной возможности, так как при разыменовании выдают дескрипторы вершип. Вместо них мы применим класс graph_property_iter_range (см. раздел 16.8) для генерации подходящих итераторов. graphj>roperty_iter_range<fi1e_dep_graah2. vertexjxxnpile_cost_t>,-.iterator ci. ci_end: tiefci. ci_end) - get_property_iter_range(g. vertex_compile_cost): std::cout << "полное время последовательной компиляции: " « sto::accumulate(cl. ci_end, 0.0) « std:-.endl Вывод этого участка кода будет таким: полное время последовательной компиляции: 21.3 Теперь предположим, что мы имеем параллельный суперкомпьютер с сотня- ми процессоров. При наличии независимых друг от друга целей они могут компи- лироваться одновременно на разных процессорах. Сколько времени будет зани- мать компиляция? Для ответа на этот вопрос необходимо найти критический путь через граф файловых зависимостей. Иначе говоря, нам нужно найти самый дли н- ный путь через граф.
72 Глава 3 • Изучаем BGL Вклады различных целей во время компиляции файла libfoobar.a изображены на рис. 3.3. Черными стрелками обозначены зависимости файла libfoobar.a. Пред- положим, что мы уже определили, когда закончится компиляция Ьаг.о и foo.o Тогда время компиляции для libfoobar.a будет максимумом времен компиляции для Ьаг.о и foo.o плюс время их компоновки в файл библиотеки. Теперь, когда известно, как вычислить «расстояние» («distance») для каждой вершины, возникает вопрос: в каком порядке мы должны обойти вершины? Понят- но, что если есть ребро (и, о), принадлежащее графу, то нужно вычислить рассто- яние для и перед v, так как вычисление расстояния для v требует знания расстоя- ния до и. Теперь необходимо просмотреть вершины в топологическом порядке. Рис. 3.3. Вклады различных целей во время компиляции libfoobar.a 3.8. Обобщенная топологическая сортировка и поиск в глубину Так как был заменен тип графа с fl 1 e_dep_graph на f 1 le_dep_graph2, стало невозмож- ным использовать функцию topo scrtO, разработанную в разделе 3.4. Несоответ- ствие касается не только графового типа, по и массива col or. использованного в де - neric_dfs_vl(), который показывает, что дескрипторы вершин являются целыми числами (это неверно для file_dep_graph2). Эти проблемы ведут к созданию даже более общей версии топологической сортировки и лежащего в ее основе поиска в глубину. Параметризация функции topo_sort() осуществляется в несколько этапов: 1. Отдельный тип f 11 e_dep_graph заменяется на параметр шаблона Graph. Простая смена параметра шаблона ничего не даст, если нет стандартного интерфейса всех типов графов, которые мы хотим использовать с алгоритмом. Здесь при- ходят на помощь концепции обхода графов из BGL Для topo_sort() нам ну- жен тип графа, который моделирует концепции \fertexListGraph и IncidenceGraph.
3.8. Обобщенная топологическая сортировка и поиск в глубину 73 2. Использование vertex_t* для вывода упорядочения является слишком жест- ким ограничением. Общий способ вывода последовательности элементов пред- полагает применение итератора вывода, как это происходит в алгоритмах STL. Это дает пользователю намного больше вариантов для хранения результатов. 3. Нам нужно добавить параметр для отображения цветов. Необходимо учесть только самые существенные моменты. Функция topo sortC) должна иметь возможность отображать дескриптор вершины в объект-маркер для дайной вершины. Библиотека Boost Property Map Library (см. главу 15) определя- ет минимальный интерфейс для осуществления этого отображения. В на- шем случае используется интерфейс b/aluePropertyMap. Внутреннее отобра- жение colorjnap, полученное из графа в разделе 3.6, реализует интерфейс LvaluePropertyMap, так же как и массив цветов из раздела 3.3.4. Указатель на массив цветовых маркеров может быть применен как отображение свойств благодаря перегруженным функциям из boost/property_map.hpp, которые адаптируют указатели под интерфейс LvaluePropeityMap. В листинге 3.8 приведена реализация обобщенного topo sortO. Далее будут обсуждаться обобщенные topo_yisitor и generic_dfs_v2(). Листинг 3.8. Функция topo_sort(), вторая версия template «typename Graph, typename Outputiterator, typename ColorMap* void topo sortCconst Graph» g. Output Iterator topo_crder. ColorMap color) { topo_v1sltor«0utputlterator> vis(topo_order). generic dfs v2(g. vis. color); } Сейчас класс topo_vl si tor является шаблоном класса (листинг 3.9). который нуж- но приспособить под итератор вывода. Вместо декремента вставляется инкремент итератора вывода (декремент итератора вывода запрещен). Для получения того же самого обращения, как в первой версии topo_sort(), пользователь может пере- дать внутрь обратный итератор или, например, итератор вставки в начало списка. Листинг 3.9. Класс topo.vlsitor template «typename Output!terator* struct topo_vis1tor : public default_dfs_visitor { topo_vis1tor(0utputlterator& order) ; topo_order<order) { } template «typename Graph* void f1ni$h_vertex(typename graph_traits«Graph>::vertex_descriptor u. const Graph») { *topo_order+* - u: } Outputlterator& topo order; }: Обобщенный поиск в глубину (листинг 3,10) изменяется через параметриза- цию типа графа и отображение цветов. Так как неизвестен тип цвета, необходимо запросить тип его значений (с помощью класса свойств property traits) у ColorMap. Вместо использования констант, таких какиППе_со1ог, мы применяем цветовые функции, определенные Bcolor_traits. Листинг 3.10. Обобщенная функция generic_dfs_v2() template «typename Graph, typename visitor, typename ColorMep* void generic_dfs_v2(const Graph» g. Visitor vis. ColorMap color) продолжвниб a
74 Глава 3 • Изучаем BGL Листинг 3.10 (продолжений) { typedef color_traits<typename property_traits<ColorMap>::value_type> ColorT: typename graph_traits<Graph>:~vertex_iterator vi, vi_end; for (tie(vi. vT_end) - vertices(g): vi 1- v1_end: ++vi) color[*vi] - ColorT;:white(); for (tle(vi. vi_end) - vertices(g): vi I- vi_end; <-+vi) if (color[*vi] — Color!. .whiteO) dfs_v2(g. *vi. color, vis); Логическое содержание dfs_vl не требует изменений, однако нужно произве- сти небольшие модификации из-за параметризации типа графа. Вместо жесткого задания vertex t в качестве дескриптора вершины мы извлекаем соответствую- щий дескриптор вершины из графового типа, используя класс свойств graph_t га its. Полностью обобщенная версия функции поиска в глубину приведена в листин- ге 3.11. Она, в сущности, аналогична depth_f 1 rst_visit() из BGL Листинг 3.11. Функция dfs_v2() template «typename Graph, typename ColorMap, typename Visitor* void dfs_v2(const Graphs g. typename graph_traits<Graph>::vertex_descriptor u. ColorMap color. Visitor vis) typedef typename property_traits«Co1orMap*::va1ue_type cclor_type: typedef color_traits<color_type> Color!: co!or[u] • ColorT: .grayO: vis.discover_vertex(u. g): typename graph_traits<Graph>.- :out_edge_lterator ei. e'_end: for (tie(ei. ei_end) - out_edges(u, gl" ei !- ei end: ++ei) if (color[target(*ei. g)] — ColorT: :whiteO) { vi$.tree_edge(*ei. g); dfs_v2(g. targetC*ei. g). color, vis): } else if (color[target(*ei. g)J — ColorT;:grayO) vis.back_edge(*ei. g): else vis.forwa-d_or_cross_edge(*ei. g): color[u] « Color!.:bTack(). vis.finish_vertex(u. g): ) Реальные функции BGL depth_f 1 rst_search() и topologlcal_sort() очень похо- жи на обобщенные функции, которые мы разработали в этом разделе. Детальный пример использования функции depth_first_sesrch;) рассмотрен в разделе 4.2, а полное описание depth_first_searc.hO приведено в разделе 13.2.3. Документация по topological_sort() дана в разделе 13.2.5. 3.9. Время параллельной компиляции Теперь, когда мы имеем обобщенные топологическую сортировку и поиск в глуби- ну, можно решать задачу определения полного времени компиляции на параллель- ном компьютере. Во-первых, проведем топологическую сортировку, сохранив ре- зультаты в векторе topo order. Затем мы передадим обратный итератор в topqjort () для того, чтобы получить прямой топологический порядок (а не обратный).
3,9, Время параллельной компиляции 75 std;:vector<vertex_t> topo_order(rium_vertices(g)): topo_sort(g. topo_order.rbegin(). color_map): Перед вычислением времен компиляции необходимо подготовить отображение расстояний (его мы будем использовать для хранения результатов вычислений времени компиляции) (листинг 3.12), Для вершин, которые не имеют входящих ребер (будем считать их исходными вершинами), инициализируем их расстояние нулем, так как компиляция этих целей в make-файле может начаться сразу. Всем другим вершинам присваивается бесконечное значение расстояния. Найдем ис- ходные вершины, помечая все вершины с входящими в них ребрами. Листинг 3.12. Подготовка к вычислению времен компиляции graph_traits<file_dep_graph2>: .vertexjterator i. i_end: graph_traits<file_dep_graph2>::adjacency_1terator vi. vi_end; li чай1и исходнне вершины nyien поме!ки всех вершин с входящими ребрами for (tied. i_end> - vertices!g): i i_end: + «i) co)or_map[*i] - white_color: for (tied. 1_end) - vertices(g): i ! i_end; +*i) fo* (tie(vi? vi_end) ~ adjacent_vertices(*i. g): vi !• vi_end: ♦+vij colorji5ap[*vi ] - black_color; И инициализируем расстояния в 0. а спя исходных вершин присваиваем время компиляции for (tied, i_end) ' vertices(g): i '• i_end: ++i) if (color_map[*i] white_color) distance_iwp[*i] - cwrpile_cost_n»ap[*1J: else di stance_map[*i] - 0; Теперь все готово для вычисления расстояния. Мы проходим через все вершины, записанные в topo_order, и для каждой из них обновляем расстояние (полное время компиляции) до каждой смежной вершины. Это несколько отличается от того, что описано выше. До этого мы говорили о том, что от каждой вершины мы смотрели «вверх»1 по графу для вычисления расстояния до этой вершины. Сейчас же мы пе- реформулировали вычисления так, что мы проталкиваем расстояния «вниз» по гра- фу. Причина такого изменения состоит в том, что для прохождения «вверх» требу- ется доступ к входящим ребрам вершшш, а этого наш тип графа не предоставляет. std::vector<vertex_t>:.iterator ui; for (ui - topo_order.beg1nO: u1 !« topo_order.end(); ++ui) { vertex_t u *~*ui: for (tTe(v1. vi_end) - edjacent_vertices{u. g): vi !- vi_end; ♦+vi) if (d1stancejnap[*vl] < di stance jMp[u] * compHe_cost2mapf»vi]) distance map(*vi] • distancejnapfuj + compile cost «ap[*vij; } Максимальное значение расстояния среди всех вершин дает нам искомое пол- ное время параллельной компиляции. Для создания итераторов свойств по рас- стояниям вершин вновь применяется graph_property_i ter range. Функция std:: nax el е - rent О используется для нахождения максимума. graph_property_iter_range<f11e_dep_graph2. vertex_di$tance_t>..-iterator ci. c’_end: tie(d. ci_end) - get_property_1ter_range(g, ve-tex_distance)? ’ Против направления стрелок, — Примеч, перел.
76 Глава 3 • Изучаем BGL std::cout « “полное время параллельной компиляции: « *std::max_element(ci, ci_end) « std erdl; В результате будет выведена такая запись: полное время параллельной компиляции; 11,9 Для каждой цели в make-файле, изображенном на рис. 3.4. показаны два чис- ла. означающие время его собственной компиляции и время, за которое он будет скомпилирован при параллельной компиляции. Критический путь обозначен чер- ными стрелками. Рис. 3.4. Время компиляции и аккумулированное время компиляции для каждой вершины 3.10. Итоги В этой главе мы применили BGL для получения ответов на вопросы, которые могут возникнуть при создании системы сборки программного обеспечения: в каком по- рядке нужно собирать цели make-файла? Нет ли циклических зависимостей? Сколько времени займет компиляция? Ответы на эти вопросы были получены при рассмотрении топологического упорядочения ориентированного графа и его вычислении алгоритмом поиска в глубину. При реализации решений для представления графа файловых зависимостей мы использовали класс adjacencyJ т st из BGL. Была написана простая реализация топологической сортировки и обнаружения циклов. После определения общих мест кода он был преобразован в обобщенную реализацию поиска в глубину. Мы использовали посетителей алгоритма для параметризации поиска в глубину и за- тем написали конкретных посетителей для реализации топологической сортиров- ки и обнаружения циклов. Мы также рассмотрели различные варианты класса adjacency_list, которые позволили приписать свойства (имя вершины и время компиляции) к вершинам графа. После этого поиск в глубину стал более обобщенным благодаря парамет- ризации графового типа и метода доступа к свойствам. Наконец, были примене- ны топологическая сортировка и поиск в глубину для вычисления времени ком- пиляции всех файлов на параллельном компьютере.
Основные алгоритмы на графах 4.1. Поиск в ширину Поиск в ширину (breadth-first search, BFS) — один из основных способов получе- ния информации о графе, который можно применить при решении множества различных задач. Библиотека BGL имеет обобщенную реализацию поиска в ши- рину в виде алгоритма breadth_f 1 rst_sea rch(). Этот функциональный шаблон па- раметризован, поэтому может быть использован во многих ситуациях. В данном разделе будет описан и применен для вычисления чисел Бэкона алгоритм поиска в ширину. 4.1.1. Определения Поиск в ширину — обход графа, который посещает все вершины, достижимые из данной исходной вершины. Порядок посещения вершин определяется рассто- янием от исходной вершины до каждой вершины графа. Ближние вершины посе- щаются раньше, чем более удаленные. Алгоритм поиска в ширину можно представить себе в виде волны, которая распространяется от камня, брошенного в воду. Вершины на гребне одной волны находятся на одинаковом расстоянии от исходной вершины. Поиск в ширину для простого графа показан па рис. 4.1. Порядок посещения вершин имеет последова- тельность {d}, {/,g}, {с, h, b, е}, (а) (вершины сгруппированы по их расстоянию от вершины d). При посещении вершины » ребро (и, с), которое привело к ее посещению, на- зывается древесным ребром. Все вместе древесные ребра образуют дерево поиска в ширину с корнем в исходной вершине. Для данного древесного ребра (и, о) вер- шина а называется предком, или родителем, для и. Древесные ребра на рис. 4Л. обозначены черными линиями, а все остальные — серыми. Вершины графа помечены кратчайшим расстоянием от исходной вершины d. Кратчайшее расстояние 8($, о) от некоторой вершины s до вершины с — мини- мальное количество ребер в любом пути из s в о. Кратчайший путь — путь, длина
78 Глава 4 • Основные алгоритмы на графах которого равна 8(з, о). Понятно, что кратчайший путь может быть не единствен- ным. Главной особенностью поиска в ширину является то, что вершины с более короткими кратчайшими путями посещаются прежде вершин с большими. Рис. 4.1. Распространение поиска в ширину по графу В главе 5 мы займемся вычислениями кратчайших путей, в которых длина пути определяется суммой весов, приписанных ребрам пути, а не просто числом ребер пути. 4.1.2. Шесть степеней Кевина Бэкона Интересное приложение поиска в ширину возникает в популярной игре «Шесть степеней Кевина Бэкона». Идея игры состоит в том, чтобы связать некоего акте- ра’ с Кевином Бэконом через цепочку актеров, которые снимались в кино вместе, менее чем за шесть шагов. Например, Теодор Хесбург (заслуженный президент Университета Нотр-Дам в отставке) снимался в фильме «Rudy» с актером Герри Беккером, который снимался в фильме «Sleepers» вместе с Кевином Бэконом. По каким-то неизвестным нам причинам три студента, Майк Джинелли, Край Фасе и Брайан Тертл, решили, что Кевин Бэкон является центральной фигурой индустрии развлечений. Математики играют в похожую игру, вычисляя свое чис- ло Эрдеша, которое является числом работ, написанных в соавторстве, — это чис- ло отделяет их от известного Пауля Эрдеша. Игра «Шесть степеней Кевина Бэкона» на самом деле является задачей на гра- фе. Для того чтобы представить эту задачу в виде графа, нужно назначить верши- ну каждому актеру и создать ребра между двумя вершинами в том случае, если актеры снимались в одном фильме. Поскольку отношения между актерами сим- метричны, ребра не имеют направлений и граф получается неориентированным. Проблема нахождения череды актеров до Кевина Бэкона становится традици- онной графовой задачей нахождения пути между двумя вершинами. Поскольку Под актерами мы будем понимать и актрис.
4.1. Поиск в ширину 79 мы хотим найти путь с длиной менее шести шагов, в идеале было бы неплохо най- ти кратчайший путь между вершинами. Как было показано в предыдущем разде- ле. поиск в ширину может быть использован для нахождения кратчайших путей. Подобно числу Эрдеша, мы будем называть длину кратчайшего пути от данного актера к Кевину Бэкону числам Бэкона. В следующем примере будет показано, как использовать функцию breadth_fi rst_search(), чтобы вычислить числа Бэко- на для группы актеров. Входной файл и создание графа В данном примере будет использована небольшая часть фильмов и актеров из базы данных по кинофильмам Internet Movie Database'. Файл example/kevin_bacon.txt содержит список пар актеров, которые снимались в одном и том же фильме. Как показано в следующем фрагменте, каждая строка файла содержит имя актера, на- звание фильма и имя другого актера из этого же фильма. В качестве разделителя используется точка с запятой. Patrick Stewart;Pr1nce of Egypt. The (1998);Steve Martin Для начала прочитаем файл с помощью std: -.ifstream и создадим граф на его основе. std::ifstream datafile!”./kevin-bacon.dat"): if (! datafile) { std::cerr « "No ./kevin-bacon dat file" « std :endl. return EX1T FAIHJRE: Для представления графа используется adjacency_l ist, a undirectedS служит признаком того, что граф неориентированный. Как и в разделе 3.6, для присваи- вания вершинам имен актеров, а ребрам — названия фильмов класс property ис- пользуется для добавления этих свойств вершинам и ребрам. typedef adjacency_11st<vecS. vecS. undirectedS. property<vertexjiame_t. std:: string*. property«edge_name_t, std::string* > Graph: Graph g; Для доступа к свойствам объекты отображений свойств должны быть получе- ны из графа. Следующий код готовит эти отображения, которые позднее исполь- зуются дескрипторами вершин и ребер для доступа к связанным с ними именам. typedef propertyjnap<Graph. vertex_name_,t>: :type actorjiamejnap t: acto-_name_map_t~actor_natre > getivertex.name, g): typedef propertyjnap<Graph. edge_namejt>: :type Bovie_name_<iiap_t: movie_name_map_t~conr:ecting_movie - get(edge_name. g): Файл читается построчно и разбирается в список лексем, разделенных точка- ми с запятой. Для создания «виртуального» контейнера лексем используется биб- лиотека лексического разбора Boost Tokenizer Library. Код для разбора файла дан- ными приведен в листинге 4.1. 1 Internet Movie Database используется факультетом информатики Университета Вирджинии для гра- фа к их «Оракулу Бэкона».
80 Глава 4 • Основные алгоритмы на графах Листинг 4.1. Разбор файла с данными for (std::string line: std-:getline(datafile.line); ) ( char delimiters_separator<char> sep(false. "". tokenizero 11ne_toks(line, sep): tokemzer<>iterator i - line_taks.begin(). ( Взять ина первого актера и добавить вершину к графу ) ( Сохранить название фильиа в переменной ) ( Взять имя второго актера и добавить к графу ) ( Добавить ребро, связывающее двух актеров, к графу > } Каждая строка ввода соответствует ребру графа, инцидентному двум вершинам, которые определяются именами актеров. Название фильма приписывается к реб- ру как свойство. Одной из проблем в создании этого графа из такого формата файла является то, что он представляет собой поток ребер. Хотя добавить ребро к файлу несложно, проще добавлять к нему вершины. Вершины появляются толь- ко в контексте соединяющих их ребер, причем во входном потоке вершина может появиться несколько раз. Для обеспечения однократного включения вершины в граф используется отображение имен актеров в вершины. По мере добавления вершин к графу последующее появление той же вершины (уже в составе другого ребра) может быть связано с правильной вершиной, уже находящейся в графе. Это легко достигается использованием std ;тар. typedef graph_traits<Graph>;:vertex_descriptor Vertex; typedef std::map<std::string, vertex* NameVertexMap. NameVertexMap actors; Первая лексема каждой строки — это имя актера. Если актера еще нет в ото- бражении, вершина добавляется к графу, свойству вершины name присваивается имя актера, а дескриптор вершины записывается в отображение (листинг 4.2). Если актер уже находится в отображении, функция std: :пир:: insert О возвраща- ет итератор, указывающий на позицию соответствующей вершины в графе. Листинг 4.2. Добавление имени первого актера как вершины в графе ( Взять иня первого актера и добавить вершину к графу ) « $td;:stnng actors_name - *i++: NameVertexMap: .'iterator pos: bool inserted; Vertex u. v; tie(pos. inserted) - actors.insertfstd::make_pair(artors_name. VertexO)); if (inserted) f u - add_vertex(g); actor_name[u] - actorsjiame: pos->second - u: ) else { u - pos->second: } Вторая лексема (название фильма ) закрепляется за ребром. соединяющим двух актеров. Однако ребро не может быть создано, пока нет дескрипторов вершин для обоих актеров. Поэтому название фильма сохраняется для последующего исполь- зования. ( Сохранить название фипыта в переменной ) » std: string movie_name - *i^;
4.1. Поиск в ширину 81 Третьей лексемой является имя второго актера. Для включения в граф соот- ветствующей вершины используется тот же способ, что и для первого актера. ( Взять икя второго актера и добавить к графу ) » tie(pos. inserted) - actors.tnsertfstd::nake_pair(*i. VertexO)); if (inserted) { v - add_vertex(g) actor_name[v] - *i; pos->second - v: ) else v « pos->second: Ila последнем шаге добавляем ребро между актерами и записываем название объединяющего их фильма. Так как для параметра EdgeL 1 st используется sets, то параллельные ребра не попадают в граф. ( Добавить ребре, связывающее двух дилеров, к графу ) grapt_traits<Graph>::edge_descriptc» е: tiefe. inserted) » add_edge(u. ч. g). if (inserted) connecting_movie[e] - movie__nare. Вычисление чисел Бэкона с помощью поиска в ширину В нашем подходе для вычисления чисел Бэкона с помощью поиска в ширину числа Бэкона вычисляются для всех вершин графа и поэтому необходимо где-нибудь их хранить. Так как мы используем ad jacencyj i st с параметром VertexLi st-vecS, де- скрипторы вершин являются целыми числами из диапазона [0, | V|). Числа Бэко- на могутбыть записаны в std:: vector, индексом которого будет дескриптор вершины. std::vector<1nt> bacorLnumber(num^vertices(g)): Алгоритм breadth_first_$earch() принимает три аргумента: граф, исходную вершину и именованные параметры. Исходная вершина должна быть вершиной, соответствующей Кевину Бэкону, и может быть получена из отображения actors (имя-вершина). Число Бэкона для самого Кевина Бэкона равно, |>азумеется, нулю. Vertex sre - actors["Kevin Bacon”]: bacon_number[src] - 0; Для вычисления чисел Бэкона фиксируются расстояния вдоль кратчайших путей. В частности, когда поиск в ширину находит древесную дугу (а, г»), расстоя- ние для сможет быть вычислено как d\v] <- d[u] А 1. Для вставки этого действия в алгоритм поиска в ширину определим класс-посетитель bacon_number_recorder, моделирующий концепцию BFSVisitor, Вычисление расстояния будет осуществ- ляться в функции-методе класса trce cdgof) в событийной точке «встретилось древесное ребро». Класс bacon__number_recorder унаследован oTdefault_bfs_v's1tor с реализацией по умолчанию (пустой) остальных функций-методов класса для оставшихся событийных точек (листинг 4. 3). Чтобы сделать посетителя более универсальным, для доступа к расстоянию до вершины используется обобщен- ный интерфейс LvaluePropertyMap. Листинг 4.3. Класс bacon_number_recorder template «typename DistanceMap> class bacon_nunber_recorder : public default_bfs_visitor > public: bacon_n«ber_recorder(DistanceMap dist) : d(df$t> { } продолжение#
82 Глава 4 • Основные алгоритмы на графах Листинг 4.3 {продолжение) template «typename Edge, typenaire Graph’ void tree_edge(Edge e. const Graphs g) const { typename graph_traits<Graph>: -,vertex_descrlptor u - scurceCe. g). v - targette. g); d[v] - cCu] + 1: private: OistanceMap d: }: И Функция для удобства template «typename DistanceHap> bacon_number_recorder<01stanceHap> recordJ>aconjiumber(DistanceMap dl ( return bacon number recorde>-<DistanceMap>(d); } Теперь все готово для вызова Dreadth_f 1 rst_seardi(). Аргумент посетителя яв- ляется именованным параметром, поэтому его необходимо передать, применив функцию vi si tor t). В качестве отображения расстояний здесь используется ука- затель на начало массива Ьвсоп_пиЛег. breadth_first_search(g. src. vi$i tor(record_bacon_number(&ba:on_riwber[0]))): Число Бэкона для каждого актера выводится в цикле по вершинам графа. graph_traits<Graph>:'vertexjterator i. end: for Ctiefi, end) « verticesfg): 1 !- end: ++i) { stdr.cout « actor" ияеет число Бэноча ’ « bacon jwberf*)J « std. :endl; Ниже представлен фрагмент вывода полученной программы. William Shatner имеет число Бэкона 2 Denise Richards имеет число Бэкона 1 Kevin Bacon имеет число Бэкона О Patrick Stewart имеет число Бэкона 2 Steve Martin имеет число Бэкона 1 4.2. Поиск в глубину Поиск в глубину является основной частью многих графовых алгоритмов. Поиск в глубину использует и алгоритм нахождения сильно связной компоненты (см. раздел 13.5.2), и алгоритм топологической сортировки (см. раздел 13.2.5). Также поиск в глубину полезен и сам по себе. Например, его можно использовать для вычисления достижимости и обнаружения циклов в графе (см. раздел 3.4). Последняя возможность делает поиск в глубину полезным н для оптимизиру- ющего компилятора, которому необходимо выявлять циклы в графе потока уп- равления программы. В данном разделе описывается, как использовать функции depth_first_search() и depth_f i rst_vi sit О при рассмотрении примера обнаруже- ния и определения границ циклов в графе потока управления.
4.2. Поиск в глубину 83 4.2.1. Определения Поиск в глубину посещает каждую вершину графа ровно один раз. При выборе следующего изучаемого ребра поиск в глубину всегда выбирает ход «глубже» в граф (откуда и происходит его название). То есть поиск в глубину выбирает следующую смежную еще не посещенную вершину, пока не дойдет до вершины, у которой нет смежных не посещенных вершин. Затем алгоритм возвращается к предыдущей вершине и продолжает идти вдоль еще не исследованных ребер, вы- ходящих из этой вершины. После того как поиск в глубину посетил все достижи- мые вершины (из конкретной исходной вершины), он выбирает одну из еще не посещенных вершин и продолжает работу. При этом процессе создается множе- ство деревьев поиска в глубину, которые вместе образуют лес поиска в глубину. Распространение поиска в глубину на неориентированном графе показано на рис. 4.2. Для каждого ребра обозначен порядок его прохождения. Подобно алгоритму поиска в ширину, алгоритм поиска в глубину помечает вершины цветами для отслеживания продвижения поиска по графу. Первоначаль- но все вершины имеют белый цвет. Когда алгоритм посещает вершину, он окра- шивает ее в серый цвет. После посещения всех потомков вершины она окрашива- ется в черный цвет. Поиск в глубину присваивает ребрам графа три категории: древесное ребро (tree edge), обратное ребро (back edge) и прямое или поперечное ребро (forward or cross edge). Древесное ребро — это ребро из леса поиска в глубину, который строит- ся (явно или неявно) в процессе обхода графа. Более точно, ребро (и, о) явля- ется древесным, если и была впервые посещена после прохождения ребра (а, о). Во время поиска в глубину древесные ребра могут быть идентифицированы по белому цвету вершины V. Вершина и называется предком, или родителем, верши- ны о в дереве поиска, если (и, v) является древесным ребром. Обратное ребро со- единяет вершину с одним из его потомков в дереве поиска. Рис. 4.2. Распространение поиска в глубину по графу
84 Глава 4 • Основные алгоритмы на графах Этот тип ребра выявляется, если конечная вершина v исследуемого ребра (а, гп имеет серый цвет. Петли считаются обратными ребрами. Прямое ребро — реб- ро (и, v), не принадлежащее дереву поиска, которое соединяет вершину и с по- томком с в дереве поиска. К поперечным относятся ребра, не входящие в пре- дыдущие три категории. Если конечная вершина v изучаемого ребра окрашена черным, то это либо прямое ребро, либо попереч ное ребро (хотя мы не знаем, ка- кое именно). Существует много лесов поиска в глубину для данного графа, а значит, много различных (и одинаково правильных) способов классифицировать ребра. Одним из вариантов реализовать поиск в глубину является использование стека. Поиск в глубину во время обработки вершины помещает в стек смежные вершины и из- влекает одну вершину в качестве следующей для обработки. Другом (эквивалент • ным этому) способом является использование рекурсивных функций. Одним из интересных свойств поиска в глубину является то, что моменты по сещения и окончания обработки вершин образуют структуру скобок. Если мы будем писать открывающую скобку при посещении вершины, а закрывающую — при окончании обработки вершины, результатом будет набор правильно вложен- ных скобок. Здесь мы приведем скобочную структуру графа с рис. 4.2 после при- менения поиска в глубину. Поиск в глубину является ядром других графовых алгоритмов, включая топологическую сортировку и два алгоритма поиска связ- ных компонент. Он может быть использован и для нахождения циклов (см. раз- дел 3.4). (а <с (f Cg (d (b (e e) b) d) gHh h) f) c) a) (i (j j) i) 4.2.2. Нахождение циклов в графах потоков управления программы Нашей задачей в этом разделе является использование поиска в глубину для на- хождения циклов в графе потока управления программы. Пример графа потоков показан на рис. 4.3. Каждый прямоугольник представляет базовый блок, содержа- щий последовательность инструкций с одной точкой входа и одной точкой выхо- да. Если между двумя блоками есть ребро как В6), то В, — предшественник Bi и Вв — последователь для Bv Цикл определяется как набор блоков, где все блоки достижимы один из другого вдоль некоторого пути графа потоков [32]. Нахождение циклов в графе потоков управления состоит из двух шагов. На первом шаге ищутся все обратные ребра в графе. Каждое обратное ребро (ы, v) идентифицирует цикл, поскольку г> — предшественник и в дереве поиска в глуби - ну, и добавление (и, п) завершает цикл. Вершина г> называется головой цикла. По- иск в глубину используется для нахождения обратных ребер графа. Ребро (Вт, В,) (рис. 4.3) является примером обратного ребра. Второй шаг состоит в определе- нии того, какие вершины какому циклу принадлежат. Эти два шага объединены в шаблоне функции findJoopsO (листинг 4.4). Эта функция имеет три параметра: вершину entry, граф g и контейнер для хранения вершин каждого цикла. Тип Graph должен моделировать BidlrectionaLGraph, чтобы можно было иметь доступ как к входящим, так и к исходящим ребрам графа (ли- стинг 4.7). Тип Loop является контейнером, элемшп ы которого — наборы вершин.
4.2. Поиск в глубину 85 Обратные |>ебра из первого шага хранятся в векторе back_edges, a col orjnap приме- няется для отслеживания продвижения поиска в глубину. Рис. 4.3. Граф потоков управления программы Листинг 4.4. Шаблон функции find_loops() ( Шаблон функции ИndJoops ) = template «typename Graph, tysename Loops* void findjoopsf typenaii» graph_traits<Graph>: :vertex_descnptor entry. const Graphs g~ LoopsS loops) // Контейнер с наборе» вершин function_requires< B1direct1onaiGraphConcept<Graph> >(); typedef typenane graph_rraits<Graph>' edge_descrtptor Edge; typedef typename graph”tr<r.t<i<Sraph>: :vertex_descriptor Vertex; std;:vector<Edge* back_edges: std-:vector«default_color_type> colcrjrap(num_vertices(g)): < Найти все обратные ребра графа ) ( Найти все вершины каждого цикла ) } На первом шаге создается back_edoe_recorde’' из DFSVisitor, который будет запи- сывать обратные ребра во время поиска в глубину. Чтобы сделать этот класс по- вторно используемым, механизм хранения обратных ребер не задан, а парамет- ризован как Outputiterator, Класс back_edge_recorder, как обычно, наследует из defaul t_dfs_y 1 si tor, с тем чтобы иметь пустые заглушки для функций событийных точек, которые back_edge_recorder не определяет. Реализовать нужно только функ- цию-метод класса back edgeO. В листинге 4.5 приведены коды шаблона класса back_edge_recorder и функции для порождения объектов этого класса.
86 Глава 4 • Основные алгоритмы на графах Листинг 4.5. Класс регистратора обратных ребер ( Класс регистратора обратных ребер > template «typename Outputiterator» class back_edge_recorder : public default_dfs_visitor { public: back_edge_recorder(OutputIterator out) : m__out(out) { } template «typename Edge, typename Graph» void back_edge(Edge e. const Graph&) { *m_out-»+ - e: } private: Outputiterator m_out; и функция для порождения объектов template «typename OutputIterator» Dack_ed9e_recorder«0utputlterator> nake_back_edge_recorder(Outputlterator out) ( return back idge_recorderOutputIterator»(out): } Теперь все готово для вызова функции поиска в глубину. Мы выбираем depth_f i rst_vi sit() вместо depth_fi rst_search(), так как все вершины графа пото- ков достижимы из исходной вершины. Поэтому вершина entry передается внутрь как начальная точка поиска. Третий аргумент — посетитель, который будет реги- стрировать обратные ребра. Аргумент для make_tack_edge_recorder() должен быть итератором вывода, адаптер stc: :back._1nsert_i Lera tor используется для записи в вектор обратных ребер. Последний параметр — depth_first_vis1t() - предназ- начен для отображения цветового свойства, которое поиск в глубину будет ис- пользовать при отслеживании продвижения по графу. Это отображение создает- ся итератором вектора color тар (см. раздел 15.2.2). ( Найти все обратные ребра графа ) deprh_first_vlsit(g. entry, make_back_edge_recorder( std: :back_i nserter(back_edges)). make J teratorj5roperty_map( col or jiiap. begird). getcvertexjndex, g))): Па втором шаге процесса определения циклов мы устанавливаем, какие вер- шины входят в каждый цикл (листинг 4.6). Для каждого обратного ребра, найден- ного на первом шаге, вызывается функция compute_loop_extent(), которая нахо- дит все вершины, принадлежащие циклу. Листинг 4,6. Нахождение всех вершин каждого цикла ( Найти все вершины каждого цикла > for (std::vector<Edge»::size_type i - 0: i < back_edges.sizc(); +-h) { 1 oops.push_back(typename Loops::value_typeO): computejoop extent (back edges[1]. g. loops.backO): } Чтобы вершина г принадлежала циклу обратного ребра (i, A), v должна быть достижима из h и t должна быть достижима из v. Поэтому функция compute_ loop extentC (листинг 4.7) состоит из трех шагов: вычисление всех вершин, до- стижимых из головной вершины, вычисление всех вершин, из которых достижим хвост цикла, и пересечение этих двух множеств вершин. Листинг 4.7. Вычисление границ цикла ( Вычислить границы цикла ) template «typename Graph, typename Set»
4.2. Поиск в глубину 87 void compute_1oop_extent( typename graph_traits<Graph>::edge_descr1ptor back.edge. const 6raph& g, Set& loop set) { fimct1on_requires< Bidirectiona1GraphConcept<Graph> >(): typedef typename graph_traits<Graph>:;vertex_descriptor Vertex: typedef color_traits<default_color_type> Color: Vertex loop_head. loop_tall: loop_tail - source(back_edge. g): loop_head = targettbacCedge. g): < Вычислить границы цикла: достижимы из головы ) ( Вычислить границы цикла: достижимы к хвосту ) ( Вычислить границы цикла: пересечение наборов достижимости ) } Для вычисления всех вершин, достижимых из головной вершины цикла, сно- ва используется depth fi rst vi sit О. В этом случае не требуется определять ново- го посетителя, поскольку нужно только знать, какие вершины были посещены, а это может быть определено по цвету уже после запуска поиска в глубину. Вер- шины, окрашенные в черный или серый (но не в белый) цвет, были посещены в ходе поиска в глубину. Цветовые свойства хранятся в векторе reachabl e_f romhead. Все вершины, достижимые из блока В(, показаны на рис. 4.4. Entry Рис. 4.4. Вершины, доступные из блока Д ( Вычислить границы цикла: достижимы из головы > • std;:vector<defau1t_color_type> raachable_from_heaa(nwri_vertices(g), Color: :whiteO), depth_first2visit(g. loopjiead. default_dfs_vHitorO. rate_i ter ator j)roperty_map( reachablejf rcmjwad. begi n 0. get(vertex_index, g))):
88 Глава 4 • Основные алгоритмы на графах На втором шаге необходимо вычислить все вершины, из которых достижим блок В7. Для этого выполним поиск в глубину «против течения». То есть вместо обхода по исходящим ребрам каждой вершины обходятся входящие ребра. Функ- ция depth_f 1 rst_vi si t() из BGL применяет функцию out_edges(1 для доступа к сле- дующим вершинам, однако она может быть применена к нашей ситуации, если мы используем адаптер reverse graph. Этот адаптер берет BidirectionalGraph и вы- дает представление графа, в котором исходящие и входящие ребра поменялись местами. Следующий код показывает, как это может быть сделано. Все вершины, из ко- торых доступен блок В7, отображены на рис. 4,5. ( Вычислить границы цикла: достижимы к хвосту ) > std::vector<default_color_type> reachable_to_tail(rum_vertices(g)); reverse_graph<Graph> reverse_g<g): depth_first_visit(reverse_g, ioop_tai I. default_df*.-_visitorO. makejterator -propertyjiiapf reachable_to_tai 1 .beg- n(), get(vertex_index. g)D: На последнем шаге вычисления вершин, принадлежащих циклу, осуществля- ется пересечение двух полученных ранее множеств достижимости. Вершина вно- сится в 1 oop set в случае, если она достижима из головной вершины и если «хвост» достижим из нее. ( Вычислить границы цикла пересечение наборов достижимости > rypenarae graph_traits<Graph>: vertex_Jterator vi. vi_end: for (tietvi. vi_end) - vertices(g). vi •« vi_end. ~+v,l if (reachable_from_head[*vi] !- Color::»hite(1 && reachab1e_to_tail[*vi] !- Color;:white(l) ioop_set-insert Exit Рис. 4.5. Вершины, из которых доступен блок S,
Задачи нахождения кратчайших путей В этой главе мы решаем некоторые задачи маршрутизации пакетов с использова- нием алгоритмов нахождения кратчайших путей. В первом разделе проблема крат- чайшего пути объясняется в общем виде и напоминаются некоторые определе- ния. Второй раздел является кратким введением в пакетную маршрутизацию. Третий и четвертый разделы описывают два наиболее часто используемых про- токола маршрутизации пакетов и представляют реализацию их основных алго- ритмов с помощью BGL. 5.1. Определения Путем называется последовательность вершин <ц„ .vt> в графе G - (V, Е), такая, что каждое из ребер (с? г, +,) находится в наборе £ (каждая зершина соеди- нена со следующей вершиной последовательности). В задаче нахождения крат- чайшего пути каждое ребро (и, v) имеет вес w(u, v). Вес пути (или длина пути) определяется как сумма весов каждого ребра пути: k-\ i-0 Вес 8(и, v) кратчайшего пути из вершины и в вершину v — это минимум весов всех возможных путей из и в v. Если пути из и в v не существует, 8(u, с) = &(u,v) “ min [®(р): и -+ о} 00 Кратчайшим путем называется любой путь, вес которого равен весу кратчай- шего пути. Пример кратчайшего пути показан на рис. 5.1. Задача кратчайшего пути между двумя вершинами состоит в нахождении крат- чайшего пути, соединяющего две данные вершины. Задача кратчайших путей из
90 Глава 5 • Задачи нахождения кратчайших путей одной вершины заключается в нахождении кратчайшего пути от заданной верши- ны до всех других вершин графа. Набор кратчайших путей, исходящих из одной вершины, называется деревом кратчайших путей. Задача кратчайшего путимеж- ду всеми парами вершин графа состоит в нахождении кратчайших путей из каж- дой вершины графа в каждую другую. Рис. 5.1. Кратчайший путь от вершины а до е обозначен черными стрелками Как оказалось, для решения задачи кратчайшего пути между двумя вершина- ми не существует алгоритмов, которые были бы асимптотически более быстры- ми, чем алгоритмы для решения задачи кратчайших путей из одной вершины Библиотека BGL включает в себя два классических метода для решения задачи кратчайшего пути из одной вершины: алгоритм Дейкстры и алгоритм Беллмана- Форда. Также BGL включает алгоритм Джонсона для нахождения кратчайших путей между всеми парами вершин. Алгоритмы нахождения кратчайших путей широко применяются во многих областях. Одним из важных современных применений является маршрутизация пакетов в Интернете. Протоколы, которые управляют информационными паке- тами при передаче через Интернет, используют алгоритмы кратчайших путей для сокращения времени прохождения пакета до его назначения. 5.2. Маршрутизация в Интернете Когда компьютер отправляет сообщение другому компьютеру с использованием интернет -протокола (I Р), содержимое сообщения укладывается в пакет. Каждый пакет помимо данных сообщения (полезная составляющая) включает метаданные, такие так адреса источника и приемника, длина данных, порядковый номер и т. и. Если сообщение большое, данные разбиваются на меньшие части, каждая из ко- торых пакуется отдельно. Индивидуальные части снабжены порядковыми номе- рами, так что исходное сообщение может быть собрано на принимающей стороне. Если адрес назначения для пакета находится вне локальной сети, пакет от- правляется с исходной машины на интернет-маршрутизатор (internet router). Маршрутизатор направляет получаемые пакеты другим маршрутизаторам, исходя из таблицы маршрутизации, которая создается на основании протоколов маршру- тизации. Путешествуя с одного маршрутизатора на следующий (то есть совершая переход, hop), пакеты прибывают на место назначения. Если сеть перегружена, некоторые пакеты могут быть потеряны. Для надежной доставки применяются протоколы более высокого уровня, например протокол управления передачей (Trans- mission Control Protocol, TCP). Он использует квитирование (установление свя- зи) между отправителем и получателем, чтобы потерянные пакеты передавались
5.3. Алгоритм Беллмана-Форда и маршрутизация с помощью вектора расстояний 91 вновь. Программа системы Unix traceroute (или ее Windows-аналог tracert) мо- жет быть использована для прослеживания пути от вашего компьютера к другим узлам Интернета. Конечная цель процесса маршрутизации состоит в доставке пакетов до пункта назначения как можно быстрее. На продолжительность доставки пакета влияет множество факторов, например количество переходов (hops), задержки внутри маршрутизаторов, задержки передачи .между маршрутизаторами, пропускная спо- собность сети и т. д. Протокол маршрутизации должен выбирать наилучшие пути между маршрутизаторами — эта информация хранится в таблице маршрутизации. Задача маршрутизации может быть смоделирована в виде графа, в котором каждая вершина является маршрутизатором, а каждое прямое соединение между маршрутизаторами — ребром. Ребру приписаны такие данные, как задержка и про- пускная способность. Граф для простой сети маршрутизаторов представлен на рис. 5.2. У соединений обозначены средние задержки передачи. Теперь задача маршрутизации сведена к задаче кратчайших путей. Рис. 5.2. Интернет-маршругизаторы, соединенные друг с другом 5.3. Алгоритм Беллмана-Форда и маршрутизация с помощью вектора расстояний Некоторые из первых интернет-протоколов маршрутизации, например протокол маршрутной информации (Routing Information Protocol, RIP) [19]. использовали вектор расстояний. Основная идея RIP — поддерживать на каждом маршрутизато- ре оценку расстояния до всех других маршрутизаторов и периодически сравнивать
92 Глава 5 • Задачи нахождения кратчайших путей эти записи со своими соседями. Если маршрутизатор узнает и более коротком пути до некоторого пункта назначения от одного из своих соседей, он обновляет запись о расстоянии до этого пункта и изменяет свою таблицу маршрутизации так, что- бы пакеты в этот пункт шли через данного соседа. По прошествии некоторого вре- мени оцененные расстояния, хранимые таким распределенным способом, гаран- тированно сходятся к истинным расстояниям, давая маршрутизаторам точную информацию о наилучшем пути. Алгоритм протокола RIP является распределенной формой алгоритма Беллма- на-Форда для нахождения кратчайшего пути из одной вершины [5,13]. Основной шаг алгоритма Беллмана-Форда называется релаксацией ребра и соответствует сравнению своих записей с соседями. Операция релаксации, примененная к ре- бру (и, и), выполняет следующее обновление: d\v] - + d\н].t/[t>]). Алгоритм Беллмана-Форда содержит цикл но всем ребрам графа, в котором релаксация применяется к каждому ребру. Алгоритм повторяет цикл | V | раз, после чего расстояния гарантированно сокращаются до минимально возможных (если, конечно, в графе нет цикла с отрицательным весом). Если в графе есть отрицатель- ный цикл, то в нем будут неправильно минимизированные ребра, то есть найдет- ся ребро (и, с), такое что ®(и, г) + d[u] < r/[r>], где а> — нес. a d — расстояние. Для проверки того, что минимизация прошла успешно, алгоритм делает дополнитель- ный проход и возвращает истину, если это так. и ложь — в противном случае. Функция bel liwn_ford_shortest_paths() реализует алгоритм Беллмана-Форда. В следующем разделе будет показано, как использовать ее для решения задачи маршрутизации. Схема файла betlman-ford-internet.cpp программы приведена в листинге 5.1. Листинг 5.1. Файл beliman-ford-intemet.cpp ( bellman-ford-internet.cpp ) Delude <wstream> ^include <boost/array.hpp> #1nclude <boost/graph/edge_list J»pp> ^include <boost/graph/bellman_fo"d_shortest_paths hpp> int main!) { using namespace boost: ( Подготовить сеть нарврутизагоров ) ( Присвоить веса ребраи ) ( Создать хранилище для свойств вершин > ( Вызвать алгоритм Беллнана-Форда > ( Вывести расстояния и родителей ) return EXIT_SUCCESS: } Первый аргумент функции belliran_ford_shortestjaths() является графовым объектом. Тип этого графового объекта должен моделировать концепцию Edge- ListGraph. Многие из графовых классов BGL моделируют EdgelistGraph и, следова- тельно, могут быть использованы с этим алгоритмом. Здесь мы применим шаб- лон класса edgejist, который является адаптером. Он позволяет представлять множество значений итератора в виде графа. Тип значений итератора должен быть
5.3. Алгоритм Беллмана-Форда и маршрутизация с помощью вектора расстояний 93 std:: ра 1 г — пара дескрипторов вершин. Дескрипторы вершил могут быть практи- чески любого типа, хотя мы используем целые числа для удобства применения их в качестве индексов в массивах. В нашем случае ребра хранятся в boost:: array, где каждое ребро есть std: :pal г. Каждой вершине приписан идентификационный номер, заданный с помощью перечисления. Параметры шаблона для edgejist содержат тип итератора, тип значения итератора и тип разности итераторов’. Код по подготовке сети маршру- тизаторов приведен в листинге 5.2. Листинг 5.2. Подготовка сети маршрутизаторов ( Подготовить сеть маршрутизаторов ) » // Идентификационные номера для маршрутизаторов (вершин) enum { А. 0. С, D. Е. F. G. Н. n_vertices ). const int n_edges —11: typedef std::pair<int. int’ Edge: // Список соединений между маршрутизаторами, сохраненный в массиве array<Edge, n_edges> edges • { { Edge(A. 8). EdgeCA. C), Edge(B. D), EdgetB. E). EdgetC. E). EdgefC. F). Edge(0. H). Edge(D. E). EdgetE. H). Edge(F. G), Edge(G. M) } }: // Указание типа графа и определение графового объекта typedef edge_list<array<Edge. n_edges>:: iterator» Graph: Graph g(edges.begin(). edges.endO); Для передачи веса ребра (задержек передачи) в алгоритм нужно определить ото- бражение весового свойства ребра, моделирующее концепцию ReadabtePropertyMap. По умолчанию параметр weight mapf) является внутренним отображением свой- ства веса ребра графа, которое может быть получено с помощью get (edge_wei ght. g). Поскольку класс edge list не поддерживает определенные пользователем внут- ренние отображения свойств, веса ребер должны храни ться не в нем и аргумент отображения свойства должен быть передан в функцию явно. Класс edgej 1 st пре- доставляет отображение свойства ребро-индекс, так что индексы ребер могут быть использованы как смещения в массиве свойств ребер. В нашем случае там хра- нятся задержки передачи. Приведенный ниже код создает массив значений за- держек передачи. ( Присвоить веса ребрам ) » // Значения задержек передачи для каждого ребра array<loat. n_edges> delay • { { 5.0. 1.0. 1.3. 3.0. 13 0. 2 0. 6.3. 0.4. 1.3. 1.2. 0.& ) }: Массив delay обеспечивает хранение весов ребер, но не предоставляет интер- фейс отображения свойств, требуемый алгоритмом для отображения дескрипто- ров ребер в вес. Необходимый интерфейс отображения свойств предоставляется классом-адаптером iterator_propertyjnap из библиотеки Boost Property Map Library. Этот класс преобразует итератор (такой, как итератор для массива значе- ний задержек) в LvaluePropertyMap. Удобным средством для создаиия адаптера яв- ляется вспомогательная функция nskejterator_propertyjiep(). Первый аргумент — * Для компиляторов с работающей версией std::iteratt>r_traits шаблонные параметры edgejist типа значения и типа разности не нужны, так как имеются правильные параметры ио умолчанию.
94 Глава 5 • Задачи нахождения кратчайших п/тей итератор, второй — отображение из ребер в индексы ребер, и третий аргумент - объект типа значений итератора, который нужен только для вывода типа. Вызов функции make_iterator_property_nwp() может быть таким: < Создать отображение свойства для задержек ) make_iterator_property_map(delay.begin(). get(edge_index. g). delayfO]) В качестве значения, возвращаемого функцией, служит созданный объект- адаптер, который затем передается функции Веллмана-Форда. Функция get() извлекает отображение edgejndex из объекта-графа и является частью интерфей- са PropertyGraph. Вершинам графа приписано несколько свойств. Как и иа рис. 5.2, вершины обозначены буквами (это их имена). Метки расстояний требуются для записи длин кратчайших путей. Наконец, отображение предков parent используется для запи- си дерева кратчайших путей. Для каждой вершины в пзафе отображение предков записывает родителя данной вершины в соответствии с деревом кратчайших пу- тей, то есть каждое ребро (parent[и], и) является ребром в дереве кратчайших пу- тей. Класс edge J т st не обеспечивает методы для задания свойств вершин (верши- ны — только целые числа). Свойства хранятся в отдельных массивах с номером вершины в качестве индекса (листинг 5.3). Начальным значением расстояний является бесконечность, и родителем каждой вершины первоначально устанав- ливается она сама. Листинг 5.3. Создание хранилища свойств вершин < Созвать хранилище для свойств вершин ) // Определение хранилищ для некоторых “внешних" свойств вершин char name[ J - "ABCOErGH" -. array<int. n_vertices> parent; for (int 1 ->~0: 1 < n_vertlces: *+t) parental - t; array<float. n_vertices> distance: distance.assignistd::numeric_1imits<f1oat>::max()): // Обозначим А как исходную вершину distanced] • 0; Поскольку описатели вершин графа edgej 1 st являются целыми числами, ука- затели на массивы свойств подходят в качестве отображений свойств, так как Boost Property Map Library включает специализации для встроенных типов указателей (см. раздел 15.2.1). Ниже показан вызов bellman_ford_shortest_paths(). Расстояния кратчайших путей записаны в векторе расстояний, а родитель каждой вершины (в соответ- ствии с деревом кратчайших путей) записан в вектор родителей. < Вызвать алгоритм Белпмна-Форда ) bool г - bel1man_ford_shortest_paths(g. int(n_vertices). weightjtiapf < Создать отображение свойства для задержек ) ). d1 stancejMp(&Hstance[0J). predecessor _iMp(4parent[0]l): Программа завершается выводом предков и расстояний для каждого маршру- тизатора в сети или выводит предупреждение пользователю об отрицательном цикле в сети.
5.4. Маршрутизация с учетом состояния линии и алгоритм Дейкстры 95 ( Вывести расстояния и родителей ) .» if (г) for (int 1 - 0: 1 < nj/ertices: ++D std::cout « name[i] « ' « distance^] « “ “ « naffe[parent[1]] « std::endl; else std;:cout « "отрицательный цикл" « std;:endl: Для нашего примера программа выдаст следующее: А: 0 А В: 5 А С: 1 А 0: 6.3 В Е: 6.7 О' F: 3 С G: 4.2 F Н: 4.7 fi Таким образом, работая в обратную сторону через предков, мы можем видеть, что кратчайший путь от маршрутизатора Л до маршрутизатор Несп ( А, С, F, С, Н). 5.4. Маршрутизация с учетом состояния линии и алгоритм Дейкстры Уже к началу 1980-х годов появились сомнения в масштабируемости маршрути- зации по вектору расстояний. Проблемы вызывали следующие два аспекта: • В среде, где топология сети часто изменяется, маршрутизация ио вектору расстояний сходилась слишком медленно, чтобы поддерживать точную ин- формацию о расстояниях. • Сообщения с обновлениями содержали расстояния до всех узлов, так что размер сообщения рос вместе с размером всей сети. Для решения этих проблем была разработана маршрутизация с учетом состо- яния линии (Link-State Routing) [28, 37]. При такой маршрутизации каждый мар- шрутизатор хранит графовое представление топологии всей сети и вычисляет свою таблицу маршрутизации по этому графу, используя алгоритм Дейкстры. Для поддержания графа в актуальном состоянии маршрутизаторы совместно исполь- зуют информацию о состоянии линий: какие линии открыты («ир»), а какие нет («down»). Когда возможности связи изменяются, по всей сети распространяется информация в виде объявления состояния линии. Поскольку совместно использовать нужно только локальную информацию (связь с соседями), маршрутизация с учетом состояния линии не страдает от про- блем с большим размером сообщения, как это происходит при маршрутизации по вектору расстояний. Кроме того, так как каждый маршрутизатор вычисляет свои собственные кратчайшие расстояния, реакция на изменения в сети и перевычис- ление точных таблиц маршрутизации занимает намного меньше времени. Недо- статком маршрутизации с учетом состояния линии является то, что она требует от маршрутизатора больше вычислений и памяти. Но даже с учетом этого данный вид маршрутизации признан эффективным и формализован в виде протокола маршрутизации с определением кратчайшего маршрута (Open Shortest Path First
96 Глава 5 • Задачи нахождения кратчайших путей protocol, OSPF) (33]. Сейчас он является одним из предпочитаемых протоколов для внутренней маршрутизации между шлюзами. Алгоритм Дейкстры находит все кратчайшие пути из исходной вершины до каждой вершины графа, последовательно наращивая множество вершин S, для которых известен кратчайший путь. На каждом шаге алгоритма вершина из мно- жества V- 5с наименьшей меткой расстояния добавляется к 5. Затем исходящие ребра вершины релаксируются с использованием того же способа, что и в алго- ритме Беллмана-Форда, по формуле d\v] - v) + r/[w],</[nj). Затем алго- ритм повторяет цикл, обрабатывая следующую вершину из V-.S с наименьшей меткой расстояния. Алгоритм завершается, когда в V оказываются все вершины, достижимые из исходной. В оставшейся части этого раздела мы покажем, как использовать функцию dijkstra_shortest_paths() из BGL при решении задачи поиска кратчайшего пути из одной вершины в сети маршрутизаторов и как вычислять таблицу маршрути- зации. Пример сети, описанной в RFC 1583, изображен на рис. 5.3. Интериет-мар- шрутизаторы используют единый протокол маршрутизации. RT обозначает мар- шрутизатор (router), N — сеть (network), под которой понимается блок адресов, трактуемых как единый пункт назначения, Н - хост. Веса ребер указывают сто- имость передачи. Для демонстрации алгоритма Дейкстры мы вычислим дерево кратчайших пу- тей для маршрутизатора RT6. Основные шаги программы показаны в листинге 5.4. Листинг 5.4. Вычисление дерева кратчайших путей для маршрутизатора RT6 ( ospf-examplе.ерр ) ♦include <fstreaffi> // для файлового ввода-вывода ♦include <boost/graph/graphviz.hpp> // для readeriu-_graphvu<) ♦include «boost/graph/dijkstra_shortest_paths.hpp> ♦include «boost/1exical_cast hop» int mainO ( using namespace boost; ( Читать орграф из dot-файла Graphviz ) ( Копировать орграф, преобразуя строковые четки i. целые веса } ( Найти шестой мрмрутизатор > ( Подготовить отображение свойства родителей для записи дерева кратчайших путей ) ( Выполнить алгоритм Дейкстры ) < Покрасить все ребра дерева кратчайших путей черный ) ( Записать новый граф в dot-файл Graphviz ) ( Записать таблицу иаршрутиаации для шестого иариругизатора ) return EX1I_SU€CESS; } На первом шаге создается граф. Граф на рис. 5.3 представлен как dot-файл Graphviz. Пакет Graphviz предоставляет инструменты для автоматической компо- новки и рисования графов. Он доступен на сайте www.graphviz.org. Программы Graphviz используют специальный формат файла для графов, называемый dot-фай- лами. Библиотека ВСЬвключает в себя анализатор для чтения этого формата в гра4» BGL. Анализатор можно использовать с помощью функции read_graphvi z(), опреде- ленной в boost/graph/graphviz.hpp. Так как граф ориентированный, мы используем тип GraphvizDigraph. Для неориентированного графа применяется тип GraphvizGrapt.
5.4. Маршрутизация с учетом состояния линии и алгоритм Дейкстры 97 < Читать орграф из dot-файла Graphviz ) GraphvtzOigraph g_dot: read_graphv i z("figs/ospf-graph.dot". g_dot): 4 3w 375
98 Глава 5 • Задачи нахождения кратчайших путей Тип Grachv i zD~. graph хранит свойства вершин и ребер как строки. Хотя строки могут быть удобны для файлового ввода-вывода и печати, веса ребер должны быть целыми числами, чтобы ими было легко манипулировать внутри алгоритма Дейкстры. Таким образом, g_dot копируется в новый граф. Каждое ребро в типе Graphv 1 zD'. graph обладает рядом атрибутов, которые записаны в std: :map<std;: stri од. stc; stnng>. Веса jieoep хранятся в атрибуте «label» каждого ребра. Эта метка пре- образуется в int при помощи boost:; 1 exiсз 1 _саsI, и затем ребро вставляется в но- вый граф. Поскольку типы Grapn и Graphv i zDi grar.h основаны на adjacency J f st с. па- раметром V<=rtexList"vecS. типы дескрипторов вершин у обоих графов — целые числа. Результат source(*ei. gjtot) может, таким образом быть непосредственно использован в вызове add_₽dgp() для графа д. Код копирования орграфа приведен в листинге 5.5. Листинг 5.5. Копирование орграфа ( Копировать орграф, преобразуя строковые *ew я истые весе > = typedef adjdcwyjhst < vecS. vecS. directedS, «_prcperty. property < edge_weight_t, int > > Graph; typedef graoh_trdits < Graph >: :vertex._descriptor vertexjtescriptor; Graph g (num_vertices(g_dot)'; propertyjMp < Graphvi/Digraph, edge attnhute_t n.type eflge^si.t-_map - get(edge_attnSute. gjlot.): grapn_tra its < GrsphvuDigraph >: ,edge_iterator <?i. ei_end: for . ei_end) ° edges (g_dot); ei !» ei_end: / int weight * 1exical_cast < int >(edge_attr_map[»eiIflaoel"]»; property < edge_weight_t. int »edge_propertyiw£lg,Tt).- add_edije!sourcef*ei, gjfot). target(*ei. gjfot!, edge_property. gi. ) Для того чтобы шестой маршрут изатор стад исходной вершиной поиска крат- чайших путей, нужно определить его дескриптор. Пршрамма ищет вершину с мет- кой атрибута «RT6». ( Найти шестой чараругазатор ) vertex descriptor router six; propertyjw < Graphvi/Digraph. verr.ex_attnbutej >-;type vertex_attrjw - get (ver texts’, tn bote. g_dot); grapr_traits < GraphvizDigraph >.,vertex_iterator v1. vijjnd; for (tletvi. v'_end) - vertuesig_dot): vi !- vi_end; +*vi) if ("RT6" vertcx_attr_m«p[«y1 lapel"]) { router .six « *vi: break: 1 ! Совместно кратчайшие пути от шестого маршрутизатора до других маршру- тизаторов образуют дерево кратчайших путей. Эффективным способом для пред- ставления такого дерева является запись родителей каждого узла в дереве. Здесь мы просто используем std;; vector для записи родителей. < Подготовить отображение свойства родителей дпя записи дерева кратчайших путей ) э std--vector < veriex descripror > parent(ni.n_vertices(gi>; // Все вершины вначале являются своини родителями typedef graph traits < Graph >; .vertices_size..typf size_tyoe;
5.4. Маршрутизация с учетом состояния линии и алгоритм Дейкстры 99 for (size_type р - 0; р < num_vertices(c): ++р) parent[p] - р. Теперь все готово для вызова алгоритма Дейкстры. Мы передаем ему массив родителей через именованный параметр predecef oo'jrapi'. < Выполнить ашоритм Дейкстры ) < dijkstra_shortestjMths(g. router_si*. predecessor мрС&раге-ч[0]j >; Для подготовки к выводу графа в clot-файл GraphvizMbi «красим» цвета дре- весных ребер в черный цвет. Древесные ребра были записаны в массив раштС. Для каждой вершины т ребро (parentt i ]. 5) является древесным ребром, если не выполняется равенство parentf i ] - i. В противном случае т — корневая или недо- стижимая вершина. ( Покрасить все ребра дерева кратчайших путей в черный ; = graph_traits < GraphvizOigraph >. reagejfescHptor e. for (size_type i 0: t < num_vertices(gi. : if (parent[i] !- i) { e - edge(parent[i], i. g_dot).first: edge_attr_map[e]["color“] * "black”: ) Теперь мы можем записать граф в dot-файл. Для ребер цветом по умолчанию выберем серый (для недревесных ребер). Вычисленное дерево кратчайших путей для шестого маршрутизатора представлено па рис. 5.4. ( Записать новый граф в dot-файл Graphлz } => graph_property < GraphvizDigraph, graph_edge_atfrwut₽__t >- type A graph_edge_attr_map - get_property(g_dot. graph_edge_aitnbute.i: graph_edge_attr_mdp["color"] - "grey”: wri te_graphvi z( ~f i gs/ospt-sptree. dot”. g_dot) На последнем шаге вычисляется таблица .маршрутизации дня шестого мар- шрутизатора. Таблица маршрутизации имеет три колонки: пункт назначения (des- tination), следующий переход (hop) в сторону узла назначения и полное рас- стояние до узла назначения. Для заполнения таблицы маршрутизации создаются записи для каждого пункта назначения в сети. Информация для каждого узла может быть получена обратным прохождением кратчайшего пути o r назначения до шестого маршрутизатора по отображению родителей. Узлы, являющиеся сво- ими собственными родителями, пропускаются, так как это либо сам шес той мар- шрутизатор, либо недостижимый узел. ( Записать таблицу маршрутизации для шестого маршрут иди: ера ;• * std: rofstream rtableCrQuting-table.dat"): rtable « “test Next Hop Total Cost” « stc.rendl. for cie(vi. vi_end) - vertices!g_dot5; vi !- vi_end: »+vii if lparent[*v7] 1- *vi) { rtable « vertex_attr_iMp[*viJ(“label"] « ’ ( Следовать по пути назад к шестому маршрутизатору по родителям } ) Во время следования по пути из конечного пункта к шестому мгфшрутизатору веса путей суммируются в path_cost. Мы также записываем дочернин узел теку- щей вершины, так как по завершении цикла эта вершина будет использоваться в качестве следующего перехода.
100 Глава 5 • Задачи нахождения кратчайших путей Рис. 5.4. Дерево кратчайших путей для шестого маршрутизатора ( Следовать по пути назад н шеетону маршрутизатору по родителям ) vertexjtescriptor v - *vi. cmld. int patl'j.ost - 0: property_map < Graph. edge_weight_t ••••type
5.4. Маршрутизация с учетом состояния линии и алгоритм weight_nap - get(edge_weigh:. ср; do ( path-cost +• get(we1ght_map. eagetparentCv], v. g).first): child “ V: v - parentM: } while (v !- parent[vj); rtable « vertex_attr_map[child][“label'J « " ' rtable « path_cost « std;:endl; Результирующая таблица маршрутизации выглядит так: Назначение След.переход Полная стойкость Dest Next Hop Total Cos'. RT1 RT3 RT2 RT3 7 RT3 RT3 6 RT4 RT3 7 RT5 RT5 . 6 RT7 RTIO 8 RT8 RT10 8 RT9 RTIO 11 RTIO RTIO 7 RT11 RTIO 10 RT12 RTIO 11 HI RT3 10 N2 RT3 IQ N3 RT3 / N4 RT3 ft N6 RTIO 8 N7 RTIO 1? N8 RTIO 10 N9 RTIO 11 N10 RTIO 13 N12 RTIO 10 N13 RT5 14 N14 RT5 14 N15 RTIO 17 Hl RTIO 21
Задача минимального остовного дерева Библиотека Btxjst Graph Library реализует дна классических алгоритма для нахож- дения минимального остовного дерева: алгоритм Краскала [23] и алгоритм При- ма [38]. Задала нахождения минимального остовного дерева появляется во многих приложениях, таких как планирование телефонной сети, построение монтажной электрической схемы, упаковка данных. В этой главе мы применяем алгоритмы BGL к задаче планирования телефонной сети. 6.1. Определения Задач// минимального остовного дерева можно определить следующим образом. Для данного неориентированного графа G = ( V, £) необходимо найти ациклическое подмножество его ребер Т<^Е. которое соединяет все вершины в графе и общин вес кото|юго минимален. Общий вес остовного дерева — это сумма весов ребер из Т. ®(Т) * У wftt.v). Ациклическое подмножество ребер, которое соединяет все вершины графа, называется осгповным деревом. Минимальным остовным деревом называется ос- товное дерево 7'с минимальным полным весом. 6.2. Планирование телефонной сети I [редположим, что в ваши обязанности входит проведение телефонных линий для некоторого отдаленного региона. Регион состоит из нескольких городов и дорож- ной сети. Проведение телефонной линии требует возможности подъезда на гру- зовике, а значит, необходимости дороги вдоль всего маршрута линии. Ваш бюд-
6.2. Планирование телефонной сети 103 жет достаточно невелик и строительства новых дорог не предусматривает: теле- фонные линии должны идти вдоль существующих дорог. Также желательно ми- нимизировать общую длину телефонного кабеля, который потребуется для со- единения всех населенных пунктов региона. Поскольку регион малонаселен, такие факторы, как пропускная способность, не рассматриваются. Пример отдаленного региона с городами, соединенными меж- ду собой дорогами, в виде взвешенного графа представлен на рис. 6.1. Длины до- рог обозначены в милях. Нашей целью является нахождение оптимального рас- положения телефонных линий. Сначала мы решим эту задачу, используя алгоритм Краскала, а затем с помощью алгоритма Прима. Рис. 6.1. Города, соединенные сетью дорог, в виде взвешенного графа
104 Глава 6 • Задача минимального остовного дерева 6.3. Алгоритм Краскала Алгоритм Краскала стартует с того, что каждая вершина является сама по себе деревом. без ребер из множества Т. которое и составит минимальное остовное де- рево. Затем алгоритм проверяет каждое ребро графа в порядке увеличения веса ребра. Если ребро соединяет две вершины в разных деревьях, алгоритм сливает эти деревья в одно и добавляет ребро к множеству Т. После того как все ребра будут просмотрены, дерево Тпокроет граф (если он связный) и станет минималь- ным «стопным деревом графа. Схема файла kruskal-telephone.cpp, в котором применяется функция kruskal_ minitnura_ipanmng_tree() для вычисления наилучшего расположения телефонных линий, представлена в листинге 6.1. Листинг 6.1. Файл kruskal-telephone.cpp ( kruskal•teleohone.cpp ) « #include «boost/config.hpp» # include <wstream> ^include «tfslreatn» include «boost11 exical_cast.npp> finciude «boost/graph/graphvi z.hpp-> th ncl ude <boost/graph.'kruska l_r,i njspdti’nr.g_tree. hpp> int ma ini) using namespace Boost: ( Ввести неориентированный граф из dot-файла Grashviz ) i Копировать неориентированный граф. преобразуя строковые метки а целочисленное веса ) ( Вызвать алгоритм Краскала и сохранить минимальное остовное дерево ) ( Вычислить вес остовного дерева ) ( Пометить древесные ребра черными линиями и вывести в dot-файл > return EXlf_SUCCESS: ) Граф, изображенный на рис. 6.1, хранится в dot-файле пакета Graphviz и счи- тывается в память с помощью функции reed graphvi z () из boost/graph/graphviz.hpp. Так как в этом примере граф неориентированный, используется тип Grapn v > zGr е р‘- < Ввести неориентированный траф из dot-файла Graphviz ) Graph vi zGraph gjtot. read_graphv i z("f1gs/tel ephone•network.dot". g_dot). Как и в разделе 5.4, метки ребер необходимо преобразовать из строк в целые числа. Для этого нужно скопировать GraphvizGraph в новый граф и применить 1 ехт ca1_cast для выполнения преобразования из строк в целые числа (листинг 6.2). Листинг 6.2. Копирование неориентированного графа с преобразованием меток ( Колировать неориентированный граф, преобразуя строковые нетии в целочисленные веса ) typedef adjacency Jist < veci. vecS. undirected?. no_property. property < edge_wei-)ht__t. int > '* Graph: Graph g(num_vertices!3_dot)j; propenyjMp « CrapnvizGraph. edge_attribute t >: type
6.3- Алгоритм Краскала 105 edge_attr_map - get(edge_attribute. gjtot): graph-traits < GraphvizGraph >:-edge_iterator ei. ejrn.j, for (tie(ei. ei_end) - edges(gjtot): ei !- ei_end: «fi) ( int weight » lexical jiast < int >(edge_attr_nap[*eiIL"laDel"]): property < edge_weight_t. wt >edge_property(weighti; add_edge(source!*ei. gjtot). target(*ei. g_dot). edge_property. g)- Вызов алгоритма Краскала требует, чтобы тип графа был одновременно Vertex- ListGraph и EdgeListGraph. В разделе 14.1.1 в описании adjacency list показывает, что выбранный нами тип Graph прекрасно подходит. Для хранения вывода алго- ритма (ребер минимального остовного дерева) мы используем std:: vector msi и применяем std: :back_inserter() для создания из него итератора вывода. Алго- ритм Краскала имеет несколько именованных параметров. В нашем примере мы ими не пользуемся, поэтому все они принимают значения по умолчанию. Ото- бражение весов и отображение индексов вершин по умолчанию извлекаются из графа (это внутренние свойства). Имя edge_wei gnt_t объявлено как свойство типа Graph, и отображение индексов вершин уже есть в ad jacency_l i s t. c Vertexli st=vecS. Отображения предшественников и рангов (используются только в пределах ал- горитма Краскала) по умолчанию создаются внутри алгоритма. < Вызвать алгоритм Краскам и сохранить минимальное осювное fit-peso ) * std::vector < graphjraits < Graph >:-edgejtescriptor > mst: kruskal-Bininiuni_spanning_tree(g, std: backjnserterimst:-1. Когда алгоритм завершил свою работу, минимальное остовнос дерево хра- нится в mst. Полный вес этого дерева вычисляется сложением весов ребер в mst. В нашем примере полный вес для минимального остовного дерева составляет 145 миль. ( Вычислить вес остовного дерева } property _яир < Graph. edge_weight_t > :type_weight - get(edge_weight. д'; mt totaT_weight - 0. for (int e - 0: e < mst.sized; +-»e) total jxeight — getfweight, mst(ej): std:xout « "полный вес: ” « tota1_w?ignt « st.dxendl: Древесные ребра затем окрашиваются в черный цвет, и граф сохраняется в dot- файле (листинг 6.3). Листинг 6.3. Отмечаем древесные ребра для вывода ( Пометить древесные ребра чернили линиями и вывести в dot-файп) = typedef graph_traits < Graph >: zvertexjiescriptcr Vertex: for (int i - 0: i < mst.sized; ++i) { Vertex u - sourcefmstfi]. g). v - target(mst[1J. g); edge attr map[edge(u. v, g dot).flr$t]["colcr'] - "black”; '/ черный цвет ) std:-ofstream out(“fTgs/telephone-mst-kruskal.dot”): grapn_property < Graphvizfiraph. graph_edge_attripute_t >:.type & graph_edge_attr_map - get_property(g_dot7 graph_edge_attribute). graph_edge_attr_map["color*] - “gray"; // серый цвет graph_edge_attrjMp["style") - “bold"; // полужирный стиль write_graphviz(cut. gjtot): Результирующее минимальное остовное дерево показано на рис. 6.2. Опти- мальное расположение телефонных линий отображается черными линиями.
10S Глава 6 • Задача минимального остовного дерева 6.4. Алгоритм Прима Алгоритм Прима наращивает минимальное осточное дерево по одной вершине за раз (а не одно ребро за раз, как в алгоритме Краскала), Основная идея алгоритма Прима состоит в последовательном добавлении вершин к минимальному остов- ному дереву. Очередная вершина должна иметь общее ребро с минимальным ве- сом, соединяющее ее с уже находящейся в дереве вершиной. Алгоритм Прима очень похож на алгоритм Дейкстры. (На самом деле реализация алгоритма При- ма в BGL есть просто вызов алгоритма Дейкстры со специально подобранными функциями сравнения расстояний и объединения.) В этом разделе алгоритм pfHn_nimmu?n_spanning_tree() применяется к той же са- мой задаче планирования телефонной сети (см. рис. 6.1). Схема файла prim-tele- phone.cpp (листинг 6.4) подобна использованной в предыдущем разделе, хотя имеет- ся некоторая разница в том, как алгоритм I Грима выводит ребра остовного дерева.
6.4. Алгоритм Прима 107 Листинг 6Д. Файл prim-telephone.cpp < prim-telephone срр ) « ♦include «iostrew» ♦include «fstream* /include <vecto*> /include <boost/lexica1_cast.hop> /include <boost/graph/graphviz.hpp> /include <boost/oraphzpn«i__minf«um_spdnning_tree.hpp> int naini) ( using namespace boost: < Ввести неориентированный граф из dot-файла G-aphviz ) ( Копировать неориентированный граф, преобразуя строковые метки 8 целочисленные веса ) ( Вызвать алгоритм Прима и сохранить минимальное остовное дерево в предшественниках > ( Вычислить вес остовного дерева > < Пометить древесные ребра черными линиями и выверти в dot-файл > return £XIT_£'JCCFSS; Первые два шага (чтение из с1о£-фаЙ1ла и копирование графа) те же самые, что и в предыдущем примере. В вызове алгоритма Примапсрвый параметр — граф, а вто- |х>й — отображение предшественников. Минимальное остовное дерево записывается алгоритмом Прима в виде отображения предшественников. Для каждой вершины v в графе parent Гу! является родителем v в минимальном остовном дереве. Внутри алгоритма parent >] может быть многократно присвоен, но псксчеднее присваивание гарантированно устанавливает правильного родителя. Для точной настройки функ- ции pnmjni niwn_spami rtg_tree() может быть применен ряд именованных парамет- ров (мы оставляем значения по умолчанию), В нашем случае используются отобра- жения весовребсри индексов вершин, внутренние для типа Graph, а вспомогательные отображения цветов и расстояний создаются внутри гип оритма. Значением по умол- чанию для корневой вершины является *vertices(g) .first, что допустимо, так как корень минимального остовного дерева произволен. < Вызван. алгоритм Прима и сохранить минимальное осювное дерево в препиесгвенниндх ) typedef graph trans<Graph'-. :vertex_descriptor Vertex: std. vectcr«Vertex> parent.(num_vertices(g)1: priiynnnMiu!n_sparirilng_treeig. Spar ent [0] '>. Когда минимальное остовное дерево записано в массиве pa rent, общий вес вычис- ляется в цикле по всем вершинам графа сложением весов всех ребер (parentfv ]. v). ЕслирагепТГу] = v, это означает, что либо v является корневой вершиной де^юва. либо она не была н той же компоненте связности, что и остальные вершины. В любом слу- чае (parent[v], v) не является ребром остовного дерева и должно быть пропущено. Вычисление для графа, изображенного на рис. 6.1, дает тот же результат — 145 миль. ( Вычислить вес остовного дерева > з property _тар«бгаоЬ.. edge weight_t>: :type_weight - get (edge weight, g>, int totd_weigrt - 0: for (int v » 0: v < num_vemces'.q), ++vl if (parent[v) '» vi total_weight •»= get (weight, edge(parentfv]. v. gj.firstl stcT.cout ’«< “полный sec1 " «« total_weight << std..endl:
108 Г пава 6 • Задача минимального остовного дерева Для наглядности ребра минимального остовного дерева отмечены черными ли- ниями и после записываются в dot-файл. Результирующее минимальное остов- ное дерево показано на рис. 6.3. Заметьте, что де]юво здесь немного отличается от полученного алгоритмом Краскала Вместо ребра между Magnetawan и Kearny имеется ребро между Magnetawan и Sprucedale. Это подчеркивает тот факт, что минимальные остовные деревья не уникальны: для конкретного графа их может быть более одного. Рис. 6.3. Минимальное остовное дерево для алгоритма Прима ( Пометить древесные ребра черчыни линиями и вывеси- в dot-файл ) for tint u • 0: u < тЯ-уегШезЛд); »+u) if (parentfu] !- u) edge_attrjw.p[edge(parent[u]. u, g_dot).first][’'color"] - "black"; std. ofstream out{"figs/telephone-mst-pnn.dof>: graph_property < GraphvizGraph. grapn__edge_attrit>ule_t >: :type S grcph_edge_attr_map • get_property(g_dot. graph_edge_attr1twte); graph_eclge_attr_mapr"color"] - "gray": write__graphvu(out. g_dot);
Компоненты связности Основным вопросом для сети является вопрос о достижимости одних вершин из других. Например, хо|юшо спроектированный веб-сайт должен иметь достаточно ссылок между страницами, чтобы все страницы были достижимы с главной стра- ницы. Кроме того, хорошим тоном является наличие ссылок на главную страни- цу или хотя бы на предыдущую страницу в серии последовательных страниц. В ориентированном графе группы вершин, достижимые друг из друга, называют- ся сильными компонентами связности'. Изучение 200 миллионов веб-страниц показывает, что 56 миллионов страниц Интернета составляют одну большую сильную компоненту связности [7]. Это ис- следование также показало, что если рассматривать данный граф как неориенти- рованный, большую компоненту связности составляют 150 миллионов страниц и 50 миллионов страниц оторваны от этой большой компоненты (они находятся в своих, гораздо меныпих, компонентах связности). Библиотека BGL предоставляет две функции для вычисления всех компонент связности: первая для однократного вычисления (граф не меняется) и вторая для случая, когда граф может расти. Библиотека BGL также реализует алгоритм Та- рьяна для вычисления сильных компонент связности графа за линейное время. В следующем разделе мы рассмотрим некоторые определения и затем приме- ним функции из BGL для компонентов связности к Всемирной паутине WWW (World Wide Web). 7.1. Определения Путем называется последовательность вершин, в которой каждая вершина пути соединена со следующей ребром. Если существует путь из вершины и в w, то мы будем говорить, что вершина w достижима из вершины и. Компонента связнос- ти — это группа вершин неориентированного графа, в которой каждая вершина Иногда они называются бикынптентами, сильными компонентами. - Примеч. иерее.
110 Глава 7 • Компоненты связности достижима из любой другой. Сильная компонента связности — это группа вер- шин ориентированного графа, которые обоюдно достижимы друг из друга. От- ношение достижимости для неориентировапит ix графов является отношением эквивалентности', оно рефлексивно, симметрично и транзитивно. Множество объектов, для которых выполняется отношение эквивалентности, образуют класс эквивалентности. Одна компонента связности является, таким образом, классом эквивалентности по отношению достижимости. Аналогично, сильная компонен- та связности является классом эквивалентности для отношения обоюдной дости жимости. В результате эти два отношения разбивают вершины графа на непере- секающиеся подмножества. 7.2. Связные компоненты и связность Интернета Компоненты связности неориентированного гр? фа вычисляются с применением поиска в глубину. Идея заключается в том, чтобы прогнать поиск в глубину на графе и отметить все вершины в одном дереве поиска в глубину как принадле- жащие одной компоненте связности. Реализация функции connected_components (' из BGL содержит вызов dept.h_firsL_search() со специальным объектом-посети- телем, кот орый присваивает каждой обнаруженной вершине номер текущей ком- поненты и увеличивает номер на единицу в событийной точке «начальная вер- шина». На рис. 7.1 представлена сеть интернет-маршрутизаторов, у которой ребра со- ответствуют прямым соединениям. Шаги для вычисления компонент связности такой сети следующие; 1) прочитать данные о сети в память; 2) представить их в виде графа BGL; 3) вызвать функцию connectea_coniponents(). Каждой вершине 1рафа присваивается целое число, отмечающее компоненту, к которой вершина принадлежит. Схема файла cc-internet.cpp приведена в листинге 7.1. Листинг 7.1. Файл cc-intemet.cpp < cc-internet.cpp ) #ind ude «fstream» ♦inc5ude «vector» ♦include «string» ♦include <boost/graph/connected_ccmpc'ients.hpp> ♦include «boost.'graph/graphvi? hpp> int mninO using namespace boost: ( Снитат ь граф s память ) ( Создать веигор для назначения компонент < &’зват|> функцию connected_ccFponent$o ) ( Скрасить вершимы в зависимости ш компонент w «писать в dot-файл ) Данные для графа на рис. 7.1 считываются из файла cc-internet.dot, записанно- го в формате dot-файла Graphviz. Выбран тип GraphvizGraph, aneGraphvIzDigraph, так как траф является неориентированным.
7.2. Связные компоненты и связность Интернета 111 Рис. 7.1. Сеть интернет-маршрутизаторов
112 Глава 7 • Компоненты связности ( Считать граф в память > GraphvizGraph g; readjraphvizl "figs/cc-internet dot”, g): Для хранения целых чисел для каждой вершины, обозначающих компоненту, используется вектор component размером nufn_vert.ices(g). ( Создать вектор для назначения компонент ) « std. ,vector<m> compor(ent<num_vertices(g)): Первый аргумент в вызове connected_componentsC) является графовым типом который должен моделировать VertexListGraph и IncidenceGraph. Тип Graphvi zGrapt подходит, поскольку моделирует эти концепции. Второй аргумент — отобра- жение свойства для преобразования вершин в номера компонент. Отображение свойств создается адаптером iterator_property_map, который служит оболочкой для итератора, указывающего па начало вектора component. Функция соппес- ted componentsf) возвращает количество найденных компонент и записывает назначения компонент для каждой вершины в вектор component. Алгоритм connected_components() также имеет именованный параметр для отображения цве- тов. используемый при поиске в глубину. В нашем случае для параметра уста- новлено значение по умолчанию. Алгоритм создаст отображение цветов внутри себя и использует отображение индексов вершин графа для индексирования этого массива. ( Вызвать функцию connected_ccmponentsО ) int num_csnp • connected components(g. make_iterator property_map(c«nponent.begin!), gettvertexjndex. g). cowponent[0])); Для визуализации результатов вычисления каждой вершине присваиваются цвета в соответствии с номером компоненты. Затем граф записывается в dot- файл. Результаты окраски вершин представлены на рис. 7.2. ( Окрасить вершины в зависимости от компонент и записать в dot-файл ) property_map < GraphvizGraph. vertex_attribute_t >:.type vertex_attr_map - get(vertex_attrlDute. g): std:-.string color[] = {"white", "gray", "black”. "Ughtgray"); !l белый, серый, черный, свэтло-серый graph_traits GraphvizGraph >; :vertexjterator vi. vi_end; for CtieCvi. vi_end) - vertices(g): vi > vi_end: > ( vertex_ar.t.r_mapL'vi]["color’l - co’or[component[*vi]], vertex_attrjnap[*vi][’‘style"] ' ’’filled’: !' стиль - заполненный If (vertex_artr_map[*viH"color“] ’black") vertex_att|' map[*vi]["fontcolor"] - “white": !' цвет шрифта - белый i wn tejjraphvizCfigs/cc-internet-out .aot”. g).
7.3. Сильные компоненты связности и ссылки веб-страниц 113 Рис. 7.2. Компоненты связности
114 Глава 7 • Компоненты связности 7.3. Сильные компоненты связности и ссылки веб-страниц Набор связанных ссылками веб-страниц можно абстрактно представить в вил ориен ги|ювапного графа (рис. 7.3). Наша задача в этом разделе — вычислить силь- ные компоненты связности для этого графа. Схема файла scc.cpp для вычисления компонент связности представлена в лис- тинге 7.2. Сначала данные графа будут прочитаны из dot-файла Graphviz в память Затем создается место для хранения назначений компонент доя вершин, которые будут вычислены алгоритмом. После этого вызывается алгоритм strong j.onponents;), и его результаты используются для окрашивания вершин графа в зависимости от номеров сильных компонент связности. Листинг 7.2. Файл scc.cpp scc.cpp > » ^include <bof'St/ccnfiq.hpp> include <fstream> finclude *чвр> ^include <string>
7.3. Сильные компоненты связности и ссылки веб-страниц 115 ^include <boost/graph/strong_co«ponents,hpp> # 1 nclude «boost/graph,'graphviz.hpp> 1nt mainO { using namespace boost: ( Считать ориентированный граф в память > ( Выделить место для назначения компонент > < Вызвать функцию strongjxxnponentsO ) ( Окрасить вершины в зависимости от номеров компонент и записать их в dot-файл ) return EXIT SUCCESS: } Данные графа считываются из файла scc.dot с использованием типа Graphvi z- D1 graph (так как граф ориентированный). ( Считать ориентированный граф в память > SraphvizDigraph g. read_graphviz("figs/scc.dot". g); В вызове strong_components () адаптер associ at’ve_property_map используется для обеспечения интерфейса отбражения свойств, требуемого функцией. Этот адап- тер создает отображение свойств из AssociativeContalner, например из std: :map. Выбор std::map для реализации отображений свойств довольно неэффективен в нашем случае, но демонстрирует гибкость интерфейса отображения свойства. Дескриптор вершины для Graphvi zOi graph является целым числом, поэтому он имеет требуемую std: :map операцию «меньше». < Выделить место для назначения компонент > « typecef graph-traits < GraphvizOigraph >;:vertex_descriptor vertex_t. std::map < vertex_t. int component: Результаты вызова strpng_components () помещаются в массив component, где каждой вершине сопоставлен номер компоненты. Номера компонент идут от нуля до num_comp - 1. Граф, передаваемый функции strong_cont>onentsO, дол- жен быть моделью концепций VertexlistGraph и IncidenceGraph. И действительно, он соответствует этим критериям. Второй аргумент — отображение компонент — должен быть ReadWritePropertyMap. Есть еще несколько именованных парамет- ров, которые могут быть заданы, но все они предназначены для внутренних вспо- могательных отображений. По умолчанию алгоритм создает массивы для этих отображений свойств и использует индекс вершины графа как смещение в этих массивах. ( Вызвать функцию strong_components() } int rum_comp - strong_comoonents(g. make_assoc_property_map(component). Программу завершает окрашивание вершин в соответствии с компонентами, которым они принадлежат. Вывод записывается в dot-файл. Граф с сильными компонентами связности представлен на рис. 7.4.
116 Глава 7 • Компоненты связности ( Окрасить вершины в зааисимести от номеров компонент и записать ил в dot-файл } property_map < GraphvizDigraph. vertex attnbute^t >:-type vertex_attr_raap • get(veri«x_attribute. g'.. std.:string color[] « ("wlrte". "gray". “Mack''. "11 ghtgray"}; H белый, серый. черный, светло-серый graph_traits < GraphvuCi graph >: :vertex_iterator vi. vi end: for (tie(vl. vi^end) • vertices(g): vi vijwd; *+vii I vertcx_attr_map[‘viJPcoWJ - colcr[canponent[*vi]]: vertex_att,"__map[*vlJfstyle"] - "filled". h стиль - заполненн if (vertex_3ttr_mapi*vi]f"color’] — “biacV) vertex attr map[*vT]["fontco’cr"] - "white": h цает шрифта - бел } writejjraphvizCfigs/scc-out.dot", g).
Максимальный поток Задача максимального потока — это задача определения того, как много некото- рого количества (например, воды) может пройти через сеть. Алгоритмы для ре- шения задачи максимального потока имеют длинную историю. Первый алгоритм принадлежит Форду и Фалкерсону [12]. Лучший же из известных на сегодня ал- горитмов обтаю назначения — алгоритм проталкивания предпотока (push-relabel algorithm) Гольдберга |9,16. П], основанный на понятии предпотока (preflow), введенном Карэаковым [20|. Библиотека BGL содержит два алгоритма для вычи- сления максимального потока: алгоритм Эдмондса-Карпа (улучшение оригиналь- ного алгоритма Форда-Фалкерсона) и алгоритм проталкивания предпотока. 8.1. Определения Потоковая сеть (flow network) — это ориентированный граф С - (К £) с верши- ной истока s и вершиной стока t. Каждое ребро имеет положительную веществен- ную пропускную способность, и на каждой паре вершин задана потоковая функ- ция/. Потоковая функция должна удовлетворять трем ограничениям; /(и, г) S с(и.о) V(u, г) eV xV (ограничение пропускной способности) - f(v,u) V(u,v)eVxV (антисимметричность) £/(w,e)- О t'eV VueV- {s, t) (сохранение потока) Поток — чистый поток, входящий в вершину стока t. Остаточная пропускная способность ребра есть г (и, о) - с(и, о) - /(и,»). Ребра с г (и, в) > 0 называются ос- таточными ребрами Е( и порождают граф G( - ( V, £z). Ребро с г (w, v) - 0 называ- ется насыщенным. |/|- Х/(«о меУ
118 Глава 8 • Максимальный поток Задача максимального потока состоит в определении максимально возможного значения для |/| и соответствующих значений для каждой пары вершин в графе Важным свойством потоковой сети является тот факт, что максимальный по- ток связан с пропускной способностью наиболее узкого места сети. По теореме о максимальном потоке и минимальном разрезе 112] максималь- ное значение потока от вершины истока до вершины стока в потоковой сети рав но минимальной пропускной способности среди всех (5, Т) разрезов. (5, Т)-раз- рез - это разделение вершин графа на два множества S и Т, где з е 5 и I е Т. Любое ребро с начальной вершиной в S и конечной в 7'является прямым ребром разреза, а ребро с начальной вершиной в Т и конечной в 5 — обратным ребром разреза. Пропускная способность разреза - это сумма пропускных способностей прямых ребер (обратные ребра игнорируются). Так что если мы рассмотрим пропускные способности всех разрезов, отделяющих s и Т, и выберем разрез с минимальной пропускной способностью, опа будет равна лрогускной способности максималь- ного потока в сети. 8.2. Реберная связность При проектировании телефонной сети, сети передачи данных крупного предпри- ятия или соединений маршрутизаторов для высокоскоростного сегмента сети Интернет очень важным вопросом, который задают себе инженеры, является ус- тойчивость сети к повреждениям. К примеру, если кабель окажется перерезан- ным. имеются ли другие кабели, по которым может проходить информация? В те- ории графов это называются реберной связностью графа — минимальное число ребер, при разрезании которых получаются две несвязные компоненты (предпо- лагается, что первоначально граф имел одну компоненту связности). Мы будем использовать a(G) для обозначения реберной связности графа. Множество ребер в разрезе, приводящем к увеличению числа компонент связности, называется се- чением (minimum disconnecting set). Вершины графа разделяются по двум компо- нентам У иУ. поэтому для обозначения минимального разреза мы будем исполь- зовать [У, У]. Оказывается, что вычисление реберной связности может быть сведено к последовательности решени й задач о максимальном потоке. В этом раз- деле мы рассмотрим алгоритм вычисления реберной связности неориентирован- ного I рафи 127]. 11уеть а(и, г) обозначает минимальное число ребер, которое может быть разре- зано для разьединения вершин и и г друг от друга. Если эти две вершины являют- ся источником и стоком и пропускная способность каждого ребра равна едини те, то разрез с минимальной пропускной способностью (вычисленной по алгоритму максимального потока) совпадет с минимальным разрезом. Таким образом, ре- шив задачу максимального потока, мы также определим минимальное число ре- бер, которые должны быть разрезаны для разъединения двух вершин. Теперь для того, чтобы найти реберную связность графа, алгоритм максимального потока нужно запустить для каждой пары вершин. Минимум всех этих попарных мини- мальных разрезов будет минимальным разрезом всего графа. Выполнять алгоритм максимального потока для каждой пары вершин слиш- ком накладно, поэтому лучше сократить количество пар, которые требуется рас-
8.2, Реберная связность 119 смотреть. Этого можно достигнуть с помощью специального свойства сечения [5', S']. Пустьр — вершина минимальной степени, а 3 — ее степень. Если a(G) = 8, то S' есть простор. Если a(G) £ б - 1, то оказывается, что для любого подмноже- ства 5" (назовем его 5), множество всех вершин, не являющихся соседними для вершин из S, должно быть не пусто. Это означает, что минимальный разрез может быть найден, если начать с 5 - р, выбрать вершину k из множества не соседних вершин S, вычислить а( р, к) и затем добавить k к 5. Этот процесс повторяется, пока множество не соседних вершин S не станет пустым. Мы реализуем алгоритм реберной связности как шаблон функции, который использует BGL-интерфейс VertexListGraph. Функция возвращает реберную связ- ность графа, и ребра в несвязном множестве (disconnected set) записываются ите- ратором вывода. Схема функции вычисления реберной связности приведена в ли- стинге 8.1. Листинг 8.1. Алгоритм вычисления реберной связности ( Алгоритм вычисления реберной связности ) template < typename VertexListGraph. typename Outputiterator > typename graph_traits < VertexListGraph >::aegree_size_type edge connect1vlty(VertexL1stGraph 4 g. Output Iterator disconnecting set) ( < Определить типы ) < Определить переменные > < Создать граф потоковой сети из неориентированного графа ) ( Найти вершину минимальной степени и вычислить соседей S и не соседей S > < Главный цикл ) ( Вычислить прямые ребра разреза ) return с: В первой части реализации (листинг 8.2) создаются некоторые определения типов, чтобы иметь более короткие имена для доступа к типам из свойств графа. Потоковый граф (ориентированный граф) создается на основе неориентирован- ного входного графа д, поэтому используется графовый класс adjacency ! 1st. Листинг 8.2. Определение типов ( Определить типы ) • typedef typename graph_tralts < VertexListGraph >.:vertex_descriptor vertex_descriptor; typedef typename graph_traits < VertexListGraph >::degree_size_type degree_slze_type: typedef color_tra1ts < default_color_type > Color: typedef typename adjacency_li$t_trait$ < vecS. vecS. directedS >;;edge_descriptor edge_descriptor; typedef adjacencyj1st < vecS. vecS, directedS. no_property. property < edge_capaclty_t. d=gree_size_type. property < edge_residual_capKity_t. degree_size_type. properly < edge__reverse_t. edge_descr1ptor > > > > flowGraoh, В листинге 8.3 мы используем std:: set для множества 5 и множества соседей 5 (переменная neighbors), поскольку при вставке должна гарантироваться уникаль- ность. Множество 5 * (переменная S star) и множество не соседей 5 (переменная nonneighbor_S) представлены std::vector в связи с тем, что мы знаем, что вставля- емые элементы будут уникальными.
120 Глава 8 « Максимальный поток Листинг 8.3. Определение переменных { Определить переменные ) - vertex_descriptor u. v. р. it; sdge_descriptor el, е2; poo। inserted: typenane graph_tra1ts < Vertex!istGraph >• .vertex..-terctor vi, vi_end, degree.size.type delta. alpha, star. alpha_5.К: std: :sc-t < vertex_№scrintor > S. neighbor_5. std:-vect.or < vertex..descriptor > S_star. nonnt :gh(:or_S: std::vector < default_color_type * color(ium_vert<ces(g‘>). std':vector < edge_descriotor > predtnumj/erticesCg)): Граф потоковой сети создастся на основе входного графа (листинг 8.4). Каж- дое ребро потокового графа имеет три свойства: пропускную способность, неияз ку и обратное ребро. Доступ катим свойствам осуществляется через объекты-ото бражсния свойств: cap, res_cap и rev_edge соответственно. Листинг 8.4. Создание графа потоковой сети из неориентированного графа ( Создать граф потоковой сети из неориентированного графа ’> » П-лйгарй f1ow_g(num_yertices(gP. // понтовый -раф typename property., map < FlowGraph. edge_cai>acity_t >.;type cap * get(edge_capacity. flow g), typename property_map < FlowSrapt. eoge_res'auajopac’tyj. >:.type res_cap - get(edge_residual_capacity. flow_g). typenarre propertyjrap < FlowGraph. edge_reversB_t >::typ<? rev_edge - get (edge reverse. <Tyw_g); typenare graph_traits < Vprti-xlistGraph >• edge_1t?ratcr ei. ei_end: for <tie(ei. eijinci) - eagestg*. e> !• e’ljmi; +»ei 1 i и - source(*ei. g>. v - target<. g). tie(el. inserted) •• add_edge(u. v, flow_g): cap[el] « 1: tle(e2. inserted) • add_=d<ie(v. u. flow_g): cap[e2] - 1: rev_edge[“l] - e2; rev”edge[e2] - el; } В главном алгоритме функциональные блоки выделены в отдельные функции (листинг 8.5). В первом блоке в цикле по всем вершинам графа находится верши- на минимальной степени. Листинг 8.5. Функция нахождения вершины минимальной степени < Функция нахождения вершины чининапьной степени } j tecnplate < typer erne Graph > Std--pair < typename graph_traits < Graph >; .verttx_descnptor. type-name graphjraits < Graph >; :degrce_size_type > min degree_vertex:Graph & g) { typename graph_trai:s < Graph >: :ver'tex_descripicr p; typedef typename graph_traits < Graph >::degree_size_type s1ze_type; size_type delta - std:: numeric J im is < size_ty?« typename graph_traitc < Graph >: .vertexjterator lend: for <tie(i. ’.end) •* vertices(gi. i H :end; «-h'. if (degree(*i. g) < delte.1 delta - degree(*i. e):
8.2. Реберная связность 121 р - . J return $W::make pairtp. сеКт): } Необходимо обеспечить возможность вставки каждого соседа вершины (и мно- жества вершин) во множество. Это осуществляется через adjacent._vertice<(). Мы предполагаем, что итератор вывода сходен с str!:: > nsertj terator для std:: set. Вспомогательные функции приведены в листинге 8.6. Листинг 8.6. Вспомогательные функции вывода соседей ( Вспомогательные функции вывода соседей > » template < typename Graph, typename Output iterator > void neighborsfconst Graph & g. typename graph_tra;ts «= Graph rve-tcx^destriptor u. Outputiterator result) typename graph_trait$ < Graph >::adjacency_iterator a1, aend: for (tieCai, tend) • adjecent_verticesa. g): ai !- aend: •’ci) *resu(t++ - *ai: } template < typename Graph, typename Vertexiterator, typename OutputIterator > void neighborsfcanst Graph & g. Vertexiterator first,.Vertexiterator last. Outputiterator result) { for <,: first '• last; *+f’.rst) neighborsig, *fir:.t. result.;; На начальном шаге алгоритма (листинг 8.7) осуществляется поиск вершины минимальной степени р. затем 5 - р, а после этого вычисляются соседи и не сосе- ди 5. Мы используем std;:set_di f ference^) для вычисления F - 5, где V - множе- ство вершин графа. Листинг 8.7. Поиск вершины и вычисление соседей и не соседей S ( Найти вершину минимальной степени и вычислить соседей S и не соседей S ) ® Help, delta) - min_dcgree_vert.ex-'g): S_star.push_back(p); alpha_star - de ta: S.insert(p); neighborjj. insertlp); neighborsig. S.begin!), S.enrtO. std- • inserter(neighbor_S. neighbor's.begin(’)1; std.:set_differenceiverticestg).first, verticesCg;.second. neighbor_S. beg'n{;, neighbors.encK}. std:: back_ Insert er' rwneigtiDor^S)): Итерации ;итгоритма заканчиваются, когда множество не соседей 5становит- ся пустым. В каждом шаге алгоритма (листинг 8.8) максимальный поток между р и не соседом k вычисляется с помощью алгоритма Эдмондса-Карпа (см. раз- дел 13.7,1). Вершины, окрашенные не л белый цвет, во время нахождения макси- мального потока соответствуют всех: вершинам на одной стороне минимального разреза. Таким образом, если размер разреза на данный момент наименьший, ок- рашенные (не белые) вершины записывают ся в 5*. затем к добавляется и S, егхеди и не соседи 5 вычисляются заново.
122 Глава 8 * Максимальный поток Листинг 8.8. Главный цикл ( Главный цикл ) » while (!nonneighbor_S.empty()) J i - nonne1ghbor_S.front;): alpha_S_k - edmunds_karp_max_flow (flow_g. p. k. cap, res_cap. rev_edge. &color[0], ipred(OJ): it (alpha_S_k « alpha_star) | alpha_$tar - alpha_SJc; S_star.clearO: for (t1e(vi. vi_end) - vertices!flowjj): vi !- vi_end; +*vi) If (color[*vij !» Color::whiteO) S star.push_bark(*vi): ) S.insertfk): neighbor_S.inserttk): neighbors; g. k. std:: inserter (neighbors. neighbor S. begin())): nonneighbor_S.clear!): std::set_difference(vertices(g).first. vertices(g).second, neighbors oeginO. neighbor_S.end(). std: back_1nserter(r,omeighbor_S)); На завершающем этапе (листинг 8.9) осуществляется поиск ребер разреза, ко- торые имеют одну вершину в 5*. а другую в 5*. Эти ребра записываются через итератор вывода disccnnecting_set (несвязное множество), а количество ребер в разрезе возвращается оператором return. Листинг 8.9. Вычисление прямых ребер разреза { Вычислить пряные ребра разреза ) std::vector < bool > in_S_star(num_vertices(g). false); typename std;;vector < vertexjfescrtptor iterator si; for <si - S_star.begin!); si I- S_star.end(): +*si' in_S_star[*si] - true: degree_size_type c » 0: for (si - S_5tar.beginO; si !- S_star.end(); { typename graph_treits «VertexListGraph*::out_edge_tterator ei. ei end; for (tietei. ei_end) - out_edges(*si. g): ei~!- ei_end; ++ei) 1f (!ln_S_star[target(*el. g)J) { ♦disconnecting_set++ - *ei; Файл edge-connectivity.cpp, в котором реализован алгоритм нахождения ребер- ной связности, приведен в листинге 8.10. Листинг 8.10. Файл edge-connectivity.cpp ( edge-connectivity.срэ ) iindude <algorithrn> ^include «utility* ^Include «boost/graph/edmunds karp_max_flow.hpp> ♦include <ixrast/graph/push_reTabel jnax^flow.hpp* findude «boost/graph/adjacencyJ 1st.hpp> ♦include <boost/graph/graphviz.hpp> namespace boost { ( Функция нахождения вершины минимальной степени )
8.2. Реберная связность 123 ( вспомогательные функции вывода соседей ) ( Алгоритм вычисления реберной связности } int вдтп({ using namespace boost; GraphvizGraph g: read_graphviz('’tigs/edge-connectivity.dot“. gi: typedef graph_traits < GraphvizGraph >: ;edge_cescripr.or edge__desr.ript,-.>r. typedef graph_traits < GraphvizGraph >: .«-gn?e_sizej:ypc ctegro-?_size_typr-. std:;vectoc « edgejlescriptor > disconnecting set. degree_size_type c~- edgc_conn»ctivi ty(g. std•backJns£rter(disconnecting_;et)): std.xout. « "Реберная связность; " « с « std- end'. propertyjnap « GraphvizGraph. vertex_attribute_t >;tyoe attrjnap - geiivertexjsttrfbute. g); swiccut « "Сечение- {". for (std;.vector < edge_descr-,otor >-.iterator , - disconnecr.ing_set.beg7n(); т disconnecting_set.end!}: ) std.-xout « ”(" « attr_map[sourcei*i. д)][”1г.ЬеГ] « « attrjnap[target(*'i. abel" 1 « std. xout « “}." « std; endl. return ГХ1Т SUCCESS. } В результате работы файла edge-connectivity.cpp выводится следующее: Реберная связность: 2. Сечение: { (О.Е) (D.H) ). Граф, к которому был применен алгоритм нахождения реберной связности листинг 8.10), представлен на рис. 8.1. Рис. 8.1. Пример графа для реберной связности
Неявные графы: обход конем Задача обхода конем состоит в нахождении такого пути для коня, чтобы он побы- вал на каждой клетке шахматной доски п х п ровно один рад. Обход конем являет- ся примером гамилыпонового пути. Гамильтонов путь — это простой замкнутый путь, который проходит через каждую вершину 1рафа только один рад. При обхо де конем клетка шахматной доски считается вершиной графа. Ребра графа опре- деляются в соответствии с шахматными правилами для хода коня (например, на две клетки вверх и на одну в сторону). В этом разделе мы используем д ля нахожде- ния пути копя обобщенный алгоритм поиска с возвратом. Алгоритм с возвратом является алгоритмом грубой силы и достаточно медлителен, поэтому мы также покажем, как улучшить алгоритм, применив эвристику Варнсдорфа [46]. Задача нахождения гамильтонова цикла является NP-полной [15] (при большой размер- ности она не может быть решена за разумное время). Пример обхода конем тра- диционной шахматной доски 8x8 показан на рис. 9.1. Рис. 9.1. Пример обхода конем шахматной доски
9,1, Ходы конем как граф 125 Одной из особенностей этого примера является отсутствие явной структуры (вроде класса ad;acency_list) для представления графа. Вместо этого неявная структура фафа knights Jour_graph следует из допустимых ходов коня на доске. 9.1. Ходы конем как граф Граф kmghts tour graph является моделью AdjacencyGraph, поэтому нам нужно ре- ализовать функцию adjacent_vertices(>, возвращающую пару итераторов смеж- ности. Итератор смежности трактует каждую из клеток, а которую может перей- ти конь, вершиной, смежной данной. Набор возможных ходов коня хранится в массиве следующим образом: typedef std.:ратг < int. int > Position. Position knight JutnpsE81 * { Position^. Positi-.>n(l. -2). Position!-!. -2). Posit<on(-2. -1). Position;-2. Is. Position;-!. 2). Position;!, 2). Position^. 1) ): Итератор km ght_ad jacencyJ terator содержит несколько полей данных-членов: текущую позицию на доске m_pos, индекс mJ в массиве ходов knight Jumps иуказа- тель на фаф m_g. Инкремент итератора смежности (операция opcrator-Ж)) увели- чивает mj. Новая позиция может быть некорректной (за пределами доски), поэто- му может понадобиться еще раз увеличить mj. что и делает функция-метод класса va1id_position(). Первая позиция тоже может быть некорректна, поэтому valid_position() вызывается также и в конструкторе итератора смежности. Ука- затель на шахматную доску необходим для того, чтобы можно было определить ее размер (так как доска может быть произвольного размера). При разыменовании итератора смежности (operator*')) возвращается смещение текущей позиции в текущем векторе ходов. Реализация итератора kmght_adjacencyjt.erator приведе- на в листинге 9.1. Вспомогательная функция boost: :forw'djteratorjielper ис- пользуется для автоматической реализации операции operator-M(int) посредством operators) и операции operator! -() посредством operator—(). Листинг 9.1. Итератор смежности knlght_adjacency_iterator struct kniyhL_ai;jacency_ iterator: public boost: forwdrd_iterator_helper < knigrt_adiacency_iterator. Position, std:-ptrdiffj. Position *. Position > { knight adjacency iterator!1 { J knight_adiacercy_iterator!int И. Position p. const knights_tour_graph & g) : m_pos'p). mjp.&g;, m_*. iii) { va1id_pD$ition(); } Position operator *O const { return m_pos + Knigntjumps[m_i]. } void operator-»* i) { ++«ij: valijpositionO; ) bool operator — (const knight _ad.iscency_ iterator 8 x) const l return nJ “ x.rrj; } protected: void vabdjiositionO. Position я jx>s; const kni gilts_tour_graph * m_g, 1 nt mJ; }:
12в Глава 9 • Неявные графы: обход конем В функции-методе класса val id_posi Li on() счетчик ходов увеличивается дотех пор, пока не будет найдена позиция на доске или не будет достигнут конец массива. void knight.adjacency.iterator::valid_positiono { Position new_pos - m_pos ♦ knight_jumps[m_1J; while (m_i <~8 &4 (new.pos.first < 0 || new.pos.>econd < 0 || new_pos. fi rst >- n»_g->ffl_board_slze II newjos. second >- m_g->m_board_size)) { + ‘ir_l: newjjos - B_pos + knightjufflps[m_iJ: } } Функция adjacent verticesO создает пару итераторов смежности, используя D для начальной позиции итератора ходов и 8 для конечной позиции итератора ходов. std::pair < kn1ghts_tcur_graph::adjacency_iterator. knights.tour.graph::adjacency_iterator > adjacent_vertices(knichts_tour_graph: vertex.descriptor v. const knights.tour.graph & g) { typedef knights_tour_graph::adjacency_iterator Iter: return std..make pair(Iter(O. v. g). Tter(8. v. g)j: ! Класс krnghts_tour_graph (листинг 9.2) содержит только размер доски (как по- ле) и операторы typedef, требуемые для AdjacencyGraph. Функция num_vertices( > возвращает число клеток на доске. Листинг 9.2. Класс knights_tour_graph struct knights_tour_graph { typedef Position vertex.descriptor; typedef std::pair < vertex.descriptor. vertex.descriptor > edge.descriptor; typedef knight.adjacency.iterator adjacency.iterator: typedef void out.edgejterator: typedef void in.edge.iterator. typedef void edge.iterator: typedef void vertex.!teratar: typedef int degree.size.type; typedef int vertices.size.type: typedef int edges_size_type: typedef directed.tag directed.category; typedef d1sallow_para1lel_edge_cag edge_para1lei.category: typedef adjacency.graph.tag traversal.category; knights_tour_graph(int n). m_board_size(n) { } int m_board_size: h int num.verticesfconst knights.tour.graph 4 g; [ return g m.board.size * g.m_board_size.' Теперь, когда ходы конем отображены в графовом интерфейсе Boost, мы мо- жем рассмотреть отдельные графовые алгоритмы, которые могут быть использо- ваны для решения задачи обхода конем шахматной доски.
9.2. Поиск с возвратом из графе 127 9.2. Поиск с возвратом на графе Идея алгоритма поиска с возвратом на графе (backtracking graph search) по/юбна поиску в глубину в том, что путь исследуется до тех пор, пота не будет обнаружен тупик. Поиск с возвратом отличается тем, что после достижения тупика алгоритм возвращается обратно, снимая пометки с тупикового пути, перед тем как продол- жить поиск по другому пути. В листинге 9.3 поиск с возвратом реализован с помо- щью стека (вместо рекурсии), а порядок посещения каждой вершины записывается с помощью отображения свойства. Стек состоит из пар отметка времени - вершина, так что правильная отметка посещения доступна после возвращения из тупика. По- иск завершается, когда все вершины посещены или все возможные пути исчерпаны. Хотя граф, определенный в предыдущем разделе, был неявным и представлял, в частности, обход шахматной доски конем, он тем не менее моделирует концепцию Graph из BGL, то есть алгоритм поиска с возвратом (аеализован для концепции Graph, а не для конкретного графа обхода конем. Получившийся в результате алгоритм можно повторно использовать для любой структуры данных, моделирующей Graph. Листинг 9.3. Алгоритм поиска с возвратом template < typenamt- Grapn. typename TwTopert.yStap > oooi PacktrackTng_search(Graph & g. typename grartytraits < Grapn vertex descriptor trc. TimePropertyMap twjw’ { Создать стек к инициапизиоояат» лнегку времени > S.pushtsW: .make_pair(tine_stamp. sre)J: while ClS.emptyO) { !> иикп юга ere* че пуст ( Получить вершину со стека. записать врем и проверить свершение > < Положить все снежные вершины на стек > { Если в тупике, откатиться ) } it while <'S.еярту(i) return false: I Для записи вершин, которые нужно проверить, используется std::stack. Вер- шина заносится в стек вместе с отметкой посещения. < Созвать стек и инициализироват» отметку времен-.' > ® typedef typenare graphjralts < Graph > •verte»_cescnptor ‘'er-.e*. typedef std::pa'-r < Int. Vertex > P; sto: -.Stack < P > 5: int wejstamp « 0: Следующий шаг — записать отметку посещения для вершины графа наверху стека и проверить, не занесены ли уже в стек все вершины графа. В последнем случае алгоритм успешно завершается. < Получить вериину со стека, записать время и проверить зааершеннп ) -- Vertex к; tie(time_stamp. х) » S.topO: put(time_map. х, ;ime_stamp); И все вершины посещены. успех! И (tiiTie_stamp — numverticesig) - 1) return true:
128 Глава 9 • Неявные графы: обход конем Теперь просматриваются все смежные вершины, и если смежная вершина еще не была посещена, она кладется в стек. Отсутствие смежных вершин - признак тупика. < Поломить все снежные вершины на стек ) « bool deadend * true: П переменная для индикации тупика typename graph_traits < Graph >•:adjacency_iterator i. end; for (tie(i. end) • adjacent_vertices(x. g): i !- end: ->+i) 1f (get(t1me_map. *1) — -1) { S.pusMstd: make_pair(ti'ne_sta<np + 1. *))); deadend » false: } Если алгоритм достигает тупика, вершины выталкиваются из стека, пока не будет найдена еще не исследованная вершина. В процессе возврата отметки посе щения для каждой вершины сбрасываются, поэтому, возможно, эти же вершины будут достигнуты по более подходящему пути. ( Если в тупике, откатиться ) 1f (deadend) { Н если тупик put(time_map. х. -I): S.popo," tip(time stamp, х) - S.top!): while (get(time_iMp, x’> !» -1) { l! откатить стек до последней не исследованной еервины putitimejnap. х. -1): S pop!): tie(t1me_stanp. х) - S.topO. } } 9.3. Эвристика Варнсдорфа Эвристика Варнсдорфа для выбора следующей клетки для хода заключается в упреждающем просмотре каждого из возможных ходов для определения того, сколько дальнейших ходов возможны из этой клетки. Назовем это числом после- дователей. Клетка с наименьшим числом последователей выбирается для следу- ющего хода. Причина, по которой эта эвристика работает, в том, что, посещая в на- чале наиболее ограниченные вершины, мы избегаем потенциальных тупиков. Следующая функция Tumber_of_successor$() в листинге 9.4 вычисляет число по- следователей вершины. Листинг 9.4. функция number_of_successors() template < typename Vertex, typename Graph, typename TfmePropertyMap > int number_of_successors!Vertex x. Graph & g. TlmePropertyMap time_map) int s_x « 0. typename graph_traits < Graph >::adjacenc/_iterator i. end; for (tied, end) - adjacent_vertices(x. g)~ i !• end: *-+1) if (get(time map. *1) — -1) *+S_X; return s_x. )
9.3. Эвристика Варнсдорфа 129 Реализация эвристики Варнсдорфа (листинг 9.5) начинается с алгоритма с воз- вратом, но вместо заталкивания смежных вершин в стек сначала происходит сор- тировка вершин по количеству последователей. Сортировка выполняется при помещении смежных вершин в очередь по приоритету (priority queue). После за- несения вершин в очередь они извлекаются оттуда и помещаются в стек. Пустая очередь сигнализирует о тупике. Листинг 9.5. Реализация эвристики Варнсдорфа template < typename Graph, typename TimePropertyHao > bool warnsdorf'(Graph & g. typename graph_traits < Graph >::vertex_descriptor src. nmePropertyMap timejnap) I < Создать стек и инициализировать отметку времени ) S.pushtstd: ;make_pair(tiiw_$tamp. src)): while (IS.eoptyO) { ( Получить верлину co стека, записать время и проверить завершение ) // поместить снежные вершины в локальную очередь по приорите:/ std::priority_queue < Р. std.:vector < Р >. compere_first > 0: typename graph_traits < Graph >: -.adjacency „iterator i, end; int num_succ: for (tied, end) - «djacent_vertices(x. g): i !• end: +♦') if (getttimejnap. *1) «- ~1) { num_succ - nuraber„o'f„successors(*i. g. time_mapv Q.pushCstd::make paircnutn succ *i)): } bool deadend - Q emptyО: /7 переместить вершины из локальной очереди в стек for (: !Q.empty!): Q.popO) { tie(num_succ. x) - Q.topO: S.push(std::make pairCtime stamp * 1. xD. } if (deadend) { putCtimejnap. x. -IV. S.popO:- tie(time_s*.amp. x) - S.topO: while (get(time_map. x) !- -1) { // откатить стек до последней не исследованной вершины put(timejnap. х. -1), S.popO: tiettime stamp, х) « S.topO. J } ll while {!S.empty()1 return false: } ' 3ar 375
Взаимодействие с другими графовыми библиотеками Хотя основной задачей Boost Graph Library является поддержка разработки новых приложений и графовых алгоритмов. существует довольно много кодов, которые мо- iyr получить выгоду от использования BGL. Одним из путей использования алго- ритмов BGLc существующими грачиными структурами данных является копирова- ние даю 1ых из старого формата в BGL-граф, который затем используется в алгоритма BGL Проблема с этим подходом заключается в .ом. что не всегда удобно и :м|х|>ек- тивпо выполнять это копирование. Другим подходом является использование суще- ствующих структур непосредственно, используя «обертку» для ВСЕ-интерфейеа. Пап ери Адаптер (14] — это один из механизмов для обеспечения нового ин- терфейса для существующего класса. Этот подход обычно требует, чтобы адап- тируемый объект хранился внутри нового класса, предоставляющего желаемый интерфейс. При адаптации BGL-графа вложенности объекта не требуется, по- скольку графовый интер<|)еис BGL полностью состоит из независимых (глобаль- ных) функций. Такой интерфейс вместо создания нового графового класса требует только перегрузки независимых функций, из которых он состоит. В раз- деле 10.3 мы покажем, как это делается, во всех деталях. Библиотека BGL включает перегруженные функции для типа GRAPH из LEDA 129|, типа Graph* из Stanford GraphBasc, а также для std:: vector из STL. LEDA — попу- лярная объектно-ориентированная библиотека для комбинаторных вычислений, включающая 1рафовые алгоритмы и структуры данных. Stanford GraphBasc, на- писанная Дональдом Кнутом, является набором графовых данных, генераторов графов и программ, которые выполняют алгоритмы на этих графах. В следующих разделах мы покажем примеры использования структур данных из LEDA и SGB с алгоритмами BGL, Затем мы рассмотрим реализацию адапти- рующих функций из ВСБдля графов LEDA, приводя пример того, как нужно писать адаптеры для других графовых библиотек. В разделе 1.4.1 мы демонстрировали гибкость алгоритмов BGL, применяя topo1ogical_$ort() к графам, представленным вектором списков, к i-рафам в ви- де boost: : adjacency __11st и std::vector<std: :1 ift<int». Мы продолжим этот при- мер - планирование выполнения взаимозависимых заданий - в следующих двух
10.1. Использование топологической сортировки из BGL с графом из LEDA 131 разделах, сначала используя LEDA-тип GRAPH, а затем — тип Graph от Stanford GraphBase (SGB). 10.1. Использование топологической сортировки из BGL с графом из LEDA Заголовочный файл boost/graph/Leda_graph.hpp содержит перегруженные функции, которые адаптируют параметризованный тип GRAPH из LEDA к интерфейсу BGL. BGL-интерфейс к LEDA GRAPH описан в разделе 14.3.5. Интерфейс между LEDA и BGL был протестирован с версией LEDA 4.1, одной из свободно распространя- емых версий LEDA. В дополнение к leda_graph.hpp библиотека LEDA должна быть установлена, настроены пути к заголовочным и библиотечным файлам, a LEDA- библиотеки использованы при сборке программы. Более детальный материал на- ходится в документации LEDA. В листинге 10.1 приведена схема программы планирования заданий, на этот раз используя GRAPH из LEDA для представления зависимостей между заданиями. Листинг 10.1. Схема программы планирования заданий { topo-sort-wlth-leda.cpp ) ♦include «vector* ^include «string* # 5nclude «boost/graph/topological~sort.hpp* # 1nclude «boost/graph/1edajjraph.hpp* // Отменить определение макросов LEDA. конфликтующих с С+-» Standard library ftindef string fundef vector int train() { using namespace boost; < Создать граф LEDA с вершинани.. обозначенными заданиями ) < Добавить ребра к графу LEDA ) ( Выполнить топологическую сортировку графа LEDA } return EXTTSUCCESS. ) Класс GRAPH из LEDA позволяет пользователю присоединить объекты-свой- ства к вершинам и ребрам графа, поэтому здесь мы прикрепляем имена (в форме std: -.string) к вершинам. Мы используем обычную функцию add vertext) для до- бавления вершин к ledajj и передаем имена заданий как объект-свойство для присоединения к вершине. Дескрипторы вершин, возвращенные из add_vertex(). хранятся в векторе, так что можно быстро использовать нужную вершину при добавлении ребер. Код для создания графа LEDA приведен в листинге 10.2. Листинг 10.2. Создание графа LEDA для задачи планирования заданий ( Создать граф LEDA с вершинами, обозначенными заданиями ) typecef GRAPH < std::string, char >graph_t; graph_t leda_g: typedef graph_traits « graph_t >::vertex_descr)ptor vertex_t: std;;vector <”vertex_t > vert(7); vert[O] - add_vertex(std::str1ng("забрать детей из школы"), leda_g): vertllj - add_vertex(5td;;string("купить продукты"). ieda_g): vert[2] • add^yertexCstd;:string("получить деньги в банкомате"). leda_g): vert[3] - add_vertex(std::string('привести детей на тренировку"). !eda_g): vert[4] - add”vertextstd; :$1гтпд(’пригото8ить ужин"), leda g): ~ ““ t flJLltlLJzIJKCFttK! O'
132 Глава 10 • Взаимодействие с другими графовыми библиотеками Листинг 10.2 [продолжение) xerttSJ - add_verte<£std- stringt“забрать детей с 'ренирлвки”), leda g): vert[6J = add_vertex(std. string('съесть уж*н"). letia.gi: Следующий шаг — добавление ребер к графу. Вновь мы используем обычную функцию add_edqe£). Добавит» ребра к графу LEOA ) add_edge(vert[O], vert[3]. !к!а g;: add>dge(vertLl 1. ver-ЦЗ]. leda_g): add2edge(vert.[l], vert.[4]. ledajp; add_edge(verti2J. vertLl]. ledajs): add_edge(vert[3], vertCS], ledaj): add2edge(vert[4], vert[6], leda_g); aadj?dge(vert[5]. ve-rt[6J. ledajj): Теперь, когда п>аф построен, можно вызвать topologiса 1 _scrt(), Благодаря ин- терфейсу между LEDA и BGLGRAPH из LEDA может быть использован в неизмен- ном виде с BGL-функцией. Мы просто передаем объект leda_g в этот алгоритм. Функция topol ogi са 1 _sort (1 требует отображения свойства окраски вершин, поэт о - му мы используем массив вершин LEDA поЦеаггаудля отображения вершин в цве- та. Функция 1 eda_node_property jrep(} также определена в boost/graph/leda_graph.hpp и создает адаптер, который удовлетворяет концепции LvaluePropertyMap в контек- сте node_a rray. В вектор topG_order записывается обратное топологическое упоря- дочение. Затем вектор переворачивается, и выводится упорядочение. Операция operator [ ]() класса GRAPH из LEDA используется для доступа к названию задания для каждой вершины. Код топологической сортировки приведен и лист инге 10.3. Листинг 10.3. Выполнение топологической сортировки ( Выполнить топологическую сортировку графа L£DA ) « std::vecier < vertex_t > topo_order: node_array < default_color_type > color.arrayi Jedajj). topo1og--cal_sort(leda_g. std - -back._inserter(topo_crder). colorjrec'ircakcJede_node_propertyjMp(coIor\array))). std: reverse!topojirder begino. topoorder.endU): int n « 1; for (std: vector < vertex_t >:-iterator 1 - topo_order.beginO: i topo_order endi). «-+1. +*n) std.-tout « n " « leda__g[*i] « stdcencl: 10.2. Использование топологической сортировки из BGL с графом из SGB Библиотека Stanford GraphBase определяет структуру Graph, которая реализует структуру данных в стиле списка смежности. Перегруженные функции в boost/ graph/stanford_graph.hpp адаптируют структуру Graph к интерфейсу BGL. Помимо включения заголовочного файла stanford_graph.hpp необходимо, чтобы была уста- новлена библиотека SGB и применен файл изменения PROTOTYPES (из дистрибу- тива SGB). Это необходимо потому, что оригинальные заголовочные файлы SGB не определяют прототипы функций согласно стандарту ANSI, что требуется для компилятора C++. При компиляции программы с использованием SGB-BGL ин- терфейса необходимо установить пути к заголовочным файлам и библиотекам для
10.2. Использование топологической сортировки из BGL с графом из SGB 133 SGB, нужно также собрать программу с библиотекой SGB. И нтерфейс между BGL и SGB задокументирован в разделе 14.3.4. Пример топологической сортировки для графа из SGB приведен в листинге 10.4. Листинг 10.4. Топологическая сортировка графа из SGB < topo-sort-with-sgb.cpp > find ude «vector* findude «string* linclude «iostream* # 1nclude «boost/graph/topological_sort.hpp* # 1nc1ude «boost/graph/stanford_graph.hpp* int mainO { using namespace boost; ( Созвать SGB-граф > ( Создать метки для заданий ) ( Добавить ребра к SG8-графу } ( Выполнить топологическую сортировку на SG8-графе ) gb recydefsgb g): return EXIT SUCCESS: } Мы создаем SGB-граф вызовом SGB-функции gb_new_graph(). ( Создать SGB-граф ) • const int n_vertices - 7: Graph *sgb_g - gb_new_graph(n_vertices>: Далее мы записываем метки для заданий (вершины) в граф. От дескриптора вершины в SGB легко перейти к целому числу, используя $gl_vertex_td_map, опре- деленный в stanforcLgraph.hpp, поэтому хранение меток в массиве удобно. ( Создать метки для заданий ) « const char *tasks[] - { "забрать детей из школы", "купить продукты’. "подучить деньги в банкомате". “привести детей на тренировку". "приготовить ужин”, "забрать детей с тренировки", "съесть ужин" }; const int n_tasks - sizeof(tasks) / sizeoftchar *1: SGB-граф хранит вершины графа в массиве, поэтому можно получить доступ к любой вершине по индексу в массиве. Функция gb_ne_edge() принимает два ар- гумента Vertex* и вес ребра (нам он не важен). ( Добавить ребра к SGB-графу ) gb_new_arc<sgb_g-’vertices + 0. sgb_g-’vertices ♦ 3. 0): gbjiew_arc(sgb2g-*vertices ♦ 1. sgb_g-’vertices + 3, Q): gb_new_arc(sgb2g-*vertices + 1, sgtTg-’vertices «-4. fl): gb_nevTarc($gb_g-*vertices 2, sgb_g-’vertices ♦ 1. 0); gb_new2arc($gb_g->vertices ♦ 3. sgb_g-’vertices < 5. 01: gbjwjrcfsgbjj-’vertices +4. sgb_g-*vertices * 6. 0). gb_new_arc(sgbj-*vertices * 5. sgb_g-*vertices ♦ 6. 0), Затем осуществляется топологическая сортировка. Мы передаем в алгоритм сам SGB-граф. Наэтот раз вместо явного создания отображения цветов мы позво- лим алгоритму самому создать его. Однако для этого функция topclogical_sort() нуждается в отображении вершин в целые числа. Интерфейс между SGB и BGL обеспечивает такое отображение свойства. Отображение вершин в индексы можно
134 Глава 10 • Взаимодействие с другими графовыми библиотеками получить вызовом get (vertex Index, sgbg;. Код топологической сортировки при- веден в листинге 10.5. Листинг 10.5. Вычисление топологической сортировки SGB-графа ( Выполнить топологическую сортировку не SGB-графе ) Typedef graph_traits < Graph * > .verte.<_de5criptor vertex_t. std::vector < vertex_t > topo_ordei •.cpologi.:a!_sort(sgfc_g. std; .back_inserteni.o}»_or<ier!, vei.tex_index_mapiget(vertex_rndex. sgb_g))i: int n - 1: for std::vector < vertex^t > :reverse_irer<!tor i topo_order.’begin! i: ' topo order rendi). -‘«i. std:-tout « 'i « " " « tasks[get(vertex_index sgb_gi[*i]] std; .entll. 10.3. Реализация адаптеров графов Написать адаптер для других графовых библиотек и структур данных совсем не сложно. В качестве примера создания новых адаптеров этот раздел предлагает детальное объяснение реализации интерфейса ВСЕдля графа из LEDA. Первое, с чем нужно определиться, — это какие из концепций будет реализовы- вать BGL граф. Следующие концепции легко реализовать поверх классов из LEDA: VertexListGraph. BidirectionalGraph, VertexMutableGraph и EdgeMutableGraph. Все типы, ассоциированные с графовым классом из BGL, можно получить с по- мощью класса graph_trg4s. Этот класс свойств может быть частично специализи- рован для графового класса GRAPH и» LEDA’ (листинг 10.6). Типы node и edge яв- ляются эквивалентами дескрипторов вершин и ребер. Класс GRAPH предназначен для ориентированных графов, поэтом}' мы выбираем тег d1rected_tag для direc- ted_category. Так как класс GRAPH автоматически не запрещает вставку параллельных ребер, в нашем случае устанавливается з 11 ow_t-aга 11 el_edge_tag для edge_par«l - 1 е 1 _са tegory. Функция •iun'ter_of_nc<ies() из LEDA возвращает целое число, поэтому данный тип указан для vertsces_size_type. Тип тега, используемый для traver- se ’_cetequry, должен отражать моделируемые графом концепции обхода, поэтому мы создаем теговый класс, наследующий от bicr-ectwnal_granh_tag, adjacency_- graph j.ag и vertexj' st_graph_tdc. Типы итераторов описаны далее в этом разделе. Листинг 10.6. Свойства графов в LEDA < Свойства графов для LEDA-графа > « namespace boost • struct leda_graph_treversel_category : oubhc virtual bidirectionaljraprjjag. public virtual adjacency_graph tag. public virtuei vertex_list_graph_tag { }; template < typename V. typename E> struct graph_trans< GRAPH<V.E> > ( typedef node vertexdescriptor; typedef edge edgejtescnptor: typedef directed_fag direct«i_category: Некоторые нестандартные компиляторы. напри мер Visual С г - 6.0. не поддерживают частичную citci.u:.- лнзацию. Для доступа к асстишронаппым типам и атом случаи класс свойств должен быть полигхтьи: специализирован ,ыя конк|>егных типов вершин и ребер В альтернативном варианте может быть применен класс-оболочка. содержащий LEDA-граф и требуемые вложенные определения типом.
10.3. Реализация адаптеров графов 135 typedef allow_parallel_etige_tag edge_paral lei ^category: typedef 1eda_graph_traversal_category traversa1_;ategory; typedef int vertlces_size_type; typedef int edges_size_type: typedef int degree_size_type: ( Тип итератора исходящих вершин ) И другие typedef дня итераторов . }: } // namespace Boost Сначала мы напишем функции source!) и target () для концепции IncidenceGraph, которая является частью концепции BfdirectionalGraph. Мы используем тип GRAPH из LEDA в качестве параметра для графа и graph_faits для задания параметра ребра и возвращаемого типа для вершины. Хотя типы LEDA могут быть применены для вершины и ребра, на практике лучше использовать graph traits. Тогда, если при- дется изменить ассоциированный тип вершины или ребра, это можно сделать в од- ном месте внутри специализации graphjraits, а не во всем коде программы. Так как LEDA предоставляет функции sourceO и target!), мы просто их вызываем. ( Получение начальной и конечной вершины ребра для графа из LEDA } « template «class vtype. class etype* typename graph_traits« GRAP+«vtype.etype* >::vertex_descriptor sourcettypename graph_traits< GRAPH«vtype.etype> >; :edge_desc,riptor e. const GRAPH<vtype.etype>& g) ! return sourceCe): 1 // по аналогии для конечной вершины Следующая функция из IncidenceGraph — out_edgesO. Эта функция возвраща- ет пару итераторов по исходящим ребрам. Поскольку в LEDA применяются ите- раторы в стиле STL, их нужно реализовать. Написание итераторов, которые со- вместимы со стандартом C++, может быть сложным процессом. К счастью, в Boost имеется удобная утилита для реализации итераторов — класс iterator_adaptor. Этот класс позволяет пользователю создавать совместимые со стандартами ите- раторы, просто предоставляя классы правил (policy class). В листинге 10.7 приве- ден класс правил для итератора исходящих ребер. В LEDA сам объект-ребро ис- пользуется как итератор. Он имеет функции Succ_Adj_Edge() и P-ed_Adj_Edge() для перемещения к следующему или предыдущему ребру. Листинг 10.7, Правила для итератора исходящих ребер ( Правила для итератора исходящих ребер > struct leda_out_edge_iteretor policies { static void initialize(leda_edgeS ) { ) template «typename Iter* static void increment(IterS 1) { i.based - Succ_Ady Edged .based. (Л. ! template «typename Iter* static void decrement(IterS 1) { i.based - Pred_AdJ_Wge(i based. 0): } template «typename Iter* static 1eda_edge dereference!const IterS 1) { return i.based; ) лродолжекие $
136 Глава 10 • Взаимодействие с другими графовыми библиотеками Листинг 10.7 (продолжение) template «typename Iter> static bool equal (const ’t.er& *. const Iterfi y) { return x.based " у based: } }• Теперь 1 terator_adaptor используется в качестве типа edgejterator. Первые два параметра шаблона для edge_iterator — адаптируемый класс и класс правил. Сле - дующие параметры указывают ассоциированные типы итератора, такие как тип значения и тип ссылки. ( Тип итератора исходяцих вегчшн ; • typedef iterator_adaptor<leoa_ed<;e. leda_out_edge_<terator_policies. leda_edge. const led<i_edge&7 const leda_edge*. std:.forwardJterator~tag. std::ptrdiff_t > out_edge_iterator; После определения итератора исходящих ребер в классе свойств можно опре- делить функцию out edgesO. В следующем определении (листинг 10.8) возвра- щаемое значение должно быть парой итераторов исходящих вершин, так что мы используем std:: pa 1 г и затем grcph_l га i ts для доступа к итераторным типам. В теле функции мы создаем итераторы исходящих ребер, передавая первое ребро для первого итератора и ноль — для второго (в LEDA с помощью нуля обозначается окончание последовательности). Ноль в качестве аргумента к Fi rst_Adj_Edge() го- ворит LEDA, что мы хотим обрабатывать исходящие ребра, а не входящие. Листинг 10.8. Функция out_edges() для LEDA С Функция out_edgesO вля LEDA ) я template «typename v typename £> std-:pair«typename graph_traits< &RAPH«V.E-> >-.-out_edge_tterator, tyfiewe graph_traits< GRAPH<V.E> >: :outTdgej terator > out_edges(typename g-aph_traits< GRAPH«V.E> >: vertex descriptor j. const GRAP'H-'V, £>& c) { typeoef typename greph_trans« GRAPHS.E* out_edge_iterator Iter: return std:-ma£e_pairi Iter (First Mj_Edge(u,O)), Iter(O) ); 1 Остальные типы итераторов и интерфейсных функций пишутся аналогично. Полный код для интерфейса оболочки к LEDA находится в boost/graph/lfida_graph.hpp. В листинге 10.9 мы используем проверку концепций BGL, чтобы быть уверенны- ми в правильности реализации интерфейса с BGL. Эти проверки не тестируют поведение времени исполнения (это тестируется в test/graph.cpp). Листинг 10.9. Проверка концепции реализованного интерфейса ( leda-concept-check.ерр ) ^include «boost.'graph/graph_cqncepts bpp> #i>ulude «boost/grapn/ieca_grach.npp> int main(’ ! using nawtspace boost. typedef G₽APH«in(.. -rrt> Graph. function requires. < Vertex!.istijrap>iConcept<Graph> >i): function_requires < 81 directicnalGraphConcept<Gr3pti> >(); function_rcjqu ires < Ve4PAMurableurdphCon*ept<€raph> >(); function requires « EdgeMurabie4raphConceDt<Graph> ><)• return ГШ SUCCESS
Руководство по производительн В этой главе мы обсудим влияние того пли иного выбранного графа из семейства графов BGL adjacencyj i st на производительность. Цель этой главы — дать пользо- вателям BGL некоторые базовые сведения о том, какие из типов графов могут быть наиболее эффективны в различных ситуациях. Мы представим серию тес- тов, отображающих производительность различных базовых операций несколь- ких разновидностей ddjacency list из BGL. Исследованы скорости выполнения операций над разреженными и плотными графами с использованием двух раз- личных компиляторов (Microsoft Visual C++ и GNU C++). Как основной компонент BGL-графа, adjacency ! :s*. помогает пользовате- лям контролировать фактические структуры данных, применяемые для внут- ренних структур графа. Первые два параметра шаблона, FdgeLis: и Vertex!.1 st, используются для выбора фактических контейнеров для представления по- следовательностей исходящих ребер и вершин соответственно. Пользователи могут применять vecS, lists или setS для EdgeList для выбора контейнеров std::vector, std:; list или std: :set соответственно. Они также могут указать vecS или 11 stS для выбора std:;vector или std- • I ist соответственно в качестве основы. 11.1. Сравнения графовых классов Мы сравнивали производительность различных вариантов adjacency ! 1st. Эк- сперименты покрывают большинство базовых операций над графами: вставка и удаление вершин и ребер, обход графа по вершинам, ребрам и исходящим ребрам каждой вершины. Тесты были выполнены с разреженными и плотными графами малого (100 вершин), среднего (1000 вершин) и большого (10 000 вер- шин) размера. Для разреженного графа число ребер в 10 раз больше числа
138 Глава 11 • Руководство по производительности вершин. Для плотного графа полное число ребер равно квадрату числа вер- шин. Замеры времени проводились на компьютере Dell с двумя процессорами по 733 МГц, с памятью 512 Мбайт. Тесты были продублированы для двух ком- пиляторов: Microsoft Visual C++6.0 и GNU С-^+ 2.95.3 (под cygwin). Для Vi sual C+ + был установлен режим оптимизации по скорости. Для GNU C+ + были установлены опции -03 и - funrol 1 -1 oops. Заметим, что реализация adjacen- cy 1 st использует компоненты из STL, которая поставляется вместе с комп и лятором. В таймере применялась переносимая POSIX-функция clock (), которая имеет довольно низкое разрешение. По этой причине эксперименты выполнялись в цик- ле до тех пор, пока затраченное время не превысило минимальное разрешение по крайней мере в сто раз. Каждый тест был повторен 3 раза, и взято минималь- ное время из этих трех прогонов. Мы заметили, что стандартное отклонение в из- мерениях времени составляет приблизительно 10 %. Далее приведен полный набор графовых типов, использованных в тестах. Так- же указаны аббревиатуры, которые были применены в результирующих диаг- раммах. • vec adjacency_list<vecS, vecS. directedS. proporty<vertex_cHstance_t. int>. property<edge_weight_t. int> > ” • list adJacency_l1st<listS. vecS, directedS, property=vertex_distance_t, int>. property<edge_weight_t. int> - • set adj3cency_list<setS. vecS, directedS. propertycvertex_d1stance_t. int>, property<edge_weight_t. 1nt> • listlist adjacencyJist<listS. lists, directedS. property<vertex_distance_t. int>, property<edge_weight_t. int-» » 11.1.1. Результаты и обсуждение Добавление ребер и вершин В первом эксперименте чередуются вызовы в add yertex() и adc_edge(), пока число ребер графа не достигнет |£ | ребер и [V | вершин. Результаты эксперимента показа ны па рис. 11.1. Победитель в этом эксперименте — один из классов adjacency ! i st с параметром VertexList-11stS. Добавление ребер В этом тесте добавляются |£| ребер к графу, который уже имеет | V | вершин. Ре- зультаты показаны на рис. 11.2. Очевидным победителем для Visual C++ являет- ся adjacency ] 1 st с VertexLi st-vecS, независимо от размера и разреженности графа. При использовании GNU C++ побеждает класс adjacency_list сEdgelist-listS для разреженного графа.
11.1. Сравнения графовых классов 139 Рис. 11.1. Результаты измерения времени для эксперимента с добавлением ребер и вершин Рис. 11.2. Результаты измерения времени для эксперимента с добавлением ребер
140 Глава 11 • Руководство по производительности Удаление ребер В этом тесте добавляются и удаляются \Е | ребер графа с | V | вершинами. Результа- ты показаны на рис. 11.3. Результат неясен для графов малого размера. Однако ясно, что adjacency_list с параметром VertexL1st-setS является победителем для больших графов. Рис. 11.3. Результаты измерения времени для эксперимента с удалением ребер Удаление вершин В этом тесте добавляются | V | вершин и |£ | ребер к графу, а затем удаляются все вершины. Результаты показаны на рис. 11.4. Очевидным победителем является listlist, который специально разработан для этой операции. Другие варианты adjacency l i st имеют очень плохую производительность, так как эта операция вы- полняется не за постоянное время. Очистка вершин В этом тесте сначала добавляются |V | вершин и \Е | ребер к графу, а потом очища- ются и удаляются все вершины. Операция clear vertext) обходит граф, удаляя все ребра, относящиеся к вершине. Результаты показаны на рис. 11.5. При увели- чении размера графа adjacencyjist с параметром VertexL1st-vecS становится по- бедителем.
11.1. Сравнения графовых классов 141 Рис. 11.4. Результаты измерения времени для эксперимента с удалением вершин Плотный,
142 Глава 11 • Руководство по производительное™ Обход вершин В этом тесте обходятся все вершимы графа, читая значение внутреннего свой- ства каждой вершины. Результаты показаны на рис. 11.6. Среди первых трех типов графа нет четкого победителя. Обход вершин был быстрым для этих классов, так как они имеют одинаковый параметр VertexList-vecS. Для боль- ших графов типа listlist обход был медленнее, так как они используют Ve1"- texList=li stS. Обход ребер В этом тесте обходятся все ребра графа, читая значение внутреннего свойства каж- дого ребра. Результаты показаны на рис. 11.7. Явным победителем здесь является aojacencyjist с параметром Edgeлst-vecS. Обход исходящих ребер В этом тесте обходятся все исходящие ребра каждой вершины графа, при счи- тывании значения внутреннего свойства каждой вершины и каждого исходяще- го ребра. Результаты показаны на рис. 11.8. Явным победителем здесь является adjacencyjlst с EdgeL1st=vecS.
11.1. Сравнения графовых классов 143 Нормализованное время выполнения Нормализованное время выполнения Разреженный, Разреженный, Плотный, Плотный, Рис, 11.7. Результаты измерения времени для эксперимента с обходом набора ребер Рис. 11.8. Результаты измерения времени для эксперимента с обходом исходящих ребер
144 Глава И • Руководство по производительности 11.2. Итоги главы Разные комбинации альтернатив являются различными вариантами компромис- са между скоростью обхода и скоростью добавления и удаления. Следующие по- ложения являются результатами наших экспериментов. • Использование vecS для EdgeLi st обычно обеспечивает эффективный обход по исходящим ребрам. • Использование vecS для Vertex!. т st обычно обеспечивает эффективный об- ход множества вершин. • Эффективное удаление вершин поддерживается sets для EdgeLlst. В част- ности, для последовательности исходящих ребер можно было бы исполь- зовать Std:: set. • Для эффективного добавления ребер нужно применять vecS или 11 stS для EdgeLi st. • Если удаление вершин происходит часто, нужно использовать 11 stS для VertexLi st. так как std::1 i st в качестве основы выполняет удаление верши- ны за постоянное время. • Для очистки вершин хорошим выбором для VertexL 1 st является vecS (функ- ция clear_vertex() может использоваться для удаления всех инцидентных ребер).
^••'"Чаййэ Справочное руководство
148 Глава 12 • Концепции BGL Таблица 12.1 {продолжение) Выражение Возвращаемый тип или описание BidirectionalGraph уточняет IncidenceGraph Итерация но входящим ребрам sid;:pair<in_edge_ iterator, in_edge _i',erator> dcgree_ size type degree size .type graph _traits<G>.:in_edge_itenitor inedg<>( v. g) in_dcgive(v.g) dcgree(e, g) AdjacencyGraph уточняет Graph graph_traits<G>::adjacency_ iterator adjacen t _ycrt ices( v, g) VertexListGraph уточняет Graph graph_traits<G>::vrrtex iterator graph_trairs<G>::vr*rtkes size type nuin_vertices(g) vertices(g) EdgeListGraph уточняет Graph graph traits<G>::edge descriptor graph_traits<(J>::edge iteraior graph trai:s<G>::cdges >i/e type inimedges(g) edges(g) source(e, g) target(e. g) AdjacencyMatrix уточняет Graph edge(u, v, g) lliepaintr по смежным вершинам. std::pair<; djacency Jterator, adjact4icyjteral'or> Итерация по набору вершин графа Целый беззнаковый тип для представления числа вершин vertices size type >>td:‘p;iir<‘vertex, iterator, vertex „iterator'» Тип объекта для обозначения ребер Итерация по набору ребер грифа Целый беззнаковый тип для представления числа ребер edges size, type sukpair<vdgc iterator, cdge_iterator> vcrtex_<le.4eript<>r vertex descriptor std:.pair<cdgc descriptor. bool> 12.1.1. Неориентированные графы Интерфейс, который BGL предоставляет для доступа и манипуляции неориенти- рованными графами, тот же, что и для ориентированных графов. Интерфейс один, так как есть определенная эквивалентность между неориентированными и ори- ентированными графами. То сеть любой неориентированный граф может быть представлен как орисн тированный, если неориен; прованное ребро (и, г) заменить двумя ориентированными (и, г) и (v, и). Такой ориентированный граф называет- ся ориентированной версией неориентированного графа. На рнс. 12.2 показан не ориентированный граф и его ориентированная версия. Заметим, что для каждого ребра неориентированного графа ориентированный граф имеет два ребра, Таким образом, BGI, использует функцию uut_ed«ies О (или tn_ edges()) для доступа к ин- цидентным ребрам в неориентированном графе. Аналогично sourceO и target() применяются для доступа к вершинам. Сначала эго .может показаться противоре- чивым фактом. Но. учитывая экиива.'П-н гпесть .между неориентированными и ори-
12.1. Концепции обхода графов 149 оптированными графами, BGL позволяет применять многие алгоритмы как к ори- ентированным, так и к неориентированным графам. Рис. 12.2. Неориентированный граф и его ориентированный эквивалент Следующий пример в листинге 12,1 демонстрирует использованиеout_edges(), sourceO HtargetOc неориентированным >рафом. Хотя обычно направление ребра не принимается во внимание для неориен тированных фафов, в случае применения функции out edgesf) к вершине и начальная вершина для дескриптора ребра все- гда и, а конечная — смежная с и. В функции 1 n_edges (), соответственно, наоборот. Листинг 12.1. Демонстрация некоторых функций для неориентированного графа template <typename UndirectedGraprp тон! undirected graph_demol() ( const int V - 3. Undirectedfiraph undigraph(V): typename graph_traits<Undirett«1Graph>--venex.descriptor zero one. two: typename graph_traits<UndirectedGraoh>'•out_edge_iterator out. out_end: typename graph_traits^UndirectedGraoh>::in_edge iterator in. in_end: zero » vertexes, undigraph}: one - vertex?1. undigraph): two - vertex(2. undigraph). add_edge(zero. one. undigraph); add_edge(zero. two. ur.dlgraph). add_edge(one. two. undigraph): std::cout "исходящие(О)• ” for (tieCout. out_end) - out„edges(zero. undigraph). out '» out_end. ♦♦out) Std::COUt « *OUt; std.:cout « std.:end! « "ахссяцие(О): "; for (tie(1n. in_end) - m_edges(zero, undigraph): in <•= inj?rd: ^in) Std::cout « »in: std: xcut « std: endl: } Вывод будет следующим: исхойящие(О): (0,1) (O.?l входящие(О): (1.0) (2.0) Хотя интерфейс для неориентированных и ориентированных графов одинаков, имеются некоторые отличия в поведении функций, поскольку равенство ребер
152 Глава 12 • Концепции BGL grdpfi_t.rai ts<G>.: di rectedjtategory Теги для этой категории: и итуп ."г*,<'й_*ад. • graph_traits<G>::edge_parallei ^category Описывает, позволяет ли графовый класс осуществлять вставку параллель- ных ребер (ребра с одной и той же парой начальной и конечной вершин). Два тега: allow_paral is!_edge_tag и d • se • kwjwral el _edge_tag. • grdphj;raits<G>:: traversel_category Описывает виды обхода итераторами, которые поддерживает данный фаф. Следующие классы тегов определены: struct mcidencc_graph_tag j }: struct adj.’.cency_graph_tag { 1. struct Mdirectional_graph_ta9 public virtual incidence_graph_tag { }: struct verte>._l :$t_.graph tag i struct edgejist_graph_tag ( i: struct adjacencyjnatri «Лад ( 12.1.3 . IncidenceGraph Концепция IncidenceGraph обеспечивает интерфейс для эффективного доступа к исходящим ребрам каждой вершины графа. Исходящие ребра (out-edges) дос- тупны через итераторы исходящих ребер. Функция out edgesfv, g) по данному дескриптору вершины v и грифу g возвращает пару итераторов исходящих вер- шин. Первый итератор указывает на первое исходящее ребро вершины г. а второй итератор - за конец последовательности ребер. Разыменование итератора исхо- дящих вершин возвращает дескриптор ребра. Инкремент итератора перемещает сто к следующему исходящему ребру, Порядок появления исходящих ребер при итерации не фиксирован, хотя конкретная реализация графа может иметь неко- торое упорядочение. Уточнение для Graph Ассоциированные типы Ниже приведены ассоциированные типы для концепции IncidenceGraph. • graph_traits<G>: :edge_descnptor Дескриптор ребра соответствует уникальному ребру в графе. Он должен быть DefaultConstructible, Assignable и EqualityComparable. • graph_traits<G>•.out_edge_itera tor Итератор исходящих ребер для вершины г обеспечивает доступ к исходя- щим ребрам вершины о. Тип значения итератора — e«ge_aescr.ptor от свое- го графа. Итератор исходящих вершин должен отвечать требованиям Multi- Passlnputlterator. • graph_ira'i t.s<R>. :degree_sizc_type Это беззнаковый целый тип, представляющий число исходящих или инцидент- ных ребер вершины.
12.1. Концепции обхода графов 153 Допустимые выражения Ниже приведены допустимые выражения для концепции IncidenceGraph. • sourcele. g) Тип результата: ver-tex_descriptor. Семантика: возвращает дескриптор вершины и для ребра (и. г), представлен- ного через е. Предусловие: е — допустимый дескриптор ребра графа д. • targetie. д'» Тип результата: vertex_de$o Ънг. Семантика: возвращает дескриптор вершины для ребра п (и, z>). представлен- ного через е. Предусловие: е - допустимый дескриптор ребра i рифа д, • out_edge$(v. д) Тип результата: std: :pa'r<out_edge_iterator.teraror>. Семантика: возвращает пару итераторов, обеспечивающих доступ к исходя- щим ребрам (для ориентированных графов) или инцидентным ребрам (для неориентированных графов) вершины ?>. Вершина п появляется как начальная во всех исходящих ребрах. Вершины, смежные с т«, являются конечными в ис- ходящих ребрах (неважно, ориентированный граф или нет). Предусловие: v —допустимый дескришор вершины графа д. • out_degree(v. д» Тип результата: degree^1 ze_type. Семантика: возвращает число исходящих ребер (для ориентированных гра- фов) или число инцидентных ребер (для неориентированных графов) вер- шины г. Предусловие: v — допустимый дескриптор вершины графа д. Гарантии сложности Функции sourceO, target О и out_edges i) должны выполняться за постоянное вре- мя. Функция o.'jt_degroe() должна выполняться за линейное время по количеству исходящих ребер вершины. 12.1.4 . BidirectionalGraph Концепция BidirectionalGraph уточняет IncidenceGraph: добавляется требование для эффективного доступа к входящим ребрам каждой вершины. Эта концеп- ция выделена из IncidenceGraph, потому как предоставление эффективного доступа к входящим ребрам ориентированного графа обычно требует больше памяти, а многие алгоритмы не требуют доступа к таким ребрам. Для неориен- тированных графового несущественно, так как дополнительная память истре- буется.
154 Глава 12 • Концепции BGL Уточнение для IncidenceGraph Ассоциированные типы Ниже приведен ассоциированный тип для концепции BidirectionalGraph. • graph_traits<G>::in_edge_iterator Итератор входящих ребер для вершины v обеспечивает доступ к входящим ребрам вершины о. Тип значения итератора — edge_descn ptor от своего графа. Итератор исходящих вершин должен отвечать требованиям MultiPassInput- Iterator. Допустимые выражения Ниже приведены допустимые выражения для концепции BidirectionalGraph. • in_edgss(v. 9) Тип результата: std: :palr<ln_edge_i lerator, in_edge_iterator>. Семантика: возвращает пару итераторов, обеспечивающих доступ к входящим ребрам (для ориентированных графов) или инцидентным ребрам (для неори- ентированных графов) вершины v. Вершина с появляется как конечная во всех входящих ребрах. Вершины, для которых вершина о — смежная, являются на чальными во входящих ребрах (неважно, ориентированный граф или нет). Предусловие: v — допустимый дескриптор вершины графа д. • in_degree(v. g) Тип результата: degree_$ize_type. Семантика: возвращает число входящих ребер (для ориентированных графов) и число инцидентных ребер (для неориентированных графов) вершины р. Предусловие: v - допустимый дескриптор вершины графа д. • degreeiv. д) Тип результата: degree_slze_type. Семантика: возвращает число входящих и исходящих ребер (для ориентиро- ванных графов) и число инцидентных ребер (для неориентированных графов) вершины V. Предусловие: v — допустимый дескриптор вершины графа д. Гарантии сложности Функция m_edges() должна выполняться за постоя иное время, функция in_degree() - за линейное время, по числу входящих ребер. 12.1.5 . AdjacencyGraph Концепция AdjacencyGraph определяет интерфейс для доступа к смежным верши- нам. Смежные вершины могут быть также получены как конечные вершины ис- ходящих ребер. Однако для некоторых алгоритмов исходящие ребра не нужны и более удобно получать доступ к смежным вершинам непосредственна.
12.1. Концепции обхода графов 155 Уточнение для Graph Ассоциированные типы Ниже приведен ассоциированный тип для концепции AdjacencyGraph. • graph_traits<G>: adjacency ^iterator Итератор смежности для вершины v предоставляет доступ к вершинам, смеж- ным с вершиной о. Тип значения итератора смежности — дескриптор верши- ны своего графа. Итератор должен отвечать требованиям MultiPassinputiterator. Допустимые выражения Ниже приведено допустимое выражение для концепции AdjacencyGraph. • adjacent_vertices(v, g) Тип результата: std: :pair<adjacency_1terator.adjacency_jterator>. Семантика: возвращает диапазон значений итератора, обеспечивающий дос- туп к вершинам, смежным с вершиной о. Более конкретно: это эквивалентно получению конечных вершин для каждого исходящего ребра вершины р. Предусловие: v — допустимый дескриптор вершины графа д. Гарантии сложности Функция adjacertj/ert 1 ces() должна отрабатывать за постоянное время. 12.1.6 . VertexListGraph Концепция VertexListGraph определяет требования для эффективного обхода всех вершин графа. Уточнение для Graph Ассоциированные типы Ниже приведены ассоциированные типы для концепции VertexListGraph. • graph_traits<G>::vertex_iteratar Итератор вершин (получаемый через ¥егИсе5(д))обеспечивает доступ ко всем вершинам графа. Тип итератора должен отвечать требованиям MultiPassInput- Iterator. Тип значения итератора вершин должен быть дескриптором вершины. • grapn_traits<G>::vertices_size_type Это беззнаковый целый тип, которым можно представить число вершин в графе. Допустимые выражения Ниже приведены допустимые выражения для концепции VertexListGraph. • vertices(g) Тип результата: std: :pair<vertex_iterator. vertex_1terator>,
156 Глава 12 • Концепции BGL Семантика: возвращает диапазон значений итератора, обеспечивая доступ ко всем вершинам графа д. • num_verdces(g) Тип результата: vemces_size_type. Семантика: возвращает число вершин в графе д. Гарантии сложности Функция vert ices О должна отрабатывать за постоянное время. Время выполне- ния функции num vert i ces () должно линейно зависеть от числа вершин. 12.1.7 . EdgeListGraph Концепция EdgeListGraph уточняет концепцию Graph. Дополнительным требова- нием является обеспечение эффективного доступа ко всем ребрам графа. Уточнение для Graph Ассоциированные типы Ниже приведены ассоциированные типы для концепции EdgeListGraph. • graph_traits<G>; :edge_descripr.or Дескриптор ребра соответствует уникальному ребру в графе. Он должен быть DefaultConstructible, Assignable и EqualityComparable. • graph_tralts<G>::edge_iterator Итератор ребер (полученный через edges (g)) обеспечивает доступ ко всем реб- рам графа. Тип edgejterator должен соответствовать требованиям Inputiterator. Тип значения итератора ребер должен быть таким же, как у дескриптора ребер данного графа, • graph_traits<G>::edges_size_type Это беззнаковый целый тип, используемый для представления числа ребер в графе. Допустимые выражения Ниже приведены допустимые выражения для концепции EdgeListGraph. • edges(g) Тип результата: std: ;pa1r<edge_1terator. edge_iterator>. Семантика: возвращает диапазон значений итератора для доступа ко всем реб- рам графа д. • sourceie, g) Тип результата: vertex_descriptor. Семантика: возвращает дескриптор вершины для начальной вершины и ре- бра (ы, г), заданного через е. Предусловие: е - допустимый дескриптор ребра графа д.
12.2. Концепции для изменения графов 157 • L&rgeUe. Тип результата: veri.ex_ce$criptgr. Семантика: возвращает дескриптор вершины для конечной вершины v ре- бра (w, v), заданного черти е. Пртдусловис: е - допустимый дескриптор ребра i-рафа с. • nurn_edgesCg} Тип результата: edges_s i ze_t,ype Семантика: возвращает количество ребер и графе д. Гарантии сложности Функции edges(source( > и ta* де. () должны отрабатывать за постоянное время. Функция nuin_edges() должна выполняться за линейное по числу ребер время. 12.1.8 . AdjacencyMatrix Концепция AdjacencyMatrix уточняет концепцию Graph и имеет дополнительное требование обеспечения эффективного Д<к? гУ,1и к любому ребру в фафе по задан- ным начальной и конечной вершинам. Уточнение для Graph Допустимые выражения Ниже приведено допустимое выражение для концепции AdjacencyMatrix. • edgefu. v, g) Тип результата: stc :р.Г1г<^дс._ртг,СГ|?гС,г_ tool>. Семантика: возвращает пару, состоящую из флага, показывающего, существу- ет ли в графе g ребро между и и v, и дескриптора ребра, если такое найдено. Предусловие: и, v — допустимые для графа g дескрипторы вершин. Гарантии сложности Функция edge:) должна возвращать результат за постоянное время. 12.2. Концепции для изменения графов Этот раздел описывает BGL-интерфейс для .модификации графа, то есть добавле- ния и удаления вершин и ребер, изменения значений закрепленных за вершинами и ребрами свойств. Как и концепции для обхода графа, концепции модификации графа раздроблены на множество п]хх:тых, для того чтобы предоставить разработ- чикам алгоритмов хороший выбор концепций для описания требований алгорит- мов. В табл. 12.2 приведены допустимые выражения и ассоциированные типы для каждой концепции, а на рис. 12.3 показаны отношения уточнения межлу концеп- циями модификации графов. Некоторые из концепций, изображенных на рис. 12.1, также фигурируют на рис. 12.3, но все их отношения уточнения пропущены.
158 Глава 12 • Концепции BGL VertexMutableGraph------► VertexMutablePropertyGraph EdgeListGraph --------► MutableEdgeListGraph Рис. 12.3. Концепции для изменения графов и отношения уточнения между ними Таблица 12.2. Краткая сводка концепций для изменения графа и доступа к свойствам графа Выражение Возвращаемый тип или описание VertexMutableGraph уточняет Graph addvertex(g) remove_vertex(v, g) EdgeMutableGraph уточняет Graph clear_vertex(v, g) add_edge(u, v, g) remove_edge(u, v, g) remove_edge(e, g) vertex_descriptor void void std::pair<edge_descriptor, bool> void void MutablelncidenceGraph уточняет IncidenceGraph и EdgeMutableGraph remove_edge(eiter, g) remove_out_edge_if(u. p, g) void void MutableBidirectionalGraph уточняет MutablelncidenceGraph и BidirectionalGraph remove_edge(eiter, g) remove_out_edge_if(u, p. g) void void MutableEdgeListGraph уточняет EdgeMutableGraph и EdgeListGraph remove_edge_if(p, g) PropertyGraph уточняет Graph property map<G, PropertyTag>::type void Тип для изменяемого отображения свойства вершины property_map<G, Property- Tag>::eonst type Тип для неизменяемого отображения свойства вершины
12.2. Концепции для изменения графов 159 Выражение Возвращаемый тип или описание get(ptag, g) Функция для получения объекта-отображения свойства вершины get(ptag. g, х) Получить значение свойства для вершины или ребра х put(ptag, g. х, v) Положить значение свойства для вершины или ребра х равным v VertexMutablePropertyGraph уточняет VertexMutableGraph и PropertyGraph add_vertex(vp, g) vertex_descriptor EdgeMutableFropertyGraph уточняет EdgeMutableGraph и PropertyGraph add edge(u. v, ep, g) std::pair<edge descriptor, bool> 12.2.1. VertexMutableGraph Концепция VertexMutableGraph — граф, модифицируемый по множеству вершин (vertex mutable), может быть изменен добавлением или удалением вершин. Уп- равление памятью выполняется при реализации графа. Пользователь графа толь- ко вызывает add_vertex() и remove_vertex(), а реализация делает все остальное. Уточнение для Graph, DefaultConstructible Допустимые выражения Ниже приведены допустимые выражения для концепции VertexMutableGraph. • add_vertex(g) Тип результата: vertex_descriptor. Семантика: добавляет новую вершину к графу. Возвращает дескриптор вер- шины для новой вершины. • remove_vertex(u. g) Тил результата: void. Семантика: удаляет вершину и из множества вершин графа. Предусловия: и — допустимый дескриптор вершины графа g и нет ребер, ин- цидентных вершине и. Функция cl ear_vertex() может быть использована для удаления всех инцидентных ребер. Постусловия: num vertices(g) уменьшается на единицу: вершина и больше не находится во множестве вершин графа, а ее дескриптор больше не может быть использован. Гарантии сложности Концепция VertexMutableGraph дает следующие гарантии сложности: • вставка вершины происходит гарантированно за амортизированное посто- янное время; • удаление вершины должно происходить не дольше чем за О(МГ | +1V |).
160 Глава 12 • Концепции BGL 12.2.2. EdgeMutableGraph Концепция EdgeMutableGraph — граф, модифицируемый по множеству ребер (edge mutable), может быть изменен добавлением или удалением ребер. Удивление памятью выполняется при реализации графа. Пользователь графа только вызы- вает add_edgc() и remove_edgei), а реализация делает все остальное. Уточнение для Graph Допустимые выражения Ниже приведены допустимые выражения для концепции EdgeMutableGraph. • add_edge(u, v. g) Тип результата: std: :pair<edge_descriptor. bool >. Семантика: пытается вставить ребро (и, в) в граф, возвращая вставленное реб- ро или параллельное ребро и флаг, который показывает, было ли ребро встав- лено. Эта операция не должна «портить» дескрипторы вершин или итераторы по вершинам графа, но она может сделать недействительными дескрипторы ребер или итераторы по ребрам. 11орядок, в котором новое ребро появится при обходе итераторами ребер qэaфa, не определи н. Предусловие: (и, ^являются вершинами графа. Постусловие: (u, v) находится в наборе ребер графа. Возвращенный дескриптор ребра имеет вершину и в качестве начальной и г - в качестве конечной. Если граф позволяет иметь параллельные ребра, то возвращаемый флаг будет исти ной. Если параллельные ребра в графе не разрешаются и ребро (u, v) уже есть в графе, флаг будет иметь значение «ложь». Если ребра (и, р) еще не было в гра- фе, флаг будет иметь значение «истина». • remove_edge(u. v, g) Тип результата: void. Семантика: удалить ребро (и, v) из графа. Если граф позволяет иметь парал- лельные ребра, функция удаляет все вхождения (и, о) в граф. Предусловие: (и,о) является ребром из графа д. Постусловие: (и, г) больше не является ребром графа q. • remove_edge(e. g) Тип результата: void. Семантика: удалить ребро е из графа. Предусловие: е является ребром графа д. Постусловие: е больше не является ребром графа д. • dear_vertex(u, g) Тип результата: void. Семантика: удалить все ребра, инцидентные вершине и графа. Предусловие: и является допустимым дескриптором вершины для д. Постусловие: и не участвует ни в одном из ребер графа в качестве начальной или конечной вершины.
12.2. Концепции для изменения графов 161 Гарантии сложности Концепция EdgeMutableGraph даст следующие гарантии сложности: • «ставка ребра должна происходить за амортизированное постоянное время или за вре.мя, равное 0(logQF !/| VQ), если выполняются проверки для пре- дотвращения добавления параллельных ребер; • удаление (зебра выполняется гарантированно за время O(l£ I); • очистка вершины от инцидентных ребер выполняется не более чем за О([Е |J- + ГП). 12.2.3. MutablelncidenceGraph Концепция MutablelncidenceGraph обеспечивает возможность удаления ребер из списка исходящих ребер вершины. Уточнение для IncidenceGraph и EdgeMutableGraph Допустимые выражения Ниже приведены допустимые выражения для концепции MutablelncidenceGraph. • remove,edge(етter. g) Тип результата: void. Семантика: удаляет ребро, на которое указывает ei нт. из графа, где eiter — итератор исходящих вершин графа. Предусловие: *eiter является ребром графа. Постусловие: *eiter больше не являе!ся ребром графа д. • remove_out_edge_if(u. р. д) Тип результата: void. Семантика: удаляет все исходящие ребра вершины и, для которых предикат р возвращает истинное значение. Эго выражение требуется, только если грае]) также моделирует IncidenceGraph. Предусловие: и является допустимым дескриптором вершины графа а. Постусловие: р возвращает ложь для всех исходящих ребер вершины н и все исходящие ребра, для которых г, давал ложь, до сих пор находятся в графе. Гарантии сложности Концепция MutablelncidenceGraph дает следующие гарантии сложности: • функция remove_edge() должна выполняться за постоянное время; • функция remc<vp_out_3ckje_i Г{) должна выполняться за линейное по числу исходящих ребер время. 12.2.4. MutableBidirectionalGraph Концепция MutableBidirectionalGraph определяет интерфейс для удаления ребер из списка входящих ребер вершины.
162 Глава 12 • Концепции BGL Уточнение для BidirectionalGraph и MutablelncidenceGraph Допустимые выражения Ниже приведено допустимое выражение для концепции MutableBidirectionalGraph. • iwove__in_edge_ifiv, х g) Тип результата: void. Семантика: удаляет все входящие ребра вершины v, для которых р возвращает истину. Предусловие: v — допустимый дескриптор вершины графа д. Постусловие: р возвращает ложь доя всех входящих ребер вершины и и все входящие ребра, для которых р давал ложь, до сих пор находятся в графе. Гарантии сложности Концепция MutableEdgeListGraph дает следующую гарантию сложности: • функция remove_ in_etigc-_iff выполняется за линейное но числу входящих вершин время. 12.2.5. MutableEdgeListGraph Концепция MutableEdgeListGraph предоставляет возможшютьудалять ребра из списк а ребер графа. Уточнение для EdgeMutableGraph Допустимые выражения Ниже приведено допустимое выражение для концепции MutableEdgeList- Graph. • remove_edge_if(э. g) Тип |юзультата: vo’d. Семантика: удаляет все ребра из графа д, для которых р возвращает истину. Постусловие: р возвращает ложь для всех ребер в графе и граф Л« сих пор со- держит все ребра, для которых р первоначально возвращал ложь. Гарантии сложности Концепция MutableEdgeListGraph дает следующую гарантию сложности: • функция remove_edge_i f() должна выполняться за линейное время по числу ребер в графе. 12.2.6. PropertyGraph Концепция PropertyGraph - 1раф, который имеет некоторое свойство, связанное с каждой из вершин пли ребер графа. Так как данный граф может иметь несколь- ко свойств, связанных с каждой вершиной или ребром, для обозначения свойства могут быть использованы теги. В описании требований PropertyTag ----- это тип тега,
12.2. Концепции для изменения графов 163 a tag — объект типа PropertyTag. Граф предоставляет функцию, которая возвраща- ет объект—отображение свойства. Уточнение для Graph Ассоциированные типы Ниже приведены ассоциированные типы для концепции PropertyGraph. • property_map<G. PropertyTag*::type Тип отображения свойства для свойства, заданного PropertyTag. Этот тип дол- жен быть изменяемым LvaluePropertyMap с таким же типом ключа, как у де- скриптора вершины или ребра графа. • propertyjw<G. PropertyTag*::const_tyре Тип константного отображения свойства для свойства, заданного PropertyTag. Этот тип должен быть неизменяемым LvaluePropertyMap с таким же типом клю- ча, как у дескриптора вершины или ребра фафа. Допустимые выражения Ниже приведены допустимые выражения для концепции PropertyGraph. • gettptag. g) Тип результата: property_map<G. PropertyTag*::type, если g является изменяе- мым — и property_map<G. PropertyTag*: :const_type - в противном случае. Семантика: возвращает отображение свойства для свойства, заданного типом PropertyTag. Объект ptag используется только ради своего типа. • getfptag. g. х) Тип результата: property_traits<PMap>: :value_type. Семантика: возвращает значение свойства (заданного типом PropertyTag), свя- занного с объектом х (вершина или ребро). Объект ptag используется только ради своего типа. Эта функция эквивалентна дет.(getlptag. д). х). Гарантии сложности Функция get() должна выполняться за постоянное время. 12.2.7. VertexMutablePropertyGraph Концепция VertexMutablePropertyGraph - это VertexMutableGraph и PropertyGraph с до- полнительными функциями для указания значений свойств при добавлении вер- шин к графу. Уточнение для VertexMutableGraph и PropertyGraph Ассоциированные типы Ниже приведен ассоциированный тип для концепции VertexMutablePropertyGraph. • vertex_property<G*::type Тип объекта-свойства, закрепленного за вершиной.
164 Глава 12 • Концепции BGL Допустимые выражения 11иже приведено допустимое выражение для концепции VertexMutablePropertyGraph. • add_vertex(vp. g) Тип результата: vertexjJescr’.ptor. Семантика: добавляет новую вершину к графу и копирует vp в объект-свойство для повой вершины. Возвращается дескриптор вершины для новой вершины. Гарантии сложности Концепция VertexMutablePropertyGraph дает следующую гарантию сложности: • edd_vertex() гарантированно выполняется за амортизированное постоянное время. 12.2.8. EdgeMutablePropertyGraph Концепция EdgeMutablePropertyGraph — это EdgeMutableGraph и PropertyGraph с до- полнительными функциями для задания значений свойств при добавлении ребер к графу. Уточнение для EdgeMutableGraph и PropertyGraph Ассоциированные типы Ниже приведен ассоциированный тип для концепции EdgeMutablePropertyGraph. • edge_property<G>..type Тип объекта-свойства. закрепленного за ребром. Допустимые выражения Ниже приведено допустимое выражение для концепции EdgeMutablePropertyGraph. • add_edge(u. v, ер. g) Тип результата: std. :pair<edge_de$cr1ptor, tool*. Семантика; вставляет ребро (ы, с) в граф и копирует объект ер в объект-свой- ство для этого ребра. Предусловие: ц, v являются допустимыми дескрипторами вершин графа д. Гарантии сложности Концепция EdgeMutablePropertyGraph дает следующую гарантию сложности: • вставка ребра должна происходить либо за амортизированное постоянное время, либоза O(log(|£ |/| V |)). если при вставке проверяется недопустимост ь добавления параллельных ребер. 12.3. Концепции посетителей Концепции посетителей (visitor concepts) играют такую же роль в BGL, как и функ- торы (functors) в STL. Функторы предоставляют механизм для расширения алго- ритмов. Посетители позволяют пользователям вставлять свои собственные one-
12.3. Концепции посетителей 165 рации в различных точках графового алгоритма. В отличие от алгоритмов STL алгоритмы на графах обычно имеют несколько событийных точек, в которые пользователь может нсталить обратный вызов (callback) через функтор. Таким образом, посетители имеют не единственный метод operator!), как у функтора, а несколько методов, соответствующих различным событийным точкам. В этом разделе мы определим концепции посетителей для основных алгоритмов BGL. Как функциональные объекты в STL, посетители передаются по значению в алгоритмы BGL Это означает, что нужно быть очень осторожным при сохране- нии состояния в объектах-посетителях. Обозначения, использованные к этом разделе, приведены ниже: • V — тип, моделирующий концепцию посетителя; • vis — объект типа V; • G — тип, являющийся моделью Graph; • g — объект типа G; • е — объект типа grapti_trait.3<G^: :edge_desc-iptor: • s. ч — объекты типа grdph_tn;ts<G>-ve'-te»:_de5.cr'pt.or 12.3.1. BFSVisitor Концепция BFSVisitor определяет интерфейс посетителя для поиска в ширину breadth_fi rst_seardi(). Пользователи могут определить классе пнте})фсйсом BFSVisitor и передавать объект этого класса алгоритму breadtki / > -sl_sea rch ), дополняя дей- ствия, производимые во время поиска по графу. Уточнение для CopyConstructible Допустимые выражения Ниже приведены допустимые выражения для концепции BFSVisitor. • vis.rmtiah;e_vertex(u. g) Тип результата: void. Семантика: вызывается для каждой вершины графа перед началом поиска в графе. • vis.discover_vertexru. g) Тип результата: void. Семантика: вызывается, когда алгоритм встречает некоторую вершину и впер- вые. Все другие вершины, более близкие к исходной вершине, были уже про- смотрены, а вершины, расположенные дальше от исходной, - еще нет. • vls.exaTnnejsdgele. а) Тип результата: void. Семантика: вызывается для каждого исходящего ребра каждой, только что по- сещенной вершины. • vis.tree_ecge<е. д) Тип результата: vtrd.
166 Глава 12 • Концепции BGL Семантика: если рассматриваемое ребро е входит в дерево поиска, вызывается эта функция. Вызов функции всегда предваряется вызовом функции examlne_edge(). • vis. noC-t.ree_edge(e. g) Тип результата: void. Семантика: если рассматриваемое ребро е не входит в дерево поиска, вызыва- ется эта функция. Вызов функции всегда предваряется вызовом функции exami ne_edgeO. Для ориентированных графов такое ребро должно быть либк обратным, либо поперечным, для неориентированных — поперечным. • vis.gray_target(e. g) Тип результата: void. Семантика: эта функция вызывается, если рассматриваемое ребро — ребро цикла и если его конечная вершина окрашена в серый цвет во время расемо трения. Вызов функции всегда предваряется вызовом функции cycle edae: ) Серый цвет означает, что ребро находится в данный момент в очереди. • vis.black targeUe. g) Тип результата: void. Семантика: эта функция вызывается, если рассматриваемое ребро является ребром цикла и если его конечная вершина окрашена в черный цвет во время просмотра. Вызов функции всегда предваряется вызовом функции cycle edge:). Черный цвет означает, что вершина уже была удалена из очереди. • vis.finish_vertex(u. g) Тип результата: void. Семантика: эта функция вызывается для вершины после того, как все ее исхо дящие ребра были добавлены к дереву поиска и все смежные вершины про смотрены (но перед тем, как были рассмотрены их исходящие ребра). 12.3.2. DFSVisitor Концепция DFSVisitor определяет интерфейс посетителя для поиска в глубину dept.h_f 1 rst_search(}. Пользователи могут определить класс с интерфейсом DFSVisito-' и передавать объект этого класса алгоритму depth_first_search( 1, дополняя дей- ствия, производимые во время поиска по 1-рафу. Уточнение для CopyConstructible Допустимые выражения Ниже приведены допустимые выражения для концепции DFSVisitor. • vis.initialize_vertex(u. g) Тип результата: void. Семантика: вызывается для каждой вершины графа перед началом поиска в графе. • vis.start_vertex(s. g) Тин результата: void.
12.3. Концепции посетителей 167 Семантика: вызывается для исходной вершины перед началом поиска в графе. • vis.discover_vertex(u. g) Тип результата: void. Семантика: вызывается, когда алгоритм встречает некоторую вершину и впервые. • vis.examine_edge(e. g) Тип результата: void. Семантика: вызывается для каждой исходящей вершины после ее посеще- ния. • vis.tree_edge(e. g) Тип результата: void. Семантика: вызывается для каждого ребра, когда оно становится частью дере- ва поиска. • vis.back_edge(e. g) Тин результата: void. Семантика: вызывается для обратных ребер графа. Для неориентированного графа имеется некоторая неопределенность между древесными ребрами и об- ратными ребрами, поскольку (и, о) и (», и) являются одним и тем же ребром, но обе функции tree_edge() и back_edge() вызываются. Одним нз способов раз- решения этой неопределенности является запись древесных ребер, а затем от- брасывание обратных ребер, которые уже отмечены как древесные. Простым способом записывать древесные ребра является запись предшественников в функции tree_edge(). • vis.forward_or_cross_edge(e. g) Тип результата: void. Семантика: вызывается для прямого или поперечного ребра в графе. Этот ме- тод никогда не вызывается при поиске в неориентированном графе. • vl$.finlsh_vertex(u. g) Тип результата: void. Семантика: вызывается для вершины и после того, как fim$h_vertex() была вызвана для всех вершин дерева поиска с корнем в вершине и. Если вершина и является листом дерева поиска, функция f ini sh vertexO вызывается для вер- шины и после просмотра всех исходящих из и ребер. 12.3.3. DijkstraVisitor Концепция DijkstraVisitor определяет интерфейс посетителя для di jkstra_shortest_- paths () и подобных алгоритмов. Пользователь может создать класс, согласованный с этим интерфейсом, и затем передать объекты класса в di jk$tra_shortest_paths () для расширения действий, производимых во время поиска по графу. Уточнение для CopyConstructible
168 Глава 12 • Концепции BGL Допустимые выражения Ниже приведены допустимые выражения для концепции Dijkstra Visitor. • vis.discover_vert<?x(u. g) Тип результата: void. Семантика: вызывается, когда алгоритм встречает некоторую вершину и впервые. • vis.examinc_edqe(e. q) Тип результата: void. Семантика: вызывается для каждого исходящего ребра каждой вершины пос- ле ее посещения. • vi$.edge_relax<?c(e. g) Тип результата: vo'd. Семантика: пусть (м, о) — это ребро е. d - отображение расстояний, а ® — ото- бражение весов. Если во время обхода «Ди] + w(u, n) < rf[n], то ребро требует релаксации (его расстояние сокращается) и вызывается этот метод. • vi$.edge_nol__relaxed(e, g) Тип результата: void. Семантика: если во время обхода ребро не требует релаксации, то вызывается этот метод. • vis.finish_vertex(u. g) Тип результата: void. Семантика: вызывается для вершины после того, как все ее исходящие ребра были добавлены к дереву поиска и все смежные вершины были просмотрены (но перед тем, как были рассмотрены их исходящие ребра). 12.3.4. BellmanFordVisitor Концепция BellmanFordVisitorопределяет интерфейс посетителя для bel lman_fora_ - shortest pathsO. Пользователь может создать класс с интерфейсом BellmanForc- Visitor и затем передать объекты класса в bel 1 man_fard_shortest_paths () через пара- метр v । si 1 or () для расширения действий, производимых во время поиска по графу. Уточнение для CopyConstructible Допустимые выражения Ниже приведены допустимые выражения для концепции BellmanFordVisitor. • v 1 s. I 11 a 11 ze_vertex( s. gi Тип результата: void. Семантика: вызывается для каждой вершины графа перед началом поиска в графе. • vis oxamintJ_edge(o, g) Тип результата: void. Семантика: вызывается для каждого ребра графа num_vertices(g) раз.
12.3. Концепции посетителей 169 • vis.edge_reldxed(e. g) Тип результата: void. Семантика: пусть (и, v) — ребро е, d - отображение рас стояний, w — отобра- жение весов. Если d[u] + ®(u, п) < то ребро требует релаксации (его рас- стояние сокращается) и вызывается этот метод. • vis .edge_not_relaxed(e, g) Тип результата: void. Семантика: если во время обхода ребро не требует релаксации (ем. выше), то вызывается этот метод. • vis .edgejmninrizedje. g) Тип результата: void. Семантика: после num_vertices(g) итераций по набору ребер графа делается одна последняя итерация для проверки того, каждое л и ребро было минимизирова- но. Если ребро минимизировано, то вызывается эта функция. Ребро (и. «) ми- нимизировано, если (/[и] + №(u, v) dlv\- • vis.edge_not_minimized(e, g) Тип результата: void. Семантика: если ребро не минимизировано, то вызывается эта функция. Это случается, когда в графе есть от рицательный цикл.
Алгоритмы BGL 13.1. Обзор В этой главе представлена детальная информация по применению всех алгорит- мов в Boost Graph Library. Обобщенные алгоритмы BGL разделены на следующие категории: 1. Основные алгоритмы поиска. 2. Кратчайшие пути. 3. Млнималыюе остовное дерево. 4. Компоненты связности. 5. Максимальный поток. 6. Упорядочение вершин. Все алгоритмы реализованы как шаблоны функций, где тип графа является параметром шаблона. Это позволяет использовать функцию с любым типом гра- фа, который моделирует требуемые концепции. Описание каждого алгоритма со- держит список требуемых графовых концепций, а описание каждого графового класса включает список концепций, которые граф моделирует. По перекрестным ссылкам через концепции можно определить, какие типы графов с какими алго- ритмами могут быть использованы. Кроме того, алгоритмы иногда параметризуются отображениями свойств, как например, отображение расстояний для алгоритмов кратчайших путей. Это дает пользователю контроль над тем, как свойства хранятся и извлекаются. Алгорит- мы также параметризованы по типу посетителя, что позволяет пользователю за- дать обратные вызовы (call backs), которые будут выполнены в определенных событийных точках алгоритма.
13.1. Обзор 171 13.1.1 . Информация об алгоритме Информация об алгоритме будет представлена в виде следующих подразделов; «П(ютотипы», «Описание», «Где определен», «Параметры», «Именованные па- раметры», «Предусловия», «Сложность». «Пример». Прототипы Раздел справочника но каждому алгоритму начинается с прототипа функции. По именам параметров шаблона обычно можно судить о назначении параметра, а иногда также о требуемой параметром концепции. Однако точные требования для каждого параметра даны в разделе описания параметров. Последний параметр для многих функций — bg1_nam?d_ params. Он служит для под- держки именованных параметров, что описано в разделе 2.7 и также рассматривается здесь. Если после params написано » е: i defau 1 t.s, то для всех именованных пара- метров имеются значения по умолчанию, и эти параметры могут быть опущены. Описание В описании функции мы определяем задачу, которую решает эта функция, объяс- няем терминологию теории графов или идеи, которые необходимы для понима- ния задачи. Затем мы описываем семантику функций с точки зрения ее воздей- ствия на параметры. Где определен В этом разделе указан заголовочный файл, который должен быть включен в про- грамму (с помощью #1 пс исе) для пользования функцией. Параметры Здесь приводится список всех обычных параметров функции (именованные рас- смотрены в следующем разделе). Обычные параметры обязательны (для них нет значений по умолчанию). Каждый параметр попадает в одну из следующих категорий: • IN параметры читаются функцией и используются для получешгя информации. Функция никак не изменяет эти параметры: • OUT параметры записываются функцией. Результаты работы функции хранятся в OUT-парамстрах; • UTU параметры требуются для работы алгоритма, однако содержимое объектов, используемых как UTIL-параметры, обычно не интересно для пользователя. Эти параметры обычно как читаются. так и записываются. Именованные параметры Как сказано в разделе 2,7, BGL использует специальную технику для повышения удобства работы с функциями с большим набором параметров когда многие
172 Глава 13 • Алгоритмы BGL параметры имеют значения по умолчанию. В этом разделе перечисляются все име- нованные параметры для функции, с использованием такой же классификации, что и для обычных параметров. Кроме того, для каждого именованного параметра приведено значение по умолчанию. Предусловия В этом разделе мы описываем любые предусловия для функции. Обычно это вклю- чает требования к состоянию параметров отображений свойств. Сложность Временная сложность для каждого алгоритма дана в 0-обозначениях. Сложность по памяти всегда не больше О([ V|). если не указано обратное. Пример Для демонстрации использования алгоритма приводится простой пример. 13.2. Базовые алгоритмы 13.2.1. breadth_first__search template -'typename Graph, typename P. typename T. typename R> vtiia breadth Jtrst_search(Greph& g, typenane graph_tratts<Graph>.- .rt-rtftxjtescr iptnr s. const bgl_named_paran!5«--P, T params' Функция breadth_first_$earc‘n( i выполняет поиск в ширину [31] ориентиро- ванного или неориентированного графа. При поиске в ширину сначала посеща- ются вершины, которые расположены ближе к исходной вершине, а потом более дальние вершины. В этом контексте расстояние определяется как число ребер в кратчайшем пути от исходной вершины. Функция breadth_first__search() может использоваться для вычисления кратчайших путей из одной вершины ко всем достижимым вершинам и длин соответствующих кратчайших путей. Определе- ния, связанные с алгоритмом поиска в ширину, и детальный пример приведены в разделе 4.1. Поиск в ширину использует две структуры данных для реалишщии обхода: цветную метку для каждой вершины и очередь. Еще не просмотренные вершины окрашены белым, серым обозначены пройденные вершины, у которых еще есть смежные белые. Черные вершины - уже посещенные вершины, причем вершины, смежные с данными, окрашены в серый или черный цвет. Алгоритм продолжает работу, удаляя вершину и из оче^юди и исследуя каждое исходящее ребро (и, ?Л. Если смежная вершина г ранее не посещалась, она окрашивается в серый цвет и помещается в очередь. После рассмотрения всех исходящих ребер вершина и окрашивается в черный цвет и процесс повторяется. Псевдокод для алгоритма по- иска в ширину приведен ниже. В псевдокоде показаны вычисления предшествен- ников р, отметки посещения d и отметки окончания обработки г. По умолча- нию функция preadtn_f-ir$t_searcn() не вычисляет эти свойства, однако имеются предопределенные посетители, которые могут быть исполыюваны для этого.
13.2. Базовые алгоритмы 173 ПОИСК_В_ШИРИНУ(<7, S) для каждой вершины и е F[G’] со/ог[ы]«- БЕЛЫЙ d[u\ <- оо «- и > инициализировать вершину и colot\s\ <- СЕРЫЙ ф] <- О В_ОЧ ЕРЕДЬ( Q, s) iioica(Q*0) и«-ИЗ_ОЧЕРЕДИ(0 для каждой вершины v е Л<ф] если (color[»] - БЕЛЫЙ) color\v] <- СЕРЫЙ ф'] <- d[u\ + 1 ZT | С’] «- и В_ОЧЕРЕДЬ(0. v) иначе если (то/оф>] - СЕРЫЙ) е> посетить вершину .V > рассмотреть вершину и t> рассмотреть ребро (и, v) о (и, v) — древесное ребро о посетить вершину v t> (ы, v) — не древесное ребро t> (и. ?’) имеет серый конец иначе t> (м, г>) имеет черный конец culor[u] ЧЕРНЫЙ возвратить (4л) о завершение обработки вершины и Функция breadth_first_searchO может быть расширена действиями опреде- ленными пользователем, которые вызываются в некоторых событийных точках. Действия должны быть представлены в форме объекта-посетителя, то есть объ- екта. отвечающего требованиям BFSVisitor. В указанном выше псевдокоде собы- тийные точки обозначены в комментариях. По умолчанию функция breadth_ f rsr_search() не производит никаких действий, даже не записывает расстояния или предшественников. Однако ;кго может быть легко добавлено определением посетителя. Где определен Алгоритм поиска в ширину находится в boost/graph/breadth_first_search.hpp. Параметры Ниже приведены параметры функции breadth_f i rst_5earch( • IN: 6raph& g Ориентированный или неориентированный граф, который должен быть моде- лью VertexListGraph и IncidenceGraph. • IN: vertex_descriptor s Исходная вершина, с которой начинается поиск.
174 Глава 13 Алгоритмы BGL Именованные параметры Ниже приведены именованные параметры функции breadth_first_search(). • IN: vIsitorCBFSViSHor vis) Объект-посетитель, который активизируется внутри алгоритма в событийных точках, указанных в концепции BFSVisitor, По умолчанию: default_bfs_visitor. • U1H/0UT-. color jnapCColorMap color) Используется алгоритмом для отслеживания продвижения по графу. Тип Color Мар должен быть моделью ReadWritePropertyMap, тип ключа - дескриптор вер- шины графа, тип значения color map должен быть моделью ColorValue, По умолчанию: iteratorjaropsrtyjnap, созданный из вектора std:: vector с эле- ментами типа default_color_type размером nun_vertices(). Он использует 1 jnap для отображения индексов. • IN: vertex_index_map(Vertex!ndexMap i_map) Отображает каждую вершину в целое число из диапазона [0, |V|). Этот пара метр необходим только при использовании отображения свойства цвета по умолчанию. Тип VertexlndexMap должен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом. В качестве типа ключа отображения должен задаваться дескриптор вершины графа. По умолчанию: getlvertexjndex. g). • UHL: buffer(Bufter& Q) Очередь используется для определения порядка, в котором будут рассматрп ваться вершины. Если используется очередь вида FIFO («первым вошел - первым вышел»), обход графа будет происходить в обычном для поиска в ши- рину порядке. Могут быть использованы и другие типы очередей. Например, алгоритм Дейкстры может быть реализован г использованием очереди по прио- ритету. Тип Buffer должен моделировать концепцию Buffer. По умолчанию: boost:: queue. Предусловия Очередь должна быть пуста. Сложность Временная сложность порядка О(|Е{ + |V'). Пространственная сложность в наи- худшем случае равна 0(1 VI,), Пример В примере (листинг 13.1) показано применение алгоритма поиска в ширину для графа, изображенного на рис. 13.1. Древесные ребра поиска в ширину обозначены черными линиями. Программа записывает порядок, в котором поиск в ширину посещает вершины графа. Исходный код для этого примера находится в файле example/bfs-example.cpp.
13.2. Базовые алгоритмы 175 Рис. 13.1. Поиск в ширину на графе истинг 13.1. Применение алгоритма поиска в ширину ( Посетитель записи времени для поиска в ширину ) « template « typename TwieMap > class bfs_time_visncr • public de*au'.t_bfs_yisitor { typedef typename prooerty_traits < TimeMap ’rvaiue_tyr>e f. dub Ис: bfs_tTme_visitcr‘fTine!i!ap |:map. T & t're_t'>meBapttmap), i } tempiete"* typename vertex, typename Graph > void discover_vertex(Vertex u. const Graph & g! const ( putim timemap. u. ): } TimeHap m timenap; I & ) ( ofs-example cpp ) .fineIude «bcost/graptVadjacencyJ'st npp> linclude «tioost/graph/breadth first...search hpp> f Ис I ude <boost/pendi ng/i nd i r-ect...anp. hpp> finc’ude <boost/pereh ng/integer j'arige hpp> include <iostream> using namespace boost: ( Посетитель записи времен;- апя поиске в ширину ; int rainc) { using namespace boost; // Выбрать графовый тип, который ми будем использовать typedef adjacency/: 1st < vecS. vecS. undirectebS > graprj.. г* Подготовить идентификаторы вершин и имена enun ( г. s. t. u. v, w. x. у. fi }. const char *name - vstuvwxy’': // Указать ребра графа typedef stfl::pair int. int >E: E edge_array[i - { E{r. s;_ E(r. v). Ets. w), tlw. ri. Eiw, r>, E(w. xi. E(x. t). Ett. u). L(x. yi. E(u. y) S ; // Создать графовый объект const int n_edges - sizeof(edge_arrayl / sizeof(E): г^цалжшие s
176 Глава 13 • Алгоритмы BGL Листинг 13.1 {продолжение) typedef g«'apn_tr8its<Graph_t>. vertices_size_tyi« v_Hz»_t; graph t g(edge_array, edge_arra.y + n_edges. v_st,-e_ t!H>)~ I1 Определения гипсе typedef graph_t.rait5 < gc,sph_t > •vei4ex_cescriptcr vertex; typedef graph traits < graph_t verticts_size sype S’zc- typedef Size* liter. if Вектор дм хранения свойсш "арен» посещения" дли каждой вершины std. vector ' Size > dtinieMuffl^vert’Lesign; Size I.line - 0. Dfs_tin®,yisnor < Size * >vis(fcitimelO]. times. !rea<lth_first_search(g. vertex('.. g). visitorfvis;j. !' Испмьзовате stdiisorr для сортировки вериин по времени посещения std; ’vector<grapn_traits<graph_t>: :vertices_size type > discover_oraerfNi: ir!teger_range < tnt >range(0. N), std;.содуirange.beginO. range.end< >. discover.oraer.peginO 1; std: .sorttfliscover_or(fer beyint! discover order endO. indfrect_cmp * liter, std Hess < Size > ^;‘Sdtimef0]>): std::CGut <-< "порядок посещенм: ". for ; int i “0. i < N: j std’ :cout « name[discover_order[.i]] « " std;,cout « 5td::endl. return EXIT SUCCESS } Вывод будет таким; порядок посещения: s г w v t х ... у 13.2.2. breadth_first_visit template <typename IncidenceGraph typename P. tyo<jna.re T. typename R> void brfradthjdrst_visi’tlr.cidenceGrapnA g, typename graph_traits<lncidenceGraph>- vertexjlescn'itor s. const ogi_nained_params«P, T, R>& params>. Эта функция аналогична breadth__firsr_seorcn() за исключением того, что цне- товые маркеры ле инициализируются в алгоритме. Пользователь должен сам ок- расить вершины в белый цвет перед вызовом хтюритма. Поэтому требуемый тип графа — IncidenceGraph вместо VertexListGraph. Также эта разница позволяет иметь большую гибкость в отображении цветовых с войт ib. 11апример, можно использо- вать отображение. которое реализует фу и кцию исключительно для полмножества вершил, что может быть более эффективно для памяти, поскольку поиск достига- ет только небольшой части графа. Параметры Ниже приведены параметры функции breacth f‘ rst_y’. s 11 (К • IN: IncidenceGraphS g Ориен пцюванный или неориентированный граф, который должен быть моде- лью IncidenceGraph.
13.2. Базовые алгоритмы 177 • IN: vertex_descnptor s Исходная вершина, с которой начинается поиск. Именованные параметры Ниже приведены именованные параметры функции breadtn_ff rst_visit(). • IN: viSitor(BFSViSi tor vis) Объект-посетитель, который активизируется внутри алгоритма в событийных точках, указанных в концепции BFSVisitor. По умолчанию: bfs_v* si tor<nu! l_vi s i iur>. • IN/UT1L/OUT: со!ог_т.ар(Со1огМгг> color! Используется алгоритмом для отслеживания продвижения по графу. Цвет каж- дой вершины должен быть инициализирован до вызова breadth_f i rst_search(). Тип Со! огМар должен быть модсл ью ReadWritePropertyMap, тип ключа — дескрип - тор вершины графа, тип значения colorjwp должен быть моделью CotorValue. По умолчанию: getvertex_co':or. g). • UTIL: bufferfBuffm Q) Очередь используется для определения порядка, в котором вершины будут посещены. Если используется очередь вида FIFO («первым вошел - первым вышел»), обход графа будет происходить в обычном для поиска в ширину по- рядке. Могут быть использованы и другие типы очередей. Например, алгоритм Дейкстры может быть реализован с использованием очереди по приоритету. Тип Buffer должен моделировать концепцию Buffer. По умолчанию: boost: .-queue. 13.2.3 depth_first_search template «typename Graph, typename f. typenamt T. typenane R’ void depth_first_search(Grephl g. const bql_named_params<P. T. R>& params) Функция depth_fi rst_seerch() выполняет поиск и глубину в ориентированном или неориентированном графе. Когда возможно, поиск в глубину выбирает вершину, смежную с текущей, для следующего посещения. Если все смежные вершины уже по- сещены или у вершины нетсмежиых вершин, алгоритм возвращается на последнюю вершину, у которой есть ие посещенные соседи. Когда все достижимые вершины про- смотрены, алгоритм выбирает вершину из оставшихся не посещенных и продолжает обход. Алгоритм завершается, когда пройдены все вершины графа. Поиск в глубину полезен для классификации ребер графа и для наведения упорядочения вершин. В разделе 4.2 описаны различные свойства поиска в глубину и рассмотрен пример. Подобно поиску в ширину, цветные пометки используются для отслеживания посещенных вершин. Белый цвет обозначает не посещенные вершины, серый — посещенные, но имеющие смежные не посещенные вершины. Черным обознача- ется посещенная вершина, у которой нет белых соседей. Функция depth_f; rst_search() активизирует заданные пользователем действия в определенных событийных точках. Это даст механизм для приспособления обобщен- ного алгоритма поиска в глубину ко многим ситуациям, в которых он может быть использован. В следующем ниже псевдокоде событийные точки поиска в глубину
178 Глава 13 • Алгоритмы BGL обозначены в метках справа. Действия, определяемые пользователем, должны быть заданы в форме объекта-посетителя — объе кта, чей тип удовлетворяет тре- бованиям для DFSVisitor. В псевдокоде показаны вычисления предшественников л. отметок посещения d и отметок окончания обработки/. По умолчанию функция depth_first_search() не вычисляет этих свойств, однако пользователь может оп- ределить объекты-посетители для этого. ПОИСК_В_ГЛУБИНУ(6) для каждой вершины ы € V s> инициализировать вершину и color [и] <-БЕЛЫЙ П [«] - и rime 4- О для каждой вершины ие V если (color [mJ - БЕЛЫЙ) call ПОИСК_ ВГЛУБИНУПОСЕЩЕН ИЕ(G, и) ъ исходная вершина и возвратить (р, d, f) 1ЮИСК_В_ГЛУБИНУ_1ЮСЕЩЕНИЕ(С, и) color [oj <- СЕРЫЙ t> посетить вершину и J[u] 4- time «- time - 1 для каждой вершины v е Л^[и| если (color [г] ” БЕЛЫЙ) г> рассмотреть ребро (и, v) x[v] = и color [v]<-СЕРЫЙ call 11ОИСК_ В_ГЛУБИНУ_ПОСЕ1ЦЕПИЕ(С, v) > (и. т) - древесное ребро иначе если (color [п] = СЕРЫЙ) с» (ы. с) - обратное ребро иначе если (color\v\ = ЧЕРНЫЙ) > (и, v) — прямое или поперечное ребре color [м] <- ЧЕРНЫЙ t> завершение обработки вершины и f [и] <- time <- time + 1 Где определен Алгоритм поиска в глубину находится в boost/graph/depth_fir$t_search.hpp. Параметры Ниже приведен параметр функции depth_first__searcti(). • IN: Graph& g Ориентированный или неориентированный граф, который должен быть моде- лью VertexListGraph и IncidenceGraph. Именованные параметры Ниже приведены именованные параметры функции deptn first searchi). • IN: visitorCDFSVisi tor vis)
13.2. Базовые алгоритмы 179 Объект-посетитель, который активизируется внутри алгоритма в событийных точках, указанных в концепции DFSVisitor. По умолчанию: defaul t_dfs_vi $1 tor. • UTIL/DUT: colorjnapCColorMap color) Используется алгоритмом для отслеживания продвижения пографу.Тип ColorMap должен быть моделью ReadWritePropertyMap,тип ключа — дескриптор вершины графа, тип значения colorjnap должен быть моделью ColorVaLue. По умолчанию: iterator_property_map, созданный из вектора std:: vector с эле- ментами типа default_color_type размером num_vertices(). Он использует 1_пир для отображения индексов. • IN: vertex_index_map(VertexInd=xMap ijwp) Отображает каждую вершину в целое число из диапазона [ 0, | И |). Этот параметр необходим, только когда используется отображение свойства цветовой окрас- ки по умолчанию. Тип VertexlndexMap должен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом, В качестве типа ключа отображения должен использоваться тип дескриптора вершины. 11о умолчанию: get (vertexj ndex. g). Сложность Временная сложность порядка О(\Е + |V |) и пространственная порядка О(|V|). Пример В примере (листинг 13.2) показан поиск в глубину для графа, изображенного на рис. 13.2. Ребра леса поиска в глубину обозначены черными линиями. Исход- ный код этого примера находится в файле example/dfs-example.cpp. Листинг 13.2. Применение алгоритма поиска в глубину < Посетитель поиска в глубину для записи времен посещения и окончания обработки вершин > template < typename TimeMap > продолжение#
180 Глава 13 • Алгоритмы BGL Листинг 13.2 {продолжение} class dfs_t1me_visitor.public default__dfs_visitor { typedef typename property_traits < TimeMap >-. :value_type T: public: dfs_time_visitor(TiineMap dmap. TimeMap fmap. T & t) : m dtimemap(daap), m_ftlremap(fmap). m timeit) { } template « typename Vertex, typename Graph > void discover_vertex(Vertex u. const Graph & g) const { put(m_dtiremap. u. m tire+d: ) template < typename Vertex, typename Graph > void finish_vertex(Vertex u. const Graph f, g) const { put(m_ftimemap. u. m_tire++): 1 TimeMap m_dtimemap. TimeMap mjrt1l’®wdp: Tim tire: }• ( df$-example.cpp > • find ude <boost/graph/adjacency_l1st,hpp> ^include <boost/graph/depth_first_searcti.hpp> # i ndude «boost/pending/i nteger_range.hpp> # i nclude «boost/pending/indirect_cmp.hpp> findude «iostreaw» using namespace boost; < Посетитель поиска в глубину для записи времен посещения и окончания обработки вершин ) int иатпО ( // Выбрать графовый тип. который мы будем использовать typedef adjacency_Fst < vecS. vecS. directedS > graph_t; typedef graph_tra7ts < graph_t >::vertices_size_*ype s?ze_type. // Подготовить идентификаторы вершин и имена enum { и. V, w. х. у. z. N }: char name[] - { 'u'. 'v'. 'w', 'x'. 'y', 'z' // Указать ребра графа typedef std::pair < int, int >£: E edge_array[] - { E(u. v). E(u. x). E(x, v). Eiy. x). E(v.~y). E(w. y). E(w, z), E(z. z) }: graph_t g(edge_array, edge_array + sizeoffedge^a-ray) / sizeof(E // Определения типов typedef boost::graph_traits < graph_t >:vertex descriptor Verte typedef size_type* liter: // Векторы для хранения свойств "время посещения" и "время окончания обработки" для каждой вераины std::vector < size_type > dtire(num_vertices(g)k std::vector < s1ze_type > ftime(num_vertices(g)); size_type t-0:~ dfs_t1 me_visitor < size_type * >vis(&dtire[O]. &ftime[OL t): depth_f1rst_search(g. visitor(vis)i; // Использовать std:.sort для сортировки вершин по времени лосе std.-vector < size_type > discoverjirder(N);
13.2. Базовые алгоритмы 181 integer_range « sve_type > г г 0. N): std::copytr.begins). r.enclO. discover _order begun):. std: :sorttdiscc-ver_order begin)). a-scover_order end!). mdirect._cmp < liter, std-: 'ess « 5iie_type > >:&dtw[0])); std::cout «•< “порядок поседения- ".- int i: for (i “0; i « N; **' std::cout « nameCd1scover_ord»rf'.J' « " ”. std::vector < size_typs > firnsh_order(N): std:-.copyfr.beginC, r end(). finisb_order beginO): std:; sort (finish order.begin(), fwsh_order,end(), indirect_cmp < liter, std; Hess < size_type > std::cout « std-endl « "порядок окончания обработки: for (i - 0: 1 < N: ♦+И Std::tout « nen»[f’r.isfijjrderfi]] « " ". std: :cout « std: -.endI: return EXIT SUCCESS: ) Вывод будет таким: порядок посещения,- u v у х w z порядок окончания обработки,- > у v u i w 13.2.4. depth_first_visit tentplate «typename IncidenceGraph, typename WSVis’tor. typename Coiorttap> void depth_f1rst_visit(lncidenceGrapn& G. typename greph_traits«IncidenceGraph>::verte*_descript(-jr s. DFSVisitor vis.’ColorMap color): Функция <3epth_first_v’sit() — рекурсивная часть поиска в глубину. Главная задача этой функции — реализовать dcpthfi г st _tea rch (), ио иногда она может быть полезна сама но себе. Для дополнительной информации см. описание depth_fi r$t_- searchO, Где определен Функция depth_first_vi$11() находится в boost/graph/depth_first_search.hpp. Параметры Ниже приведены параметры функции tkptri_f i i st_visпt(). • IN: IncidenceGraph^ g Ориентированный или неориентированный граф, который должен быть моде- лью IncidenceGraph. • IN: vertex_descriptor s Исходная вершина, е которой нужно начинать поиск. • IN: DFSVisitor visitor Объект-посетитель, который активизируется внутри алгоритма в событийных точках, указанных в концепции DFSVisitor. • UTIL: ColorMap color Используется алгоритмом для отслеживания продвижения по графу, Тип ColorMap должен быть моделью ReadWritePropertyMap, тип ключа — дескриптор вершины графа, тип значения colorjnap должен быть моделью ColorValue.
182 Глава 13 • Алгоритмы BGL Сложность Временная сложность порядка О(|£ |) и пространственная -- порядка О(|У |). 13.2.5. topological_sort :emplate «typename Graph, typename Output Iterator. typename ?. typename T. typename R> voia topo1ogical_sort(Graphs G. Output Iterator result. const bgl_named_params<₽. T. R>4 params - c’1 defaults) Функция topological sortO реализует алгоритм топологической сортировки, который создает линейное упорядочение вершин, такое, что если ребро (и, v) при- сутствует в rpai|>e, то и стоит в упорядочении раньше, чем v. Граф должен быть ориентированным ациклическим графом. Обратное топологическое упорядочение записывается итератором вывода resu't. поэтому его нужно обратить для получения нормального топологического порядка. Есть несколько способов это сделать. Можно создать std: .-vector размером jVj для сохранения вывода и затем использовать обратный итератор вектора для итератора result. Или можно использовать итератор вставки в конец backjnsert iterator с пу- стым вектором, а затем применить std:: reversed. Еще одна альтернатива - итератор вставки в начало front_insert_iteratorc контейнером вроде std:: 1 тst или std::deque Реализация включает простой вызов поиска в глубину [ 10]. В разделе 1.4.1 есть пример использования топологической сортировки для планирования заданий а в главе 3 топологическая сортировка применяется при написании обрбщенного графового алгоритма. Где определен Алгоритм топологической сортировки находится в boost/graph/topolog1cal_sort.hpp. Параметры Ниже приведены параметры функции topologicf:l_sort(). • IN; Graphs g Ориентированный или неориентированный граф, который должен быть моде- лью VertexListGraph и IncidenceGraph. • IN; Outputiterator result Вершины выводятся этим итератором в обрат! юм топологическом порядке. Тип Output Iterator должен принимать дескрипторы вершин как выход, и тип ите- ратора должен быть моделью Outputiterator. Именованные параметры Ниже приведены именованные параметры функции topological_sort(). • UT1L/0UT: color_nap(ColorMap color) Используется алгоритмом для отслеживания продвижения по графу. Т ип Col огМао должен быть моделью ReadWritePropertyMap, тип ключа дескриптор вершины графа, тип значения color_map должен быть моделью Colorvalue. По умолчанию: iter,ator_propertyjnap, созданный из вектора std:: vector с эле- ментами типа defaul t_col or_type, размером nun_vert 1 ces (). Он использует i_map для отображения индексов.
13.3. Алгоритмы кратчайших путей 183 • IN: vertexjndex_map(VertexIndsxMap т_пир) Отображает каждую вершину в целое число из диапазона [0, | V [>. Этот параметр необходим, только когда используется отображение свойств цветовой окрас- ки по умолчанию. Тин VertexIndexHapдолжен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом. В качестве типа ключа отображения требуется задавать тип дескриптора вершины. По умолчанию: get(vertexjndex. g). Сложность Временная сложность порядка O(|V| + |£|) и пространственная — О(| V|). Пример См. раздел 1.4.1, где дан пример использования toplogical sortO. 13.3. Алгоритмы кратчайших путей 13.3.1. dijkstra_shortest_paths template <typename Graph, typename P. typename T. typename void d1jkstra_shortest_paths(const Graphs g. typename graph_tra1ts<Graph>::vertex_descriptor s. const bgl_named_params<P. T, R>& params) Алгоритм Дейкстры [10, 11] решает задачу нахождения кратчайших путей из одной вершины на взвешенном, ориентированном или неориентированном графе для неотрицательных весов ребер. Для случая, когда у некоторых ребер имеются отрицательные веса, лучше использовать алгоритм Беллмана-Форда, а когда все веса равны единице, применяется алгоритм поиска в ширину. Описание задачи кратчайшего пути приведено в разделе 5.1. Есть два варианта получения результата из функции di jkstra_shortest_paths О. Первый — предоставить отображение свойства расстояний через параметр di stan- cejnapO. В этом случае кратчайшее расстояние от исходной вершины до любой другой в графе будет записано в отображении расстояний. Второй вариант — за- писать дерево кратчайших путей в отображение предшественников; для каждой вершины и е V, я [«] будет предшественником и в дереве кратчайших путей (если, конечно, не выполняется я [и] - и, так как в этом случае и либо является исходной, либо недостижима из исходной). В дополнение к этим двум вариантам пользова- тели могут предоставить своего собственного посетителя, который может произ- водить определенные действия в любой из событийных точек. Алгоритм Дейкстры находит все кратчайшие пути из одной исходной вершины к любой другой вершине, итеративно наращивая множество вершим S, к которым он знает кратчайший путь. На каждом шаге алгоритма добавление следующей вершины к 5 определяется очередью по приоритету. Очередь содержит вершины из множества V - 5, а значением приоритета является их метка расстояния — дли- на кратчайшего пути, который известен на данный момент для каждой вершины'. Алгоритм, использованный здесь, сохраняет память, не помещая все вершины V - S в очередь по прио- ритету, а только те вершины из V-S, которые были посещены и поэтому имеют расстояние меньше бесконечности.
184 Глава 13 • Алгоритмы BGL Вершина и в начале очереди добавляется к S, а каждое из ее исходящих ребер релаксируется. Если расстояние до вершины и в сумме с весом исходящего ребра меньше, чем метка расстояния для вершины п, то оценка расстояния для верши- ны п уменьшается. После этого алгоритм обрабатывает следующую вершину с го- ловы очереди. Алгоритм завершается, когда очередь пуста. Алгоритм использует цветовые маркеры (белый, серый и черный) для отсле- живания принадлежности вершины некоторому множеству. Черные вершины принадлежат 5, белые — множеству V- S. Белые вершины еще не посещались, а серые находятся в очереди по приоритетам. По умолчанию алгоритм заводит массив для хранения цветовых маркеров для каждой вершины графа. Вы можете предоставить свое собственное хранилище и получать доступ к цветам с помо- щью именованного параметра со1ог_пир(). Ниже приведен псевдокод алгоритма Дейкстры для вычисления кратчайших путей из одной вершины. Здесь w обозначает вес ребра, d — метка расстояния. п — предшественник каждой вершины, используемый для кодирования дерева кратчайших путей. Q — очередь по приоритету, которая поддерживает операции. УМЕНЬШИТЬКЛЮЧ. Событийные точки для алгоритма обозначены рядом с метками справа. ДЕЙКСТРА(С,х, ®) для каждой вершины и е V > инициализировать вершину и d[u\ со *[«]«-« color[u] БЕЛЫЙ со/оф] 4- СЕРЫЙ ф] <- О ВСТАВИТЬСЯ $) > посетить вершину s покаЩ#0) и ♦- ВЗЯТЬ_МИНИМ АЛЬНОЕ (Q) > рассмотреть вершину и S<-Su{u} для каждой вершины ?; е Аф'[и| > рассмотреть ребро (и, о) если (ю(и, v) + d[u] < ф»]) ф] <- ®(и, v) + d[u] t> ребро (и, г) релаксируется п [г>] 4— и если (color[v] - БЕЛЫЙ) coloi\v\ <- СЕРЫЙ ВСТАВИТЬСЯ,с) г» посетить вершину v иначе если (color [с] - СЕРЫЙ) УМЕНЫПИТЬ_КЛЮЧ(О. о, ®(«. г») + ф]) иначе о ребро (и, v) не релаксируется color [и] 4- ЧЕРНЫЙ о завершение обработки вершины и возвратить (d, я )
13.3. Алгоритмы кратчайших путей 185 Где определен Алгоритм Дейкстры находится в boost/graph/dijkstra_shortest_paths.hpp. Параметры Ниже приведены параметры функции ch jkstra_shortest_paths(). • IN: const Graph& g Графовый объект, к которому применяется алгоритм. Тип Graph должен быть моделью VertexListGraph и IncidenceGraph. • IN: vertex_descripto" s Исходная вершина. Все расстояния вычисляются от этой вершины, и в ней находится корень дерева кратчайших путей. Именованные параметры Именованные параметры функции dijkstra_shortest_paths() приведены ниже. • IN: weightjnapCWeightMap wjrap) Вес или «длина» каждого ребра в графе. Тип WeightMap должен быть моделью ReadablePropertyMap. Дескриптор ребра требуется задавать в качестве типа ключа для отображения весов. Тип значения отображения весов должен быть таким же, как тип значения для отображения расстояний. По умолчанию: get(edge_weight. g). • IN: vertex_index_map(VertexlndexMap i_map) Отображает каждую вершину в целое число из диапазона [0, |V |). Этот пара- метр необходим для эффективного обновления структуры данных при релак- сации ребра. Тип VertexIndexMap должен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом. Тип дескриптора вершины графа требуется задавать в качестве типа ключа отображения. По умолчанию: get(vertex_index. g). • OUT: predecessor_nap(PredecessorMap pjrap) Отображение предшественников записывает ребра минимального остовного дерева. По завершении алгоритма ребра (я [«J, a) Vue V находятся в мини- мальном остовном дереве. Если ж [и] - а, это означает, что либо и является ис- ходной вершиной, либо и недостижима из исходной. Тип PredecessorMap дол- жен быть ReadWritePropertyMap с типами ключа и вершины такими же, как тип дескриптора вершины графа. По умолчанию: dummy_property_map. • UTIL/OUT: di stancejreplDistanceMap djrap) Вес кратчайшего пути из исходной вершины и каждую вершину графа записан в этом отображении свойств. Вес кратчайшего пути — это сумма весов ребер, из которых состоит путь. Тип DistanceMap должен моделировать ReadWrite- PropertyMap. Тип дескриптора вершины графа должен задаваться в качестве типа ключа отображения расстояний. Тип значения — элемент типа Monoid, состоя- щий из функционального объекта combine и объекта zero для нейтрального эле- мента (см. главу 16). Также тип значения расстояния должен обеспечивать
186 Глава 13 • Алгоритмы BGL StrictWeakOrdering (ослабленное строгое упорядочение), что и дает объект-функ- ция compare. По умолчанию; iterator_property_map, созданный из вектора std::vector с эле- ментами того же типа, что и тип значения WeightMap, размером num_vertices(). Он использует 1 jnap для отображения индексов. • IN; distance_comtine(BinaryFunction combine} Объект-функция, являющийся операцией концепции Monoid для типа значе- ния расстояния. Этот функциональный объект складывает длины для получе- ния полной длины пути. По умолчанию: closed_plus<0>, где 0 — тип значения отображения расстояний. Тип closed_plus определен в boost/graph/relax.hpp. • IN: dlstance_compare(BmaryPredicate compare) Объект-функция, который задает StrictWeakOrdering на значениях расстояний. Используется для определения того, какой на путей короче. По умолчанию: std::1 ess<D>, где D — тип значения отображения расстояний. • IN: d1stance_inf(D inf) Объект inf должен давать наибольшее значение любого объекта D. То есть compared, inf) — true для любого d I-inf. Тип 0 — тип значения 01 stanceMap. По умолчанию: std: :numeric_l 1mits<D>: :max(). • IN: distance_zero(0 zero) Значение должно быть нейтральным элементом для Monoid, состоящего из значений расстояний и объекта-функции combine. Тип 0 — тип значения для Di stanceMap. По умолчанию: 0. • UTIL/OUT: color_map(ColorMap cjnap) Используется во время выполнения алгоритма для маркировки вершин. В на- чале алгоритма вершины имеют белый цвет и становятся серыми, когда они попадают в очередь. Они перекрашиваются в черный при удалении из очере ди. В конце алгоритма все вершины, достижимые из исходной, имеют чер- ный цвет. Остальные вершины будут белыми. Тип Col огМар должен бытьмо делью ReadWritePropertyMap. Дескриптор вершины требуется задавать в качестве типа ключа для отображения, и тип значения отображения должен моделиро вать Colorvalue. По умолчанию: 1terator_property_map, созданный из вектора std:: vector с эле- ментами типа detail' t_col or_type, размером num_vert1ces (). Он использует i _map для отображения индексов. • IN: visitorCVls v) Используйте этот параметр для задания действий, которые должны выполнять- ся в определенных событийных точках внутри алгоритма. Тип Vi s должен быть моделью DijkstraVisitor. По умолчанию: defaul t_di jkstra_vi si tor.
13.3. Алгоритмы кратчайших путей 187 Сложность Временная сложность порядка О((|+ |£|) log |У|) или просто О(|£ | log |V[), если все вершины достижимы из исходной вершины. Пример Исходный код примера из листинга 13.3 находится в файле example/dijkstra- example.cpp. Граф, использованный в этом примере, показан на рис. 13.3. Ребра в дереве кратчайших путей обозначены черными линиями. Рис. 13.3. граф, использованный в примере для алгоритма Дейкстры Листинг 13.3. Пример алгоритма Дейкстры typedef adjacency_11st < lists. vecS. directedS. nojiroperty, property < edge_weignt_t. mt > > graph_t: typedef graph_traits < graph.t >::vertex descriptor vertex_cescriptc-r. typedef graptTtraits < graph^t >.- :edge_descrwtor edge_descriptor; typedef std::pair<int. int> Eage: const mt num_nodes * 5: enum nodes { А. В. C. 0. E J: char natneU ~ "ABODE": Edge edge_array[] - { Edge(A. C). EdgecB. 8). EdgeCB. D). ЕадеСВ. E'i. EdgefC. B). EdgeiC. 0). EdgefD. E). EdgeiE. A), EdgeCE. B; b int weights!] -{1.2, 1. 2. 7. 3, 1, 1. 1 }: 1nt num_arcs - sizeof(edge_array) / s-zeof(Edge): graph_t gtedgejrray. edge„array + num_arcs. weights, numjiodes): propertyjnap<graph_t. edge_weight_t>::type weightmap - get(edge_weight. g): std; :vector<verte.x.descriptor? p(num vertices(g)); std:-vector<int> dinum_vertices(g)): vertex_descriptor s • vertex(A. g); dijicstra_shortest_paths£g. s. predecessor_map(4p[0]).distance_nap(&d[0]n: std :cout « "Расстояния и родители." « std::endl; graph_traits < graph_t >: :vertex_iterator vi. vend:
188 Глава 13 • Алгоритмы BGL Листинг 13.3 {продолжение} for (tietvi. vend) • vertices(g): vi > venc: ++vi) < std::cout « "distance!" « na№[*v:] « d[*vi] « ”, ". stakeout « “parenti'' « namcC*vi] « ’) - ’ « n,w[p[*vi]] std endl; } std::cout « std::endl: Программа выводит следующее: Расстояния и родители: distance(A) - 0. parert(A) *- А distance(B) " 6. parert(B) - Е distance(C) * 1. parent(C) - A distance(D) - A. parentlDi - C distance(E) - 5, parenttE) - 0 13.3.2. bellman_ford_shortest_paths template «typename EdgeListGraph. typenamc Size. . typename P. typename T, typer.anie R> bool bel1man_ford_shortestjjath‘>(EdgeListGrapr.’> л. Size 14. const bgl_nared_params<P. I, R--& params) Алгоритм Веллмана-Форда [5, 10. 13,26] решае т задачу кратчайших путей из одной вершины для графа с положительными и отрицательными весами ребер. Постановка задачи кратчайших путей приведена в разделе 5.1. Если вам нужно решать задачу кратчайших путей для положительных весов ребер, алгоритм Дей кстры предоставляет более эффективную альтернативу. Если веса всех ребер рав- ны единице, поиск в ширину подходит еще больше. Перед вызовом функции bel lnidn_fonjj.iiortcst_paths() пол ыюва гель должен на- значить исходной вершине расстояние ноль (или нейтральный элемент Monoid, состоящий из значений расстояния и функции combine), а всем другим вершинам -- бесконечное расстояние (наибольшее значение расстояния согласно упорядоче- нию, определяемому функциональным объектом compare). Обычно std - :rwmeric_ i mi ts<D>: :max() является подходящим выбором для бесконечности, где П — тип зна- чения отображения |>асстояний. Алгоритм Беллмана-Форда выполняется в цикле по всем ребрам в графе, с применением операции релаксации к каждому ребр;. В следующем псевдокоде v является пери иной, смежной с и, отображает ребрт на вес, d — отображение расстояний, кото|юе записывает длину кратчайшего пути до каждой рассмотренной на данный момент вершины. РЕЛАКСАЦИЯ (м, v, w, d) если (re(u, г) + <М<Ф1) < i[p] «- ю(и, с) + d[a] Алгоритм повторяет этот цикл|Е| раз, после чего гарантируется, что расстоя- ния до каждой вершины были сокращены до минимально возможных, если в гра- фе нет отрицательных циклов. Если отрицательный цикл присутствует, в графе будут ребра, которые не минимизированы до конца, то есть будут ребра (и. v), для которых w(«, v) + d[u] < dfc]. Алгоритм проходит циклом по всем вершинам еще раз, чтобы проверить, что все ребра были минимизированы, возвращая истину, если это так, и «ложь» в противном случае.
13.3. Алгоритмы кратчайших путей 189 Имеется два основных способа получения результатов из функции Ье11шап_- ford_shortest_paths(). Если пользователь представляет отображение расстояний через параметр distancejnapl), то кратчайшее расстояние из исходной вершины до каждой другой вершины в графе записывается в изображение расстояний (ко- нечно, если функция возвращает истинное значение). Пользователь может также записать дерево кратчайших путей, задавая отображение свойства предшествен- ников через параметр predecessor_map(). В дополнение к этим способам можно ис- пользовать своего собственного посетителя, который будет выполнять действия в любых событийных точках алгоритма (см. BellmanFordVisitor). Если вы заинтере- сованы только в некоторых событийных точках, создайте своего собственного по- сетителя из defaul t_bel lman_visitor для того, чтобы ненужные действия были за- менены пустыми в не интересующих вас событийных точках. Где определен Алгоритм Веллмана-Форда находится в boost/graph/bellman_ford_shortest_paths.hpp. Параметры Ниже приведены параметры функции bel lman_ford_shortest_pathsO. • IN: EdgeListGraph& g Ориентированный или неориентированный граф, который должен быть моде- лью EdgeListGraph. • IN: Size N Количество вершин в графе. Тип Size должен быть целочисленным типом. Именованные параметры Именованные параметры функции bel lman_ford_shortest_paths() приведены ниже. • IN: weight_map(WeightMap w) Вес (также известный как «длина» или «стоимость») каждого ребра в графе. Тип Wei ghtMap должен быть моделью ReadablePropertyMap. Типом ключа для этого отображения свойства должен быть дескриптор ребра графа. Тип значения для отображения весов такой же, как и тип значения отображения расстояний. По умолчанию: get (edge_weight. g). • OUT: predecessorjnaplPredecessorMap pjnap) Отображение предшественников записывает ребра минимального остовного дерева. По завершении алгоритма ребра (я [и], u) Vue V находятся в мини- мальном остовном дереве. Если п [и] - и, то и является исходной вершиной или вершиной, которая недостижима из исходной. Тип PredecessorMap должен быть моделью концепции ReadWritePropertyMap, чьи типы ключа и значений та- кие же, как у дескриптора вершины графа. По умолчанию: dummy jropertyjnap. • UTIL/OUT: distance_map(DistanceMap djnap) Вес кратчайшего пути из исходной вершины s в каждую вершину графа запи- сан в этом отображении свойства. Вес кратчайшего пути — это сумма весов
190 Глава 13 • Алгоритмы BGL ребер, из которых состоит путь. Тип D'stanceMap должен моделировать Read- WritePropeityMap. Тип дескриптора вершины графа требуется задавать в качестве типа ключа отображения расстояний. Тил значения — элемент типа Monoid, состоящий из функционального объекта combi пе и объекта zero для нейтраль- ного элемента. Также тип значения должен быть моделью StrictWeakOrdering (ослабленное строгое упорядочение), что обеспечивает объект-функция compare. По умолчанию: get(vertexjjistance, g). • IN: visitor(BellmanFordVisitor v) Объект-посетитель, который активизируется внутри алгоритма в событийных точках, указанных в концепции BellmanFordVisitor. Посетитель по умолчанию — defdult_bel !man_vis‘tor, не делает в событийных точках ничего, По умолчанию: default_bellman_visitor. • IN: distancej:ombine(B1naryFunction combine) Объект-функция, являющийся операцией концепции Monoid для типа значе- ния расстояния. Этот функциональный объект складывает длины для получе- ния полной длины пути. По умолчанию: closed_plus<D>, где D — тип значения отображения расстояний closed_plus определен в boost/graph/relax.hpp. • IN: dlstance_compare(BlnaryPred1cate compare) Объект-функция, который задает StrictWeakOrdering на значениях расстояний. Используется для определения того, какой из путей короче. По умолчанию: std :less<D>, где D ~- тип значения отображения расстояний Сложность Временная сложность порядка О(|V | х |£|). Пример Исходный код примера из листинга 13.4 находится в файле example/bellman- example.cpp. Граф, использованный в примере, показан на рис. 13.4. Листинг 13.4. Применение алгоритма Беллмана-Форда enum { u, V. х, у. z. N }: char патеО - ( 'и'. V. 'х'. ’у', 'z' }; typedef std::pair < int. int >E: const int n_edges - 10; E edge_array[] - { E(u. y). E(u. x). E(u. v!, Etv. u). E(x. y). E(x. vi. E(y. v). E(y. z). £(z, u). E(Z x) }; int weight[n_edges] - { -4, 8, 5. -2. 9, -3. 7. 2. 6. 7 }; typedef adjacencyjist < vecS, vecS. directedS. no_property. property < edge_weight_t. int > > Graph; Graph g(edge_array. edge_array » n_edges, N): graph_traits * Graph >::edge_iterator ei. ei_end: property_map<Graph. edge_weight_t>: :type weightjxiwp - get (edge _weight. g): int i - 0;
13.3. Алгоритмы кратчайших путей 191 for (tiefei. ei_end) - edges(g); ei !» ei_end: ++ei. weight_pmap[*ei] - weight[ij: std-:vectcr<1nt> distanced. std:-nunericji.mts < short >::тах(П; std: :veclor<std::size_t> parent^!: for (i =0; i < N: > parent[1] - i; distance^] « G: bool г « bellaan_ford_shortest_path$ (g. int <N). we1ghtjnap(weight_p<wp> distancejMp(8distance[0'|) predecessor jiapf&parenttOJ) 1. if ir) for (1 - 0: i < N; *-*•-."> std::cout « name[i] « " « std::setw(3) « distance^] « " " « name[parenr[-i]] std:-endl: else std::cout <-< 'отрицательный цикл" « sti!--.end1; Расстояние и предшественник для каждой вершины такие: и: 2 v v: 4 х х: 7 г у: -2 и z: 0 z Рис. 13.4. Граф, использованный в примере алгоритма Беллмана-Форда 13.3.3. johnson_all_pairs_shortest_paths template «typenawe Graph, typename DistanceHatrlx. typename P. typename 1. typename R> bool johnson_all_pairs_shortest_pathsfGraph& g. DistanceHatrixi 0. const bg1_named_para<ns<P. T. R>8 params - all defaults)
192 Глава 13 • Алгоритмы BGL Функция johnson_al l_pa1rs_$hortesb_paths() (алгоритм Джонсона) находит кратчайшие расстояния между всеми парами вершин графа. Алгоритм возврата- ет fa 1 se, если в графе присутствует отрицательный цикл, и true в противном слу- чае. Расстояние между каждой парой вершин хранится в матрице расстояний D. Это один из наиболее требовательных ко времени графовых алгоритмов, так как он имеет временную сложность порядка 0(|V1| log |lz|). Где определен Алгоритм Джонсона находится в boost/graph/johnson_all_pairs_shortest_paths.hpp Параметры Параметры функции jcnnson_all_pdirs_shcTtest_paths() приведены ниже, • IN: const Graphs g Графовый объект, к которому применяется алгоритм. Тип Graph должен бып моделью VertexListGraph. IncidenceGraph и EdgeListGraph. • OUT: DistanceMatnxS D Кратчайшая длина пути из вершины и в с хранится в /)|wj|г]. Именованные параметры Именованные параметры функции johnsor_a1 ' rs_shortesl_paths() приведены ниже. • IN: weight_map(WeigntMap wjnap's Вес или «длина» каждого ребра в графе. Тип WeigntMap должен быть моделью ReadablePropertyMap. Типом ключа для этого отображения свойства должен быть дескриптор ребра графа. Тип значения для отображения весов такой же, как и тип значения отображения расстояний. По умолчанию: get(edge_weight. g). • UTIL: wetght_map2(Wet-gfitMap2 w_map2) Вспомогательное отображение весов. Тип K»^ghtMap2 должен быть моделью ReadablePropertyMap. Типом ключа для этого отображения свойства должен быть дескриптор ребра графа. Тип значения для отображения весов такой же. как и тип значения отображения расстояний. По умолчанию: get i eage_wei gM.2. ц). • IN: vertex_index_map(VertexIndexMdp 1_тар: Отображает каждую вершину в целое число из диапазона 10,1V|). Этот пара- метр необходим для эффективного обновления структуры данных при релак- сации ребра. Тип VertexlnaexHap должен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом. Тип дескриптора вершины графа требуется задавать и качестве типа ключа отображения. По умолчанию: get. i vertex_i ndex. g). • UTIL/OUF: dr stance^raap(Di?tanceMap d_rap) Вес кратчайшего пути из исходной вершины s в каждую вершину графа g запи- сан в этом отображении свойства. Вес кратчайшего пут и — это сумма весоь
13.3. Алгоритмы кратчайших путей 193 ребер, из которых состоит путь. Тип D i stanceMap должен моделировать ReadWrite- PropertyMap. Тип дескриптора вершины графа должен задаваться в качестве типа ключа отображения расстоянии. Тип значения — элемент типа Monoid, состоя- щий из операции сложения и объекта zero для нейтрального элемента. Также тип значения должен быть моделью LessThanComparable. По умолчанию: iteratorjyropertyjnap, созданный из вектора std:: vector с эле- ментами типа, как тип значения WeightMap, размером num_vertices(). Он исполь- зует i_map для отображения индексов. • IN: distance_zero(D zero) Значение должно быть нейтральным элементом для Monoid, со значением рас- стояний и операции сложения. Тип D — тип значения для DistanceMap. По умолчанию: DO. Сложность Временная сложность порядка O(|V| |£| log | V |). Пример Алгоритм Джонсона для кратчайших путей между всеми парами вершин (лис- тинг 13.5) применен к графу со с. 568 «Introduction со Algorithms» [10], который также показан на рис. 13-5. Результирующая матрица DZu][v] дает кратчайший путь от вершины и к г<. Рис. 13.5. Граф, использованный в примере алгоритма Джонсона Листинг 13.5. Алгоритм Джонсона для кратчайших путей между всеми парами вершин typedef adjacency_list*vecS. vecS. directedS. m_property. property* edge_weight_t. int. property* edge_weight2_t. int > > > Graph: const int V • 6;~ typedef std:;paiг < int. int >Edge: Edge edge array!] • { EdgefO. 1). Edge(0. 4). Edge(t). 2). Edged. 3). Edged, 4). Edge(2. D. Edge(3, 2>. Edge(3. 0), Edge{4. 3) ) * продолжение £ 1 Час W
194 Глава 13 • Алгоритмы BGL Листинг 13.5 {продолжение} const std::size_t Е - sizeof(edge_array) / sizecrffEdge) Graph g(edge_array, edge_array + E. V); propertyjrap < Graph. edge_weight_t >.;type w = get(ecge_weight. gJ: Int we-.gntsn - { 3, -4, 8. 1, 7. 4. -5, 2. 6 ); int *wp ” weights: graph_traits <= Graph >. ,edge_iterator e. e_enfl: for (boost, .f.iete. e_end) » ndges(g); e !« e_end: +<e) w(*e] - *wp’-r; std: vector < mt >dtv. std::nu»erlc_limits < int >:.maxO): mt D[VjM: Johnson al: pairs,snertest_paths(g. D. disUncejwrc&dCSP): Ниже приведена результирующая матрица расстояний: 0 1 2 3 4 5 0 0 0 -1 -5 0 -4 1 inf 0 1 -3 2 -4 2 inf 3 0 -4 I -1 3 inf 7 4 0 5 3 4 inf 2 I £ 0 2 S 1nf Й 5 1 6 0 13.4. Алгоритмы минимальных остовных деревьев 13.4.1. kruskaLminimum_spanning_tree template 'typenume Graph, typename Outputiterator, typename P. typename T. ty penart® R> void kruska’_mimmjm_spanning_tree(Graph& g. Output Ilerator spanni ng_t ree_edges. const bgl /iamed_paramr><P. T. R>& params - all defaults) Функция Kruskal jmnlmum_spanning_lree() находит минимальное остовное де- рево в неориентированном трас]» со взвешенными ребрами. Минимальное остов- ное дерево - это набор ребер, который соединяет все вершины в графе, где общей вес ребер дерева минимизирован. Функция kruskal_miniRum_s3anmng_tree() выво- дит ребра остовного дерева итератором spanning_tree_edges, используя алгоритм Краскала 110, 18, 23, 44]. Алгоритм Краскала начинается с того, что каждая вершина является сама по себе деревом, без ребер из множества Т. в котором будет строиться минимальное остовное дерево. Затем алгоритм рассматривает каждое ребро графа в порядке увеличения веса ребра. Если ребро соединяет две вершины в разных деревьях, алгоритм сливает эти деревья в одно и добавляет ребро к множеству Т. Мы ис- пользуем объединение по рангу и эвристики сжатия пути для обеспечения быст- рой реализации операций над непересекающимися множествами (СДЕЛАТЬ- МНОЖЕСТВО. НАЙТИ-МНОЖЕСТВО и ОБЪЕДИНИТЬ-МНОЖЕСТВА). Ниже приведен псевдокод алгоритма Краскала.
13.4. Алгоритмы минимальных остовных деревьев 195 КРАСКАЛ_МИН_ОСТ_ДЕРЕВО(б, ®) для каждой вершины и eV СДЕЛАТЬ_МНОЖЕСТВО(5. и) Т*-0 для каждого ребра (u, v) е Е в порядке неуменьшенля веса если НАЙТИ_МНОЖЕСТВО(5, и) # НАЙТИ_МНОЖЕСТВО(5, и) ОБЪЕДИНИТЬ_МНОЖЕСТВА(5, и. г») ru {(«. г)} возвратить Т Где определен Алгоритм Краскала находится в boost/graph/kruskaL_minimum_spanning_tree.hpp. Параметры Ниже приведены параметры функции kruskal_nnn'.fnum_sparimnq_tree(). • IN: const Graphs g Неориентированный граф. Тип Graph должен быть моделью VertexListGraph и EdgeListGraph. • IN: Outputiterator spanning_tree_eages Ребра минимального остовного дерева выводятся этим Outputiterator. Именованные параметры Именованные параметры функции kruskal__mimnun_$pdnmng_tree() приведены ниже. • IN: weightjnaptwelghtMap wjnap) Вес или «длина» каждого ребра в графе. Тип WeightMap должен быть моделью ReadablePropertyMap, и тип его значения — LessThanComparable. Типом ключа для этого отображения свойства должен быть дескриптор ребра графа. По умолчанию: get(edge_weight. g). • UTIL: rankjrapIRankMap rjrap) Тип RankMap должен быть моделью ReadWritePropertyMap. Тип дескриптора вер- шины графа требуется задавать в качестве типа ключа отображения ранга. Т ип значения отображения ранга должен быть целого типа. По умолчанию: iterator_property_map, созданный из вектора std.: vector с эле- ментами целого типа, размером num_vertices(). Он использует ijnap для ото- бражения индексов. • UTIL: predecessorjrapIPredecessorMap pjiwp) Тип Predecessor-Map должен быть моделью ReadWritePropertyMap. Тип ключа и тип значения отображения предшественников должны быть типом дескриптора вершин графа. По умолчанию: 1 terator_property_map, созданный из вектора std;: vector с эле- ментами — дескрипторами вершин, размером ra.im_vert4-cesig). Он использует ijnap для отображения индексов.
196 Глава 13 • Алгоритмы BGL • IN: vertexj ndexjnapc Vertex I ndexMap ijw) Отображает каждую вершину в целое число из диапазона [0, | V |). Этот параметр необходим, если значения но умолчанию использованы для отображений ранга и предшественников. Тип VertexIndexMap должен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом. Тип дескрип- тора вершины графа требуется задавать в качестве типа ключа отображения. По умолчанию: getlvertexjnoex. g). Сложность Временная сложность порядка О(\Е | log |£[). Пример Исходный код примера из листинга 13.6 находится в файле example/kruskal-exampte.qra. Граф, использованный в этом примере, показан на рис. 13.6. Рис. 13.6. Граф, использованный в примере алгоритма Краскала Листинг 13.6. Пример использования алгоритма Краскала typedef adjacency_lin « vecS. vecS. unci•>recledS no_property. property < edge_weight_t. ’nt > » Graph, typedef graph_trait$ < Graph >..edgejiescriptor Edge: typedef graph_traits < Graph >•:vert?x_descriptor Vertex: typedef std::pair<int. mt> £: const int numjiooes - 5: £ edge arravll - { ECO. 2i. E(l. 3k Ш. 4). Ш. Г;, E(2. 3). E(3. 4). E<4. 0). E(4. D int weights!] - { 1. 1, 2. 7. 3. 1. 1. 1 ): int num edges ~ siaeofCedye array) / sizeofs'D: Graph g(edge_array. edge_array + num_edges. weights. num_nodesk propertyjW « Graph, edge.weight_t >::type weight ~ get(edge_w=1glit. g): std:'vector < Edge > spanning_tree; kruskal__пГ| rnnium. spann i ngj ree( g. std: • bactj nserter (sparm i ng_tree)); std".cout « “Ребра остовного дерева;" « std: :endl; for (std. vector < Edge >; iterator ei « spanning_tree.begin(;: et •= span» ing_tree end О : ++ei) { std:-cout «~sourcef*ei. g) « " <--> « target(*ei. g) " с весом " « weight[*ei] <*• std..-endl;
13.4. Алгоритмы минимальных остовных деревьев 197 Вывод этот программы следующий: Ребра остовного дерева: О <--> 2 с весом 1 3 <--> 4 с весом 1 4 < > 0 с весом 1 1 <-> 3 с весом 1 13.4.2. prim_minimum_spanning_tree template <typenane Graph, typename PredecessorMap. typename P. typename T. typename R> void prim_mlnimun_spanning_tree(GraphS G. PredecessorMap pjnap. const bgl_namecjMrams<P. T. R>& params - all defaults) Функция prim_iT)in1nxjni_spanning_treei 1 находит минимальное остовное дерево в неориентированном графе со взвешенными ребрами. Минимальное остовное де- рево — это набор ребер, который соединяет все вершины в графе, где общий вес ребер дерева минимизирован. Функция primjnininum_spanrnng_tree() выводит ребра остовного дерева в отображении предшественников: для каждой вершины се V, р[г) является родителем р в вычисленном минимальном остовном дереве. Реали- зация использует алгоритм Прима [10, 18,38,44]. Алгоритм Прима наращивает минимальное остовное дерево по одной вер- шине за раз, что очень похоже на построение кратчайших путей алгоритмом Дей- кстры’. На каждом шаге алгоритм выбирает ребро для добавления к минималь- ному остовному дереву. Это ребро — кратчайшее из всех, что соединяют любые вершины в дереве с вершинами вне дерева. Алгоритм использует очередь по приоритету для того, чтобы делать этот выбор эффективно. Если вершина и пер- вая в очереди по приоритету, то ребро (/>[«], и) является следующим наикрат- чайшим ребром и добавляется к дереву. Псевдокод этого алгоритма приведен ниже. ПРИМ_МИН_ОСТ_ДЕРЕВ0(6, г, w) для каждой вершины и е V d[u]<— оо л-[м[ <— и co/orfu] <- БЕЛЫЙ colour] <- СЕРЫЙ ф]*-0 В_ОЧЕРЕДЬ(С£ г) пока Q, * 0 и <- ИЗ_ОЧЕРЕДИ(0 дая каждой вершины v е Аф'[м] если (®[и, о] < rf[o]) t> инициализировать вершину и t> посетить вершину г > рассмотреть вершину и > рассмотреть ребро (и, с) ’ Фактически реализация BGLajiropinMa Прима -этовызовзлгоритмаДсйкстрысосо6сн1и>1миаргу- меитами для параметров distanre_comparcO и distance_combine().
198 Глава 13 • Алгоритмы BGL 4- w[u. г] t> релаксация ребра (и, v) X [?>] 4- U если (color[?] ~ БЕЛЫЙ) color[v] 4- СЕРЫЙ В_ОЧЕРЕДЪ( Q, р) > посетить вершину v иначе если (со1ш[ь] “ СЕРЫЙ) ОБНОВИТЬ(Р(2, г) иначе о ребро (и, г) нс релаксируется colot\u\ ч- ЧЕРНЫЙ о завершение обработки вершины и возвратить (л) Где определен Алгоритм Прима находится в boost/graph/prim__minimum_spanning„tree.hpp. Параметры Ниже приведены параметры функции prim_fflin-,mum_spanning_tree(). • IN: const Graphs g Графовый объект, к которому применяется алгоритм. Тип Graph должен быть моделью VertexListGraph и IncidenceGraph. • OUT: PredecessorMap pjnap Отображение предшественников записывает ребра минимального остовного дерева. По завершении алгоритма ребра (п [ы], u) Vwe V находятся в мини- мальном остовиом дереве. Если л [«] - и, то и является исходной вершиной или вершиной, которая недостижима из исходной. Тип PredecessorMap должен быть моделью концепции ReadWritePropertyMap, чьи типы ключа и значений та- кие же, каку дескриптора вершины графа. Именованные параметры Именованные параметры функции pr'mjn1mnmni_spanmng_t''eeO приведены ниже. • IN: root_vertex(vertex_descriptor г) Вершина, которая будет корнем минимального остовного дерева. Выбор кор- невой вершины произволен, он не влияет на способность алгоритма находить минимальное остовное дерево. По умолчанию: *vertices(g). fi rst. • IN: weignt_m3p(WeightMap w_map) Вес или «длина» каждого ребра в графе. Тип Weight Мар должен быть моделью ReadablePropertyMap, Тином ключа для этого отображения свойства должен быть дескриптор ребра графа. Тип значения для отображения весов такой же, как и тип значения отображения расстояний. По умолчанию: get(edge_weight. g).
13.4. Алгоритмы минимальных остовных деревьев 199 • IN: vertex_index_map(VertexlndexMap ijnap) Отображает каждую вершину в целое число из диапазона [0. | V [). Этот параметр необходим для эффективного обновления структуры данных на куче при ре- лаксации ребра. Тип VertexIndexMap должен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом. Тип дескрип- тора вершины графа должен быть задан в качестве типа ключа отображения. По умолчанию: get (vertexj ndex. g). • UTIL: distance_map(D1stanceMap d_map) Вес кратчайшего пути из исходной вершины s в каждую вершину графа g запи- сан в этом отображении свойства. Вес кратчайшего пути — это сумма весов ре- бер, из которых состоит путь. Тип Di staлсеМар должен моделировать ReadWrite- PropertyMap. Тип дескриптора вершины графа требуется задавать в качестве типа ключа отображения расстояний. Тип значения - элемент типа Monoid, состоящий из операции сложения и объекта zero для нейтрального элемента (строимого по умолчанию). Также тип значения должен быть моделью LessThanComparable. По умолчанию: iterator_property_map. созданный из вектора std;: vector с эле- ментами типа, как тип значения WeightMap, размером num verticesC). Он исполь- зует i_map для отображения индексов. • UTIL/OUT: colorjnapCColorMap cjnap) Используется во время выполнения алгоритма для маркировки вершин. В нача- ле вершины имеют белый цвет и становятся серыми, когда попадают в оче- редь. Они перекрашиваются в черный при удалении из очереди. В конце алгорит- ма вершины, достижимые из исходной, имеют черный цвет. Все остальные вершины будут белыми. Тин Col огНар должен быть моделью ReadWritePropertyMap. Дескриптор вершины должен быть задан в качестве типа ключа для отображе- ния, и тип значения отображения должен моделировать ColorValue. По умолчанию: iterator_property_map, созданный из вектора std::vector с эле- ментами THnadefault_color_type, размером num_vertices(). Он использует i_map для отображения индексов. Сложность Временная сложность порядка О(\Е | log | V|). Пример Исходный код примера из листинга 13.7 находится вфайлеехатр1е/рпт-ехагпр1е.срр. Листинг 13.7. Пример использования алгоритма Прима typedef adjacencyjist < vecS. vecS. undirected?. property<vertex2distance_t, int>. property < edge_weight_t. int > > Graph: typedef std:;pair < int. int >£ const int numjiodes - 5: E edges[] - { ЕГО. 21. E(l.l). ECI. 3). £(1. 4). E(2. 1). V2. 3). EC3. 4). E(4. 0) }: mt weights!] -{1.2. 1. 2. 7. 3. 1. 1 }. Graph gtedges. edges + sizeof(edges) 1 sueof(E). weights, rwjwdes); продолжение
200 Глава 13 • Алгоритмы BGL Листинг 13.7 (продолжение) property_nwp<Graph, edg<?_weight_t>: Дуре wightnap * gct(edge_weight. g): std::vector < grapn_traits < Grape >::vertexjtescriptor > p<num_vertices(g>). pr4,m_miniuwm_spanning_tree(g. &p[Q]): for (std:;s1?e_t i-O:i s size!). *+!) if <’p[l] !“ i) std:.coot <• "parentf* •-< > « "j - ” <* p[i 1 « std: -.endl; else std.xout « "parentl” « 1 "J - no parent" «' std::end1: Вывод этой программы такой: parent[O] • О parentfl] • 3 parentlS] - О pa rent[3] - a parentr.4] » 0 13.5. Статические компоненты связности 13.5.1. connected„components teriplate <t.ypename Graph, typename ComponentMap. typename P. typename I. typename R> typena.-ne property _traits*CompcnentMap>::value_type connected_components(const Graphs g. ComponentMap c, const bgl jiamedjHrams<P. T, R>& params - all defaults) Функция connected_components() вычисляет компоненты связности неориенти- рованного графа, используя подход, основанный на поиске в глубину. Компонен- та связности — это группа вершин неориентированного графа, в которой каждая вершина достижима из любой другой. Если компоненты связности нужно вычи< - лить для растущего графа, то метод, основанный на непересекающихся множествах (функция 1 nc remental components ()), является битее быстрым. Для статических графов поиск в глубину быстрее [10]. Результат алгоритма записывается в отображение свойства компоненты с, ко- торое содержит номера компонент для каждой вершины. Полное чисто компо- нент — возвращаемое значение этой функции. Где определен Алгоритм связанных компонент находится в boo$t/graph/connected_components.hpp. Параметры Ниже приведены параметры функции connectec_coTOonentsO. • IN: const Graphs g Неориентированный граф. Тип графа должен быть моделью VertexListGraph и IncidenceGraph. • ОШ : Component Мар с
13.5. Статические компоненты связности 201 Алгоритм вычисляет, сколько компонент связности находятся в графе, и при- сваивает каждой компоненте целочисленную .метку. Затем алгоритм записы- вает. какой компоненте принадлежит каждая вершина графа, в отображение свойства компонент. Тип ComponentMap должен быть моделью WritablePropertyMap. Тип значения должен быть verflce$_size_type графа. Тин ключа - тип де- скриптора вершины графа. Именованные параметры Ниже приведены именованные параметры функции connected_components(). • UTIL: со’ог_пир(Со1огМар color) Использует ся алгоритмом для отслеживания гцюдвижения по графу. Тип Col огНар должен быть моделью ReadWritePropertyMap, тип ключа — дескриптор вершины графа, тип значения сэ!сг_тар должен быть моделью ColorValue. По умолчанию: i terator_propert.yjnap, созданный из вектора std -: vector с эле- ментами типа defaul t_co’ or_type, размером nuni_vert ices (1. On использует ijnap для отображения индексов. • IN. vertexjndexjnapCVertexlndexMap т_пир) Отображает каждую вершину в целое число из диапазона 10. | V |). Этот параметр необходим, только когда используется отображение свойства цветовой окрас- ки по умолчанию. Тип Ve“tex IndexMaр должен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом. Тип дескрип- тора вершины графа должен быть задан в качестве типа ключа отображения. По умолчанию: get (vertex_index. g). Сложность Временная сложность для сильных компонент связности порядка О(| V| + |Е |). Вре- менная сложность для компонент связности того же порядка. Пример Вычисление компонент связности неориентированного графа приведено в лис- тинге 13.8. Листинг 13.8. Вычисление компонент связности неориентированного графа ( conrected-coniponents.cpp ) ihnclude <boost/config hpp> # int Iude <lostream* include «vector* # 1 nd ude «boost/graph/connectedjtaoiponenis. hpp* # include «boost/graph/adjacency_list.hep* int nainO ( using namespace boost: typedef adjacencyjlst < vecS. vecS. undmectedS > Graph: typedef graph_traTts < Graph >::vertex_descriptor Vertex: const int H-5: Graph G(N): add_edge(O. 1. G); продолжение
202 Глава 13 • Алгоритмы BGL Листинг 13.8 (продолжение) add_edge(l. 4. G): add~edge(4. 0. G): ad<edgef2. 5. G): std-:vector<int> c(num_vert1ces(G)): Int num « connectedjzomponents (G. make jteratorj>rcperty_map(c. begin О, gettvertex_index, G). c[0])): std::tout '-< std: -endl; std::vector < int >: iterator i: std::cout « "Общее число «оппонент: " « num <♦ std:-endl: for (i “c.hegmO; i !- c.endO; *+1) std::cout « ‘Вершина " « i - c.begwO « ” в компоненте " « *1 « std::endl: std.'xout « std;:endl; return EXIT_SUCCESS; Вывод будет следующим: Общее число компонент: 3 Вершина 0 в компоненте О Вершина 1 в компоненте О Вершина 2 в компоненте 1 Вершина 3 в компоненте 2 Вершина 4 в компоненте О Вершина 5 в компоненте 1 13.5.2. strong_components template «class Graph, class ComponentMap, class P. class T, class R> typename propei'ty_traits<Component.Hap>: :va'цезуре strong_components(Graprt g. ComppnentMap comp? const Dgl__named_params<P. T. R>& params - all defaults) Функция strong_ccmponents() вычисляет сильные компоненты связности ориен- тированного графа с использованием алгоритма Тарьяна, который основан на по- иске в глубину [43]. Результат алгоритма записывается в отображение свойства компонент comp, которое содержит номер компоненты для каждой вершины. Идентификационные номера изменяются от нуля до числа компонент в графе минус один. Полное чис- ло компонент - возвращаемое значение этой функции. Где определен Алгоритм Тарьяна находится в boost/graph/$trong_components.hpp. Определения Сильная компонента связности ориентированного графа G = ( V, Е) — максимальный набор вершин U а V, такой, что для каждой пары вершин и иив t/мы имеем как - путь из и в v, так и путь из г» в и, то есть а и v достижимы друг из друга. Ниже дано неформальное описание алгоритма Тарьяна для вычисления силь- ных компонент связности. В основном эго вариация поиска в глубину с дополни- тельными действиями в событийных точках «посещение вершины» и «окончание обработки вершины». Можно думать о действиях и точке «посещение вершины»
13.5. Статические компоненты связности 203 как о выполняемых «на пути вниз» по дереву поиска в глубину (от корня к листь- ям), а о действиях в точке «окончание обработки вершины» — как о выполняе- мых «на пути вверх». Три вещи должно произойти по пути вниз. Для каждой посещенной вершины и мы записываем отметку посещения ефД, помещаем и во вспомогательный стек и при- сваиваем гооХ| и] “ и. Поле root в итоге будет отображать каждую вершину на самую верхнюю вершину в той же сильной компоненте связности. Устанавливая rootfи] = и. мы начинаем с ситуации, когда каждая вершина - компонента сама по себе. Опишем теперь то, что происходит по пути вверх. Предположим, что мы только что закончили посещение всех вершин, смежных с некоторой вершиной и. Теперь можно рассмотреть каждую из смежных вершин снова, проверяя корень каждой из них. Корень, имеющий наименьшее значение метки посещения, назовем корнем а. Затем мы сравниваем а с вершиной и и рассматриваем следующие случаи: 1. Если «'/[а] < «/[и], то мы знаем, что а — предок и в дереве поиска в глубину, и значит, мы имеем цикл и и должна быть в одной сильной компоненте связ- ности с а. Тогда мы присваиваем гооД«] “ а и продолжаем путь назад, вверх i:o дереву поиска в глубину. 2 Если а = и, то мы знаем, что и должна быть самой верхней вершиной подде- рева, определяющего сильные компоненты связности. Все вершины витом поддереве расположены ниже в стеке по отношению к вершине и. так что мы выталкиваем вершины из стека, пока не достигнем и, и отмечаем каж- дую как принадлежащую той же самой компоненте. 3. Если <i|«] то смежные вершины находятся в разных сильных компонен- тах связности. Мы продолжаем путь назад вверх по дереву поиска в глубину. Параметры Ниже приведены параметры функции strong_components(). • IN: const Graphs g Ориентированный граф. Тип графа должен быть моделью VertexListGraph и IncidenceGraph. • OUT: ComponentMap coup Алгоритм вычисляет количество компонент связности в графе и присваивает каждой компоненте целочисленную метку. Затем алгоритм записывает, какой компоненте связности принадлежит каждая вершина графа, записывая номер компоненты в отображение свойства компонент. Тип CoinponentMap должен быть моделью WritablePropertyMap. Тип значения требуется задавать целым числом, лучше таким же, как vertices_size_type графа. Тип ключа должен быть типом дескриптора вершины. Именованные параметры Ниже приведены именованные параметры функции strcmg comporentsO. • UTIL: rootjnaptRootMap r_map) Используется алгоритмом для записи кандидатов в корневые вершины для каждой вершины. В конце работы алгоритма у каждой сильной компоненты связности всего одна корневая вершина и get (г map. v) возвращает корневую
204 Глава 13 • Алгоритмы BGL вершину для той компоненты, в которую входит v. RootHap должен быть ReadWrite- PropertyMap, где тип ключа и тип значения должны совпадать с типом дескрип- тора вершины в графе. По умолчанию: iterator_property_map. созданный из вектора std:: vector описа- телей вершин, размером num_vertic.es (д). Использует 1_таэ для отображения индексов. • UTIL: discover_time(TimeMap t_map) Используется алгоритмом для отслеживания упорядочения вершин поиска в глубину. TimeMap должен быть моделью ReadWritePropertyMap и его тип значе- ния должен быть целым типом. Тип ключа должен быть дескриптором верши- ны графа. По умолчанию: 1 terator_property_map, созданный из вектора std:: vector целых чисел, размером num_vertices(g). Использует _тар для отображения индексов • UTIL: со1ог_гпар(Со1огМар с_тар) Используется алгоритмом для отслеживания продвижения по графу. Тип Col огМар должен быть моделью ReadWritePropertyMap, тип ключа — дескриптор вершины графа, тип значения col or тар должен быть моделью ColorValue. По умолчанию: iterator_property_map, созданный из вектора std::vector с эле- ментами типа default_col or_type, размером nun_vertices(). Он использует ijrap для отображения индексов. • IN: vertex_index_map(Vertex!ndexMap ijnap) Отображает каждую вершину в целое число из диапазона [О, N), где .V — число вершин графа. Этот параметр необходим, только когда используется значение по умолчанию для одного из других именованных параметров. Тип Vertex index Мар должен моделировать ReadablePropertyMap. Тип значения отображения дол- жен быть целочисленным типом. Тип дескриптора вершины графа требуется задавать в качестве типа ключа отображения. По умолчанию: get(vertex_index. g). Сложность Временная сложность для алгоритма нахождения сильных компонент связности порядка О(| V | + \Е |). Смотри также Для дополнительной информации смотрите connected components!) и i ncremental-Com- ponents!). Пример Вычисление сильных компонент связности для ориентированного графа приве- дено в листинге 13.9. Листинг 13.9. Вычисление сильных компонент связности для ориентированного графа < strong-coroponents.cpp > #lnclude <boost/ccnf1g.hpp> #1nclude «vector* ^include «iostream*
13.6. Растущие компоненты связности 205 nc1ude <boost/gr aph/strong_components.hpp> f i ndude <boost/graph/adjacency_l i st. hpp> int mam() { using namespace boost. typedef adjecency_list < vecS, vecS, directedS > Graph, const int N » 6. Graph G(N); add_eugetO. 1. S); add_edgeH . 1. G); aad_edg₽(l .-s. 6). add_edge(l, 4. G); atla_edge(3. 4. Gt add_edg€(3. 0. id: add_edge(4, 3. G): addjJdge(5. 2. G): std:-vector<lnt> c(N); int num - strong_components (G. make_lterator_property_map(c begin!). get(vertex_wdex. G). c(0p): std.-rcout « "Обвее число коятонею: " « num « srcf-en-Jl. std:-vector < int >: iterator v. for n - c.begint): i c.endO; +*i) std:.cout « ’’Вершина ” « i - c.begin!) « “ в компоненте “ « *1 « std::end I: return EXITJICCESS. Программа выводит следующее: Ote число компонент; 3 Вервию 0 в компоненте О Вершина 1 в компоненте О Вершина 2 в компоненте 1 Вершина 3 в компоненте о Вернина 4 в компоненте О Вершина 5 в компоненте 2 13.6. Растущие компоненты связности Этот раздел описывает семейство функций и классов, которые вычисляют ком- поненты связности неориентированного графа. Алгоритм. который используется здесь, основан на структуре данных для представления не пересекающихся мно- жеств (disjoint-sets) [10,44], которая является лучшим средством для ситуации, когда граф растет (к нему добавляются ребра) и информация о компонентах связ- ности нуждается в постоянном обновлении. Класс непересекающихся множеств (НМ) описан в разделе 16.6. Следующие операции являются основными функциями для вычисления и поддержания компонент связности Использованные здесь объекты — граф д, НМ-обьект ds и вершины и и v. • initialize_incremental_cOTponents(g.d$) Основная инициализация структуры непересекающихся множеств. Каждая вершина графа g в своем собственном множестве. • incremental jtomponents(g.ds) Компоненты связности вычисляются на основе ребер в графе g и информации, включенной в НМ-объект ds.
206 Глава 13 • Алгоритмы BGL • ds.find_sel(v) Выдает информацию о компоненте для вершины v из НМ-объекта. • ds.umon_set(u,v) Обновляет НМ-объект, когда ребро (и, о) добавляется к графу. Сложность Временная сложность для всего процесса порядка O(|V| + )£|а(|Я|, | V |)), где |£| — полное число ребер в графе (в конце процесса) и [V | — число вершин, а — функция, обратная функции Аккермана. Последняя имеет взрывной рекурсивно-экспонен- циальный рост. Значит, обратная ей функция растет крайне медленно. На практи- ке а(т, п) < 4, что означает временную сложность немного больше, чем О(| V | + \Е'). Пример В примере из листинга 13.10 мы поддерживаем компоненты связности графа при до- бавлении ребер, используя структуру данных непересекающихся множеств. Полный исходный код этого примера находится в файле example/incremental-components-eg.cpp. Листинг 13.10. Пример увеличивающихся компонент связности и Создать гоаф typedef adjacencyjlst < vecS. vecS. undirectedS > Graph: typedef graph traits < Graph >::vertex-descriptor Vertex: const int N « 6; Graph. GiN.i; add_edge(O. 1. G): add__edge(l. 4, G), // создать НМ-объект. для которого нужны свойства ранга и родителя вершины std :vector < Vertex > rank(num_verttces(G)): std:-.vector < Vertex > parent(num_vertices(GD. typedef g.-aphJraits<Graph>: :vertices_size_type* Rank: typedef Vertex* Parent: disjoint_sets < Rank. Parent > ds(&rank[Q], &parent[O]): // определить компоненты связности, сохраняя результат в НМ-обьекте initial1 zejnc remental-Component siG. ds): incrementaT_components(G. ds): li Добавить еще пару вершин и обновить негересекаюшиеся множества graphjraits < Graph >::edge_descnptor е. bool flag: He(e. flag) • add_edge(4. 0. fil; ds.umcn_set(4. 0); tie(e. flag) - add_edge(j. 5. G). ds umon_set(2. 5h graphjraits < Graph >:-vertex_iterator iter, end: for (tie!iter, end) « verticesiG): iter !- end: +*-1ter) std.:coot « "лредставительГ « *iter « "] - “ « ds find_set(*iter) « std::endl:; std.:cout << std: .end!: typedef component-index < unsigneO int Components, Components components(parent,begin!). parent.endo), for (Components: :si?e type 1-0; i < crxnconents.sizeO: »+i) { std :ccwt « "компонента “ « т « " содержит: ".
13.6. Растущие компоненты связности 207 for (Components- :value_type-iterator j - components[i].beginO j I- romponents[i].end(): +.]) std::cout « *j « " std.:tout <•' std::end!: Вывод будет следующим: представитель^] - 1 представитель!!] - 1 представитель!?] - 5 представитель!?] - 3 представитель!*!] - I лредсгавитель[5] - 5 компонента 0 содержит; 4 1 о компонента 1 содержит: 3 компонента 2 содержит: 5 2 Где определен Все функции этого раздела определены в boost/graph/incrementaLcomponents.hpp. 13.6.1. initializeJncremental_components template «typename Vertex!istGraph, typename DisjointSets* void initialize_incremental_conponent$(VertexListGraph8 G, Disjo^ntSetsS dsi Функция imfiahze_incremental_componentsO инициализирует структуру не- пересекаюшихся множеств для алгоритма увеличивающихся компонент связнос- ти, делая каждую вершину неориентированного графа членом своей собственной компоненты. Сложность Временная сложность порядка 0(| V |). 13.6.2. incremental-components template «typename Edge!istGraph. typename DisjointSets^ void 1ncrementa1_components(EdgeListGraphS g. DisjointSetsS dsi Функция Incremental components () вычисляет компоненты связности неориен- тированного графа, включая результаты в НМ-структуру данных. Сложность Временная сложность порядка 0(\Е |). 13.6.3. same_component template «typename Vertex, typename Disjo’ntSets> bool same_component(Vertex u. Vertex v. DisjointSets^ dsj Функция sare_component() определяет. находятся ли и и v в той же самой ком- поненте. Сложность Временная сложность — 0(а(|£ |, |V |)).
208 Глава 13 • Алгоритмы BGL 13.6.4. componentjndex component^ ndex<Index> Класс componentjndex обеспечивает представление данных для компонент графа, подобное контейнеру STL. Каждая компонента является контейнероподобным объектом, и объект conponentJndex обеспечивает доступ к объектам компоненты через operator!]. Объект component J ndex инициализируется свойством родители*! в непересекающихся множествах, вычисленных функцией 1 ncremental_components Параметры шаблона • Index — целый беззнаковый тип, используемый для подсчета компонент. Где определен Класс component Jndex находится в boost/graph/incremental_components.hpp. Ассоциированные типы Ниже приведены ассоциированные типы класса componentJ ndex. • component-index::va1ue_type Тип для объекта-компоненты. Тип компоненты имеет следующие члены. • component_index::size_type Тип, используемый для представления числа компонент. Функции — методы класса Ниже приведены функции — методы класса component-index. • template <typenane ComponentsContair.er> component_index::componentjndex(const Component$Container& c) Создаег component-index, используя информацию из контейнера компонент с, который был результатом выполнения Incremental-Components. • template <typename Parentlterator> component-index::component_index(ParentIterator first. Parent Iterator last) Создает индекс компонент из «родителей», вычисленных функцией incre mental-Components (). • value_type componentjndex::operator! ](size_type i) const Возвращает 1 -to компоненту графа. • size_type componentjndex: ;$1ze() const Возвращает число компонент графа. Ассоциированные типы для компоненты Тип val ue_type для component J ndex — компонента, которая имеет следующие ассо- циированные типы. • v-21 ue_tyре:: va 1 ue_type Тип значения для объекта-компоненты — идентификатор вершины. • valuejype: iterator
13.7. Алгоритмы максимального потока 209 • va 1 ue_type:: constj terator Этот итератор может быть применен для обхода всех вершин компоненты. Данный итератор разыменовывается и идентификатор вершины. Функции — методы класса компоненты Тип val je_type для conoonentjnaex является представлением компоненты и имеет следующие функции - методы класса. • iterator begin;) const Возвращает итератор, указывающий на первую вершину компоненты. • iterator end() const Возвращает итератор, указывающий на последнюю вершину компоненты. 13.7. Алгоритмы максимального потока 13.7.1. edmunds_karp__max_flow template «typename Graph. typename F. typename 1. typename typename detail' edgejzapaatyjwlue^Graph, >•_ r, -type edtnunds_karp_max_flow(Graphfc g. lypenatse grapn_traits<Graph>. :verte>\jiescriptor sre, typename grapt-_tra’ts<Graph>-.vtri-exjjescrtptor tint. const bg1_nared_params<P. T. R>S params «all def au’t s 1 Функция edmunds_karp_max_f'I ow() вычисляет максимальный поток в сети (см. гла- ву 8). Максимальный поток является возвращаемым значением этой функции. Функция также вычисляет значения потока J'(u, v) V(и, с) е £, которые возвра- щаются в форме остаточной мощности r(u, v) - c(w. v) - f(u, v). Где определен Алгоритм находится в boost/graph/edmunds_karp_max_flow.hpp. Параметры Ниже приведены параметры функции e.tTun;!<_karpj«v.j‘].jwr у • И: Graphs д Ориентированный граф. Тип графа должен быть моделью VertexListGraph и Inci- denceGraph. Для каждого ребра (и, г>) обратное ребро (v, и) также должно быть в графе. • IN: vertexjdescnptor sre Исходная вершина для графа потоковой сети. • IN: vertex_ue$cr-iptcr- sink Сток для графа потоковой сети. Именованные параметры Ниже приведены именованные параметры функции edTunds J.-jrp_nax_How(). • IN: capacity,тарССарасиуЕйоеМар cep)
210 Глава 13 • Алгоритмы BGL Отображение свойства мощности ребра. Тип должен быть моделью констант- ной LvaluePropertyMap. Тип ключа отображения должен быть типом дескрипто- ра ребра графа. По умолчанию: get(edge_capac'.ty, g). • OUT: residual j:apacityjnap(ResidualCapacity£dgeMap res) Отображение свойства остаточной мощности ребра. Тин должен быть моде- лью изменяемой LvaluePropertyMap. Тип ключа отображения должен быть ти- пом дескриптора ребра графа. По умолчанию: get;edge_residual_capacity, д). • IN: reverse_edge_map(Reverse£dgeMap rev) Отображение свойства ребра, которое отображает каждое ребро графа (а, г) в об- ратное ребро (v, и). Это отображение должно быть моделью константной Ivalue- PropertyMap. Тип ключа отображения должен быть типом дескриптора ребра графа. По умолчанию: get(edge__reverse. g). • UTIL; predecessor_map(PredecessorMap p_map) Это отображение предшественников отличается от обычного отображения предшественников тем, что тип значения является типом дескриптора ребра, а не вершины. Тип ключа для этого отображе ния — дескриптор вершины. По умолчанию: 1г.ега1ог_ргорепу_тар,созданный из вектораstd:: vector описато- лей ребер, размером num vertices(g). В качестве индекса отображения исполь- зуется 1 _пир. • ШИ: color_map(Co'iorMap cjnap) Это отображение используется во внутренних вычислениях. Тип ColогМар дол- жен быть моделью ReadWritePropertyMap. Дескриптор вершины требуется зада- вать в качестве типа ключа отображения, и тип значения отображения должен быть моделью ColorValue. По умолчанию: iterator_property_map, созданный из вектора std;: vector эле- ментов типа default_color_type, размером num_vertices(g). В качестве индекса отображения используется i_map. • IN: vertexjndex_map(VertexIndexMap index map) Это отображение необходимо, если применялось отображение цветов по умол- чанию или отображение предшественников по умолчанию. Каждая вершина графа отображается в целое число из диапазона [0, | V ]). Отображение должно быть моделью константной LvaluePropertyMap. Тип ключа отображения должен совпадать с типом дескриптора вершины графа. По умолчанию: get(vertex_index. g). Пример Программа в листинге 13. И читает пример задачи максимального потока (граср с мощностями ребер) из файла а формате D1MACS [1]. Листинг 13.11. Решение задачи максимального потока ( edmuncfs-kar-p.eg.cpp ) « #includ<? <t>OGst/config.hpp>
13.7. Алгоритмы максимального потока 211 /include «iostream» /include «string» # 1 ncl ude «boost/graph/edtnundsJiarpjMxJ'l ow. hpp # include «boost/graph/adjacencyj i st.ipp> /include «boost/graph/read.dimacs,hpp> # 1nclude <boost/graph/graph_uti11ty.hop» int main!) { using namespace boost: typedef adjacency_list_tra1ts « vecS. vecS. directedS > Traits; typedef adjacencyjist < UstS. vecS. directedS. property < verlex_name_t. std::string >. property < edge_capacity_t. long. property < edge_residual_capacity_t. long. property < edge_reverse_t. Traits.:edge_descriptor > > > > Graph: Graph g: propertyjnap « Graph. edge_capacity_t >:;type capacity • get(edge_capacity. g): property map < Graph.~edge_reverse_t >::type rev - get(edge_reverse. g); property~fnap < Graph. edge_residuaT_capaclty_t >-:type residual_capacity - get(edge_residual_capacity. g): Traits;:vertex_descriptor s. t; read_dimacs_trax_flow(g. capacity, rev. s. t): long flow - edmundsjcarpjnax_flow(g. s. t): std::cout « "с Полный поток:" « std;:endl; stdi.cout « "s " « flow « std::endl « std:-endl; std::cout « “с значения потока:’ « std::endl: graph_tralts < Graph >;:vertex_iterator u_iter. u_end; graptTtraits < Graph >::out_edge_iterator ei. e_end; for Ctie(u_iter. u_end) - vertices(g): u_iter !- u_end: +*и_11ег) for (tieiei, e_end) - out_edges(*u_iter. g); ei e_end: *+ei) if (capacity[*eij > 0) std: :cout « "f " «< *u_Tter « '' “ <« target(*ei. g) « " " « (capacity[*ei] • residual_capaclty[*ei]) « std::endl : return EXIT-SUCCESS: } Программа выводит следующее; с Полный лоток: s 13 с значения потока: f 0 6 3 f 0 1 6 f 0 24 f 1 5 1 f 1 0 0 f 1 3 5 f 2 4 4 f 2 3 0 f 2 0 О f 3 7 5 f 3 2 0
212 Глава 13 • Алгоритмы BGL f 3 I & f 4 6 4 14 6 0 15 4 0 15/5 f 6 ? 3 f 6 4 0 f 7 6 0 f 7 5 0 13.7.2. push_relabeLmaxJlow tempi die <l>'peneme Graph. typename P, typename T. typename R> typename detail::edge_capacity_va’ue<Graph. P. T. P.>-:type re । cBe i_ma •<_ fl ow( Graphs g. type-.» cr jph'tra’.ts<Graph>^:vertex descriptor f-rc. typename graptrtr/PtvGi aph>. •vertexjtesc'-iutor $ ля. const Ьд1_пагей_рагаяв*Р. !, R>& params) Функция pjsh_relabel_max_f;ow() вычисляет максимальный поток сети (см, гла- ву 8). Максимальный поток является возвращаемым значением этой функции. Функция также вычисляет значения потока/(м, л) V(«, ?,•) е Е, которые возвраща- ются в форме остаточной мощности г (а, г1) ~ с(и.v) - f(u. v). Сеть с ребрами, от- меченными значениями потока и мощности, изображена на рис. 13.7. Имеется несколько особых требований к входному графу и параметрам отобра- жений свойств для этого алгоритма. Во-первых, ориентированный граф С, “ (V, Е). представляющий есть, должен быть расширен так, чтобы для любого ребра из £ в него вошло также и обратное ребро. То есть входной граф должен быть C’m(V,{£U£ ’})• Аргумент rev для R-werseEdeeMap должен отображать каждое ребро в исходном графе на обратное ретро, то есть (и, н) (г, и) V(«, п) е Е. Аргумент r.as от Capaci r.yEdgeMap должен отображать каждое оебро в Ена положительное чис- ло, а каждое ребро в Е г в 0. Другими словами, отображение мощности должно удовлетворять этим ограничениям: с(и, г>) > 0 и t(v, и) - 0 для каждого (и, о) е £. Рис. 13.7. Потоковая сеть, а которой ребра помечены значениями потока и мощности Где определен Алгоритм находится в boost/graph/push_relabel_max_flow.hpp. Параметры Ниже приведены параметры функции pusL_reli»L’el_nax_flowl).
13.7. Алгоритмы максимального потока 213 • IN: Graphs g Ориентированный граф, Графовый тип должен быть моделью VertexListGraph и IncidenceGraph. Для каждого ребра (и, г) обратное ребро (о, и) также должно быть в графе. • IN- vertex_descriptor src Исходная вершина для графа потоковой сети. • IN: vertex_jescriptor $iri«: Сток для графа потоковой сети. Именованные параметры Ниже приведены именованные параметры функции push_rehbel jrex_f]ow( Г • IN-. c.apacity_mapiCcpacityEdgeMcp cap) Отображение свойства реберной мощности. Тип должен быть моделью кон- стантной LvaluePropertyMap. Тип ключа отображения должен быть дескрипто- ром ребра графа. По умолчанию: getteagr^capacity, g). • OUT: residual_eapaciiy_map(Re$ic!ualCapacityEdgeMap res) Отображение свойства остаточной реберной мощности. Тип должен быть мо- делью неконстантной LvaluePropertyMap. Тип ключа отображения должен быть дескриптором ребра фафа. По умолчанию: get(edge_residu<T' _сарас ’.ту. g}. • IN; reverse_edgejnap(Rever5eFdgM)p rev) Отображение свойства )>сбра, котике отображает каждое реб|«> (и, р) в rpaijx.- на обратное ребро (?••, и). Отображение должно быть моделью константной Lvalue- PropertyMap, Тип ключа отображения должен бы гь дескриптором pwpa графа. По умолчанию: get redge_"i?ver$e. g). • IN: vertexjndexjnapivertexlndexMap indexjnap) Это отображает каждую вершину на целое в диапазоне [О, N ), где# - количе- ство вершин в графе. Отображение должно быть .моделью константной Lvalue- PropertyMap. Тип ключа отображения должен быть дескриптором ребра j-рафа. I|оумолчанию: get(vertex^ nde/. о). Пример Пример в листинге 13.12 читает пример задачи максимального потока (граф с мощностями ребер) из файла в формате DIM ACS j t ]. Листинг 13.12. Решение задачи максимального потока методом проталкивания предпотока ( push-relabel-ед срр > а ihnclucte «boost/config hpo> include «iostream» #ir>clu<3e «string» fir.cluae <bcost/graph/push reDbel_ma>:_r';o<v hpp> #inchide <boost-’graph/adjacencyJ ist npp> ^include «boost, 0ra₽n/reao_diiMcs hpp>
214 Глава 13 • Алгоритмы BGL Листинг 13.12 {продолжение) using namespace boost: typedef adjacency_!'st_traits < vecS. vecS. directedS > Traits: typedef adjacencyjist < vecS. vecS. directedS. property < vertex_name_t, std::strmg >. property < edge_capacity_t. long. property < edge2res1dual_capacity_t. long. property < edge^reverseji. Traits::edge_descnptor > > > > Graph Graph g: propertyjrap < Graph. edge_capacity_t > -type capacity - get(edge_capacity, gi.~ property_map < Graph. edge_residual_capacity_t >;:type residual_capacity - get(edge_residual_capadty. g): prooerty_map < Graph. edge_rever$e_t >::type rev - gettedge_revers Traits::vertex_descriptor s. t: read_difMc:sjnax_flow(g. capacity, rev. s. t). long flow - push_relabel_max_f1ow(g. s. t); std::cout « "c The total flow:" « std-:endl: std.-.cout « "s " « flow « std;:endl «•< std::endl. std::cout « “c flow values:” « std::endl: graph_traits < Grapn >; :vertex_1terator u_iter. j_er.d; graph_traits < Graph >::out_edge_iterator ei. e_end: for >tie(u_iter. u_end) = verttcesCg): ujter I- u_end; ++u_iter) for (tietei. e_end) « out_edges(*u_iter. g): ei !« e_end; ++ei) if (capacity[*ei] > 0) std :cout « “f " « *u_iter « " " « target(*ei, g) « " ’ « (capacity[*ei 1 residual capacity[*ei]i <<std::encl: return Exn_SUCCESS: Программа выводит следующее: с Полный поток s 13 С значения потока: Г 0 б 3 f 0 1 о f О ? 10 f 1 5 1 ( 1 0 0 f 1 3 0 f 2 4 4 f 2 3 6 f 2 0 О f 3 7 5 f 3 2 0 f 3 1 1 f 4 6 4 f 4 i 0 f 5 4 c f b i b f 6 ? 3 f 6 4 0 ? (> C f 7 5 0
Классы BGL 14.1. Классы графов 14.1.1. adjacencyJist adjacencyJist<EdgeL,st. VertexList. Directed. VertexProperties. Edgeproperties. Graphproperties» Класс adjacency_l i st реализует интерфейс BGL-графа, используя несколько раз- личных вариантов традиционного представления графа в виде списка смежности. Представление графа в виде списка смежности содержит последовательность исходящих ребер для каждой вершины. Для разреженных графов это экономит место по сравнению с матрицей смежности, поскольку памяти требуется только порядка О(| V | + [£ |), а не 0(| V |2). Кроме того, доступ к исходящим ребрам для каж- дой вершины может быть сделан эффективным. Представление ориентированно- го графа в виде списка смежности показано на рис. 14.1. Рис. 14.1. Представление ориентированного графа в виде списка смежности Параметры шаблона класса adjacency ! 1st предоставляют много возможнос- тей, это позволяет вам выбрать наиболее подходящий вариант класса. Параметр
216 Глава 14 • Классы BGL шаблона VertexLI st класса adjacencyj isi задает вид контейнера для представле- ния последовательности вершин (прямоугольник на рис. 14.1). Параметр шабло- на WgeL’ist задает вид контейнера для представления последовательности исхо- дящих ребер для каждой вершины (овалы на рис. 14.1). Выбор EuqeLi st и VertexL'ist определяет затраты намята и эффективность различных операций на графе. Воз- можные варианты и компромиссы рассмотрены далее в этом разделе. Параметр шаблона DI rected указывает, является ли граф ориентированным, не- ориентированным пли ориентированным с доступом к входящим и исходящим ре- брам (который мы называем двунаправленным). Двунаправленный граф требует в 2 раза больше места (на каждое ребро), чем ориентированный, так как каждое ребро появляется как в списке исходящих, так и в списке входящих ребер. Представ- ление и виде списка смежности неориентированного графа показано на рис. 14.2. Двунаправленное представление ориентированного графа показано на рис. 14.3. Рис. 14.2. Представление неориентированного графа в виде списка смежности Рис. 14.3. Двунаправленное представление списка смежности ориентированного графа Пример В следующем примере (листинг 14.1) граф используется для представления генеа- лог! ! чет кого дере на. Листинг 14.1. Работа с фамильным деревом / family-г’-ее-ед срр } » include «lostrean» find ude <vector>
14.1. Классы графов 217 #include <stnrg> #lnclude <boost/grapn/adjecercyJ ил hpp> ^include <boost/tuple/tuple hpp> enur family { Jeanie. Debbie. Rick. John. Amanda. Mjcga-'et. Benjamin. N ); int mainO { using namespace boost; const char *name[l - { ’Jeanie". "Dertie". "Rick" "John". "Amanua". 'Margaret’ "Benjamin" i: acjacency_b’t <> g(N); Md_edge<3eanie. Debbie. g>: add_edge(Jeanie. Rick, g); add^edgelJeanie. John, g); aacTedge (Debbie. Amanda, gi; addj»dge<Rick, Margaret gl: acd_eogeiJohn. Benjamin g;; graphjraits < adjacency J i st <> >: rvertexj terator i. w.c. grapiftrans < adjacencyJ i st <> >.: adjacency iterator a;, a „end; property_map < adjacency!ist <>. vertex_ir;de>'j. tye index jnap • get(vertex_wdex. g). for (tiefj. end) - verticestg): i end: ►•<! < sta-:cout « name[getcindex_map. *i)J: tieiai. a_end) - adjacent_vertices!*i. «); if tai — a_ena) std: xout-** " не имеет детей"- el se std::cout « " является родителем дпя ". for Г; at '• a_end: »+aii { std::cout name[grf(indcx_map. *01']. if (boost:-.next(ail a_end> Std: .cout « ~. ”. std.-xout « stdxendl; } return EXIT_5UCCESS: } Эта программа выводит следующее: Jeanie является родителем для ОеьЫе. R;ck, John Debbie является роаи.:епеи для Amanda Rick является родителем для Margaret John является родителей для Benjamin Amanda не имеет детей Margaret не имеет детей benjamin не имеет детей
218 Глава 14 • Классы BGL Параметры шаблона Ниже приведены параметры шаблона класса adjncencyjist. EdgeList Контейнер для представления списка ребер каждой вершины. По умолчанию: vecS. Vert exl. 1st. Контейнер для представления множества вер- шин графа. По умолчанию: vecS. Di rected Определяет, является ли граф ориентирован- ным, неориентированным или ориентирован- ным с двунаправленным доступом к ребрам (доступ к исходящим и входящим ребрам). Соответствующие значения: di rectedS, undh - rectedS и indirect топа IS. По умолчанию: directedS. VertexProperties Задает внутреннее хранилище для свойств вершин. По умолчанию: no_property. EdgeProperties Задает внупреннее хранилище для свойств ребер. По умолчанию: no_property. GraphProperties Задает хранилище для свойств графа. По умолчанию: no_property. Модель для DefaultConstructible, CopyConstructible, Assignable, VertexListGraph, EdgeListGraph, Incidence- Graph, AdjacencyGraph, VertexMutableGraph и EdgeMutableGraph. Также adjacencyj! st моделирует концепцию BidirectionalGraph, когда Di rected’b i di recti onal S или Di rected=undi rectedS, и моделирует VertexMutableFropertyGraph и Ed- geMutablePropertyGraph, когда добавляются соответствующие внутренние свойства. Где определен Класс adjacencyj i st находится в файле boost/graph/adjacency_list.hpp. Ассоциированные типы Ниже приведены ассоциированные типы класса adjacencyj 1st. • graphjraits<adjacencyjist>:;vertex_descriptor Тип дескрипторов вершин, ассоциированный с adjacencyj 1st. (Требуется для Graph.) • graph_tralts<adjacencyjist>: :edge_descriptor Тип дескрипторов ребер, ассоциированный с adjacencyj 1st. (Требуется для Graph.) • graph_tra'its<adjacencyjist>: :verte>Jterator Тип итераторов, возвращаемый verticesC). (Требуется для VertexListGraph.)
14.1. Классы графов 219 • g'-aph_traits<adjacency_list>: -.edgejterator Тип итераторов, возвращаемый edgesO. (Требуется для EdgeListGraph.) • graph_tra1ts<adjacenc>_list>'. :out_odgejterator Тип итераторов, возвращаемый out_edges(). (Требуется для IncidenceGraph.) • graph_tralts<adjacencyjlst>::in_edgejterator Этот тип доступен для неориентированных или двунаправленных списков смежности, но не для ориентированных. Итератор 1n_edgejterator — тип ите- ратора, возвращаемый функцией in_edges(). (Требуется для BidirectionalGraph.) • graph_traits<adjacency_l ist>::adjacencyjterator Тип итераторов, возвращаемый adjacent_vertices(). (Требуется для AdjacencyGraph.) • graph_traits<adjacency_list>::directed_category 11редостанляет информацию о том, является ли граф ориентированным (di r ec - ted_taq) или неориентированным (undlreeledjaq). (Требуется для Graph.) • graph_traits<adjacencyJist>::edge_parallel_category Дает информацию о том, позволяет ли граф вставку параллельных ребер (ребер, у которых одна и та же начальная и конечная вершины). Два возможных тега: al low_paral leljdge и di sal 1ow_parallel__edge_tag. Варианты c sets и hash jets не позволяют задавать параллельные ребра, тогда как другие варианты позволяют. (Требуется для Graph.) • graph_traits<adjacencyjist>::traversal_category Категория обхода (traversal category) отражает поддерживаемые графовым классом виды итераторов. Для списка смежности это включает итераторы вер- шин, ребер, исходящих ребер и итераторы смежности. Итератор входящих ре- бер доступен для неориентированных и двунаправленных, по не для ориенти- рованных списков смежности. • g<"aph_traits<adjacencyjist>:: vert ices jizejype Тип используется для представления числа вершин в графе. (Требуется для VertexListGraph.) • graph_traits<adjacencyjlst>:: edges jizejype Тип используется для представления числа ребер в графе. (Требуется для EdgeListGraph.) • graph_traits<adjacencyJ1st>:: degree jizejype Тип используется для представления числа исходящих ребер в графе. (Требуется для IncidenceGraph.) • property_map<adjacencyjist, PropertyTag>::type property_map<adjacencyJ i st. PropertyTag>::constJype Тип отображения для свойств вершины или ребра графа. Свойство задается ар- гументом PropertyTag шаблона и должно совпадать с одним из свойств из Vertex- Properties или EdgeProperties для графа. (Требуется для PropertyGraph.)
220 Глава 14 • Классы BGL Функции — методы Ниже приведены функции - методы класса adj? сепсу_ и st. • adjaconcy_Hst(ccnst GraphPropernesX р = GraphPrcpert'est}) Конструктор по умолчанию. Создает пустой объект-граф с нулевым числом вершин и ребер. (Требуется для DefaultConstructible.) • adjdcency_l',.st(verfi.ces_size_type п. const Gr».nhProperties6 р * GrapnPropert’es(') Создает объект-граф с п вершинами и без ребер. • template 'dypename Edgeiterator* adjacencyji$t(Edgelteraror frrst. Edgerte'atnr last. vcmce5_sizej:ype n. ec!ges_size_type m ’ 0. const GraphPropertiesK p = SraphPropertiesO) Создает граф с n вершин и т ребер. Ребра заданы в списке ребер из диапазона [first. last). Если п или т равно нулю, число вершин или ребер вычисляется по списку ребер. Тли значения для Edgel terator должен быть 5 W:: pal г, где в парт используется целый тип. Целые числа соответствуют вершинам, и все они должны относиться к диапазону 10, и). • template Чурепаге Edgelterator. typename LdgePropertiesIterator* adjacencyj i st (Edgeiterator first. Edgelterator last. EdgeProptrtiesItercTOr epjier. verf,ces_si2e_type n, edges_size_iype m < 0. const GraphPropert >es& p •= GraphPropertiesi ) Создает графовый объект с п вершин и т ребер. Ребра заданы в списке ребер из диапазона [first, last). Если п или т равно нулю, число вершин или ребер вычисляется по списку ребер. Тип значения для Edge Iterator должен быть std;; pa iг, где в паре используется целый тип. Целые числа соответствуют вер- шинам, все они должны относиться к диапазону [0, и). Тип va‘iue_tyoe итерато- ра epjter должен совпадать с параметром шаблона EdgeProperties. Функции — не методы Ниже приведены функции — не методы класса a ujaccncyJ 1st. • std: :pair<vertex_iterator. vc-rb?xjt.erator- vert ices(const adjacencyJistS g) Возвращает пару итераторов, обеспечивающую доступ к множеству вершин графа g. (Требуется для VertexListGraph.) • std-;pair<edge_iterator, edge_nerator> edges (const adjacencyj ist& g) Возвращает пару итераторов, обеспечивающую доступ к набору ребер графа g. (Требуется для EdgeListGraph.)
14.1. Классы графов 221 • std::pair<afljacency_iterator-. adjacency_nerator> adjacent_vertices(vertex_descnptor ••/. const adjacency_listj g) Возвращает пару итераторов, обеспечивающую доступ к вершинам, смежным с вершиной v графа g. (Требуется для AdjacencyGraph.) • std:: pai r<out_ecige_i terator. out_edge_iterator* out_edges(vertex_descriDtor v. const acjacency_list& c) Возвращает пару итераторов, обеспечивающую доступ к исходящим ребрам вершины v графа g. Если граф является неориентированным, этот итератор обеспечивает доступ ко всем ребрам, инцидентным вершине <>. (Требуется для IncidenceGraph.) • std::pair<ln_edge_iteratcr. in_edge_iterator> in_edges(vertex_descriptor v. const adjacencyJrst& g) Возвращает пару итераторов, обеспечивающую доступ к входящим ребрам вер шины v графаg. Эта операция недоступна, если для параметра шаблона 01 rected указано directedS, и доступна, если используются undi rectedS и bidirecti опа IS. (Требуется для BidirectionalGraph.) • vertex_descriptor source(enge_descriptor е. const adjacency J ir.tS g) Возвращает начальную вершину ребра е. (Требуется для IncidenceGraph.) • vertex_descriptor targei£edgt_descriptor <?. const adjacericyJisU g) Возвращает конечную вершину ребра е. (Требуется для IncidenceGraph.) • degree_size_type out_degree(vertex_descriptor u. const adjacency_list8 g) Возвращает число ребер, исходящих из вершины и. (Требуется для IncidenceGraph.) • degree_s1ze_type m_degree(vertex_descripr.Gf и. const adjacency_lTst& g) Возвращает число ребер, входящих в вершину и. Операция доступна, только если параметр шаблона Directed был задан как bi d i recti опа IS. (Требуется для BidirectionalGraph.) • veriices_size_type num_vervices(const adjacencyj ist& g; Возвращает число вершин в графе g. (Требуется для VertexListGraph.) • edges_slze_type nun-_edges(const adjacericyj KU q) Возвращает число ребер в графе g. (Требуется для EdgeListGraph.)
222 Глава 14 • Классы BGL • vertexjlescHptor vertex(vertices_size_type n. const adjacencyjista g) Возвращает п-ю вершину в списке вершин графа. • s td::pa ir<edge_descriptor. bool> edge(vertex_descriptor u, vertex_descriptor v, const adjacencyJistS g) Возвращает ребро, соединяющее вершину и с вершиной и в графе g. (Требуется для AdjacencyMatrix.) • std::pair<out_edge_iterator, out_edge_iterator> edge_range(vertex_descriptor u. vert.ex_descnptor v, const adjacencyJ1st& g) Возвращает пару итераторов исходящих ребер, дающих доступ ко всем парал- лельным ребрам из и в V. Эта функция работает, только когда EdgeLI st для adjacencyjlst является контейнером, сортирующим исходящие ребра по ко- нечным вершинам, а также когда параллельные ребра разрешены, multi sets в ка- честве EdgeList является таким контейнером. • std:-pair<edge_descriptor. bool> add_edge(vertex_descriptor u. vertex_descriptor v. adjacencyJist8 g) Добавляет ребро (и, и) к графу и возвращает дес криптор ребра для нового ребра. Для тех графов, которые не разрешают иметь параллельные ребра, в случае если ребро уже присутствует в графе, дубликат добавлен не будет, а флаг bool будет иметь значение «ложь». Также, если и и г1 являются дескрипторами од- ной и той же вершины, а граф является неориентированным, создающее пет- лю ребро добавлено не будет и флаг bool будет иметь значение «ложь». Когда флаг ложен, дескриптор ребра не является правильным и его нельзя использо- вать. Место нового ребра в списке исходящих ребер в общем случае не опреде- лено, хотя задать порядок в списке исходящих ребер можно при выборе EdgeLI st. Если VertexLi st-vecS и если один из дескрипторов вершин и или v (целые числа) имеет значение, большее, чем текущее число вершин графа, граф увеличивается таким образом, что число вершин становится равным std: :max(u.v) + 1. Если EdgeLI st-vecS, добавление ребра делает недейс твительным любой итератор иг - ходящих ребер (out_edge_iterator) для вершины и. То же самое происходит, если EdgeList — определенный пользователем контейнер, итераторы которого «портятся» при вызове pushfcontainer. х). Если граф является двунаправлен- ным, то итераторы входящих ребер для v (iri fdgejterator) тоже «портятся». Если граф является неориентированным, то любой итератор out_edge i terator для о также «портится». Если граф является ориентированным, add_edge() «пор- тит» любой итератор ребер (out_edge). (Требуется для EdgeMutableGraph.) • std::palr<edge_descriptor, bool> add_edge(vertex_descnptor u. vertex_descriptor v. const EdgeProperties& p, adjacencyjist& g) Добавляет ребро («, v) к графу и присоединяет р как значение внутреннего свойства ребра. См. также описание предыдущей функции — не метода класса. (Требуется для EdgeMutablePropertyGraph.)
14.1. Классы графов 223 • void remove_edge(vertex_descnptor u. vertex_descriptor v. adjacency_Hst& g) Удаляет ребро (w, о) из графа. Эта операция вызывает сбой во всех еще не об- работанных дескрипторах ребер и итераторах, которые указывают на ребро (и, о). Кроме того, если в качестве EdgeList выбран vecS, тогда эта операция вы- зывает сбой во всех итераторах, указывающих на элемент списка ребер верш и- ны и. То же самое происходит для вершины г в случае неориентированного или двунаправленного графа. Аналогично, для ориентированных графов эта операция вызывает сбой любого итератора ребер (edge i cerate'-). (Требуется для EdgeMutableGraph.) • void remove_edge(edge_descrlptcr е. adjacencyJistS д) Удаляет ребро е из графа. Отличается от функции remove_edge< J. v. g) в слу- чае мультиграфа. Эта функция удаляет единственное ребро графа, тогда как функция edgeCu, v, g) удаляет все ребра (и, v). Данная операция делает не- действительными любые еще не обработанные дескрипторы ребер и итерато- ры для ребра в. Кроме того, эта операция «портит» все итераторы, которые ука- зывают на список ребер для targetCe. g). Аналогично, для ориентированных графов эта операция вызывает сбой любого итератора ребер. (Требуется для EdgeMutableGraph.) • void remove_edge(out_edge_1 terator Пег. adjacencyjistS g)~ Имеет тот же эффект, чтои remove_edge(*iter, g). Разница состоит в том, что эта функция выполняется за постоянное время в случае ориентированных графов, тогда как remove_edge(e. g) имеет временную сложность порядка O(lE j/|V|). (Требуется для MutableIncidenceGraph.) • temolate «typename Predicate? void remove_out_edgeJf (vertex_descript.or u. Predlcate~predlcat€. adjacencyJist& g) Удаляет все исходящие ребра вершины и из графа, которые удовлетворяют пре- дикату, то есть если предикат возвращает истину при применении к дескрип- тору ребра, ребро удаляется. Эффект для дескриптора и итератора такой же, что и при вызове remove_edge() для каждого из подлежащих удалению ребер. (Требуется для MutableIncidenceGraph.) • template «typename Predicate? void removejn_edge_lf (vertex_descnptor v. Predicate predicate. adjacency_list& g) Удаляет все входящие ребра вершины v из графа, которые удовлетворяют пре- дикату, то есть, если предикат возвращает истину при применении к дескрип- тору ребра, ребро удаляется. Эффект для дескриптора и итератора такой же, что и при вызове remove_edge() для каждого из подлежащих удалению ребер. (Требуется для MutableBidirectionalGraph.) • template «typename Predicate? void remove_edge_if (Predicate predicate, adjacencyJisU g)
24 Глава 14 • Классы BGL Удаляет все ребра из графа, которые удовлетворикн предикату, то есть если предикат возвращает истину при применении к дескриптору ребра, ребро удаляется. Эффект для дескриптора и итератора такой же, что и при вызове re.TOve_edge() для каждого из подлежащих удалению ребер, (Требуется для MutableEdgeListGraph.) • vertexjiescnptor add_vertex(adjacenc,y_’i‘t8. g) Добавляет вершину к графу и возвращает дескриптор вершины для вновь со- зданной вершины. (Требуется для VertexMutableGraph.) • v-.’! u>x_dr.>~Hpr.ut adJ_wrLex?.::cnst verlexP^perVesS p. aajacencyJwS g' Добавляет вершину к графу и возвращает дескриптор вершины для вновь со- зданной вершины. (Требуется для VertexMutablePropertyGraph.) • vend clear_vertex(vertex_de5cr1ptoc u. ad.iacency_list& g) Удаляет все ребра, входящие и исходящие из вершины и. Вершина остается во множестве вершин графа. Эффект для дескриптора и итератора такой же. что и ври вызове ren»ve_edge( > для всех ребер. у которых и — начальная или конечная вершина. (Требуется для EdgeMutableGraph.) • void c’ear_out_edges(vertexdescriptor u. adjacency_hsU g) Удаляет все ребра, исходящие из вершины и. Вершина остается во множестве вер- шин 1рафа. Эффект для дескриптора и итератора такой же, что и при вызове 'е- 1посе_-:<*5е< 1 лля всех ргбер. У которых и — начальная вершина. Эта операция не применяется к неориентированным графам (вместо нее используйте cl ear_vertex()). • void cledr_tn_edges(vertex_de$cr-iptar u. ad.iacency_Bsi& gj Удаляет все ребра, входящие в вершину и. Вершина остается во множестве вершин графа. Эффект для дескриптора и итератора такой же. что и при вызо- ве >'eniove_edge() для всех ребер, у которых и — конечная вершина. Эта опера- ция применима только к двунаправленным графам. • void rerrove_vertex(verte>_descript.or и. adiacencyJIstS g) Удаляет вершину и из множества вершин графа. Предполагается, что на мо- мент удаления у этой вершины нет входящих пли исходящих ребер. Чтобы гарантировать такое состояние, можно заранее применить с 1 еа ^vertex (). Если параметр шаблона Vert exList списка ас jacencyj ist - vecS, то все дескрипторы вершин, дескрипторы ребер и итераторы для графа становятся недействитель- ными. Встроенные свойства vertexjndex_t для каждой вершины после этой операции будут перенумерованы таким образом, что индексы вершин ло-преж - нему образуют непрерывный диапазон [0. jV ). Если вы используете внешнее хранилище свойств, основанное на встроенных индексах вершин, то внешнее хранилище должно быть соответствующим образом перестроено, Другой воз-
14.1. Классы графов 225 можностью является отказ от использования встроенного индекса вершин в пользу использования свойства для добавления собственного индекса вер- шин. Если вам необходимо часто использовать функцию remove_vertex(), 1 1 stS является гораздо лучшим выбором для шаблонного параметра VertexLI st. (Требуется для VertexMutableGraph.) • template «typename PropertyTag* property_map<adjacencyjlst. PropertyTag*: - type get(PropertyTag. adjacencyJistS g) Возвращает изменяемый объект-отображение свойств для свойства вершины, заданной PropertyTag. PropertyTag должен совпадать с одним из свойств, задан- ных в шаблонном параметре VertexPropertles графа. (Требуется для PropertyGraph.) • template «typename PropertyTag* property_map«adjacencyjist. PropertyTag*:: const jype getCPropertyTag. const”adjacencyjist& g) Возвращает константный объект-отображение свойств для свойства вершины, заданной PropertyTag. PropertyTag должен совпадать с одним из свойств, задан- ных в шаблонном параметре VertexPropertles графа. (Требуется для PropertyGraph.) • template «typename PropertyTag, typename X* typename property_traits< typename property_map«ad jacencyj i st. PropertyTag*::const_type*:: value jype getCPropertyTag. const adjacencyJlstS g, X x) Возвращает значение свойства для х, где х — дескриптор вершины или ребра. (Требуется для PropertyGraph.) • template «typename PropertyTag. typename X. typename Value* void put(PropertyTag. const adjacencyJistS g. X x. const Values value) Присваивает значение value свойству x, где x — дескриптор вершины или ре- бра. Значение должно быть преобразуемым к typename property_traits<proper- ty_map«adjacencyJ1st. PropertyTag*:;type*::value_type. (Требуется для PropertyGraph.) • template «typename GraphProperties. typename Graphproperties* typename property_value«GraphProperties. Graphproperties*::types get_property(adjacency_11st& g. GraphProperties): Возвращает свойство, заданное no GraphProperties и присоединенное к графовому объекту д. Класс свойств property_value определен в boosVpending/property.hpp. • template «typename GraphProperties. typename GraphProperties* const typename property_value«GraphProperties. GraphProperties*::types get_property(const adjacencyjist& g. GraphProperties); К ЗахЗП
226 Глава 14 « Классы BGL Возвращает свойство, заданное no GraphPropert i es, присоединенное к графовому объекту д. Класс свойств property val ие определен в boost/pending/property.hpp. Выбираем EdgeLlst и VertexUst В атом разделе уделено внимание тому, какую версию класса adjacency_11st ис- пользовать в той или иной ситуации. Список смежности имеет очень много возможно- стей для конфигурирования. Интересующие нас параметры EdgeList и VertexList задают структуры данных, используемые для представления графа. Выбор EdgeL i st и VertexList влияет на временную сложность многих графовых операций и про- странственную сложность графового объекта. BGL используег контейнеры из STL, такие как вектор std:: vecto', список sto: .-list и множество std::set для представления множества вершин и структу- ры смежности (входящие и исходящие ребра) графа. В качестве контейнера для EdgeLlst и VertexUst могут быть выбраны разные типы: • vecS задает std;: vector. • lists задает std: :hst. • siistSзадает std: :sl 1st'. • sets задает std: set. • hash_setS задает std: :liash_set2. Выбираем тип VertexUst Параметр VertexUst определяет вид контейнера, который будет использовал дли представления множества вершин или двумерной структуры графа. Контей- нер должен быть моделью Sequence или RandomAccessContainer. В общем случае 11 stS является хорошим выбором, если вам нужно быстро добавить и удалит» вершины. Но тогда появляются дополнительные накладные расходы по сравне- нию с vecS. 1 Грострапствеиная сложность: std:: 11 si требует хранить больше информации для каждой вершины, чем std:: vector, так как дополнительно хранит двауказателя. Временная сложность: выбор VertexList влияет на временную сложность сле- дующих операций. • cdd_vertex() Эта операция выполняется за амортизированное постоянное время как для vecS, так и для 11 stS (реализована с push_back()). Однако, когда тип VertexListiver/'; является типом vecS, время выполнения этой операции иногда больше из-за того, что вектор приходится размещать в памяти заново, а весь граф копировать • rernove_vertex() Эта операция выполняется за постоянное время для 1 i stS и за время O(j V | +1£ I) для vecS. Большая временная сложность vecS объясняется тем, что дескрипто- ры вершин (которые в этом случае являются индексами, соответствующими Если }Х'алпл^пия STL. которую вы применяете, использует std :slist. Если реализация STL которую вы применяете, имеет std::h;i«h jset. Например, SGI STI. является такой (к-али-кщией.
14.1. Классы графов 227 месту в списке вершин) должны быть скорректированы в исходящих ребрах всего графа. • vertex() Эта операция выполняется за постоянное время для vecS и О(|V|) для lists. Выбираем тип EdgeList Параметр EdgeList определяет, какой вид контейнера используется для хранения исходящих ребер (и, возможно, входящих тоже) для каждой вершины в графе. Контейнеры, используемые для списков ребер, должны удовлетворять требова- ниям либо Sequence, либо AssociativeContainer. Одним из первых вопросов, которые необходимо рассмотреть при выборе EdgeLI st, является то, хотите ли вы гарантировать отсутствие параллельных ребер в грас|>е. Если нужна гарантия того, что граф не станет мульгнграфом, можно ис- пользовать sets или hash_setS. Если вы хотите иметь мультиграф или знаете, что параллельные ребра вставляться не будут, тогда можно выбрать один из типов- последовательностей: vecS, I’stS или si; stS. Помимо этого, необходимо принять во внимание разницу во временной и пространственной сложности для различ- ных графовых операций. Мы используем |И| для обозначения общего числа вер- шин графа и |Е| для числа ребер. Операции, которые здесь не рассмотрены, вы- полняются за постоянное время. Пространственная сложность: выбор EdgeLI st влияет на объем памяти, выде- ляемой на одно ребро в графовом объекте. В порядке возрастания требуемого ме- ста следуют: vecS, si 1 stS, 1i stS, hash_setS и setS. Временная сложность: в следующем описании временной сложности различ- ных операций мы используем |E|/|V| внутри обозначений с «большим О» для выражения длины списка исходящих ребер. Строго говоря, это не совсем точно, так как |£|ДЕ| А367 только среднее число ребер на вершину в графе. В худшем случае число исходящих ребер для вершины равно jV| (если граф — не мульти- граф). В разреженных графах \Е j обычно намного меньше, чем |V|, и может рас- сматриваться как константа. • acd_edge() Когда EdgeList является UniqueAssociativeContainer (ассоциативный контейнер с уникальными элементами) как std:: set, отсутствие параллельных ребер после добавления ребра гарантировано. Дополнительное время поиска имеет времен- ную сложность O(log({£ | /1V |)). Тины EdgeL i st, которые моделируют Sequence (последовательность), не осуществляют такую проверку и поэтому add_edge(; выполняется за амортизированное постоянное время. Это означает, что если вам безразлично, имеет ли граф параллельные ребра, или вы уверены, что па- раллельные ребра не будут добавляться к графу, то лучше использовать осно- ванный на последовательностях EdqeList. Функция add_edge() для последова- тельного EdgeList реализована как Dush_frcnt<} или pusnjoackO. Однако для std: :list и std::sl 1st эта операция обычно выполняется быстрее, чем для std:: vector, который иногда перемещается в памяти и копирует свои элементы. • remove_edge() Для последовательных типов EdgeList эта операция реализуется с использо- ванием std::remove т f(). Это означает, что среднее время равно ]Е \/\VI. Для
228 Глава 14 • Классы BGL основанных на множествах типов EdgeL* st используется функция — метод клас - са erased, которая имеет временную сложность log(|£ | /1V|). • edget) Временная сложность этой операции равна О(\Е | /1V |), когда тип EdgeL 1 st я вл>- - ется Sequence, и O(log(|£ | /1 У|)), когда тип EdgeL! st является AssodativeContainer. • clear_vertex() Для ориентированных графов с последовательным типом EdgeL i st временная сложность порядка О(|£| + |У|), тогда как для EdgeLi st на основе ассоциатив- ного контейнера операция выполняется быст]>ее, всего за О(| V |log(|E |/|^!))- Для неориентированных графов данная операция имеет временную сложность по- рядка О(([Е|/1У|Пи O(|£|log(|£|/|V|)/|V:). • remove_vertex() Временная сложность порядка О(|£ | +1V |) вне зависимости от типа EdgeL1 st • out_edge_iterator::operator++() Эта операция выполняется за постоянное время для всех типов EdgeLi st. Од- нако имеется значительная разница (постоянный множитель) по времени меж- ду различными типами. И это важно, поскольку операция является «рабочей лошадкой» многих алгоритмов на графах. Скорость этой операции в порядке ее уменьшения: vecS, si istS, 1 i stS, sets, hash_setS. • in_edge_lterator::operator+*() См. выше. • vertexjterator: :operator++() Эта операция выполняется за постоянное время и достаточно быстро (выпол- няется со скоростью инкремента указателя). Выбор OneD не влияет на скорость этой операции. • edgejterator: :operator++() Данная операция выполняется за постоянное время и показывает похожее упо- рядочение по скоростям, что и out_edge_iterator в отношении выбора EdgeLisc. Обход всех ребер имеет временную сложность порядка О([£ | + jV |). • adjacencyj terator :operator++() Данная операция выполняется за постоянное время и показывает похожее упо- рядочение по скоростям, что и out_edgejterator в отношении выбора EdgeL i st. Стабильность и сбои итераторов и дескрипторов При изменении структуры графа (путем добавления или удаления ребер) необхо- димо действовать осторожно. В зависимости от типа adjacencyj i st и от операции некоторые объекты-итераторы и объекты-дескрипторы, указывающие на граф, могут стать некорректными. Например, результаты выполнения кода в листин- ге 14.2 неопределенны и могут оказаться разрушительными. Листинг 14.2. Пример сбоя итераторов // VertexLtst-vecS typedef acjacencyJist<HstS. vecS* Graph:
14.1. Классы графов 229 Grach G(N); Н Наполнить граф.., Н Попытка удалить все вернины. Неверно! graph_tra1ts<Graph>::vertex_1terator vi, vl_end; for (t1e(v1. v1_end) - vertices(G): vi ! vi_end; -«vi) remcve_vertex(*vi. G); Il Другая попытка удалить все вернины Все равно неверно1 graph_traits<Graph>::vertex_iterator vi. vl_end, next: tie(vl. v1_end) - vertices(G): for (rext « v1: v1 !- vl_end: vi - next) ( ♦-'next: remcve_vertex(*vi, G); Причина этой проблемы в том, что мы вызываем remove vertexO, который при использовании adjacencyj 1 st с VertexList=vecS вызывает сбой всех итераторов и дескрипторов графа (в нашем случае — vi и vi_end), таким образом, ошибка по- является в последующих итерациях цикла. При использовании другого вида adjacency J 1st, где VertexLi st«l istS, итерато- ры не «портятся» при удалении вершин, если, конечно, итератор нс указывал на удаляемую вершину. Это демонстрирует код из листинга 14.3. Листинг 14.3. Пример сбоя итераторов (2) Н VertexList-11stS typedef adjacency_Bst<listS. 11stS> Graph; Graph G(N): И Наполнить граф... // Попытка удалить все вераины. Неверно! graph_tra1ts<firaph>::vertex_lterator vi. vi_end: for (tiefvi. vi_end) - vertTces(G): vi !- vi_end; *+vi) remove_vertex?*v1. G); И Удалить все вервины. Правильно. graph_traits<Graph>::vertexJterator vi. vl_end. next; tie(v‘. vi_end) - vertices(G); for (next • v1: vi !- vjend; vi next) { T+next; remove_vertex(*vi. G): } Наиболее безопасным и эффективным способом массового удаления ребер из adjacencyj 1 st является применение функции remove_edge_i f(). Вопрос корректности касается также дескрипторов вершин и ребер. Напри- мер, предположим, что вы используете вектор дескрипторов вершин для от- слеживания родителей (или предшественников) вершин в дереве кратчайших путей (см. файл example/dijlcstra-example.cpp). Вы создаете вектор родителей вы- зовом di jkstra_shortest_paths(), а затем удаляете из графа вершину. После это- го вы пытаетесь использовать вектор родителей, но поскольку все дескрипторы вершин стали некорректны, результат также неверен. Это можно проследить в листинге 14.4.
230 Глава 14 • Классы BGL Листинг 14.4. Пример сбоя дескрипторов вершин std::vector<Vertex> parent!num_vertice$iG)); std::vettor<Vertex> distanceCtwjierticesCGi): dijkstrd_snortest_pathsiG, s. distance_map(Mistance[O]) predecessor,map(Sparent(0]i): Н Плохая идея! Дескрипторы аераин становятся непоррентныж // в векторе родителей removeyvertexts. G): ti Псгучаен неверные результата forctTe'.'vi, vend) “ verticesfGi: vi !- venc<: ->+vJ.' std::cout p[*vi] « " является родитепеи для “ « *vi « stc::end!: При поиске причины сбоев итераторов и дескрипторов следует обратить вни- мание. что затрагиваются дескрипторы и итераторы, не участвующие в операции непосредственно. Например, выполнение remove edgeiu. v. g) всегда делает не- действительными дескриптор ребра (и, V) или итератор, указывающий на {и, t.), независимо от вида adjacency ! 1st. То есть в этом разделе нас волнуют эффекты, которые вызывает remove_edge(u. v. g) для дескрипторов и итераторов, указыва- ющих на отличные от (и.г) ребра. В общем случае, если вы хотите, чтобы дескришгоры вершин и ребер были ста- бильными (никогда не «портились*), следует использовать lists или sets для шаблонных параметров VertexUst и EdgeList класса adjacencyjist. Если для вас важнее затраты памяти и скорость обхода графа, используйте vecS для шаблон- ных параметров Ve”t.exList и/или EdgeList. Ориентированные и неориентированные списки смежности Класс adjacency _1 i st может быть использован для представления как ориентирован- ных, так и неориентированных графов, в зависимости от аргумента, присвоенною шаблонному параметру Di rected. Указанием directedS или bidi rectionalS выбирается ориентированный граф, тогда как undi rectedS выбирает неориентированный. См. раз- дел 12.1.1, где дано описание различий между ориентированным и неориентирован- ным графами в BGL. Выбор bidirectional? указывает, что граф предоставит функ- цию in_edges() в дополнение к функции out_edge:,(), Это требует двойных расходов памяти на одно ребро (и служит причиной того, почему in_edces() необязательна). Внутренние свойства Свойства могут быть закреплены за вершинами и ребрами графа, заданного спис- ком смежности, через интерфейс свойств (property interface). Шаблонные пара- метры VmexProperties и EdgeProperties класса adjacencyji st подразумевают за- полнение классом свойств, который определен следующим образом, template Чурепда PropertyTag, typename I. typename NextProperty - no_property> struct property: PropertyTag — это тип, который просто идентифицирует или даст уникальное имя свойству. Имеются несколько предопределенных тегов (см. раздел 15.2.3), и очень легко добавить новые. Для удобства BGL также предоставляет предопре- деленные объекты теговых типов (в данном случае значения перечисления епип ) для использования в качестве аргументов к функциям, которые принимают объек- ты-теги свойств (к ним относится, например, функция get О из adjacencyJ i st).
14.1. Классы графов 231 Параметр Т свойства property обозначает тин значений свойств. Параметр Next Property позволяет указать следующее свойство, так что произвольное число свойств может быть связано с одним и тем же графом. Следующий код показывает, как свойства вершин и ребер могут быть исполь- зованы при создании графа. Мы связали свойство «расстояние» со значениями типа float и свойство «имя» со значениями типа std::string с вершинами графа. С ребрами графа связано свойство «вес» со значениями типа float. typedef property<distance_t. float, property<nane_t, std.-string» > VertexProperties. typedef property<weight_t. float» EdgeProperties: typedef adjacencyj isbreipS. vecS, undirectedS. VertexProperties. EdgePrcperties» Graph; Graph ginuffl_vert1ces): // построить графовый объект Значения свойств могут быть прочитаны и записаны с. использованием отобра- жений свойств. Описание того, как извлечь отображения свойств из графа, с.м. в разделе 3.6. Глава 15 целиком посвящена использованию отображений свойств. Свойство индекса вершины Если VertexLi st графа есть vecS, то граф имеет встроенное свойство «индекс», ко- торое может быть получено через свойство vertex i ndex J. Индексы находятся в ди- апазоне [0. |VI), без пропусков. Когда вершина удаляется, индексы перестраива- ются так, что опять находятся в соответствующем диапазоне и без пропусков. Необходима некоторая осторож’поггь при пользовании этими индексами для до- ступа к свойствам, хранящимся вне графового объекта, поскольку пользователь должен обновить внешнее хранилище в соответствии с новыми индексами. Свойства ребер, созданные пользователем Создание пользовательских типов свойств - достаточно простое дело. Нужно только определить теговый класс для нового свойства. В следующем коде опреде- ляются теговый класс для свойств «мощность» и «поток», которые мы закрепля- ем за ребрами графа. enum edge_capacityj { edge_capacity }; enum edge_flow_t { edge_flow }: namespace boost { BOOSTJNSTALL PROPERTY (edge, flow): BOOSTJNSTALLJWPERTYtedge. capacity): Теперь вы можете использовать тег нового свойства в определении свойств пак же, как и один из встроенных тегов. typedef property<capaclty_t. int» Cap: typedef property<flowt. int. Cap» EdgeProperties; typedef adjacency_)ist<vecS. vecS. na_property. EdgeProperties» Graph: Как обычно, отображения свойств для этих свойств могут быть получены из графа через функцию get(). propertyjnap^Greph. edge_capacity_t»::tyoe capacity - get<edge_capacity, Gk property_map<Graph. edge_f'ow_t>::type flew - get(edge_flow. G).
232 Глава 14 • Классы BGL В файле edge_property.cpp приведен полный исходный код для рассмотренного примера. Свойства вершин, созданные пользователем Закреплять за вершинами некоторые свойства так же легко, как закреплять их за ребрами. Здесь мы хотим закрепить за вершинами графа имена людей. enum vertex_fi rst_name_t { vertex_fi rst_name }; namespace boost { 800ST_INSTALL_PROPERTY(vertex. first name); } Теперь мы можем использовать новый тег в классе свойств property при сбор- ке графового типа. Следующий код (листинг 14.5) показывает создание графово- го типа и затем создание объекта-графа. Мы заполняем ребра и так же назначаем имена вершинам. Ребра представляют информацию о том, «кто кому должен». Листинг 14.5. Создание графового типа и объекта typedef adjacency_list<vecS. vecS. directedS. property<vertex_first_name_t. std::string» > MyGraphType: typedef pair<int.int> Pair; Pair edge_array[ll] - { Pair(O.l). Pair(0.2). Pair(0.3). Pair(0.4). Pair(2.0). Pair(3.0). Pa1r(2.4). Pair(3.1). Pair(3.4). Pair(4.0). Pair(4.1) }; MyGraphType G<5); for (int i»0: 1<11: ♦+() add_edge(edge_array[i],first. edge_array[i],second. 6); propertyjnap<HyGraphType. vertex_first_name_t>::tyoe папе - get(vertex_first_name. G): boost:: put (name. 0. ’Jeremy"): boost::put(name. 1. “Rich"): boost::put(name. 2. “Andrew”); boost;:put(name. 3. "Jeff"); name[4] - "Kinis": И ложно и гак who_owes_wno(edges(G).first, edges(G).second. G): Функция who_owes_who(), написанная для этого примера, была реализована в обобщенном стиле. Ввод задан классом-шаблоном, так что мы не знаем реального тина графа. Для нахождения типа отображения свойства для свойства fi rst_nare нам необходимо использовать класс свойств ve'tex_property_map. Тип const_type использован из-за того, что параметр-граф также является константой. Как только мы получили тип отображения свойства, мы можем сделать заключение о типе значений свойства, используя класс property_traits. В нашем примере известно, что тип значения свойства — std:; str 1 ng, но напитанная в таком обобщенном стиле функция who_owes_who() может работать и с другими типами значений свойства. Код функции приведен в листинге 14.6. Листинг 14.6. Функция who_owes_who() template «class Edgelter. class Graph» void who_owes_who(Edgelter first. Edgelter last, const Graphs G)
14.1. Классы графов 233 { и Доступ к типу средства доступа н свойству ам awe- графа typedef typeneme propertyjiwp<Graph, verte/_-'ir5t_name_t> ::const_type NamePA: NaitePA name - get(vertex_fn st_name. G). typedef typename boost: :property_tran$«№mesA> *value_tyf-e Nan»~ype: NameType src_name. targjwe; while (first !- last) { src_name - boost;:get(name. source(*first.G)>. targ name - boost::get(name. target(«first.Gi); cout « src_narne <« * домен « targ_name « " деньги" « endl: ♦♦first: ) 1 Эта программа выводит следующее. Jeremy должен R-ch деньги Jeremy должен Andrew деньги Jeremy должен Jeff деньги Jeremy должен K’nis деньги Andrew должен Jeremy деньги Andrew должен К-mis деньги Jeff должен Jeremy деньги Jeff должен Rich деньги Jeff должен Kinis деньги Kinis должен Jeremy деньги Kims должен Rich деньги Полный исходный код этого примера можно найти в файле interior_proper- ty_map.cpp. Настройка хранилища для списка смежности Класс adjacencyj i st реализован с использованием двух видов контейнеров. Один из типов контейнеров содержит все вершины графа, а другой - список исходящих ребер (и, возможно, входящих) для каждой вершины. BGL предоставляет клаесы- селекторы для того, чтобы пользователь мог выбрать среди нескольких контей- неров из STL. Также есть возможность для использования своего собственного контейнерного типа. При настройке Vertex! 1st вам необходимо определить ге- нератор контейнера. При настройке FageList нужно определить генератор кон- тейнера и свойства параллельных ребер. Файл container_gen.cpp является при- мером того, как использовать настройку хранилища. Генератор контейнера Класс adjacencyj! st использует класс свойств, называемый cental rer gen. для ото- бражения селекторов EdgeL) st и VertexList на реальные контейнерные типы, при- меняемые для хранения графа. Версия класса свойств по умолчанию приведена в листинге 14.7 вместе с примером того, как класс спещнлнзируется для селекто- ра lists. Листинг 14.7. Версия класса свойств по умолчанию namespace boost { template «typename Selector, typename ValneType-»
234 Глава 14 • Классы BGL Листинг 14.7 (продолжение) truct container_gen { }: template «typename ValueType* truct centalner_gen«listS, ValueType* { typedef std::11st«ValueType* type; }: } Для использования другого контейнера на ваш выбор определите класс-селек- тор и затем специализируйте container_gen для вашего селектора (листинг 14.8) Листинг 14.8. Версия класса свойств со специализацией для селектора struct custom_containerS { }: // ваш селектор namespace boost { h‘ специализация для вашего селектора template «typename ValueType* struct container_ger«custom containers. ValueType* ' typedef custom_container«ValueType* type; j: } Могут возникнуть ситуации, когда вы хотите использовать контейнер, который имеет больше шаблонных параметров, чем просто Vai ueType. 11апример, вы может-; захотеть представить тип распределителя памяти (allocator type). Один из спосо- бов сделать это — четко прописать в дополнительных параметрах в специализации centalner gen. Однако если вы хотите большей гибкости, то можно добавить шаб- лонный параметр к классу-селектору. В следующем коде в листинге 14.9 показано, как создать селектор, который позволяет задать распределитель памяти в std::11 st. Листинг 14.9. Версия класса свойств со специализацией для селектора и типа размещения template «typename Allocator* struct list_with_allocators {}; namespace boost { template «typename Alloc, typename ValueType* struct container gen«11st with allocatorS«Alloc>. ValueType* { typedef typename Alloc:;tempiate_rebind«ValueType*::other Allocator; typedef std::list«ValueType. Allocator* type; 1 // теперь вы можете определить граф, исполэзуя std::11st н и специальное разрешение typedef adjacencyjist< list_with_allocators* std::allocator«int* >. vecS. directedS* MyGraph: Свойства параллельных ребер В дополнение к специализации класса contai ner_gan можно также специализировать класс pa га 1 lei _edge_traits для задания того, позволяет ли контейнерный тип иметь параллельные ребра (является Sequence) или не позволяет (является Associative- Container). template «typename Storageselector* struct parallel_edge_traits { ); template <* struct parallel_edge_traits«vecS* { typedef allow_parallel_edge_tag type; }:
14.1. Классы графов 235 template о struct paraHei_edge_traits<setS> { typedef disallowjjaral lel_edgc tag type: }: // . Контейнер списка ребер: функции push() и erase() Необходимо указать adjacency_1 i st как ребра метут быть эффективно добавлены и удалены из контейнера списка ребер. Это выполняется перегрузкой функций push!) и erased для собственного контейнерного типа. Функция pi.sho должна возвращать итератор, указывающий на только что вставленное ребро, и лет инее- кий флаг, говорящий о том, было ли ребро вставлено. Если было задано al 1 ггл ра ral lel_edge_tag для paral lel_edge_trai ts, to push!) должна всегда вставлять ребро и возвращать истину. Если было 3aflaHodisallow_paraE:el_eoge_tag. функция push! ’• должна возвратить ложь и не вставлять ребро, если такое ребро уже есть в кон- тейнере, и итератор должен указывать на уже существующее ребро. Следующие функции - posin') и erased (листинг 14.10) по умолчанию уж»,- написаны для контейнеров из STL. Семейство перегруженных функций pusп_аi s patch!) и erase_dispatch( 1 обеспечивает различные способы вставки и удаления, которые могут быть выполнены для стандартных контейнеров. Листинг 14.10. Функции push() и erased template <typename Container. typename т> std.:pair<typename Container::iterator, bocl> pushIContainer& c. const 14 v> ( return push_dispalch(c. v. container_category(c;). } template «typename Container, typenane T> void erase(Container& c. cons! ’’S xi { erase_dispatch(c. x. container catecory(c); } 14.1.2. adjacency_matrix adjacency_matri:«Dlrected. vertexproperty. EdgeProperty. GraphPropert.y> Класс adjacencyjnatrix реализует интерфейс BGL-графа, используя несколько раз- личных вариантов традиционной графовой структуры матрицы смежности. Для i-рафа с |У| вершин используется матрицаjV | х |V |, где каждый элемент появляет- ся логическим флагом, свидетельствующим о том, имеется ли ребро из вершины i в вершину/. Представление графа в виде матрицы смежности покзано на рис. 14.4. Преимуществом такого матричного формата над списком смежности являет- ся то, что ребра вставляются и убираются за постоянное время. Есть и ш-сколы;: > недостатков. Во-первых, объем используемой памяти имеет порядок О(| 1- ) !>м- сто О(|V| + |£|) (где |£| — число вершин графа). Во-вторых, операции по всем ис- ходящим ребрам каждой вершины (как поиск в ширину) имеют временную слож- ность 0(| V |2) в отличие от 0((V | + \Е |) для списка смежности. Матрицу смежности лучше использовать с плотными графами (где |Е| ® |V|2), а список смежности — с разреженными (где \Е | намного меньше |V |2).
236 Глава 14 • Классы BGL А В С D Е F А В С D Е F О О О О О О OO1OO1 10 10 0 0 0 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 0 0 Рис. 14.4. Представление графа в виде матрицы смежности Класс adjacency_matn х расширяет традиционную структуру данных, позволяя прикреплять объекты к вершинам и ребрам посредством параметров шаблонов свойств. Информацию по использованию внутренних свойств см. в разделе 3.6. В случае неориентированного графа класс adjacency matrlx использует не всю матрицу | V | х | V а только ее нижний треугольник (диагональ и ниже), поскольку матрица для неориентированного графа симметрична. Это сокращает расходы на хранение до (| V] х | V|)/2. Представление неориентированного графа в виде мат- рицы смежности показано на рис. 14.5. А В С D Е F А В С О Е F 0 0 0 1 1 0 0 0 0 0 0 0 0 1 0 1 1 0 0 0 0 Рис. 14.5. Представление неориентированного графа в виде матрицы смежности Пример В листинге 14.11 приведен пример построения графа, изображенного на рис. 14.4, а в листинге 14.12 — изображенного на рис. 14.5. Листинг 14.11. Создание графа (на рис. 14,4) enum { А. В. С. 0. Е. F. N }: const char* name - "ABCOEF": typedef adjacency_raatrlx«directedS> Graph; Graph g(N): add_edge(B. C. g): add_edge(B. F. g); add_edge(C. A. gr. add2ea9e(C- C. g); <5dd_edge(D. E. g>: add_edge(E. 0. g): add_edge(F. A, g); std;;cout « "набор вершин: pnnt_vertices(g. name); stakeout « std:-.end).
14.1. Классы графов 237 std: cout « "набор ребер: ", pr1nt_edges(g. name); std: .cout « std::endl: std: cout « "исходящие ребра' " « std::endl: print_graph(g. name): std; cout « std:;endl; Эта программа выводит следующее: набор вершин; А В С D £ F набор ребер: (В.С) (B.F) (С,А) (С.С) [D.E) (E.D) (F.A; исходящие ребра: А -> В -> С F С А С D Е Е -> О F --> А Листинг 14.12. Создание графа (на рис. 14.5) enum { А. В. С, 0. Е. F. N ]: const char* name - "ABCDEF": typedef adjacency_matrix<uncn rectedS» UGrapti; UGraph ug(H): add_edge(B. C. ug); add_edge(B, F. ug): add_edge(C. A. ug); add~edge(D, E. ug); add^edgeCF. A. ug); std:.-cout « "набор вершин; print_vert1ces(ug. name); Std::cout « std::endl; std.:cout « "набор ребер: ": print_edges(ug, name); std;:cout « std:;endl: std::cout « "инцидентные ребра: " « std:.endl; print_graph(ug. name); std::cout « std::endl: Эта программа выводит следующее: набор вершин: А В С D Е F набор ребер: (С.А) (С,В) СЕ.О) (F.A) (F.B) инцидентные ребра. А <-» С F В <-> С F С <--> А В 0 <-> Е Е <--> D F <-> h В
238 Глава 14 • Классы BGL Где определен Класс ijjicericyjnatrix находится в boost/graph/adjacency_matrix.hpp. Параметры шаблона Ниже приведены параметры шаблона класса ddjacency^matnx. Directed Селектор для выбора графа: ориентированный или неориентированный. Соответствующие опции: directedS и undi rectedS. 11о умолчанию: di rectedS. Vertexproperty Задает внутреннее хранилище свойств вершин. По умолчанию: no_property. EdgeProperty Задаст внутреннее хранилище свойств ребер. По умолчанию: no_property. GraphProp€rty Задает внутреннее хранилище свойств графа. По умолчанию: no_property. Модель для VertexListGraph, EdgeListGraph, IncidenceGraph, AdjacencyGraph, AdjacencyMatrix, Vertex MutablePropertyGraph и EdgeMutabiePropertyGraph. Требования к типам Значение свойства должно быть DefauitConstructible и CopyConstructibie. Ассоциированные типы Ниже приведены ассоциированные типы класса adjacency_matri х. • graph._traits<adjacency_matrix>: :vertex_descriptor Тип дескрипторов вершин, ассоциированных с матрицей смежности. (Требуется для Graph.) • graph_crs!t><adjacency_marrix>- :edge_de$cnptor Тип дескрипторов ребер. ассоциированных е матрицей смежности. (Требуется для Graph.) • QraphJ:raits<adjacencyj^rv.<>:: vertex_iterator Тил итераторов, возвращаемых функцией verticesO. (Требуется для VertexListGraph.) • graph_traits<adjacency_matnx>::edgeJterator Тип итераторов, возвращаемых функцией edges □. (Требуется для EdgeListGraph.)
14.1. Классы графов 239 • graph_traits<adjacency_matrix>::out_edge_iterator Тип итераторов, возвращаемых функцией cut_edges(). (Требуется для IncidenceGraph.) • graph_tralts<adjacency_matrix>:: adjacencyj terator Тин итераторов, возвращаемых функцией adjacentjerticesO. (Требуется для AdjacencyGraph.) • graph_traits<adjacency_matrix>::directed_category Предоставляет информацию о том, является граф ориентированным (d ree ted tag) или неориентированным (undi rected_tag). (Требуется для Graph.) • graph_tra1ts<adjacency_matrix>::edge_parallel_category Матрица смежности не позволяет вставлять параллельные ребра, так что тип всегда di sa 11 ow_pa га 11 el _edge_tag. (Требуется для Graph.) • graph_traits<adjacency_matrix>: :vertices_size_type Тип для работы с числом вершин в графе. (Требуется для VertexListGraph.) • graph_traits<adjacency_matrlx>;:edge$_size_type Тип для работы с числом ребер в графе. (Требуется для EdgeListGraph.) • graph_tra1ts<adjacency_matr1x>::degree_size_type Тип для работы с числом исходящих ребер в графе. (Требуется для IncidenceGraph.) • property_map<adjacency_matnx. PropertyTag»: :type property_map<adjacency_matrlx. PropertyTag»::const_type Тип отображения для свойств вершины и ребра графа. Свойство задается ар- гументом шаблона PropertyTag и должно совпадать с одним из свойств, указан- ных в VertexProperty или EdgeProperty графа. (Требуется для PropertyGraph.) Функции — методы Ниже приведены функции — методы класса adjacencyjnatrix. • adjacency_matrix(vertices_size_type n. const GraphPropertyS p - GraphPropertyO) Создает графовый объект с п вершин и без ребер. • template <typename Edgeiterator» adjacency_matrix(EdgeIterator first. Edgeiterator last. vertlces_size_type n. const GraphProperty& p *• GraphPropertyO)
240 Глава 14 • Классы BGL Создает графовый объект с п вершин и ребрами, заданными списком ребер в ви- де диапазона [first, last). Тип значения итератора Ldgelterator должен быть std : раз г, где тип внутри пары является целым типом. Целые числа соответ- ствуют вершинам и должны находиться в дшишзоие [0, п). • template <Lypename Edge Iterator, cypenamc- EdgeProperty Iterator'-* adiacericy_matrix(EdgeIterotor first. Edgeiterator last. EdgeProperty Iterator epjtei. vertices_size_type n. const GrdphProperty& p - GraphPropertyt)) Создает графовый объект с п вершин и ребрами, заданными списком ребер в виде диапазона [first, last), и свойствами, заданными ep iter в списке свойств ребер. Тип значения итератора Fdgelterator должен быть std: :pair, где тит внутри пары является целым типом. Целые числа соответствуют вершинам и должны находиться в диапазоне [0, п). Тип значения ep i ter должен быть EdgeProperty. Функции — не методы Ниже приведены функции — не методы класса 5djacency_matrix. • std: :pair<vertex_iterator. vertexjteratcr* vert'ces>const~adjacencyjnatnx& g) Возвращает пару итераторов, обеспечивающих доступ к множеству вершин графа д. (Требуется для VertexListGraph.) • std:r<edge_iterator. edgejterator* edges(const adjacency_matrix& g) Возвращает пару итераторов, обеспечивающих доступ к набору ребер графа д. (Требуется для EdgeListGraph.) • std::pair<ftdjacency_iterator. adjacency_iterator> adjacent_vertices(vertex_descriptor v. const adjacencyjnatri x& g) Возвращает пару итераторов, обеспечивающих доступ к множеству вершин, смежных с данной вершиной v графа 9 (Требуется для AdjacencyGraph.) • std::pair<out_edge_iterator. out_edge_lterator> outj?dge$(vertex_descriptor v. const adjac“ncy_matrix& g) Возвращает пару итераторов, обеспечивающих доступ к исходящим ребрам вершины v фафа д. Если граф неориентированный, эти итераторы обеспечи- вают доступ ко всем ребрам, инцидентным вершине v. (Требуется для IncidenceGraph.) • vertex_descriptor source(edge_descri₽tor е. const adjacencyjnatrlxS g) Возвращает начальную вершину ребра е. (Требуется для IncidenceGraph.)
14.1. Классы графов 241 • vertex_descriptor target udge_de«nptor const adjacency jnatrixS g) Возвращает конечную вершину ребра е. (Требуется для IncidenceGraph.) • degree_size_type out_decreeivertex_descnptor и. const adjacencyjna?riA& g) Возвращает число ребер, исходящих из вершины и. (Требуется для IncidenceGraph.) • vertices_size_type num_vertices(const acjacencyjnstrikl g) Возвращает число вершин в графе g. (Требуется для VertexListGraph.) • edges_size_type num_eoges(const adjacencyjnatmi g) Возвращает число ребер в графе с. (Требуется для EdgeListGraph.) • vertex descriptor_ve4ex(vertices_size_rype n. const adjacency_matrix& g) Возвращает п-ю вершину в списке вершин графа. • std:.pair<edge_descriptor. boo!» edge<vcrtex_descriptor u. vc-Ше ^descriptor v. const. adjacericyjnatr.x& g) Возвращает ребро, соединяющее вершину и с вершиной г> в графе «х (Требуется для AdjacencyMatrix.) • std: :palc<edge_descripr.cr, bocT- add_edge(vertex_descriptor и. vertex_descnotor v. adjacency_fnatnx& g) Добавляет ребро (и,») к графу и возвращает дескриптор ребра для нового реб- ра. Если ребро уже присутствует в графе, то повторно оно добавлено не будет и логический флаг будет ложью. Эта операция нс «портит» итераторы и де- скрипторы графа. (Требуется для EdgeMutableGraph.) • std: :pair<edge_descnptor. bool» add_edge(vertex_descriptor u. vertex_descriptor v. const EdgePropertyS p. adjacency_mat.rix& g) Добавляет ребро (и, г) к графу и присоединяет р как значение свойства (зебра для внутреннего хранения (ext. также предыдущую функцию-метод класса adcjidgef)). • void remove_edge(vertex_descriptor u. vertex_descriptcr v. odjacency_natrix& g) Удаляет ребро (и, v) из графа. (Требуется для EdgeMutableGraph.) • void remove_edge(edge_descrwtor е. adjacency_m=tnx& g) Удаляет ребро e из графа. (Требуется для EdgeMutableGraph.) 9 Змг 375
242 Глава 14 • Классы BGL • void rledc_vertex(ver“ex_d?5criptcr u. adjacency_niatrtx& g) Удаляет все ребра, исходящие и входящие, для вершины и. Вершина остается во множестве вершин графа. Воздействие на корректность дескрипторов и и гераторов такое же, как если бы для всех этих ребер отдельно вызывалась remove_edge(). (Требуется для EdgeMutableGraph.) • template «typenaire Property* prnpe'4ty_map<adjac«,ncyjnatri x. Property*:: type get (Property, adjacency jratrixS g) Возвращает объект-отображение свойств, указанный с помощью Property (свой ство). Свойство должно совпадать с одним из свойств, указанных в шаблон- ном аргументе Vertexproperty графа. (Требуется для PropertyGraph.) • template <r.ypenane Property» propertyjr£p'<adjdcency_malr1x. Property*: :const_type yet (Property. con$t adjacenc.y_matrix& g) Возвращает объект-отображение свойств, указанный с помощью Property (свой- ство). Свойство должно совпадать с одним из свойств, указанных в шаблон ном аргументе VertexPrcperty графа. (Требуется для PropertyGraph.) • template <typename Property, typename X> typename property_traits< typename prcperiy_map<adjacency_matnx. Property*. :tonst_type *: •. va1 ue_type get (Property, const adjacency_iratnxS g. X x) Возвращает значение свойства для х, где х - - дескриптор вершины или ребра. (Т|>ебуется для PropertyGraph.) ♦ template nypename Property, typenatne X. typename Value* void put.(Property, const adjacercy_natrixS g, X x, cons: Values value) Устанавливает значение свойства для х в value, где х — дескриптор вершины или ребра. Значение value должно быть преобразуемым к типу значения ото бражения свойства, указанного тегом Property. (Требуется для PropertyGraph.) • template <typename GraphProperties. typenane GraphProperty* tyDoname property_value<GraphPropertles. GrdphProperty*::type& get_prnperty(adjacency_matrix& g. Graphproperiy): Возвращает свойство, указанное Graphproperty, которое было закреплено за гра- фовым объектом д. Класс свойств property_val je определен в файле boost/ pending/property.hpp, • template <typename GrapnPrcperties, typename GraphPrnperty*
14.2. Вспомогательные классы 243 const typename property_value<GraphProperties. GraphProperty»::types get_property( const adjacencyjnatrixS g. Graph Property); Возвращает свойство, указанное GraphProperty, которое закреплено за графо- вым объектом д. Класс свойств propertyjal ие ощзеделен в файле boost/pending/ property.hpp. 14.2. Вспомогательные классы 14.2.1. graph_traits graph j;raits<Graph> Класс graph traits обеспечивает механизм для доступа к ассоциированным типам графового типа согласно определениям различных графовых концепций BGL (см. раздел 12.1). Когда вы хотите использовать один из ассоциированных типов [рафа, создайте экземпляр цаблона graph traits с графовым типом и за- дайте соответствующий дескриптор типа (typede f). Например, для получения типа дескриптора вершины для некоторого графа можно выполнить следующее: template «typename Graph* void my_graph_a]g<rlthmfGraph& gj j // Получить экземпляр graphjraics с графовым типом Graph. typedef boost-; graph jraits^Graph* Traits: // Описание типа для доступа к ассоциированному типу. typedef typename Traits: vertexjesen ptor Vertex: П ... ) Неспециализированная версия (no умолчанию) шаблона класса graphjra ns подразумевает, что графовый тип предоставляет составные дескрипторы типов для всех ассоциированных типов. Эта версия приведена в листинге 14.13. Листинг 14.13. Неспециализированная версия класса graphJraits namespace boost { template «typename G> struct graph_trait$ { И итераторы. typedef typename G :vertex_descriptor typedef typename G: :edge_descnptor typedef typename G::adjacencyjterator typedef typename G:;out_edge_ite'ator typedef typename G: :1n_edgejteraior typedef typename G:ivertexj locator typedef typename G::edge_iterato- // категории vertex.descriptor; edgejesenptor: ad jacencyj teratar: out_edgejterator; in_edgejterator, vertexjterator; edgejterator; typedef typename G: .'directedjategory d i reel ed_c a lego rj; typedef typename G: :edge_pa.-allel_category edge_paral leljategory; typedef typename G::traversal.category traversaljategor.y: // типы размеров typedef typename G; :vertices_s-izejype vertices_sin.-Jype; typedef typename G: -.edgesjizejype edges, size, type. typedef typename G: ;degree_sizejype degreejnzejype: }: } н namespace boost
244 Глава 14 • Классы BGL С другой стороны, graph_:ralts может быть специализирован но графовому типу. Например, следующий код специализирует graph-traits для структуры Grap! библиотеки Stanford GraphBase. Полностью оболочка для SGB-графов описана в заголовочном файле boost/graph/$tanford_graph.hpp. namespace boost { temp!ate <» struct graph traits<5raph*> ( h. ... b I Если тип графа является шаблоном класса, то класс graph_traits может быть частично специализирован. Это означает, что еще остаются некоторые «свобод- ные» параметры. Ниже приведена частичная спещшлизация graph_tra i т.$ для па- раметризованного типа GRAPH из LEDA. Полностью интерфейс оболочки для это- го типа находится в файле boost/graph/leda_graph.hpp. namespace boost { template <typenaine ‘.Type. typename etype* struct graph traits»: GRAPiHvtype.etype* > •' // . h } ' Ни одна конкретная графовая концепция нс требует, чтобы были определе- ны все ассоциированные типы. При реализации графового класса, который дол- жен удовлетворять одной или более графовым концепциям, для ассоцииро- ванных типов, не затребованных этими концепциями, можно использовать vc-’ 2 в качестве типа (когда используются составные дескрипторы типов внутри гра- фового класса) или оставить typedef вне специализации graph„traits для этого графового класса. Теги категорий Категория di recred_catdgory должна быть дескрнйтором одного из следующих двух типов. namespace boost { struct directedj.ay ( ): struct undirected tag I 1: 1 Категория edge_parc11 el „category должна быть дескриптором одного из следу- ющих двух типов, namespace boost { struct allGw_parallel_«!ge_tag {}, struct dl5anow_parelTe! ,edge_tag {•: Категория traversal_category должна быть дескриптором одного из следующих типов или типа, который наследует от одного из этих классов. namespace boost { struct inctdencejraph tag ' }: struct adjacency_grapf4ao ! 't; struct lndirectional_gMpti__tag -
14.2. Вспомогательные классы 245 public virtual incidence_graph__tag ( }: struct vertexj 1 $t_gra[>h_tag public virtual incidence_arap*i_tag. public virtual adjacency_graph_tag { }. struct edge_list_grapn_tag { }: struct vertex_and_edge_list_graph-tag - public virtual edgejist_graph_tag public virtual vertex_11st graph_tag { >: struct adjacency _matnx_tag { ); Параметры шаблона Ниже приведен параметр шаблона класса grap!'_traits. Graph Графовый тип, модель для категории Graph. Где определен Класс graph-traits находится в файле boost/graph/graph_.traits.hpp. Методы Ниже приведены методы классаgraph_tra jr.s. • graph_trait$: :vertex_clescript.or Тип дескрипторов вершин, ассоциированных с Gryph. • graph_traits::eoge_descr1ptor Тип дескрипторов ребер, ассоциированных с Graph. • graph_traits.- :vertexjterator Тип итераторов, возвращаемых функцией vertices (). • graph-traits;:edge_iterator Тип итераторов, возвращаемых функцией edges:). • graph_traits: :out_edge_'.terator Тип итераторов, возвращаемых функцией ojt_edges( <. • graph_traits::adjacency_iterator Тип итераторов, возвращаемых функцией adjacent_vc''t 1 ces 11. • graph_traits:;directed_category Сообщает, является граф ориентированным или неориентированным. • graph_traits::edge_parailei_categnry Сообщает, позволяет ли граф иметь параллельные ребра. • graph_traits: :traversa\category Сообщает, какие виды обхода обеспечиваются графом. • graph-traits: :vertices_size_type Беззнаковый целый тип. используемый для работы е количеством вершин в графе. • graph_trait5::edgeS-Size_type Беззнаковый целый тип, используемый для работы с количеством ребер в ц?афе.
246 Глава 14 « Классы BGL • graphjjcits: . degree j izejype Беззнаковый целый тип, используемый для работы с количеством исходящих ребер в графе. 14.2.2. adjacencyjist_traits aa,jacency_l-ist_traits<EageL'st. VertexList. Direcrea* Класс adjacencyj i stjra i is предоставляет альтернативный метод для доступа к некоторым ассоциированным типам класса adjacency 11st. Главной причиной создания этого класса является то, что иногда требуются свойства графа, значе- ниями которых являются дескрипторы вершин или ребер. Если вы попытаетесь использовать для этого graphtraits, возникнет проблема с взаимно-рекурсивны- ми типами. Для решения этой проблемы предлагается класс adjacencyj i st jrai t*,, который предоставляет пользователю доступ к типам дескрипторов вершин и ре- бер, не требуя задания типов свойств графа. template <typename EdgeLtst. typer®» VertexList. typename Directed* stract adjacencyj istjrait? { typedef .. vertexjescnptor; typedef .. edgejlescriptor: typedef . directedj:ategory; typedef ... edge_para1lel_category: Где определен Класс adjacencyj i st jraits находится в файле boost/graph/adjacency_list.hpp. Параметры шаблона Ниже приведены параметры шаблона класса adjacencyj ist Jeans. EdgeList Тип селектора для реализации контейнера ребер. По умолчанию: vecS. VertexList Тип селектора для реализации контейнера вершин. По умолчанию: vecS. DI recced Селектор лчя выбора ориентированного или неориентированного графа. По умолчанию: di rectedS. Модель для DefaultConstructible и Assignable. Методы Ниже приведены методы класса adjacencyj 1st Jraits. • adjacencyj ist jraits: rvertexjescnptor Тип объектов, используемых для идентификации вершин графа. • adjacencyjistjraits: :edge_descnpter
14.2. Вспомогательные классы 247 Тип объектов, используемых для идентификации ребер графа. • adjacency_Hst_traits: :directed_category Сообщает, является ли граф неориентированным (undirected сад) или ориен- тированным (di rected_tag). • adjacency_list_trait$: :edge_parallel_car.egory Сообщает, позволяет ли граф добавлять параллельные ребра (allow para lel_edge_tag) или автоматически удаляет их (disallow_parallel_edge_tag). Смотри также Дополнительная информация находится в описании класса adjacency ! i st. 14.2.3. adjacency_matrix_traits ad Jacencyjnatri x_traits<Oi r»cwd> Класс adjacency_mat n x_t га 11$ предоставляет альтернативный метод дл я доступа к некоторым ассоциированным типам класса adjacency jratrl х. Главной причиной создания этого класса является то, что иногда требуются свойства графа, значе- ниями которых являются дескрипторы вершин или ребер. Если вы попытаетесь использовать graph trai tb для этого, возникнет проблема с взаимно-рекурсивными типами. Для решения этой проблемы предлагается .класс adjacencyjnatri x_t гаi ts, который предоставляет пользователю доступ ктипам дескрипторов вершин и ре- бер, не требуя задания типов свойств графа. template <typename Directed struct adjacency_matrix_traits { typedef ... vertex_descriptor: typedef . . edge_descrptor; typedef ... directed_category; typedef ... edge_paraTel category: b Где определен adjacency_matrix_traits находится в файле boost/graph/adjacency_matrix.hpp. Параметры шаблона Ниже приведен параметр шаблона класса adjacency_inatnx_traits. DI rected У казывает. является фаф ориентированным или неориентированным. По умолчанию: di rectedS. Модель для DefajltConstructible и Assignable. Методы Ниже приведены методы класса ad.iacency_matrix__traits. • adjacency_iratrix_traits: :vertex_descriptor Тип объектов, используемых для идентификации вершин графа.
248 Глава 14 • Классы BGL • adjacency j«tnxj.rait$: : edge jjescm ptor Тип объектов, используемых для идентификации ребер графа. • adjacency_matrix_trdits::directed_c3tegory Сообщает, является граф неориентированным (undirected_tag) илиориенти рованным (di rected_tag). • adjacercy_matrix_trans - :edge_paral Iel_caiegory Матрица смежности не позволяет добавлять параллельные ребра, поэтому здесь используется di sal icwjwal ' е! _e<jge_taq. Смотри также Дополнительная информация находится в описании класса adjacericyjnat: и. 14.2.4. property.map property _map«Graph. Propertyla<_:> Класс propectyjnap - класс свойств для доступа к типу отображения свойства хранящемуся внутри графа. Специализация этого класса свойств требуется для типов, которые моделируют концепцию PropertyGraph. Пример В следующем примере (листинг 14.14) создается граф с внутренним свойством для хранения имен вершин, а затем осуществляется доступ к типу свойств имен вершин с помощью класса свойств property тар. Объект - отображение свойств; подумается из гргфа, с использованием функции geu). Листинг 14.14. Пример внутреннего хранения имен вершин < property map-traits eg.ерр > include «boost/config.r-pp> #incUde <stnng> findude «boost/graph/adjacency_list,hpn-» ;nt rsaini) { using nsrespace boost; typedef adjacencyjlst < 'dstS. I'-stS. directedS, property < vertex_name^t std.-siring > >graph_t. graphj g; grapnjraits « graph_t >: .vertexjeserptor u •• add.vertex-fg). properly_map < graphJ. vertex_name_t . type »ame_map - gete/ertex^name. gi: name_map[u] “ "Joe’, SW-:cout « name~map[ij] « stdi.endl. return EXH SUCCESS: } Программа выводит следующее: Joe Где определен Класс propertyj’ijp находится в файле boost/grapn/properties.hpp. Параметры шаблона Ниже приведены параметры шаблона класса Drcpertyjnap.
14.2. Вспомогательные классы 249 Graph Тип графа, который должен быть моделью PropertyGraph. PropertyTag Теговый класс для падания свойства. Модель для Нет. Общедоступные базовые классы Нет. Ассоциированные типы Ниже приведены ассоциированные типы класса p/'op?rty_rrop. • proDertyjTiap<Groph. PropertyTag*:-type Тип изменяемого отображения свойства для доступа к внутреннему свойству, указанному PropertyTag. • property_inap<Graph. VopertyTag>. :const_type Т ип константного отображения свойства для доступа к внутреннему свойству, указанному Property Гад. Функции — методы класса Нет. Функции — не методы класса Нет. 14.2.5. property prooerty<PropertjTatj. 7. HextProperty» Класс property может исполызоваться с. классами adjacencyj i st и adjacency_ma - trix для указания видов свойств, закрепляемых за вершинами и ребрами 1рафа и за самим графовым объектом. Параметры шаблона Ниже приведены па{>аметры шаблона класса property. PropertyTag Тип для обозначения (придания уникально- го имени) свойству. Имеются несколько предопределенных тегов, и несложно опре- делить новые. Для удобства BGL также пре- доставляет предопределенные объекты теговых типов (в данном случае — значений перечисления егш) для их использования в качестве аргументов функций, которые ожидают объекты теговых типов (напри- мер, как функции отображения свойства get О к acjacencyjist). I Этот тип указывает тип значений свойств.
250 Глава 14 • Классы BGL NextProperty .Этот параметр позволяет типам свойств быть вложенными, так что с графом может быть связано любое количество свойств. По умолчанию: no_property. Где определен Класс property находится в файле boost/pending/property.hpp. Теги свойств Следующие теги свойств (листинг 14.15) определены в boost/graph/properties.hpp Листинг 14.15. Определения тегов свойств namespace boost { !' Геги свойсш ребер: enum edge_name_t { edge_name }; н имя enum edge_weight_t { edge_welght ): и aec etiun edge J nctex_t ( edgejndex ). // индекс enum edge_capacity_t { edge_capacity }.- И мощность enum edge_residual_capac1ty_t { edge__residuai_cacacity }: н остаточная мощность enum ed-ge_reverse_t { edge_reverue }. И обратное /7 Теги свойств вершин: enum vertex_name_t { v₽rtex_name }-. И имя enurn vertex_dibi.dnce_t { .ertex_distance ); /•' расстояние до вершины enum vertex_Tndex_t { vertexjnaex // индене enum vertexjtolor _t { vert.ex.cclor j: И цвет enum vertex, d&greej. ( vertex degree }: // степень emm vertex out_degree^t ( vertex_out_degree ): •'/ степень по исходящим ребрам enum vertex_in_degree_t { vertexjri degree ). // степень no входящим ребрам enum vertex_discover_t-.rae_t { vertexjjiscovcr.two I! время посещения enum vert.ex_fiinsh_time_t { vertex f-.лish_ti» }: 11 время окончания обработки // Ter свойства графа- ernrm graplijiamej { дгарЬ_паве ): •'/ имя 800ST_iNSTALL_PS0PERTY(vertex, ’ndex); BOOST_lN£TALL_PRO₽FRTY(edge. iroexT: n ..7 ) 14.3. Графовые адаптеры 14.3.1. edgejist edgejist<Edgelterator, ValueType. GitfTyoe> Класе edge_hst — это адаптер, который превращает пару итераторов ребер в класс, моделирующий EdgeListGraph. Тип значения (value_type) итератора ребер должен быть std: .-pair (или. по крайней мере, иметь.методы first (первый) и second
14.3. Графовые адаптеры 251 (второй)). Типы first Jype и secondjype должны быть одинаковыми и исполь- зоваться в качестве дескриптора вершины графа. Тилы шаблонных параметров ValueType и DiffType нужны только в случае, если наш компилятор не поддержива- ет частичную специализацию. В противном случае они имеют правильные значе- ния по умолчанию. Пример См. раздел 5.3, где дай пример использования edgej ist. Параметры шаблона Ниже приведены параметры шаблона класса edgej 1st. Edgeiterator ValueType Di ft"ype Модель для Inputiterator, чей valutj.y ре дол- жен быть парой дескрипторов вершин. Тип значения для Edge Iterator. По умолчанию: std : • iteraLor_trai‘$<Edgelte- !'асог>:: value Jype. Тип разности для Edge Iterator. Но умолчанию: std:: ;terator_trans<Edgp!te- rator>:: difference Jype. Модель для Класс edgejist поддерживается концепцией EdgeListGraph. Где определен Класс edgejist находится в файле boost/graph/edgejisthpp. Ассоциированные типы Ниже приведены ассоциированные типы класса edge l 1st. • g<“aph_traits<edge_l ist>: :vertex_descriptor Тип дескрипторов вершин, ассоциированный с edge J i st. Это гот же самый тип, что и first Jype для лары std: .pa"r. являющейся типом значения для Edgeiterator. • graphjraits<edgejist>:: edge Jescri ptor Тип дескрипторов ребер, ассоциированный c edgej 1st. • graphjraits<edgejist>: :edge_iterator Тип итераторов, возвращаемый функцией edges(). Категория 1 rerator_category для итератора ребер та же, что и у Edgeiterator. Функции — методы Ниже приведена функция — метода класса edge J i st. • edgejist(EdgeIterator first. Edgeiterator last) Создает графовый объект из п вершин с ребрами, заданными в списке ребер из диапазона [first, last).
252 Глава 14 • Классы BGL Функции — не методы Ниже приведены функции — не методы масса cdgejist. • std: :pair<edge_iterator, edgejwraior* edges(const edge_list& q) Возвращает пару итераторов, обеспсчнвакшшх доступ к набору ребер графа с • vertex_descmptor* source(edge_descriptor е. const edge_li$“& g) Возвращает начальную вершину ребра а. • ver'texjjesceiptoc target(edgejtescmptor е. const edge_list& g) Возвращает конечную вершину ребра е. 14.3.2. reverse_graph reverse^graph^Bid:rectionalGraph> Класс revcrsejgraph меняет местами входящие и исходящие ребра BidirectionalGrapI (двунаправленного) графа, •.«|«1xikthbhv транспонируя граф. Построение обращен нота графа выполняется за постоянное время, таким образом обеспечивая высоко аффективный способ для получения транспонированного подставления графа Пример Пример в листинге 14.16 взят из файла examples/reverse-graph-eg.cpp. Листинг 14.16. Обращение направлений ребер графа typedef adjacency_iist < vecS, vecS, bichredions')S > Graph; Graph 6(a). addjjdgeiu. 2, G'l: add_ edged, 4. G;. ado_edged. 4. 5’.. add_eflue(4. 0 G): add_edged. i. Gi; add_edge£2. 1. GL aa<edg?(3. 1. G): ada_edne(4, 1 Gi: sda_edged 3. G): add_edgf*(2 3. G), aad_ed«ge(3 4. Gi. std:;cout «г “исходный граф;" •« std: end!: printgraphiG. get<vertex index. G)’i: std: .cout « itd-:end1 « “с&раиенный граф:'1 « std:-.endl; print graph(iMfce_reverse_graph(G). get(vertexjndex. GM. Эта программа выводит следующее: исходный граф: С - 2 1 --> j 3 4 2 - > 1 3 4 3 1 4 4 - О 1 окрашенный граф- П > 4 ". -->1334 2 О 3 --*> 1 2 4 • > 1 2 3
14.3-Графовые адаптеры 253 Параметры шаблона Ниже приведен параметр шаблона класса reverse_graph. Bicn rGraph Тип графа, для которого строится адаптер. Модель ДЛЯ Класс reverse_gr<iph поддерживается концепциями BidirectionalGraph и (необяза- тельно) VertexListGraph и PropertyGraph. Где определен Класс. reverse_graph находится в файле boost/graph/reverse_graph.hpp. Ассоциированные типы Ниже приведены ассоциированные типы класса f'everse_graph, • g’'apn_irait$<reverse_gi'aph>^: ve^tex^dcscri^ior Тип дескрипторов вершин, ассоциированных с обращенным граг|юм. (Требуется для Graph.) • grarh_i'’aits<reverse_graph>: edge_descrlptcr Тип дескрипторов ребер, ассоциированных с обращенным графом. (Т[>ебуется для Graph.) • grapn_traits<reverse_graph>:.vertex_iterator Тип итераторов, возвращаемых функцией vert ices(). (Т^буетея для VertexListGraph.) • graph_tmts<reverse_graph>: ,edge_iterator Тип итераторов, возвращаемых функцией edges(). (Требуется для EdgeListGraph.) • graph_traits<reverse_graph>- ;out_edge_>terator Тип итераторов, возвращаемых функцией out_edges(). (Т[>ебуется для IncidenceGraph.) • craph_tra1ts<reverse_graph>::adjacency_-terator Тип итераторов, возвращаемых функцией adjacent_vert1c.es (). (Требуется для BidirectionalGraph.) • crapr._tralrs<reverse_cj’’aph>: :directed__ratego'’y Предоставляет информацию о том, является граф ориентированным или нео- риентированным. (Требуется для Graph.) • craph_traits<reverse_graph>: ledqejwai lel_cateoory Сообщает, позволяет ли граф осуществлять вставку параллельных ребер (ре- бер с одинаковыми начальными и одинаковыми конечными вершинами). Теш; аПсй_рагаПе1_еаде_ид и disalicw_pardiiel_edge_taq. Варианты графовс пара- метрами sets и hash_setS всегда используют disal low_pard' iel_edge_tag, тогда как другие могут позволить включение параллельных j>e6ep. ( Требуется для Graph.)
254 Глава 14 • Классы BGL • graph_traits<reverse_graph>:: traverse! jsategory Категория обхода отражает, какие возможные виды итераторов поддержива- ются графовым классом. Для reversejjraph :>то будет тот же тип, что и для traversal _categcry исходного графа. (Требуется для Graph.) • graph_traits<reverse_graph>::vertices_size_tyPe Тип для работы с числом вершин в графе. (Требуется для VertexListGraph.) • grdph_tra iis<reverse_grdpf»:: edue_s ize_type Тип для работы с числом ребер в графе. (Требуется для EdgeListGraph.) • graph_tra1ts<reverse_graph>::degree_size_type Тип для работы с числом |>ебер, инцидентных вершине в графе. (Требуется для IncidenceGraph.) • f'.roperty_mp<reversejjraph. Property*-:: type propertyj'iap<reverso2graph. Property*: :cons retype Тип отображения свойств для свойств вершин и ребер графа. Конкретное свой- ство указывается шаблонным аргументом Property и должно совпадать с од- ним из свойств в Vertexproperty или EdgeProperty графа. (Требуется для PropertyGraph.) Функции — методы Ниже приведена функция — метод класса reversejjraph. • revorse_graph(Bidirectic>na-Graph& g) Конструктор. Создает обращенное (транспонированное) представление графа д. Функции — не методы класса Ниже приведены функции — не методы класса reversejjraph. • template '-class Bid^rectlonalGraoh* reverse__qraph<Bid'rectionalGraph> make_reverse_graph(Bid i ”есt ionalGrap'n& g) Вспомогательная функция для создания обращенного графа. • std: :palr<vertex_rterat.or. vertexjterator* v₽rtJce$( const reverse_graph& g) Возвращает пару итераторов, обеспечивающих доступ к множеству вершин графа д. (Требуется для VertexListGraph.) • std: :pair<out_edgejterator. out_edgejterator> out_edges(vertex_descnptor v. const revene_graph& g) Возвращает пару итераторов, обеспечивающих доступ к набору исходящих ребер вершины v графа д. Эти исходящие ребра соответствуют входящим ре- брам исходного графа. (Требуется для IncidenceGraph )
14.3. Графовые адаптеры 255 • std: :pair<in_edge_iter?.tor. ir_edge_iteratcr> in_edges(vertex_descnptcr v. const reve-sejraphS g) Возвращает пару итераторов, обеспечивающих доступ к набору входящих ре- бер вершины v графа д. Эти входящие ребра соотнетствуют исходящим ребрам исходного графа. (Требуется для BidirectionalGraph.) • std; :pair<adjacency_iterator. adjacencyj terator» adjacent_vertices(vertex_descr:pt.or v. const reverse_graphi g) Возвращает пару итераторов, обеспечивающих доступ к смежным вершинам вершины v графа д. (Требуется для AdjacencyGraph.). • vertexjescriptor source(edge_descriptO” е. const reverse jrapti& g) Возвращает начальную вершину ребра е. (Требуется для IncidenceGraph.) • vertex_descnpcor targetledgejescriptor е. const reverse_graph& g) Возвращает конечную вершину ребра е. (Требуется для IncidenceGraph.) • degree_size_type out_degree(verte.x_descriptor u. const reverse_grapr& g) Возвращает число ребер, исходящих из вершины и. (Требуется для IncidenceGraph.) • degree_s1ze_type 1njegreefvertex^descriptor u. const rever$e_grap!J g) Возвращает число ребер, входящих в вершину и. Операция доступна, только если был указан селектор bi di rect i onaIS. (Требуется для BidirectionalGraph). • verticesjizejype num_vertices(con$t rever$e._graph& g) Возвращает число вершин в графе g. (Требуется для VertexListGraph.) • vertex_descr1ptor vertex(vertice$_size_r.ype п. const reverse_graph& g) Возвращает и-ю вершину в списке вершин графа. • std: :palr<edge_descnptor, bool» edge(vertex_descript.or u, vertex_descriptor v. const reverse_graph& g) Возвращает ребро, соединяющее вершину и с вершиной v. (Требуется для AdjacencyMatrix.) • template «class Property» property_map<reverse_graph. Property»::type
256 Глава 14 • Классы BGL get(Property. rever$e_grapWJ gi template «class Property* property_map«reverse_graph. Tag*:.const_type get(Property, const rever$e_graph& g) Возвращает объект-отображение свойств, указанный с помощью Property. Свой с । во должно совпадать с одним из свойств, указанных в шаблонном аргументе iterte.-Propcrty графа. (Требуется для PropertyGraph.) • template «class Property, c1ass X> typename propertyjraics«propertyjMp<reverse__graj:>n, Property*: :cori$f_type>-: value, type geUProperty. const ’ever$e_yraph& u. x x) Возвращает значение свойства для л, где х — дескриптор вершины или ре- бра. • template «class Property, class X, dess Value* void put (Property, const, reverse_graoh8 g. x x. const Value» value) Устанавливает значение свойства для х в value, где х — дескриптор вершины или ребра. Значение value должно быть преобразуемым к типу typename proper- ty_trans<propertyjnap<reverse_graph. Property*::type>::value_type • template «class GraphProperties, class Graphproperty* typename property_va1ue<fir3phP(.operties. GraphProperty*;:type& get._properlyi reverse_graph& g. GraphProper ,y): Возвращает свойство, указанное Graphproperty, которое относится к графово- му объекту. Класс свойств property11 ie ощжделеп в заголовочном файле boost/ pending/property.hpp. • template «class Graphproperties, 'lass GraphProperty* const typename property_vaiue--GraphProperfies. GraphProperty*: :type?, get_property(const rever$e_graph& g. GraphProperty): Возвращает свойство, указанное шарНРгорегу, которое относится к графово- му объекту. Класс свойств pr< у_м«ч ««определен в заголовочном файле boost/ pending/property.hpp. 14.3.3. filtered_graph ti !tered,graijh<GfaDh. tagePt eci i cate. vertexPrecbcate' Класс fi ;tsrpd_gr<o>h является адаптером, который создает фильтрованное представление графа. Функция-предикат для ребер и вершин определяет. ка- кие вершины и ребра исходного графа показывать в фильтрованном графе. Лю- бая вершина, для которой предикатная функция возвращает ложь, и любое реб- ро, для которого предикатная функция возвращает ложь, будут показываться удаленными в результирующем представлении графа. Класс fi Iteredjjraph не создает копии исходного графа, но использует ссылки на него. Время жизни исходного графа должно быть дольше любого использования фильтрованного
14.3. Графовые адаптеры 257 графа. Объект f 11 teredjraph не изменяет структуру исходного графа, хотя свой- ства исходного графа можно изменить с помощью отображения свойств филь- грованного графа. Пример Следующий функциональный объект, определяемый в листинге 14.17, являет- ся примером предиката, который отфильтровывает ребра с неположительным весом. Листинг 14.17. Предикат, определяющий, имеет ли ребро положительный вес template «typename EdgeWei^htMao» struct positive_edge_weight { П положительный sec ребра' po$itive_edge_weigMO ( } posttive_edge_weHint(EdgeWeightMap weight) : nyweight(weight) > } template «typename £dge> bool operator!)'const EageS c) const { return 0 < boost--act fra weight, e*• } EdgeWeightMap mweight: >: Пример в листинге 14.18 использует фильтрованный граф с описанным выше предикатом pos',nve_edge_weight. для создания фильтрованного представления небольшого графа. Ребра (zl, С), (С, Е) и (£, С) имеют нулевой иес и потому не по- являются в фильтрованном графе. Листинг 14.18. Граф, фильтрованный с помощью предиката typedef edjacency_!ist<vec$. vecS. directedS. no property, presorty«edge_weight^t wt> > Graph: typedef propertyjiap«Gracm. «fge_weight. t>: type EdgeWeightMap; erwm {A. 6. t. D. E. N ). const char* name <• "ABOT". Graph gtN); aciJ_eage(A. 9. I. gi. adu_edgeiA, C, 0. g): add_edge!C. D. 1. gj. adtTedgetC. E. 0. g). addedgeED. B. 3. g): add, edger E, C. 0. g): pos 111 ve_eclge_wei gl> t«F. dgeWe i ghtMap> f i1 ter(get (edgejie ight. g} 1: fiileced_graph«.-Graph. posH’-ve.edge.we1gm<EdgefeitThtMep> > fgig. filter): std. cout «« "етфипьтроваечуй набор ребер: ". print_edgestfg. name). sW rcout « "отфильтрованные печалящие ребра'" « std::end" . prniygraphf!g. name): Эта программа выводит следующее: (^фильтрованный набор ребер: <А.В» (C.D) 10.В) отфильтрованные исходящие ребра а -> е. в с о о -> в Е ->
258 Глава 14 • Классы BGL Где определен Класс filtered_graph находится в файле boost/graph/filtered_graph.hpp. Параметры шаблона Ниже приведены параметры шаблона класса fi 1 tered graph. Graph Графовый тип для адаптации. EdgePredicate Функциональный объект, выбирающий, ка- кие ребра исходного графа будут присутство- вать в отфильтрованном графе. Должен быть моделью Predicate. Тип аргумента — тип де- скриптора ребра графа. Также предикат дол жен быть DefaultConstructible Vertexpredicate Функциональный объект, выбирающий, ка кие вершины исходного графа будут присут ствовать в отфильтрованном графе. Должен быть моделью Predicate. Тип аргумента — тип дескриптора вершины графа. Также предикат должен быть DefaultConstructible. По умолчанию: keep_al1 (сохранить все) Модель для Концепции, которые моделирует fi1tered_graph<Graph.EP.VP>, зависят от типа Graph. Если Graph моделирует VertexListGraph, EdgeListGraph, IncidenceGraph. Bidirec- tionalGraph, AdjacencyGraph или PropertyGraph, то же самое делает и ЙНегеб_ graph<Graph.EP.VP>, Ассоциированные типы Ниже приведены ассоциированные типы класса fi ltered_graph. • graph_traits<filtered_graph>::vertex_descriptor Тип дескриптора вершины, ассоциированный с fi ltered_g“aph. (Требуется для Graph.) • graph_trans<filtered_graph>: :edge_descnptor Тип дескриптора ребра, ассоциированный с fl 1 tered graph. (Требуется для Graph.) • graph_trarts<filtered_graph>: :vertex__iteretor Тип итераторов, возвращаемых функцией vertr ces О. Тип vertex ! terator — тот же самый, что и для исходного графа. (Требуется для VertexListGraph.) • graph_tralts<flltered_graph>::edge_lterator Тип итераторов, возвращаемых функцией edges О. Итератор моделирует кон- цепцию MultiPassinputiterator. (Требуется для EdgeListGraph.) • graph_traits<filtered_graph>::out_edge_1terator
14.3. Графовые адаптеры 259 Тип итераторов, возвращаемых функцией cut edgesO. Итератор моделирует концепцию MuitiPassInputlterator. (Требуется для IncidenceGraph.) • graph_trans<filtere<J_grapn>:: in_edge_iterator Тип итераторов, возвращаемых функцией in edgesO. Итератор моделирует концепцию MultiPassinputiterator. (Требуется для BidirectionalGraph.) • graph_traits<filtered_graph>:: adjacencyj terator Тин итераторов, возвращаемых функцией adJacent verticesO. Этот итератор моделирует ту же концепцию, что и итератор исходящих вершин. (Требуется для AdjacencyGraph.) • graph_traits<filtered_graph>::directed_category Сообщает, является граф неориентированным (und’< rectedjag) или ориенти- рованным (di rectedjag). (Требуется для Graph.) • graph_traits<filtered_graph>: .-edge_ parallejcategory Сообщает, позволяет ли граф осуществлять вставку параллельных ребер (ре- бер с одинаковыми начальными и одинаковыми конечными вершинами). Тот же самый, что и edge_parallel_category исходного графа. (Требуется для Graph.) • graph Jraits<flltered_graph>:-.verticesjizejype Используется для работы с количеством вершин в графе. (Требуется для VertexListGraph.) • graph_traits<filtered_graph>::edges_size_type Используется для работы с количеством ребер в графе. (Требуется для EdgeListGraph.) • graph_tralts<filtered_graph>: :degree_sue_type Используется для работы с количеством исходящих ребер вершины. (Требуется для IncidenceGraph.) • property_map<filtered_graph. PropertyTag*::type property_map<filtered_graph. PropertyTag*::const_type Тип отображения свойств вершины или ребра в графе. Типы отображения в адаптированном графе те же, что и в исходном. (Требуется для PropertyGraph.) Функции — методы Ниже приведены функции — методы класса filteredjraah. • filtered_graph(Graph& g. EdgePredicate ер) Конструктор для представления графа g с отфильтрованными ребрами на ос- новании предиката ер. • filtered_graph(Graphs g. EdgePredlcate ep. VertexPredlcate vp)
260 Глава 14 • Классы BGL Конструктор для отфильтрованного представления графа g на основании щк - диката для ребер - ер, и для вершин - vp. Функции — не методы Функниональшкть, поддерживаемая классом f 11 t.ered_graph, зависит от лежапц • го в основе исходного графа. Например, если тип Graph не реализует in edges’ , именно это будет делать отфильтрованный граф. Ниже перечислены возможные функции, которые ft ;tered_grapn может поддерживать, если задан тин Graph, яг - ляюшийся моделью VertexListGraph, EdgeListGraph, IncidenceGraph, BidirectionalGraph. AdjacencyGraph, PropertyGraph и BidirectionalGraph. • std: :pair<vertex_iterator. vertexjierato.-> vertices (const fiiterecl_grapn& g) Возвращает пару итераторов, обеспечивающих доступ к множеству вершин графа д. (Требуется мя VertexListGraph.) • std::pair<eage_iterator. ecfge_itr-rator> edgesCconst filtered_graph& g: Возвращает пару итераторов, обеспечивающих доступ к набору ребер графа i. (Требуется для EdgeListGraph.) • std::pair<adjacency_iteratce. adjacency_ir.erat.or> adjacent_vemces(vertex_descriptor v, const fi1tered_graph& g) Возвращает пару итераторов, обеспечивающих доступ к смежным вершинам вершины v в графе д. (Требуется для AdjacencyGraph.) • std;:parr<out_edge_iterator. out_edge_iterator> out_ecges(vertex_descriptar v. const filtared_graph& g) Возвращает пару итераторов, обеспечивающих доступ к исходящим ребрам вершины v в графе д. Если граф неориентированный, эти итераторы предос- тавляют доступ ко всем ребрам, инцидентным вершине v. (Требуется для IncidenceGraph.) • verte*_descriptor source(edge_descriptor е, const fi'itered_graph& g) Возвращает начальную вершину для ребра ы (Требуется для IncidenceGraph.) • vertex_descriptor target(edge_descriptor e, const *iltered_grapM <?) Возвращает конечную вершину для ребра е. (Требуется для IncidenceGraph.) • degree_sue_type &at_deijree(vertex.jejcr'ipto‘' и. const filtered_graph.J g) Возвращает число ребер, исходящих из вершины и. (Требуется для IncidenceGraph.)
14.3. Графовые адаптеры 261 • vertices_size_type nurwerocestcerisi filterea_graph& g) Возвращает число вершин в нижележащем графе д. (Требуется для VertexListGraph.) • edges_size_type num_edges(const filtered_graph& g) Возвращает число ребер в графе g. (Требуется для EdgeListGraph.) • template «typename Property? properr.y_map<f11 tered_graph. Property? :type get(Property, filtered_grapha q) template «typename Property? propertyjnap<fllrerea_graph. Property? :const_r.ype get (Property, const ;'iltered_graph& g) Возвращает объект-отображение свойств, заданный Property. Свойство Property должно совпадать с одним из свойств в шаблонном аргументе Vertexproperty графа. (Требуется для PropertyGraph.) • template «typename Property, typename X> typename property_traits< typename property_map<fi it,ered_graph. Property?::const_type >::value_tyPe get(Property, const tT 1 tered_graph& g. X x) Возвращает значение свойства для х, где х - дескриптор вершины или ребра. (Требуется для PropertyGraph.) • template «typename Property, typename X. typename Value? void put(Property. const flltered_gr?.phj g. X x, const valued value) Устанавливает значение свойства для х в value. Здесь х — дескриптор верши- ны или ребра. Значение должно быть преобразуемо в тип значения указан нога свойства. (Требуется для PropertyGraph.) 14.3.4. Указатель на SGB Graph Graph* Заголовочный файл boost/graph/stanford_graph.hpp из BGL адаптирует Stanford GraphBase-указатель (SGB) 122] на Graph в граф, совместимый с BGL. Заметим, что класс графового адаптера не используется, a Graph* из SGB сам становится моделью нескольких графовых концепций (см. раздел «Модель для» ниже) с по- мощью определения нескольких перегруженных функций. Обязательно применяйте файл изменений PROTOTYPES к вашей установке SGB, с тем чтобы заголовочные файлы SGB соответствовали z\NSI С (и, значит, могли компилироваться компилятором C++). Мы благодарны Андреасу Шереру за помощь в реализации и документирова- нии адаптера Graph* из библиотеки SGB.
262 Глава 14 • Классы BGL Пример Примеры см. в файлах example/miles_span.cpp, example/roget_components.cpp и example/girth.cpp. Параметры шаблона Пет. Модель для VertexListGraph, IncidenceGraph, AdjacencyGraph и PropertyGraph. Набор тегов свойств, который можно использовать для SGB-графа, дан ниже в разделе «Свойства вер- шин и ребер». Где определен Указатель на SGB Graph находится в файле boost/graph/stanford_graph.hpp. Ассоциированные типы Ниже приведены ассоциированные типы указателя на SGB Graph. • graph_tralts<Graph*>;:vertex_descri3tor Тип дескриптора вершин, ассоциированный с SGB Graph*. Мы используем т и i Vertex* в качестве дескриптора вершины (где Vertex — typedef в заголовочном файле gb_graph.h.) (Требуется для Graph.) • graph_traits<Graph*>::edge_descriptor Тип дескриптора ребер, ассоциированный с SGB Graph* Используется тип boost:: sgbjdge Jype. В дополнение к поддержке всех требуемых в BGL опера- ций дескриптора вершины класс boost:: sgbjdge имеет следующий конструк- тор: sgbjdge::sgbjdge(Arc* arc. Vertex* source). (Требуется для IncidenceGraph.) • graph jraits<Graph*>:: vertexj terator Тип итераторов, возвращаемых функцией vert ices О. Этот итератор должен моделировать RandomAccessIterator. (Требуется для VertexListGraph.) • graph_trait$<Graph*>::out_edge_iterator Тип итераторов, возвращаемых функцией out_edges(). Если EdgeL1st-vecS, этот итератор моделирует MuitiPassInputlterator. (Требуется для IncidenceGraph.) • graph Jraits<Graph*>::adjacencyjterator Тип итераторов, возвращаемых функцией adjacent_vert1ces(). Этот итератор моделирует ту же концепцию, что и out_edgej terator. (Требуется для AdjacencyGraph.) • graphjra its<Graph*>: :dl rectedjategory Предоставляет информацию о том, являй ся граф ориентированным или неори- ентированным. Так как SGB Graph* — ориентированный, этот тип — di reeled jag. (Требуется для Graph.)
14.3. Графовые адаптеры 263 • graph J;raits<Graph*>: :edge_paraHel_categ3ry Описывает возможности графа по вставке параллельных ребер (ребер с одина- ковыми начальными и конечными вершинами). Graph* из SGB не препятствует добавлению параллельных ребер, поэтому этот тип имеет al i ow_pa га "11 е I _edge_tag. (Требуется для Graph.) • graph_trait$<Graph*>: traversal_category Graph* из SG В обеспечи вает обход множества верш ин, исходящих ребер и смеж- ных вершин. Таким образом, тег категории обхода определен следующим об- разом: struct sgb_traversal_tag : public virtual vertexj ist_graph Jag. public virtual incidence_graph_tag. public virtual adjacency_graphjag { }. (Требуется для Graph.) • graph_t.raits<Graph*>- verticesjsizejype Тип используется для работы с количеством вершин в графе. (Требуется для VertexListGraph.) • graph_traits<Graph*>::edges_size_type Тип используется для работы с количеством ребер в графе. (Требуется для EdgeListGraph.) • graph_traits<Graph*>::degree_size_type Тип используется для работы с количеством исходящих ребер вершины. (Требуется для IncidenceGraph.) • property_map<Graph*. PropertyTag»::type propertyjnap<Graph*. PropertyTag»: :const_type Тип отображения для свойств вершин и ребер графа. Свойство задается шаб- лонным аргументом Property Тад и должно быть одним из тегов, описанных ниже в разделе «Свойства вершин и ребер». (Требуется для PropertyGraph.) Функции — методы Указатель на SGB Graph функций — методов класс не имеет. Функции — не методы Ниже приведены функции — не .методы указателя на SGB Graph. • std:: pa ir< vertexjterator. vertexjterator» verticestconst Grap“* g) Возвращает пару итераторов, обеспечивающих доступ к набору вершин графа д. (Требуется для VertexListGraph.) • std::palr<edge_iterator. edgejterator» edgestconsl Graph* g) Возвращает пару итераторов, обеспечивающих доступ к набору ребер д. (Требуется для EdgeListGraph.)
264 Глава 14 • Классы BGL • std- p3ir<acijacencyjterato'-. adjacencyjterror* ad.iacent_vertices(vertex_a&scr-!ptor v. const Graph* g) Возвращает пару итераторов. обеснечиваюших доступ к вершинам, смсжш с вершиной v в графе д. (Требуется для AdjacencyGraph.) • std- :paH'<ont_edge_ittrator. out_edge_lte-ator> out edges(vertex_descnptor v, const. Graph* g) Возвращает пару итераторов, обеспечивающих доступ к исходящим ребр вершины » в Г|«фе д. Если граф неориентированный, эти значения дают; ступ ко всем ребрам, инцидентным данной вершине v. (Требуется для IncidenceGraph.) • vertex_aescriptor soorce'eoge_aescnptor е, const Graph* g) Возвращает начальную вершину ребра е. (Требуется для IncidenceGraph.) • vertex_descnptor target(edgejfescnptor e. const Graph* g) Возвращает конечную вершину ребра . (Требуется для IncidenceGraph.) • degree_size_type out_aegree(vertex_descriэЮг u. const Graph* g) Возвращает число ребер, исходящих из вершины и. (Требуется для IncidenceGraph.) • vertlces_s ize_type num_verttees(const Graoh* g) Возвращает число вершин в 1рафе g. (Требуется для VertexListGraph.) • edqes_stze_type num_edges(const Graph* g) Возвращает число ребер в гра<|к: у. (Требуется для EdgeListGraph.) • vcrtex_descriptor vertex*;vert ices_$ize_type n. const Graph* g) Возвращает п-ю вершину в списке вершин графа. • template «typename PropertyTag» property_map<Graph*, Prcper:yTau--- get(PropertyTag. Graph* g) template «typename PropertyTag'- property_map<Graph*. PropertyTag»:-const-type get(PropertyTag. const Graph* g) Возвращает объект-отображение свойств, заданный PropertyTag. (Требуется для PropertyGraph.) • template «typename PropertyTag. typename X> typename proper,y_trait$< typename praperty_map<Graph*. Prcpertylag>::con$t_type
14.3. Графовые адаптеры 265 >. ivaluejype getlProperiyTag. const Graph* g. X /) Возвращает значение свойства для х, где х — дескриптор вершины или ребра. (Требуется для PropertyGraph,) • template «typename PropertyTag. typename X, typename Value> void putfPropertylag, const G'-cpf;’ a. X const Values value) Устанавливает значение свойства для х u value. Здесь ,< — дескриптор верши- ны или ребра. Значение должно быт ь преобразуемо в тип значения свойства, соответствующий Property! ад. (Требуется для PropertyGraph.) Свойства вершин и ребер Структуры Vertex и Аге из SGB предоставляют вспомогательные поля для хране- ния дополнительной информации. Мы предлагаем BGL-оболочки, которые обес- печивают доступ к этим полям через отображения свойств. Кроме того, предос- тавлены отображения индекса вершины идлины ребра. Объект-отображение свойств может быть получен из SG В Graph* применением функции get (), описанной в пре- дыдущем разделе, а тип отображения свойства — через класс свойств property Указанные ниже теги свойств могут быть использованы для задания вспомо- гательного поля, для которого требуется отображение свойства (листинг 14.19). Листинг 14.19. Теги свойств для SGB Graph» /! Теги свойств вершин-. template «typename i> u_property: «plate «typename I> v_property: template «typename T> wj>roperty: template «typename T> x_prooerty. template «typename T> у property; template «typename T> z2Prpperty: // Теги свойств ребер: ernpl<ne «typename T> a_property. template «typename T> b2.PrMerG • Шаблонный параметр T для этих тегон ограничен типами в объединении util, декларированном в заголовочном файле gb_graph.h библиотеки SGB. Перечис- лим эта типы: Vertex*, Arc*, Graph*, char* и long. Отображения свойств для вспомо- гательных полей являются моделями LvaluePropertyMap. Отображение свойств для индексов вершин может быть получено с помощью тега verlex_inaex_l и это отображение свойств моделирует ReadablePropertyMap. Отображение свойств для длин ребер указывается тегом eage_l er.gth_t, и это ото- бражение свойств — модель LvaluePropertyMap. чей тип значения - long. 14.3.5. GRAPH<V,E> из библиотеки LEDA GRAPKV.E* Шаблон класса GRAPH из LEDA можст быть напрямую использован как BGL- граф благодаря перегруженным функциям, определенным в заголовочном файле boost/graph/leda_graph.hpp.
266 Глава 14 • Классы BGL Реализация BGL-интерфейса для класса GRAPH из LEDA обсуждалась в разделе 10.3 при написании адаптеров для графовых классов из сторонних графовых библиотек. Пример В листинге 14.20 приведен пример работы с LEDA графом, как если бы это был BGL-граф. Листинг 14.20. Работа с графом LEDA ncl ude <boost/graph/ 1 edajraph. hpp> ♦include <iostre<Mn> ♦undef string н накрос из LEDA Int ma1n() ( using namespace boost: typedef GRAPH < std :string, int >graph_t: graph J; g; g. new_node Г PM 1 octetes"): g.new_node("Heracles”): g.newjiodel“Al cmena'); g.new_node("Eurystheus“): g. newjiode (" AnpM t rycn ’): typedef propertyjnap < graphj. vertex_all_t >.:type NodeHap: NodeMap node_name_rMp - getfvertexjll. g): graph j.raits~< graphj >:-.vertexj terator vi. vi_end: for (tielvi, vi_end) - vertices(g): vi !- vi_end: ++vi) std::cout « node_na« raap[*vij « std::endl; return EXITJUCCESS: Эта программа выводит следующее: PM 1 octetes Heracles Alcmena Eurystheus Amphitryon Параметры шаблона Ниже приведены параметры шаблона класса GRAPH. V Тип объекта, прикрепленного к каждой вер- шине в графе LEDA. F Тип объекта, прикрепленного к каждому ре- бру в графе LEDA. Модель для VertexListGraph, BidirectionalGraph и AdjacencyGraph. Также VertexMutablePropertyGraph и EdgeMutablePropertyGraph для тегов свойств vertex al I t и edge al I t, которые обес- печивают доступ к объектам V и Е в графе LEDA. Тип GRAPH также является Proper- tyGraph для vertex_1ndex_t wedge lndex t, который предоставляет доступ к иденти- фикационным (ID) номерам, которые LEDA присваивает каждому узлу (вершине).
14.3. Графовые адаптеры 267 Где определен Класс GRAPH находится в файле boost/graph/Leda_graph.hpp. Ассоциированные типы Ниже приведены ассоциированные типы класса GRAPH. • graph_tra1ts<GRAPH>::vertex_descriptor Тип дескрипторов вершин, ассоциированный с GRAPH. Используется тип node из LEDA. (Требуется для Graph.) • graph_tra1ts<GRAPH>:.-edge-descriptor Тил дескрипторов ребер, ассоциированный с GRAPH. Используется тип edge из LEDA. (Требуется для Graph.) • graph_traits<GRAPH>::vertex_iterator Тип итераторов, возвращаемых функцией vertices!). (Требуется для VertexListGraph.) • graph_tralts<GRAPH>:;out_edge_iterator Тип итераторов, возвращаемых функцией out_edges(). (Требуется для IncidenceGraph.) • graph_traits<GRAPH>: in_edge_iterator Тип итераторов, возвращаемых функцией in_edges(). (Требуется для BidirectionalGraph.) • graphicalts<GRAPH>: :adjacency_iterator Тип итераторов, возвращаемых функцией adjacent_vertices(). (Требуется для AdjacencyGraph.) • graph_traits<GRAPH>:.-di rected_category Тип GRAPH из LEDA — для ориентированных графов, поэтому здесь использу- ется di rected_tag. (Требуется для Graph.) • graph_tralts<GRAPH>::edge_paraliel_category Тип GRAPH из LEDA позволяет добавление параллельных ребер, поэтому здесь используется aliow_paral1el_edge_tag. (Требуется для Graph.) • graph_traits<GRAPH>::traver$al_category Описываемый тип графа представляет итераторы вершин, исходящих и вхо- дящих ребер, итераторы смежности. Тип тега категории обхода следующий: struct leda_graph_traversal_category public virtual bldirectlonal_grapn_tag.
268 Глава 14 • Классы BGL public virtual aajacency_grapt*_’.ag. puolic virtual vertex, 1ist grapf' tag ( }, (Требуется для Graph.) • graph_traits<GRAPH>: :vertices_size_type Тип используется для представления числа вершин графа, а это int. (Требуется для VertexListGraph.) • graph_traits<GRAPH>::edges_$ize_type Тин используется для представления числа ребер графа, а это тоже mt. (Требуется для EdgeListGraph.) • graph_traits<GRAPH>:;degree_size_tyoe Тип используется для представления числа исходящих ребер графа — ото int (Требуется для IncidenceGraph.) • propcrty_map<GRAPH. PropertyTag*::type property_map<GRAPH, PropertyTag*: ;c.Gnst_typc Тип отображения для свойств вершин и ребер в графе. Конкретное свойство задастся шаблонным аргументом PropertyTag и должно быть одним из следую- щих: vertex_index_t, edge_inflex_t, vertex_al l_t или edge_a I :_t. 1еги c «alb ис- пользуются для доступа к объектам V и Е графа LEDA, Теги vertexjndexj. и edge_mdex_t обеспечивают доступ к идентификационным номерам, которые LEDA присваивает каждому узлу и ребру. (Требуется для PropertyGraph.) Функции — методы У класса GRAPH ист дополнительных функций — методов (так как это потребовало бы модификации исходного кода LEDA) Функции — не методы Ниже приведены функции — не методы класса GRAPH. • std; :pair<vertex_iterator, vertexj terator* verticestconst GRAPHS g} Возвращает пару итераторов, обеспечивающих доступ к множеству вершин графа д. (Требуется для VertexListGraph.) • std::palr<edge_iterator, edgejterator* eagestconst adjacency_matrix& g) Возвращает пару итераторов, обеспечивающих доступ к набору ребер графа g (Требуется для EdgeListGraph.) • std::pair<adjacency_iterator. adjacency_nerator> adjacent_vertices(vertex_descriptor v. const adjacency_matrix& g) Возвращает пару итераторов, обеснечиваюших доступ к множеству вершин, смежных с данной вершиной v графа д. (Требуется для AdjacencyGraph.)
14.3. Графовые адаптеры 269 • std::pair<cut_edge_iterator. cut_edge_iterator* ojt_edges(vertex_descriptcr v. const GRAPHS g) Возвращает пару итераторов, обеспечивающих доступ к исходящим ребрам вершины v в графе д. Если граф неориентированный, эти значения дают до- ступ ко всем ребрам, инцидентным вершине v. (Требуется ддя IncidenceGraph.) • std-pair<in_edge_it?rator. ir,_edge_iterator* in_edges(vc-rtex_descriptor v. con$t~GRAPHS g) Возвращает пару итераторов, обеспечивающих доступ к входящим ребрам вер- шины v в графе д. Операция недоступна, если в качестве шаблонного парамет- ра D recced было указано th rectedS. и доступна при una: rectedS и Ы :h rect' опа 1S. (Т[жбуется для BidirectionalGraph.) • vertex_descriptor source(edge_descr-ptor е. const GRAPHS g) Возвращает начальную вершину ребра е. (Требуется для IncidenceGraph.) • vertex_descriptor targeUedgejiescnptor е, const GRAPHS g) Возвращает конечную вершину ребра е. (Требуется для IncidenceGraph.) • degree_s i ze_type out_cegree(vertc-x_d-:-scnptcr u, const GRAPHS g) Возвращает число ребер, исходящих из вершины и. (Требуется для IncidenceGraph.) • degree_size_type in_aegreeivertex_aesmptGr- о. const GRAPHS g) Возвращает число ребер, входящих в вершину и. Эта операция доступна. толь- ко если bi di recti one IS было указано в качестве шаблонного параметра Di rect ecl. (Требуется для BidirectionalGraph.) • vertices_s4ze_type num_ver* ices (const GRAPHS- g) Возвращает число вершин в гра«|>е g. (Требуется дли VertexListGraph.) • edges_size_type rium_edges(const GRAPHS g) Возвращает число ребер в графе g. (Требуется для EdgeListGraph.) • std:-pair<edge_descriptoi-. nool* add edge(vertex descriptor u, vertex descriptor v. GRAPHS g) Добавляет ребро (u, v) к графу и возвращает дескриптор ребра для вновь добав- ленного ребра. Для этого графового типа логический флаг всегда будет ложным. (Требуется для EdgeMutableGraph.)
270 Глава 14 • Классы BGL • std::palr<edge_descriptor. bool> add_edge(vertex_descriptor u. vertex_descriptcr v, const ES ep, GRAPHS g) Добавляет ребро (и, v) к графу и закрепляет за ним ер в качестве значения для хранения внутреннего свойства. (Требуется для EdgeMutablePropertyGraph.) • void remove_edge(vertex_de$criptor u, vertex_cescriptor v. GRAPHS g) Удаляет ребро (a, v) из графа. (Требуется для EdgeMutableGraph.) • void remove_edge(edge_descriptor е. GRAPHS g) Удаляет ребро (и, v) из графа. Функция отличается от remove_edge(u. v, g) в слу- чае мультиграфа: данная функция удаляет одно ребро, тогда как функция ге move_edge(u. v. g) удаляет все ребра (и, г). (Требуется для EdgeMutableGraph.) • vertex_descriptor add_vertex(GRAPHS g) Добавляет вершину к графу и возвращает дескриптор вершины для новой вершины. (Требуется для VertexMutableGraph.) • vertex_descriptor add_yercex(const VertexFropertiesS p. GRAPHS g) Добавляет вершину (и ее свойство) к графу и возвращает дескриптор верши- ны для новой вершины. (Требуется для VertexMutablePropertyGraph.) • void c1ear_vertex(vertex_cescriptor u. GRAPHS g) Удаляет все ребра, исходящие и входящие, для данной вершины. Вершина ос- тается во множестве вершин графа. (Требуется для EdgeMutableGraph.) • void remove_vertex(vertex_descriptor u. GRAPHS g) Удаляет вершину u из множества вершин графа. (Требуется для VertexMutableGraph.) • template <typename PropertyTag* property_map<GRAPH. PropertyTag*::type get(PropertyTag. GRAPHS g) Возвращает изменяемый объект-отображение свойств для свойства вершины, указанной с помощью PropertyTag. (Требуется для PropertyGraph.) • template <typename PropertyTag* property_map<GRAPH. PropertyTag*::const_tyoe get(PropertyTag. const GRAPHS g} Возвращает константный объект-отображение свойств для свойства вершины, указанной с помощью PropertyTag. (Требуется для PropertyGraph.)
14.3. Графовые адаптеры 271 • template «typename PropertyTag. typename X* typename property_tralts< typename propertyjna:)<GRAPH. PropertyTag*::const_type >::va1ue_type get(PropertyTag. const GRAPHS g. X x) Возвращает значение свойства для х, где х — дескриптор вершины или ребра. (Требуется для PropertyGraph.) • template «typename PropertyTag. typename X. typename Value* void put(PropertyTag. const GRAPHS g. X x. const Value& value) Устанавливает значение свойства для х в value. Здесь х — дескриптор верши- ны или ребра. (Требуется для PropertyGraph.) 14.3.6. std::vector<EdgeList> std::vector<EdgeList> Перегрузка функций в boost/graph/vector_as_graph.hpp делает возможным трак- товать тип вроде std: :vector«std: :list<int» как граф. Пример В этом примере (листинг 14.21) мы создаем граф, используя контейнерные клас- сы из стандартной библиотеки и применяя функцию pri nt_graph() из BGL (кото- рая написана в терминах графового интерфейса BGL) для вывода графа. Листинг 14.21. Вектор в качестве графа ( vector-as-greph.cpp ) » # include «vector* # include <11 st* # include <boo5t/graph/vector_as_graph.hpp* ^include <boost./graph/grapti_utiHty.hpp> int mainO { enum { r. s. t, u, v. *. «. y. N ]. char name[] - "rstuvwxy'; typedef std::vector < std;:list « int > > Graph; Graph g(N): g[r].push_back(v); g[s] .pushjMckfr). g[s] .pushjyack(r); g[s].push_back(w); git].push_back(x): g[u].pushjjack(t): g[w].push_back(t): g[w].push_back(x); g[x].push_back(y): g[yLpush_back(u); boost-:prTnt_graph(g. name): return 0; } Эта программа выводит следующее: Г • > V S -> - г W t X
272 Глава 14 • Классы BGL л t X х > у / U Где определен Тип vector_as_graph находится в файле boost/graph/vector_as_graph.hpp. Параметры шаблона Ниже приведен параметр шаблона типа vector_a^_graph. Edgel i st Container, в котором val ue_rype позволяет л образование к si ze_typ₽ для -,W:: vector (ч бы значения можно было использовать н чсстве дескриптора вершины). Модель для VertexListGraph, IncidenceGraph и AdjacencyGraph. Ассоциированные типы Ниже приведены ассоциированные типы vector_8S_graph. • graph_trails<std: :vector>; ;v<?rtexjJescripr.<.-r Тип дескрипторов верши», ассоциированных с гр:н}юм. (Требуется для Graph.) • granh_traits<std::vector>::edge jlescriptor Тип дескрипторов ребер, ассоциированных с графом. (Требуется для Graph.) ♦ graph_traits<std: -.vector*: :vertex_it?raror Тип итераторов, возвращаемых функцией vert;ces(). (Требуется для VertexListGraph.) • grapn_trait$<std::vector*::out_edge_iteratur Тип итераторов, возвращаемых функцией out jdgesf (Требуется для IncidenceGraph.) • graph_trans<std: .-vector*.;adjacencyj teraror Тип итераторов, возвращаемых функцией acij.!cent_vert scc*s(). (Требуется для AdjacencyGraph.) • graph_traits<std:: vector*: :di rectea jtategor-y Графовый тип для ориентированных графов, поэтому здесь используется rected_tag. (Требуется для Graph.) • graph_trairs<std: :vector>: .-erigt-_psral iei_cat?gnry Этот графовый тип позволяет иметь параллельные ребра, поэтому тип кап рии - alk'w_par311e1_edge_tag. (Требуется для Graph.)
14.3. Графовые адаптеры 273 • graph_tra1ts<std::vector>;:vertices_size_type Тип, используемый для представления количества вершин в графе. (Требуется для VertexListGraph.) • graphJга1ts<std::vector*::degree_s ize_type Тип, используемый для представления количества исходящих ребер для вер- шины графа. (Требуется для IncidenceGraph.) Функции — методы Her дополнительных функций — методов класса, Функции — не методы Ниже приведены функции - не методы класса vector_as_graph, • std:;palr<vertex_iterator. vertexjterator* vertices(const stld::vector& g) Возвращает пару итераторов, обеспечивающих доступ к множеству вершин графа д. (Требуется для VertexListGraph.) • std; :pair<adjacencyjtcrator. adjacencyjterator>“adjacent_vertices(vertex_descriptor v. const std:;vectors g) Возвращает пару итераторов, обеспечивающих дехтуп к вершинам, смежным вершине v в графе д. (Требуется для AdjacencyGraph.) • std: :pair<out_edgejterator, out_edgej terator* out_edges(vertex_descripior v. const std'sectors g) Возвращает пару итераторов, обеспечивающих доступ к исходящим ребрам вершины v в графе д. Если граф неориентированный, эти значения дают до- ступ ко всем ребрам, икцидентпым вершине v. (Требуется для IncidenceGraph.) • vertex_descriptor sourceCedgeJescnptor е. const std::vectors g) Возвращает начальную вершину для ребра е. (Требуется для IncidenceGraph.) • vertex_descriptor target(edge_descriptor е. const std;:vector& g) Возвращает конечную вершину для ребра е. (Требуется для IncidenceGraph.) • degree_size_type out_degree(vertex_descriptor u. const std”:vectors g) Возвращает число ребер, исходящих из вершины и. (Требуется для IncidenceGraph.) • vertices_size_type num_vertices(const std::vectors g) Возвращает число вершин в графе g. (Требуется для VertexListGraph.) 10 За 375
Библиотека отображений свойств Большинство алгоритмов на графах требуют доступа к различным свойствам, относящимся к вершинам и ребрам графа. Например, такие данные, как длина или мощность |>ебра, могут быть необходимы для алгоритмов, равно как и вспомо- гательные флаги, такие как цвет, для индикации посещения вершины. Существует много способов хранить эти данные в памяти: от полей данных объектов-вершин и объектов-ребер до массивов, индексированных некоторым индексом, или свойств, вычисляемых при необходимости. Для отделения обобщенных алгоритмов от де- талей представления свойств вводится абстракция, называемая отображением свойств'. Несколько категорий свойств доступа предоставляют различные возможности: readable (только чтение). Свойство может быть только прочитано. Данные воз- вращаются по значению. Многие отображения свойств для входных данных задачи (таких, как веса ребер) могут определяться как отображения свойств только для чтения. writable (только запись). Свойство может быть только записано. Массив родите- лей для записи путей в дереве поиска в ширину является примером отобра- жения свойст ва, которое может быть определено как только для записи. read/write (чтение и запись). Свойство может быть как записано, так и прочита- но. Свойство расстояния для алгоритма кратчайших путей Дейкстры требует как чтения, так и записи. lvalue (1-значение). Свойство фактически расположено в памяти и есть возмож- ность получить на него ссылку. Отображения свойств этой категории также поддерживают как чтение, так и запись. Для каждой категории отображений свойств определена теговая структура. ’ В предыдущих статьях, описывающих BGL, концепция средства доступа была названа [к-согьсог(«укра- шатель»), В магистерской диссертации Дитмара Кюля [24] средства доступа ксвойствам названы <'1>едствами доступа к данным (data accessors).
15,1. Концепции отображений свойств 275 namespace boost { struct readable_propertyjnap_tag { ;: struct writable_property_map_tag { j: struct read_wrlte_property_map_tag : public readat>le_property_map_tag. public writable_property_map_tag { j. struct lvalue_property_map_tag : public read_wnite property вар tag { } Подобно классу 1terator_traits из STU есть класс. property_tr3 its (листинг 15.1). который может быть использован лля выведения ассоциированных типов ото- бражения свойств: типа ключа л типа значения, а также категории отображения свойств. Имеется специализация property_tга i ts, чтобы указатели могли быть ис- пользованы как объекты отображений свойств. Листинг 15.1. Класс property_traits namespace boost ( template «typename PropertyMap> struct property_traits { typedef typename Props rtyMap:-key jiype key_type: typedef typename PropertyMap::value_type value_type: typedef typename Property-Map: reference reference: typedef typename PropertyMap::category category: !: // специализация для использования указателей «к отображений свойств template «typename Т> struct property_traits<T*> { typedef 1 value_type: typedef T& reference, typedef std: :ptrdiff_t key ..type: typedef lva!ue_property map tag category. }: template <typename T> struct property,traits<const T‘> { typedef T value_type.- typedef const T& reference: typedef std::ptrdiff_t key_type. typedef lvalue_property_map_tag category; } 15.1. Концепции отображений свойств Интерфейс отображений свойств состоит из набора концепций, которые определяют общий .механизм для отображения объектов-ключей на соответствующие объекты- значения, скрывая, таким образом, детали реализации отображения от алгоритмов, использующих отображения свойств. Для обеспечения большей гибкости, требо- вания к отображениям свойств по типу’ объектов-ключей и значений специально не определены. Поскольку операции отображений свойств — глобальные функции, можно перегрузить функции отображения так, что будут использоваться почти произвольные типы отображений свойств и типов ключей. Интерфейс отображе- ний свойств состоит из трех функций: get(), put() и operator^. В листинге 15.2
276 Глава 15 • Библиотека отображений свойств показано, как эти три функции могут' быть использованы для доступа к адресам ассоциированным с разными людьми. Листинг 15.2. Пример использования интерфейса отображения свойства template 'typename AddrcssHap> void footAddressMap address) { typedef typename boost;property_traits<AddressMrp>:: value_type value_type; typedef typename boost;;property_traits<AcdressMap>.:key_type key_type; value_type old_address. new_address; key_type fred - “Fred"; oldjiddress • getCaddress. fred): new_address - "384 Fitzpatrick Street* puttaddress. fred. new_address): key_type joe - "Joe": value_type8 joes_address - addresstjoe]; joes address - “325 Cushing Avenue"; } Для каждого объекта-отображения свойств имеется набор допустимых клю- чей, для которых отображение на объекты-значения определено. Вызов функции отображения свойств для недопустимого ключа приводит к неопределенному по ведению. Концепции отображений свойств не определяют, как это множество до- пустимых ключей создается или модифицируется. Функция, которая использует отображение свойств, должна указывать ожидаемый набор допустимых ключей в своих предусловиях. Обозначения В следующих разделах использованы такие обозначения: • РМар — тип отображения свойств; • ртар — объект отображения свойств типа РМар; • key — объект типа property_traits<PMap>:: key_type; • val — объект типа property_traits<PMap>; :vaiue_type. 15.1.1 . ReadablePropertyMap Концепция ReadablePropertyMap предоставляет доступ почтению к объекту-значе- нию, ассоциированному с данным ключом, через вызов функции get (). Функция get() возвращает копию объекта-значения. Уточнение для CopyConstructible Ассоциированные типы Ниже приведены ассоциированные типы концепции ReadablePropertyMap. • property_traits<PMap>: :value_.type Тип свойства.
15.1. Концепции отображений свойств 277 • property_traits<PHap>: reference Тип, преобразуемый к типу значения. • propertyj:raH$<PMap>:: key_type Tun объекта-ключа, используемого для поиска значения свойства. Отображе- ние свойств может быть параметризовано по типу ключа, в некотором случае этот тип может быть void. • property_trait$<PMap>: category Категория свойства: тип, преобразуемый к readat>le_property_map_tag. Допустимые выражения Концепция ReadablePropertyMap имеет одно допустимое выражение. • get(pnap. key) Возвращает тип: reference. Семантика: поиск свойства объекта, ассоциированного с ключом key. 15.1.2 . WritablePropertyMap Концепция WritablePropertyMap имеет возможность присваивать объект-значение, ассоциированный с данным объектом-ключом, посредством функции put (). Уточнение для CopyConstructible Ассоциированные типы Ниже приведены ассоциированные типы концепции WritablePropertyMap. • property _traits<PA>::value_type Тип свойства. • property_traits<PA>::key_type Тип объекта-ключа, используемого для поиска значения свойства. Отображе- ние свойств может быть параметризовано по типу ключа, в некотором случае этот тип может быть vol d. • property_traits<PA>:-.category Категория свойства: тип, преобразуемый Kwritat>1e_propertyjnap_tag. Допустимые выражения Концепция WritablePropertyMap имеет одно допустимое выражение. • putlpmap. key. val) Возвращает тип: void. Семантика: присваивает va I свойству, ассоциированному с ключом key. 15.1.3 . ReadWritePropertyMap Концепция ReadWritePropertyMap уточняет концепции ReadablePropertyMap и WritablePropertyMap. ReadWritePropertyMap также добавляет требование, чтобы
278 Глава 15 • Библиотека отображений свойств property_traits<PA>; category имела тип, преобразуемый к read_write_pro- pertyjnap. 15.1.4 . LvaluePropertyMap Концепция LvaluePropertyMap обеспечивает доступ к ссылке на объект-свойство (вместо копии объекта, как в get О). LvaluePropertyMap может быть изменяемой и не- изменяемой. Изменяемая L val uePropcrtyMap возвращает ссылку, тогда как неизме- няемая — ссылку на константу. Уточнение для ReadablePropertyMap для неизменяемой, ReadWritePropertyMap для изменяемой. Ассоциированные типы Ниже приведены ассоциированные типы концепции LvaluePropertyMap. • Droperty_trait$<PMap>: reference Ссылочный тип, который должен быть ссылкой или константной ссылкой на value_t.ype отображения свойств. • property_traitS'-PMap>; xategory Категория свойства: тин, преобразуемый к lvalue_property_map_tag. Допустимые выражения Концепция LvaluePropertyMap имеет одно допустимое выражение. • omaplkey] Возвращает тип: reference. Семантика: получает ссылку на свойство, идентифицированное ключом. 15.2. Классы отображений свойств 15.2.1. property_traits property_tr.jits<ProperryMap> Класс property traits предоставляет механизм для доступа к ассоциированным типам отображения свойств. Неспециализированная (использует значения по умол- чанию) версия класса property traits предполагает, что отображение свойства предоставляет определения для всех ассоциированных типов. namespace boost { template -Чурепдае ЭА> struct prooerty_*raits { typecef typename ЭА: Aeyjtype key_type; typedef typename ЭА. .«lue_type va1ue_type: typedef typename ЭА::reference reference: typedef typename 3A--.category category: } a namespace boost Задание типа category должно быть определением для одного из следующих типов или типа, который наследует из одного из следующих типов:
15.2. Классы отображений свойств 279 namespace boost ( struct readable_propertyjn<ip tag { }. struct table jjropertyjMp_tag { j; struct read_wnte_property_mip_tag : readab1e_prcpertyjnap_tag. wrttable_property_map_tag { }: struct lvalue_property_map_tag : read_write_prcperty_map_tag { }: ) H namespace boost Часто удобно использовать указатель как объект-отображение свойств, тогда key_type является целочисленным смещением. Такая специализация для ргорег- ly traits и перегрузка функций отображений приведены в листинге 15.3. Листинг 15.3. Специализация для property_tralts и перегрузка функций namespace boost { template «typename T> struct property_traits<T*> ( typedef std:;ptrdiff_t key_type: typedef I value_type; typedef value_type& reference: typedef lvalue_property map_ta<? category: }.' template «typename T> void put(T* pa. std::ptr<iiff_t k. const Г8 val) ( palk] = val; } template «typename T> const T8 gertconst T* pa. std; :ptrdiff_t k) { return pa[k]; } template «typename T> T& at(T* pa. std-,-ptrdiff_t k) { return oa(k]; } } И namespace boost Параметры шаблона Класс property_traits имеет один параметр шаблона. • PrcpertyMap Тип отображения свойств. Где определен Класс property_traits находится в файле boost/property_map.hpp. Методы Ниже приведены методы класса property_tra1ts. • property_trai ts::key_type Тип объекта-ключа, используемого для поиска свойства, • property_tran$: :value_type Тип свойства. • propertyjzaits: reference Ссылка на тип значения. • property_traits; category Тег категории отображения свойств.
280 Глава 15 • Библиотека отображений свойств 15.2.2. iterator_property_map iterator_propertyjnap<Iterator. IndexMap. Т, R> Адаптер lterator_property_rnap создает оболочку для типа, моделирующего Ran - domAccessIterator для создания LvaluePropertyMap. Этот адаптер часто полезен для создания отображения свойств из массива, где ключом являются целые числа- смещения от начала массива, а массив содержит объекты-значения. Когда тин ключа — целый, можно просто использовать 1dentity_property_map для парамет- ра шаблона IndexMap. В противном случае придется предоставить отображение свойств, которое преобразует ключ в целое. Например, граф может иметь внут- реннее свойство для vertexj. ndex t, которое может быть получено применением класса property_тар. Пример Пример в листинге 15.4 демонстрирует создание отображения свойств из массива. Листинг 15.4. Пример создания отображения свойств ^include <iostream> # 1 nc 1 ude <t>oost /propertyjwp. hpp> int mainO { using namespace boost; double x[J - ( 0.2. 4.5. 3.2 ): iterator_property_map < double *. 1dentity_property_map. double, doubles > pmapO.); std::cout « "x[l] - ~ « getlpmap. 1' « std;:endl: puttpmap. 0. 1.7); std::cout « "x[0] - " « [яир[0] « std::endl: return 0; } Вывод будет следующий: x[l] - 4.5 xfO] - 1.7 Где определен Адаптер iterator_property_map находится в файле boost/graph/property_map.hpp. Параметры шаблона Ниже приведены параметры шаблона адаптера iterator_property_map. Iterator Адаптируемый тип итератора. Должен моделировать RandomAccessIterator IndexMap Отображение свойств, которое преобразует тип ключа в целое смещение. Должно быть моделью ReadablePropertyMap т Тип значения итератора. По умолчанию:1урепате std; :iteratcr_tra-its<Iterator>: :value_type
15.2. Классы отображений свойств 281 R Тип ссылки итератора. По умолчанию: typerame std::iterator_tra1ts<Iterator>::reference Модель для LvaluePropertyMap Ассоциированные типы Все типы, требуемые для LvaluePropertyMap. Функции — методы Ниже приведены функции — методы «адаптера iterator_property_map. • iterator_3roperty_map(Iterator iter “ IteratorO. IndexMap indexjnap = IndexMap!I) Конструктор. • template «typename Key* • reference operator! ](Key Ю const: Возвращает*(iter т get(index_map, k),'. Функции — не методы Ниже приведены функции — не .методы адаптера iterator_property_map. • template «typename Iterator, typename IndexMap* • iterator_property_map«Iterato''. IndexMap. • typename std::iterator_traits<lterator>::va1ue_type. • typename std::iterator_traits<Iterator>:reference* • make_iterator_property_map(Iterator iter, • IndexMap indexjnap) Создать отображение свойств итератора. 15.2.3. Теги свойств В листинге 15.5 приведены теги для некоторых свойств BGL. Листинг 15.5. Теги свойств namespace boost { enum vertexjndex_t { vertexjndex « 1}: enum edge_index_t { edge_mdex - 2): enum edge_name_t { edge_name - 3 }; enum edge weight{ edge weight - a }; enum vertex_name_t { vertex_name • 5 }: enum graph_name_t { graphjnan® - 6 }: enum vertexjiistance_t { vertex_distance - 7}; enum vertex_color_t ( vertex_coTor - 3 }; enum vertex2degree_t { vertexjfegree = 9 }; enum vertex_in_degree_t { vertex_in_aegree « 10 \\ "степень вершины по входящим ребрах \\ индекс вершины \\ индекс ребра \\ ння ребра \\ вес ребра \\ пня вершины \\ иня графа \\ расстояние вершины \\ цвет вершины \\ степень вершины }; продолжение
282 Глава 15 • Библиотека отображений свойств Листинг 15.5 (продолжение} enum vertex_out_degree_t < vertex_out_degree • II }; \\ степень вершины по исходник ребргн enum veriex_d1scover_time_t ( ve«'tex_dtscover__ti№j - 12 }; \\ время посещения вершины enum vert.ex_f'inish_titne_t { vertex_finish_tirne - -3 j*. \\ время окончания обрайотки вершины } namespace boost { w установить свойство BOOST J NST ALL PROPERTY (vertex, index). BOOSTJ «STALL J3 WERTY (edge. 1 ndex i; BOOSTJNSTALL PROPERTY(edge. name); 15.3. Создание пользовательских отображений свойств Главным назначением интерфейса отображений свойств является повышение гибкости обобщенных алгоритмов. Это позволяет хранить свойства многими воз- можными способами, н то же время предоставляя алгоритмам общий интерфейс. Следующий раздел содержит пример использования отображений свойств для адаптации к сторонней графовой библиотеке Stanford Graph Base (SGB) (см. раз- дел 14.3.4). После этого мы рассмотрим реализацию отображения свойств с ис- пользованием std:;тар. 15.3.1. Отображения свойств для Stanford GraphBase Адаптер BGI. для Stanford GraphBase включает в себя отображение свойств для доступа к различным полям структур Vertex и Аге из SGB. В этом разделе мы опи- сываем одну из сторон реализации адаптера SGB в качестве примера реализации отображений свойств. SGB использует следующую структуру Vertex для хранения информации о вер- шинах в графе. Указатель arcs — связный список исходящих ребер вершины. Поле name и второстепенные поля от и до z — свойства вершины (uti 1 — это объедине- ние C++ (union), позволяющее хранить различные сущности в вершине). Этот раздел описывает, как создать отображение свойств для доступа к полю тате. typedef struct vertex__struct { struct arc_struct* arcs, cnar* name: utiI u. v w. x, y. z: } Vertex: Основная идея реализации этого отображения свойств — определение функ- ций operatorfJO, get С) и put() в терминах доступа к методам структуры данных. Эту работу проще сделать с помощью класса put_get_hel per, в котором реализова- ны put () и get() посредством operators’]. Следовательно, остается реализовать толь- ко операцию operatorf ]. Кроме того, ассоциированные типы, требуемые отобра- жением свойств, также должны быть определены.
15.3. Создание пользовательских отображений свойств 283 В листинге 15.6 приведена реализация sgb_vertex_name_map. Мы используем класс put_get_helper (онопределенвЬоо5^ргореАу_тар.Ьрр) для упрощения создания этого отображения свойств. Мы реализуем operator[] (), a put_get_helper реализует put С} и get (). Первый аргумент-тип в шаблоне класса put_get_hel per — тип возвращаемо- го значения для operator[], который в нашем случае есть char*. Второй аргумент — тип самого отображения свойств. Тип reference должен действительно быть ссыл- кой, только если отображение свойств должно быть Lva 1 uePrcpertyHap. В нашем слу- чае мы создаем ReadablePropertyMap. Адаптер SGB использует Vertex* для дескрип- тора вершины vertexjjescriptor графа, так что это key_type отображения свойств. Листинг 15.6. Реализация sgb_yertex_name_map class sgb_vertex_narne_map : public put_get„heTper< char*. sgb_vert.ex_name_raap > { public: typedef boost::readable_property_map_tag category. typedef char* value_type: typedef char* reference typedef Vertex* key„type. reference operatori ](Vertex* v) const { return v->name; i 15.32. Отображение свойств из std::map В предыдущем примере объект-отображение свойств не нуждался в сохранении какого-либо состояния, так как объект-значение мог быть получен непосредственно по ключу. Так бывает не всегда. Обычно ключ используется для поиска объекта-зна- чения в некоторой вспомогательной структуре данных. Очевидным кандидатом на такую структуру является std: ;шар. Отображение свойств, которое использует stdтар в качестве реализации, нуждается в хранении указателя на этот ассоциа- тивный контейнер. Мы сделали тип контейнера параметром шаблона, так что отображение свойств может быть использовано с другими контейнерами, таки- ми как hashjnap. Концепция, описывающая такой вид контейнера, называется UniquePairAssociativeCantainer (листинг 15.7). Листинг 15.7. Реализация associative_property_rnap template «typename UniquePairAssociativeContainer* class associative_property_map : public put_get_helper< typename UniquePairAssociativeContainer: ;va1ue_type::secondlype&. assoc1ative_property_map<UniquePairAssociativeContainer.> > typedef UniquePairAssociativeContainer C; public: typedef typename C::key_type key„type; typedef typename C::value_type::second_type va1ue_type: typedef value_type& reference; typedef lvalue_property_map„tag category. associativej>roperty_map() 7 m_c(0> { } associative,property_map(C& cl": m_c(4c) ( ) reference operatori Jfconst key_type& k) const { return (*m_c)[k]: pri vate: C* m c: }:
Вспомогательн концепции, класс и функции 16.1. Buffer Концепция Buffer (буфер) — это нечто, во что можно поместить некоторые эле- менты, а затем удалить. Концепция Buffer имеет очень мало требований. Она не требует какого-то определенного порядка хранения элементов или порядка, в котором они удаляются. Однако обычно имеется некоторое правило упорядо- чения. Обозначения В данной главе будут использованы следующие обозначения: В — тип. .моделирующий Buffer; Г — тип значения элементов В; t — объект типа Т. Требования Чтобы тин моделировал Buffer, он должен иметь следующие методы класса. • В; :value_type Тип объекта, хранимого в буфере. Тин значения должен быть Assignable. • В:-size_type Беззнаковый целый тип для представления числа объектов в буфере. • b.piiSh(t) Вставляет t в буфер. При этом b.sizeO увеличивается на единицу. • Ь.рорО Удаляет объект из Suffer. Это тот самый объект, который возвращает b.topO. При этом b.sizeO уменьшается на единицу. Предусловие: Ь.empty() - ложь.
16.3, MultiPassInputlterator 285 • b.topO Возвращает ссылку (или константную ссылку) на некоторый объект в буфере. Предусловие: b.empty О — ложь. • b.sized Возвращает число объектов в буфере. Инвариант: b.sized >= 0. • b.emptyO Тип возвращаемого значения — bool. Результат эквивалентен b.sizel) — 0. Гарантии сложности Для концепции Buffer имеются следующие гарантии сложности. • push!), рор() и s 1 ге() должны выполняться не более чем за линейное время от размера буфера. • top() и empty() должны выполняться за амортизированное постоянное время. Модели std::stack., boost: :mutable_queue, boost:: prion ty_queue и boost:: queue. 16.2. ColorValue Концепция ColorValue описывает требования к типу, используемому для значений цветов. Многие алгоритмы BGL применяют отображение свойства «цвет» для отслеживания продвижения алгоритма по графу. Тип значений цвета должен быть EqualityComparable. Класс color traits специализирован для Т так, чтобы были оп- ределены следующие функции. Здесь Т — тин, который моделирует ColorValue. • color_traits<T>::white() Возвращает тип: Т. Семантика: возвращает объект, который представляет белый цвет. • color_traits<f>: :grayd Возвращает тип: Т. Семантика: возвращает объект, который представляет серый цвет. • color_traits<T>;:b1ack() Возвращает тип: Т. Семантика: возвращает объект, который представляет черный цвет. 16.3. MultiPassInputlterator Концепция MultiPassInputlterator многопроходного итератора является уточнени- ем Inputiterator. Она добавляет требования, чтобы итератор мог быть использован для неоднократного перебора всех значений, несли ill -» it2 и itl можно разы- меновать, то *++1tl — *++П2.
286 Глава 16 • Вспомогательные концепции, классы и функции Итератор MultiPassinputiterator подобен Forwarditerator. Разница состоит в том, что Forwarditerator требует, чтобы тип reference был va 1 ue_ types, тогда как MultiPass- Inputlterator похож на Inputiterator и том, что тип reference должен быть преобра- зуем к типу va lue_type. 16.4. Monoid Концепция Monoid описывает простой вид алгебраической системы. Она состоит из множества элементов 5, бинарной операции и нейтрального элемента (identity element). В C++ моноид представляет собой объект-функцию, реализующий би- нарную операцию, .множество объектов, представляющих элементы 5, и объект — нейтральный элемент. Уточнение для Тип элемента множества 5 должен быть моделью Assignable и CopyConstructible. Тип функционального объекта должен моделировать BinaryFunction. Правильные выражения Тип х — это тип элемента. Объекты а. Ьис - объекты типа X, представляющие элементы множества S. Объект I -• объект типа X, обладающий свойствами нейт- рального элемента (приведены ниже). Объект ор — это функциональный объект, который реализует операцию на моноиде. • ор(а, Ь) Возвращает тип: X. Семантика: см. ниже. • d Ь Возвращает тип: bool. Семантика: возвращает истину, если а и b представляют один и тот же элемент • а !“ b Возвращает тип: bool. Семантика: возвращает истину, если а и b представляют разные элементы 5. Инварианты • Замкнутость, Резул ьтат ор( а. b) также является элементом 5. • Ассоциативность. ар(ор(а. b). с) — оо(а. ор(Ь. с)) • Определение нейтрального элемента. ор(а, D ” а 16.5. mutable_queue nutable_quei.e<indexedType. Container. Compare. I0> Адаптер mutable_queue представляет собой специальный вид очереди по при- оритетам (реализована с использованием кучи), которая имеет операцию обнов-
16.5. mutable_queue 287 ления (update). Это позволяет изменять порядок элементов в очереди. После того как критерий упорядочения для объекта элемента х меняется, необходимо вызы- вать Q.update(x). Чтобы эффективно находить х в очереди, необходимо написать функтор (объект-функцию) для отображения х в уникальный идентификатор (ID), который mutable queue затем использует для отображения размещения элемента в куче. Генерируемые идентификаторы должны быть от 0 до N, где N — значение, переданное конструктору mutable_queue. Параметры шаблона Ниже приведены параметры шаблона адаптера mutab ie queue. • IndexedType Если ID не поддерживается, то должна быть определена функция mdexXt) (где t — объект тина IndexedType), которая возвращает некоторый целый тип. • Container Модель для RandomAccessContainer. Тип значения контейнера должен быть та- ким же, как тип IndexedType. По умолчанию: std:: vector<IndexedType>. • Compare Модель для BinaryPredicate (бинарный предикат), которая получает объекты типа IndexedType в качестве аргументов. По умолчанию: std;; less«typenare Container: :value_type>. • ID Модель для ReadablePropertyMap, которая принимает IndexedType в качестве типа ключа, а возвращает некоторый целый тип в качестве типа значения. По умолчанию: 1dentity_property_map. Методы Ниже приведены методы адаптера mutable_queue. • value_type Тот же тип, что и IndexedType. • size_type Тип, используемый для представления размера очереди. • mutable_queue(size_type n. const Compare! с. const ID! id - IDO) Конструктор. Резервируется место для п элементов. • template «class Inputiterator* mutable_queue(Inputiterator first. Inputiterator last. const Compare! c. const ID! id - IDO) Конструктор. Контейнер std::vector по умолчанию заполняется из диапазона [first, last). • bool empty!) const Возвращает истину, если очередь пуста.
2S8 Глава 16 • Вспомогательные концепции, классы и функции • void рор() Удаляет объект из очереди. • value_type& t.opO Возвращает ссылку на первый элемент очереди. • vdlue_type& frontО Другое имя для topO. • void oush(con$t valm?_type& x) Вставляет копию объекта х в очередь. • void updatetconsi va]ue_type& х) «Значение» элемента изменилось, и должно быть заново произведено упо- рядочение кучи. Этот метод подразумевает, что имеется старый элемент у в куче, для которого index(y) =-= indexix), и что х — новое значение эле- мента. 16.6. Непересекающиеся множества 16.6.1. disjoint_sets disjoint_sc-ts<RankMap, PsrentMap. FmdCompress> Класс disjoint_sets предоставляет операции ни непересекающихся множествах (НМ), иногда называемых структурой данных для объединения и поиска (union- find data structure). Структура данных НМ поддерживает набор 5 - 5,, 52,.... 54 непересекающихся множеств. Каждое множество идентифицируется своим представителем, взятым из множества. Множества представлены корневыми деревьями, которые закодированы в отображении свойства ParentMap. Для уско- рения операций используются две эвристики: объединение по рангу и сжатие путей. Параметры шаблона Ниже приведены параметры шаблона класса dis;oint_sets. RankMap Должен быть моделью ReadWritePropertyMap с целым типом значения и типом ключа, совпадающим с ти- пом элемента множества ParentMap Должен быть моделью ReadWritePropertyMap. Типы ключа и значения должны совпадать с типом элемен- та множества FindCompress Должен быть одним из функциональных объектов, которые мы обсудим позднее в этом разделе. По умолчанию: find_with_full_Dath_cofnpression Пример Типичный образец использования непересекающихся множеств можно обнару- жить в алгоритме kru5kal_minimuni_spanmng_tree( L В примере в листинге 16.1 мы
16.6. Непересекающиеся множества 289 вызываем 11nk() вместо union_$et(), так как и и v получаются из f i nd_set(), а зна- чит, уже являются представителями своих множеств. Листинг 16.1. Использование непересекающихся множеств и ... disjoint_sets«RankMap. ParentMap. FindCompress? dsets(rank, p): for (ui - vertices(G).first: ui !- vertices(G).second: ++ui) dsets.make_set(*ui); // ... while ( !Q.empty!) ) { e - Q.front!): O.popO: u - dsets.find_set(source(e)): v - dsets.fin<Tset(target<e)): if ( u !» v ) { *out+* - e: dsets.Hnktu. v). И связывание } ! Методы Ниже приведены возможные методы класса disjoint_sets. • disjoint_sels(RankMap г. ParentMap р) Конструктор. • dlsjoint_se'.s(const dlsjoint_sets& х) Конструктор копирования. • template «typename Element? void make_set(Element x) Создает множество из одного элемента, содержащего элемент х. • template «typename El erent? void UnkCElement x. Element y) Объединить два множества, представленные элементами х и у. • template «typename El erent? void unlon_set( Element x. Element y) Объединить два множества, содержащие элементы х и у. Это эквивалентно Hnk(find_set(x). find_set(y)). • template «typename Element? Element find_set(Element x) Возвращает представителя множества, содержащего элемент х. • template «typename Element Iterator? std::size_t count_sets(Element Iterator first. Element Iterator last) Возвращает число непересекающихся множеств. • template «typename Element Iterator? void compress_sets(Element Iterate- first. Element!terator last) Выравнивает дерево родителей так. что родитель каждого элемента является его представителем.
290 Глава 16 • Вспомогательные концепции, классы и функции Сложность Временная сложность порядка 0(та(гп. п)), где а — функция, обратная функции Аккермана, т — число операций на непересекающихся множествах (n»ke_set(), find_set() и link()) и п — число элементов. Функция, обратная функции Аккер- мана, растет очень медленно, намного медленнее логарифма. 16.6.2. find_with_path_halving flnd_with_path_halving Этот функтор находит вершину-представш еля для компоненты, в которой содержится элемент х, и в то же время сжимает дерево, используя деление пут пополам. template «typename ParentMap. typename Element* Element operator;)(ParentMap p. Element x) 16.6.3. find_with_full__path_compression find_with_ful l_path .compression Этот функтор находит вершину-представителя для компоненты, в которой содержится элемент х, и в то же время сжимает дерево, используя полное сжатие пути. template «typename ParentMap. typename Element* Element operator;)(ParentMap p. Element x> 16.7. tie template «typename Tl. typename T2> tuple«Tl. T2> tie(Tl& a. T2& 0): Эта функция из Boost Tuple Library (библиотека кортежей), написанная Яак- ко Ярви, делает более удобной работу с функциями, которые возвращают пары (или. в общем случае, кортежи). Функция ti е() позволяет присваивать два значе- ния пары двум различным переменным. Где определена Функция tiе() находится в файле boost/tuple/tuple.hpp. Пример В примере функция tied используется вместе с функцией vertices;), которая возвращает пару типа std:; pa i r<vertexj terator. vertexj terator*. Пара итерато- ров присваивается переменным 1 и end. graph_tralts«graph t>;; vertexj terator f. end; forftied, end) - verticestg)” 1 !- end; +*d ll ... В листинге 16.2 приведен другой пример, который использует 11е() для рабо- ты с std; :set.
16.8. graph j>ropertyjter_range 291 Листинг 16.2. Пример использования функции tie(). ♦include «set* ♦include «algorithm* ♦include «lostream* ♦include «boost/tuple/tuple.hpp* int mainO { typedef std: :set«int> Sett; SetT: .-iterator i. end: bool inserted: int valsl&J - ( 5. 2. 4. 9. 1 SetT steals, vals + 5): Int new_vals[2] - { 3. 9 }: for (int k - 0: k < 2: **k) { // Используем tieO co значением типа pa 1r<--terator. bool* boost::tie(i.inserted) - $.ln$ert(new_vals[k]): if (’inserted) std:.xout « *1 « ” уже находилось во ииохестве." « std:: endl: else std::cout « *i « " успешно вставлено.” «« std:;endl: return £XIT_SUCCESS: ) Программа выводит следующее: 3 успешно вставлено. 9 уже находилось во множестве 16.8. graph_propertyjter_range graph_property_1ter_range<Graph, PropertyTag* Этот класс генерирует пару итераторов begin/end, которые предоставляют до- ступ к свойству вершины для всех вершин в графе или к свойству ребра для всех ребер в графе. Пример В примере (листинг 16.3) перебираются все вершины в графе, с присвоением строк в свойство «имя». Затем снова перебираются все вершины и выводятся имена в стандартный вывод. Листинг 16.3. Файл graph-property-iter-eg.cpp ( graph-property-iter-eg.cpp ) « ♦include «string* findude «troosVgraph/adjacencyjist.npp> #include <t»ost/graph/propsrty_i ter range.hpp> int main!) /^ожижение
292 Глава 16 • Вспомогательные концепции, классы и функции Листинг 16.3 {продолжение) using namespace boost: typedef adjacencyjlst < H$tS. vecS. directedS. property < vertex_name_t. std::string * *graph_t: graphJ g(3): const char *vertex__names[] - { 'Kubrick". "Clark", "Hal” }: int i - 0: graph_property_iter_range < graphj. vertex_name_t >. iterator v. v_end; for (tielv. v_end> - get_property_iter_range(g, vertex_name): v !- v_end; ++v. +J) *v - vertex_names[ij: tie(v, v_end) - get_property_iter_range(g. vertex_nane): std::copy(v. v_end. std::ostrea»jterator < std::string > (Std::COUt. “ ”)): std::cout « std::endl: return 0: } Программа выводит следующее: Kubrick Clark Hal Где определена Класс graph jdroperty J ter_range находится в файле boo$t/graph/propertyjter_range.hpp. Параметры шаблона Ниже приведены параметры шаблона класса graph_propertyjter_range. Graph Графовый тип должен быть моделью PropertyGraph. PropertyTag Тег указывает, к какому свойству ребра или верши- ны происходит доступ. Ассоциированные типы Ниже приведены ассоциированные типы класса graph_property_1ter_range. • graph_property_iter_range::iterator Изменяемый итератор, тип значения которого — свойство, указанное тегом свойства. • graph_property_iter_range::constJterator Константный итератор, тип значения которого — свойство, указанное тегом свойства. • graph_propertyjter_range: :type Тип std:: pa i r<1 terator, i terator*. • graph_property_iter_range:: const J.ype Тип std: :pair<const_iterator. const_iterator*. Функции — методы He имеет.
16.8. graphjxopertyJterjange 293 Функции — не методы Ниже приведены функции — не методы класса graph_property_iter^range. • tempiate<typename Graph, typename Tag> typename graph_property_iter_ra.nge<Graph. Tag>::type get_property_iter_range(Graph& graph, const Tag& tag) Возвращает пару изменяемых итераторов, которые дают доступ к свойству, указанному тегом. Итераторы пробегают по всем вершинам или по всем реб- рам графа. • tempiate<typename Graph, typenane Tag> typename graph_property_1ter_range<Graph. Tag>::const_type get_property_iter_range(const Graphs graph, const TagS tag) Возвращает пару константных итераторов, которые дают доступ к свойству, указанному тегом. Итераторы пробегают по всем вершинам или по всем ре- брам графа.
Библиография 1. Dimacs implementation file format. http://dimacs.rutgers.edu/Chal.lenges/. 2. A. Alexandrescu, Better template error messages. C/C++ Users Journal, March 19911. 3. M. H. Austern. Generic Programming and the STL. Professional computing series, Addison-Wesley, 1999. 4. G. Baumgartner and V. F. Russo. Signatures: A language extension for impro- ving type abstraction and subtype polymorphism in C++. Software-Practice and Experience, 25(8):863-889, August 1995. 5. R. Bellman. On a routing problem. Quarterly of Applied Mathematics. 16(1): 87-90,1958. 6. Boost. Boost C++ Libraries, http://www.boost.org/. 7. A. Broder, R. Kumar, F. Maghoul. P. Raghavan, S. Rajagopalan, R. Statu. A. Tomkins, and J. Wiener. Graph structure in the web. In 9th World Wide Web Conference, 2000. 8. К. B. Bruce, L. Cardelli, G. Castagna, the Hopkins Objects Group, G. T. Leaven >, and B. Pierce. On binary methods. Theory and Practice of Object Systems, 1:221-242, 1995. 9. В. V. Cherkassky and A. V. Goldberg. On implementing push-relabel method for the maximum flow problem. Technical report, Stanford University, 1991. 10. T. Cormen, C. Leiserson, and R. Rivest. Introduction to Algorithms. McGraw- Hill, 1990. 11. E. Dijkstra. A note on two problems in connexion with graphs. Numcrische Mathematik, 1:269-271,1959. 12. L. R. Ford and D. R. Fulkerson. Maximal flow through a network. Canadian Journal of Mathematics, pages 399-404, 1956. 13. L R. Ford and D. R. Fulkerson. Flows in Networks. Princeton University Press, 1962.
Библиография 295 14. Е. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Professional Computing. Addison- Welsey, 1995. 15. M. Garey and D. Johnson. Computers and Intractibility: A Guide to the Theory of NPCompleteness. W.H. Freeman, New York, 1979. 16. A. V. Goldberg. A new max-flow algorithm. Technical Report MIT/LCS/TM- 291, MIT, 1985. 17. A. V. Goldberg and R. E. Tarjan. A new approach to the maximum flow problem. Journal of the ACM, 1988. 18. R. Graham and P. Hell. On the history of the minimum spanning tree problem. Annals of the History of Computing, 7( 1 ):43-57,1985. 19. C. Hedrick. Routing information protocol. Internet Requests For Comments (RFC) 1058, June 1988. 20. A. V. Karzanov. Determining the maximal flow in a network by the method of preflows. Sov. Math. Dokl., 1974. 21. S. E. Keene. Object-Oriented Programming in Common LISP: A Programmer’s Guide to CLOS. Addison-Wesley, 1989. 22. D. E. Knuth. Stanford GraphBase: A Platform for Combinatorial Computing. ACM Press, 1994. 23. J. B. Kruskal. On the shortest spanning subtree of a graph and the traveling salesman problem. In Proceedings of the American Mathematical Sofiety, volume 7, pages 48-50,1956. 24. D. Kiihl. Design patterns for the implementation of graph algorithms. Master’s thesis, Technische Universitat Berlin, July 1996. 25. J. Lajoie and S. B. Lippman. C+ + Primer. Addison Wesley, 3rd edition, 1998. 26. E. L. Lawler. Combinatorial Opimization: Networks and Malroids. Holt, Rinehart, and Winston, 1976. 27. D. Matula. Determining edge connectivity in o(mn). In Sumposium on Founda- tions of Computer Science, pages 249-251, 1987. 28. J. McQuillan. The new routing algorithm for the arpanet. IEEE Transactions on Communications, May 1980. 29. K. Mehlhorn and S. Naher. The LEDA Platform of Combinatorial and Geomet- ric Computing. Cambridge University Press, 1999. 30. B. Meyer. Object-oriented Software Construction. Prentice Hall International Series in Computer Science. Prentice Hall, 1988. 31. E. Moore. The shortest path through a maze. In International Symposium on the Theory of Switching, pages 285 -292. Harvard University Press, 1959. 32. R. Morgan. Building an Optimizing Compiler. Butterworth-Heinemann, 1998. 33. J. Moy. Rfc 1583: Ospf version 2. Network Working Group Request for Comment, March 1994.
2®б Библиография 34. D. R. Musser, G. J. Derge, and A. Saini. STL Tutorial and Reference Guide. Addison-Wesley, 2nd edition, 2001. 35. D. R. M usser and A. A. Stepanov. A library' of generic algorithms in ada. In Using Ada (1987 International Ada Conference), pages 216-225. New York, NY. Dec. 1987. ACM SIGAda. 36. N. C. Myers. Traits: a new and useful template technique. C++ Report, June 1995. 37. R. Perlman. Fault-tolerant broadcast of routing information. Computer Networks. December 1983. 38. R. Prim. Shortest connection networks and some generalizations. Bell System Technical Journal, 36:1389-1401,1957. 39. J. Siek and A. Lumsdaine. Concept checking: Binding parametric polymorphism in C++. In First Workshop on C++ Template Programming, Erfurt, Germany, October 102000. 40. A. A. Stepanov and M. Lee. The Standard Template Library. Technical Report X3J16/94-0095, WG21/N0482, ISO Programming Language C++ Project, May 1994. 41. B. Stroustrup. Design and Evolution of C++. Addison-Wesley, 1994. 42. B. Stroustrup. The C++ Programming Language. Addison Wesley, special edi- tion, 2000. 43. R. Tarjan. Depth-first search and linear graph algorithms. SIAM Journal on Computing, 1(2): 146-160,1972. 44. R. E. Tarjan. Data Structures and Network Algorithms. Society for Industrial and Applied Mathematics, 1983. 45. B. L. van der Waerden. Algebra. Frederick Ungar Publishing, 1970. 46. H. C. Warnsdorff. Des roesselsprungs einfachste und allgemeinste loesung, Schmalkalden, 1823.
Дополнение к библиографии Теория графов 1. Ахо А., Хопкрофт Дж., Ульман Дж. Построение и анализ вычислительных алгоритмов. — М.: Мир, 1979. 2. Веллман Р., Дрейфус С. Прикладные задачи динамического программиро- вания. — М.: Наука, 1965. 3. Евстигнеев В. А., Касьянов В. Н. Теория графов: Алгоритмы обработки де- ревьев. — Новосибирск: Наука, 1994. 4. Касьянов В. Н., Евстигнеев В. А. Графы в программировании: обработка, ви- зуализация и применение. — СПб.: БХ В-Петербург, 2003. 5. Кристофидес Н. Теория графов. Алгоритмический подход. — М.: Мир, 1978. 6. Липский В. Комбинаторика для программистов. — М.: Мир, 1988. 7. Оре О. Теория графов. — М.: Наука, 1968. 8. Форд Л. Р„ ФалкерсонД. Р. Потоки в сетях. — М.: Мир, 1966. 9. Харари Ф, Теория графов. — М.: Мир, 1973. C++ и STL 1. Александреску А. Современное проектирование на C++: Обобщенное про- граммирование и прикладные шаблоны проектирования. — Киев: Издатель- ский дом «Вильямс». 2002. 2. Буч Г. Объектно-ориентированный анализ и проектирование с примерами приложений на C++. — М.: Издательский дом «БИНОМ», 2001. 3. Вандевурд Д., Джосаттис Н, Шаблоны C++: Справочник разработчика. — Киев: Издательский дом «Вильямс*. 2003.
298 Дополнение к библиографии 4. Гамма Э., Хелм Р„ Джонсон Р., Влиссидес Дж. Приемы объектно-ориентиро- ванного проектирования: Паттерны проектирования. — СПб.: Питер, 2001 5. Джосьютис Н. C++ стандартная библиотека- Для профессионалов. — СПб. Питер, 2003. 6. Мейерс С. Эффективное использование STL: Библиотека программиста. - СПб: Питер. 2002 7. Остер» М. Г. Обобщенное программированием и STL: использование и на- ращивание стандартной библиотеки C++. - СПб.: Невский Диалект, 2004. 8. Плаугер П„ Ли М., Массер Д.. Степанов A. STL — стандартная библиотека шаблонов С+. — СПб.: БХВ-Петербург, 2004. 9. Саттер Г. Решение сложных задач на C++. Серия C++ In-Depth. — Киев: Издательский дом «Вильямс». 2003. 10. Страуструп Б. Язык программирования С-+. — М.: Издательский дом «БИНОМ», 2001. 11. Страуструп Б. Дизайн и эволюция языка C++. — М.: ДМК, 2000.
Алфавитный указатель А adapters 32 algorithm visitor 67 algorithm visitors 31 allocator type 234 В back edge 69, S3 backtracking graph search 127 bidirectional graph 32 Btx>st Concept Checking Library, BCCL 54 Boost Graph Library, BGL 17, 59 Boost Tokenizer Library, BTL 79 breadth-first search 77, 89 c Callback 165 collections 28 concept 26, 39 concept covering 55 D depth-first search 62 disconnected set 119 disjoint-sets 205 E edge 25 edge descriptors 26 external property storage 64 F flow network 117 forward or cross edge 83 framework 45 G generic programming 39 H hop 90 I identity element 286 internet protocol 90 К keyword parameters 58 L LEDA 130 Link-State Routing 95 literate programming style 20 M Matrix Template Library. MTL17 minimum disconnecting set 118, 12 N named parameters 58 О overloaded (unctions 33 P policy class 135 preflow 117 priority queue 129 property interface 230 property map 27 push-relabel algorithm 117
300 Алфавитный указатель R refinement 47 s signatures extension 42 Standard Template library, STL 26 Stanford GraphBase 130 T tag dispatching 52 template specialization 50 traits class 47, 49, 151 Transmission Control Protocol, TCP 90 traversal category 219 tree edge 83 u union-find data structure 288 V vertex 24 vertex descriptors 26 visitor concepts 164 w WWW, World Wide Web 109 A адаптер 33 filtered_graph 33 iterator_property_map 280 mutable_queue 287 reverse_j*raph33 для Stanford GraphBase 282 аддитивная абелева группа 40 алгебра многосортная 11 алгоритм Беллмана-Форда 92, 188 Дейкстры 96, 183 Джонсона 192 Краскала 102,194 обобщенный 13 поиска в глубину 62, 82, 177 поиска в ширину 77, 89, 172 Прима 102, 197 алгоритм (продолжение) связных компонент 200 Тарьяна 202 топологической сортировки 182 увеличивающихся компонент связности 207 Форда-Фалкерсона 117 Эдмондса-Карпа 117,209 ассоциированные типы 47 Б базовый блок 84 библиотека LEDA 130 .ииоритмов иа |рафах 59 лексического раэбора 79 проверки концепций 54 бикомпонент 109 В вершина достижимая 109 предок 77. 89 родитель 77, 89 смежная 25 внешнее хранилище свойств 64 время окончания обработки вершин! время посещения 62 Г гарантии сложности 48 граф 25 kmghts_tourj»raph 125 вершина 24 двунаправленный 32. 216 дуга 25 неориентированный 25 ориентированный 25 ребро 25 узел 24 графовые адаптеры 32 д дерево поиска в глубину 62 поиска в ширину 77, 89 диспетчеризация тегов 52
Алфавитный указатель 301 диспетчеризация вызовов времени выполнения 42 времени компиляции 42 допустимые выражения 47 3 задача кратчайшего пути из одной вершины 90 кратчайшего пути между всеми парами вершил 90 кратчайшего пути между двумя вершинами 89 минимального остовного дерева 102 обхода конем 124, 130 И именованные параметры 58 инварианты 48 интернет-маршрутизатор 90 интернет-протокол 90 интерфейс 26 интерфейс свойств 230 итератор 28. 45 вершин 28 входящих ребер 28 исходящих ребер 28 ребер 28 смежности 28 К класс adjacency_list 32,215, 274, 284 adjacency _list_traits 246 adjaeency_matrix 32,33,235 ad'acency_matrix_traits 247 array_traits 50 back_edge_recorder 85 bacon number_recorder 81 color_traits 285 ColorPoint 43 Color Point2 44 componentindex 208 disjoint_sets 288 edge_list 250 filteredgraph 256 graph_property_iter_range 71,291 graph_traits 53,243 класс (продолжение) Point 43 property 70,79,249 property_map 248 property__traits 275, 278 put_get_helper 282 reverse_graph 252 std 48 topo_visitor 73 класс свойств 47, 49 класс эквивалентности 110 компонента связности 200 сильная 110 контейнер 45 концепция 26,39 AdjacencyGraph 154 AdjacencyMatrix 157 BellmanFordVjsitor 168 BFSVisitor 165 BidirectionalGraph 153 Buffer 284 Color Value 285 DFSVisitor 166 Dijkstra Visitor 167 EdgeListGraph 156 EdgeMutableGraph 160 EdgeMutablePropertyGraph 164 Graph 151 IncidenceGraph 152 LvaluePropertyMap 278 Monoid 286 MultiPassinputiterator 285 MutableBidirectionalGraph 161 MutableEdgeListGraph 162 MutableIncidenceGraph 161 PropertyGraph 162 ReadablePropertyMap 276 ReadWritePropertyMap 277 UniquePairAssociativeContainer 283 VertexListGraph 155 VertexMutableGraph 159 VertexMutablePropertyGraph 163 WritablePropertyMap 277 отображения свойства 27 кратчайшее расстояние 77, 89 кратчайший путь 77, 89 вес 89
302 Алфавитный указатель Л лес поиска в глубину 62 М маршрутизация с учетом состояния линии 95 матрица смежности 32 модель 41 мульти граф 25 мультиметод 42 н иепересекающиеся множества 205 О обобщенное программирование 16, 39 обобщенный указатель 45 обратное топологическое упорядочение 182 обратный вызов 170 объект-функция 30 описатель вершин 26 ребер 26 отображение свойств 274, 284 отображение свойства 27 identify_property_map 36 п параметр compare 30 переход 90 поиск Кенига 56 покрытие концепции 55 полиморфизм 40 параметрический 39, 41 подтипов 40 посетитель 31, 67 последователь 84 постинкремент 48 поток 117 предпоток 117 предшественник 84 преннкремент48 проверка концепции 54 пропускная способность остаточная 117 разреза 118, 125 протокол маршрутной информации 91 протокол маршрутизации с определением кратчайшего маршрута 95 прототип-класс 55 путь 89. 109 путь длина, вес 89 Р разрез 118. 125 расширение сигнатур 42 ребро входящее 25 древесное 77,83,89 ишшндентное 25 исходящее 25 конечная вершина 25 насыщенное 117 начальная вершина 25 обратное 69,83,118,125 параллельное 25 петля 25 поперечное 83 прямое 83,118,125 С свойство 27 сечение 118, 125 специализация шаблона 50 список смежности 32 среда разработки 45 стандартная библиотека шаблонов стиль грамотного программировав cTpyirrypa Ап- 282 Vertex 282 т тег 52 тип данных абстрактный 39 тип распределителя памяти 234 топологическое упорядочение 34 У уточнение концепции 47
Алфавитный указатель 303 Ф файл adjacency_list.hpp 218, 246 adjacency_mat.rix.hpp 238, 247 ijellman-examptexpp ISO bell man-ford-internet.epp 92 belhnan_ford_shortest_paths.hpp 189 bfs-example.cpp 174 breadth_first_search.hpp 173 cc-intemet.cpp 110 connected_coinponents.hpp 200 container_gen.cpp 233 depth_first_search.hpp 178, 181 dfs-example.cpp 179 dijkstra-example.cpp 187, 229 dijkstra_shortest_paths.hpp 185 edge-connectivity.cpp 122 edge_list.hpp251 . edgeproperty.cpp 232 ed munds_karp max_flow.hpp 209 family-tree-eg.cpp 216 filtered _graph.hpp 258 gb_graph.h 262. 265 girth.cpp 262 graph archetypes.hpp 55 graph_concepts.hpp 54 graph-property-iter-eg.cpp 291 graph.cpp 136 graph_traits.hpp 245 graphviz.hpp 96. 104 incremeiital-c.omponents-eg.cpp 206 incremental components.hpp 207, 208 mteriorpropertymap.cpp 233 johnson _afi_pairs_shorl estpaths.hpp 192 kevin_bacon.txt 79 kruskalexample.cpp 196 kruskal_minimum_spanning_tree.hpp 195 kruskal-telephone.cpp 104 leda graph.hpp33, 131. 136, 244. ' 266. 267 miles span.cpp 262 prim-example.cpp 199 prim_minimuin_spanning_tree.hpp 198 prim-telephone.cpp 106 property.hpp 66,225, 226, 242. 243, 248,250. 256 property_iter_range.hpp 292 файл (продолжение) property tnap.hpp 73, 279, 280, 283 PROTOTYPES 132, 261 relax.hpp 186, 190 reverse-graph-eg.cpp 252 revcrsejgraph.hpp 253 roget_eoniponents.< pp 262 scc.cpp» 112 Stanford_gfi»ph.hpp 33, 132. 244, 261, 262 strong_components.hpp 202 topological_sort.hpp 182 topo-sortl.cpp 35 topo-sort2.cpp 37 topo-sort-with-sgh.epp 133 t uple.hpp 290 vector_as_graph.hpp 33.271, 272 формат DIMACS 210 функтор 30,164 функция accumulate 46 add_edge 29 add_vertex 29 adjacent vertices 29, 64 backedge 67 backtrackingsearch 127 bellman _ford_shortest_path 58.92 breadth_first_search 31, 79,172 cornpute_loop_extcnt 86 connected_components 110, 200 depth_first_search 34, 38, 82,177 depth_first_visit 82. 88,181 dijkstra_shortest_paths 96, 183 discover_vertex 67 edmunds_karp_max_flow 209 equal 43 find_loops 84 finish_vertex 67 forward_or_cross_edge 67 get 71.275 has cycle 66, 69 has_cycle_dfs 66 in_edges29. 148 incremental components 207 initialize_incremental_components 207 is_self_loop 26 johnson_ail_pairs_shortest_paths 192
304 Алфавитный указатель функция (^одолжение) kruskal_minimum spanning_tree 104, 194 make Jterator_property_map 93 шип. vertices 56 nuinber_of_successors 128 operator 275 out_edges29,148 prim_minimum_spanning_tree 197 printtransdelay 27, 28 print_vertex_name 27, 28 put 275 push_relabel_max_flow 212 read_graphviz 96 same_component 207 std 30. 80 sum 40 source 148 strong components 202 tie 28,290 topo_sort 65, 69 функция (продолжение) topo_sort_dfs 63 topological_sort 34,182 tree_edge 67, 81 target 148 vertex_index_map 36 visitor 31, 82 wamsdorff 129 who_owcs_who 232 встраиваемая 42 обобщенная 14 перегруженная 33 u цикл 84 голова 84 ч частотная специализация 51 число Бэкона 79 число Эрдеша 78