Книга
Книга
Глава 1. Введение
1Л. Немного терминологии из теории графов,...
1.2. Графовые концепции
Описатели вершин и ребер
Отображение свойств
Обход графа
Создание и модификация графа
5. Посетители алгоритмов
Классы и адаптеры графов
1. Классы графов
Адаптеры графов
Обобщенные алгоритмы на графах
.1. Обобщенный алгоритм топологической сортировки .....
Обобщенный алгоритм поиска в глубину....
Глава 2. Обобщенное программирование в C++
2,1* 6ВбД€НИб ><«i»*it*a*«i»«i**ia«*i«*i*«*it»iii*ii*ta<*i»**i*«««t*i*«ai«ii*«ii*ti*«i*t«**«i*«l»«t*a<*ai*(i»««»*a> ■ 2.1.L Полиморфизм в объектно-ориентированном программировании
Полиморфизм в обобщенном программировании
Сравнение ОП и ООП
Обобщенное программирование и STL
Концепции и модели
2.3 *1* Наборы требований
2*3*2. Пример: Inputiterator
Содержание 7
Ассоциированные типы и классы свойств
Ассоциированные типы в шаблонах функций *
Определители типов, вложенные в классах
Определение класса свойств
Частичная специализация
Диспетчеризация тегов
Проверка концепции
.1. Классы для проверки концепций
・2. Прототипы концепций
Пространство имен
Л. Классы *
.2. Поиск Кенига...*
Именованные параметры функций
Глава 3・ Изучаем BGL
Зависимости между файлами
Подготовка графа
Решаем, какой графовый класс использовать
Строим граф с помощью итераторов ребер
Порядок компиляции
Топологическая сортировка через поиск в глубину
Маркировка вершин с использованием внешних свойств
Доступ к смежным вершинам
Обход всех вершин *
Циклические зависимости
3*5.《На пути» к обобщенному поиску в глубину: посетители
Подготовка графа: внутренние свойства
Время компиляции
Обобщенная топологическая сортировка и поиск в глубину
Время параллельной компиляции
9990123456668 9 9О11224А567914445555555555 5 56666666666679990123456668 9 9О11224А567914445555555555 5 566666666666772力72力Глава 4. Основные алгоритмы на графах
9990123456668 9 9О11224А56791
4445555555555 5 5666666666667
9990123456668 9 9О11224А56791
4445555555555 5 5666666666667
72力
72力
Поиск в ширину
Определения
Шесть степеней Кевина Бэкона
Поиск в глубину
Определения
Нахождение циклов в графах потоков управления программы Глава 5. Задачи нахождения кратчайших путей
Определения
Маршрутизация в Интернете
Алгоритм Беллмана-Форда и маршрутизация с помощью вектора расстояний
Маршрутизация с учетом состояния линии и алгоритм Дейкстры ....
Глава 6. Задача минимального остовного дерева
Определения
Планирование телефонной сети
Алгоритм Краскала
"7777788283848989909195"77777882838489899091956,4, Алгоритм Прима
"7777788283848989909195
"7777788283848989909195
Глава 7. Компоненты связности 109
Определения 109
Связные компоненты и связность Интернета 110
Сильные компоненты связности и ссылки веб-страниц114
Глава 8. Максимальный поток 117
Определения **117
8*2. Реберная связность *118
Глава 9. Неявные графы: обход конем * 124
9*1. Ходы конем как граф 125
92 Поиск с возвратом на графе* 127
Эвристика Варнсщорфа 128
Глава 10. Взаимодействие с другими графовыми библиотеками130
Использование топологической сортировки из BGL с графом из LEDA 131
Использование топологической сортировки из BGL с графом из SGB*132
Реализация адаптеров графов 134
「лава 11. Руководство по производительности 137
11Л. Сравнения графовых классов 137
1. Результаты и обсуждение*138
11,2, Итоги「лавн 144
Часть II. Справочное руководство... 145
Глава 12. Концепции BGL 146
Концепции обхода графов 146
Неориентированные графы 148
Graph 151
12Л.З. IncidenceGraph 152
BidirectionalGraph 153
AdjacencyGraph 154
VertexUstGraph 155
EdgeListGraph 156
AdjacencyMatrix * 157
Концепции для изменения графов 157
VertexMutableGraph 159
EdgeMutableGraph 160
MutablelnddenceGraph 161
MutableBidirectionalGraph 161
MutableEdgeListGraph 162
PropertyGraph 162
VertexMutablePropertyGraph 163
EdgeMutablePropertyGraph ...»* 164
Концепции посетителей *164
BFSVisitor 165
DFSVisitor 166
DijkstraVisitor 167
BellmanFordVisitor 168
Глава 13. Алгоритмы BGL 170
Обзор 170
Информация об алгоритме 171
Базовые алгоритмы 172
breadth_first_search172
breadth_first_visit...» 176
depth_first_search177
depth_first_visit• 181
topo!ogical_sort 182
Алгоритмы кратчайших путей183
dijkstra_shortest_paths183
bellman_ford_shortest_paths188
johnson_all_pairs_shortest_paths191
Алгоритмы минимальных остовных деревьев 194
kruskal_minimum_spanning_tree194
2. prim_minimum_spanning_tree197
Статические компоненты связности200
connected_components200
strong_components202
Растущие компоненты связности:205
initializeJncrementaLcomponents 207
incremental_components 207
same_component 207
componentjndex 208
Алгоритмы максимального потока209
7.1. edmunds_karp_max_flow209
push_relabeLmax_flow212
Глава 14. Классы BGL215
Классы 「рафов215
" adjacencyJst 215
. adjacency一matrix 235
Вспомогательные классы 243
graph_traits 243
adjacencyJist_traits246
adjacency_matrix_traits247
property_map248
property249
Графовые адаптеры 250
edge_list 250
reverse_graph 252
1433. filtered_graph256
Указатель на SGB Graph261
GRAPH<ЧЕ> из библиотеки LEDA265
std: :vector<EdgeList>: 271
Глава 15. Библиотека отображений свойств 274
Концепции отображений свойств 275
ReadablePropertyMap 276
WritablePropertyMap277
ReadWritePropertyMap277
LvaluePropertyMap278
Классы отображений свойств278
property_traits 278
0 12 2282828280 12 22828282813428281342828iterator_property_map
0 12 228282828
0 12 228282828
134
2828
134
2828
Теги свойств
15*3. Создание пользовательских отображений свойств
Отображения свойств для Stanford GraphBase
15*3,2. Отображение свойств из std::map
Глава 16, Вспомогательные концепции, классы и функции
ColorValue 285
16*3. MultiPassinputiterator 285
Monoid 286
mutab!e_queue 286
Непересекающиеся множества 288
L disjolnt_sets *288
find_with_path_halving 290
find一with_full_p己由—Compression 290
tie., 290
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: :11st::splice переме* щают части из одного контейнера в другой подобно операции по пересадке органов: моя почка — это моя почка, до тех пор, пока ее не пересадят кому-нибудь другому,
В любом случае, я был твердо убежден, что программные компоненты должны быть функци овальным и и основываться на системе функционального програм» мирования Джона Бэкуса (John Backus's FP system). Единственной новой идеей было то, что функции могут быть связаны с некоторыми аксиомами. Например, «алгоритм русского крестьянина», позволяющий вычислить 胃-ю степень за 0(log п) шагов, подходит для любого объекта, для которого определена бинарная ассоциативная операция. Другими словами, я веркл, что алгоритмы должны быть связаны с тем, что сейчас мы называем концепциями (см. раздел 2*3 этой книги), которые я обозначал как структурные типы (structure types), а теоретики называют многосортньши алгебрами (multi*sorted algebras).
Удачей для меня было то, что в Политехгшческом университете был замечательный человек — Аарон Кершенбаум, обладавший глубокими знаниями в области
алгоритмов на графах и стремлением их реализовать. Аарон заинтересовался моими попытками расчленить программы на простые примитивы и провел много времени, обучая меня графовым алгоритмам и раб口тая со мной над их реализацией. Он также показал мне, что есть фундаментальные вещи, которые нельзя сделать в функциональном стиле без высоких изменений в сложности. Хотя я часто мог реализовать алгоритмы, требующие линейного времени, в функциональном стиле без изменения асимптотической сложности, реализовать на практике алгоритмы с логарифмическим временем без изменения сложности на линейную я не мог. В частности, Аарон объяснял мне, почему очередь по приоритетам столь важна для алгоритмов на графах. Он был хорошо осведомлен н этом вопросе: Кнут в его книге по Stanford GraphBase [22] приписывает авторство Аарону в применении частично упорядоченных бинарных деревьев (binary heaps) к алгоритмам Прнма и Дейкстры.
Мы очень обрадовались, когда смогли получить алгоритмы Прима и Дейкстры как два случая одного обобщенного алгоритма (высокоуровневого). Это просто замечательно, что код BGL схож с нашим кодом (см. например сноску в разде* ле 13.4.2). Следующий пример на языке Scheme показывает, как два алгоритма были реализованы в терминах одного высокоуровневого алгоритма, Единствен* ная разница заключается в том, как комбинируются значения расстояний: сложением у Дейкстры и выбором второго операнда у Прима:
(define dijkstra
(make-scan-based-algorlthm-with-mark
make-heap-with-membership-and-va1ues + < ))
(define prim
(make'Scan-based-algorithm-with-mark
make - heap-wl th -menibershi p-and -^ al ues (1 ambda (ку) у) < ))
На поиск подходящего языка программ!!рования для эффектизной реализации подобного стиля у меня ушло почти десять лет. Наконец я нашел C++, который помог мне создавать программы, полезные для общества. Более того, C++ серьезно повлиял на мой проект, основанный на модели машины для С. Шаблоны и перегрузка функций — вот особенности C+斗,позволившие создать STL.
Я часто слышу от людей выражения недовольства по поводу перегрузки в Сн--, но, как это обычно бывает для большинства полезных механизмов, перегрузка может быть использована неправильно. Для разработки полезных абс^акций перегрузка незаменима. Если мы обратимся к математике^ то многие ее идеи получшш развитие именно благодаря перегрузке. В качестве примера можно привести расширение понятия чисел от натуральных к целым, к рациональным, к гауссовым, к р-адияеским имслам и т. д. Можно легко догадаться о некоторых вещах без знания точных определений. Если я вижу выражение, в котором использу!отся операции сложения и умно- жсьшя, я предполагаю наличие дистрибутивности. Если я вижу знак «меньше» н сложение, то предпола1'аю, что если а< Ь.тоа-^ с< Ь^- с(я редко складываю несчетные количества). П ерефузка позволяет нам переносить знание от одного типа к другому.
Важно понять, что можно писать обобщенные алгоритмы, просто используя перегрузку, без шаблонов, но это требует много нажатий на клавигли. То есть для каждого класса, например у до влетворя юще го требованиям итератора произвольного доступа, нужно вручную определять все относящиеся к нему алгоритмы. Это утомительно, но осуществимо (нужно определять только сигнатуры: тела будут те же). Нужно заметить, что пастраиваемые моду;ш (generics) в языке Ада требу-
ют ручной реализации и, поэтому, не так полезны, поскольку каждый алгоритм нужно реализовывать вручную. Шаблоны C++ решают эту проблему, позволяя определять такие вещи единожды.
Еще есть вещи, нужные в обобщенном программировании, но непредставимые в C++. Обобщенные алгоритмы — это алгоритмы, которые работают с объектами, обладающими похожими интерфейсами. Не идентичными, как в объектно-ориентированном программировании, а именно похожими. Использование не только бинарных методов вызывает проблему (см. раздел 2.1.3), интерфейсы фактически описываются с помощью одного типа (односортная алгебра). Если мы внимательно посмотрим на объекты вроде итераторов, то увидим, что они могут быть описаны только в терминах нескольких типов: тип самого итератора, тип значения и тип расстояния. Другими словами, необходимо три типа, чтобы определить интерфейсы для одного типа. В C++ для этого нет нужного аппарата. В результате мы не можем определить итераторы и, следовательно, откомпилировать обобщенные алгоритмы. Например» если мы определим адгоритм reduce таким образом:
template <class Inputiterator, class Bi па ryOpe rati onWi thIdenti ty> type name iterator_traits<lnputlterator>: :value__t.ype reduce(InputIterator first. InputIterator last?
BlnaryOperationWithldentity op)
{
typedef typename iterator_tra1ts<lnputlterator>: :value_t.ype T;
If (first — last) return""identity_element(op):
T result 亠 *first:
while (++first !工 last) result - op (result. *first): return result:
}
но вместо ++f 1 rst != 1 ast напишем ++f 1 rst < last, никакой компилятор не сможет обнаружить ошибку в месте определения. Хотя стандарт ясно декларирует, что operatorv не нужен для итераторов ввода, у компилятора нет возможности знать об этом. Требования к итераторам сформулированы только на словах. Мы пытаемся программировать с концепциями (многосортными алгебрами) на языке, в котором для них нет поддержки.
Насколько сложно распшрить C++, чтобы действительно позволить этот стиль программирования? Во-первых, нам нужно ввести концепции как новое средство интерфейса. Например, мы можем определить следующие концепции:
concept Semi Regular : Assignable. DefaultConstructi ble {}:
concept Regular : Semi Regular. Equal it>Coniparabl e {};
concept Inputiterator : Regular. Incrementable {
Semi Regular value_type;
Integral dlstance^type;
const val ue_type&^operator*():
}: 一
value_type(InputIterator)
reduce(Inputiterator first. Inputiterator last, BlnaryOperationWithldentity op)
(va1ue_type(Inputlterator) — argument_type(BinaryOperationWithldenti ty))
{ -_
if (first — last) return 1 dentity_element(op);
value_type(Inputlterator) result =~*first:
wliil e""(++fi rst != last) result = opt result. *first);
return result:
Обобщенные функции (generic functions) — это функции, которые принимают концепции как аргументы и в добавление к списку аргументов имеют список ограничений типов. Теперь полная проверка типов может быть выполнена в месте определения, без рассмотрения мест вызова или в местах вызова без рассмотрения тела алгоритма.
Иногда требуется несколько реализаций одной и той же концепции. Напри* мер, для слияния возможна следующая запись:
Outputiterator merge(lnputlterator[l] flrstl, Inputlterator[l] lastl. InputIterator[2] first?. Inputlterator[2] last2. OutputIterator result)
(bool opcrdtor<(value_type{InputIteratQr[U), vdlujtype(lnputlterator[2])).
value_type(Inputlterator[l j) == value_type(lnputlterator[2]).
output_type(OutputIterator} ~ value_type(lnputlterator[2]));
Заметим, что это слияние не такое эффективное, как в STL Невозможно соединить список чисел с плавающей запятой и вектор чисел двойной точности в дек целых чисел. Но алгоритмы STL часто производят неожиданные и, по моему мнению, нежелательные преобразования типов* Если нужно слить числа с двойной точностью и числа с плавающей запятой в целые, лучше использовать явный функциональный объект для сравнения и специальный итератор вывода для преобразования.
В языке C++ поддерживается два различных механизма абстракции: объектноориентированный подход и шаблоны. Использование объектно-ориентированного подхода позволяет точно определить интерфейс н провести диспетчеризацию времени исполнения. Вместе с этим диспеп черизация бинарных или мультиметодов неосуществима, а связывание времени исполнения часто неэффективно. Шаблоны предназначены для более сложных интерфейсов и разрешены во время компиляции. Серьезным препятствием для их использования разработчиками программного обеспечения является тот факт, что в шаблонах отсутствует разделение между интерфейсами и реализацией. Например, недавно я пытался откомпилировать пример STL- программы из десяти строк, используя один из наиболее популярных компиляторов C++…Я был в шоке, увидев несколько страниц переборчивых сообщений об ошиб* ках. Можно предположить, что введение концепций объединит возможности обоих подходов и уберет ограничения, накладываемые ими. Кроме того, можно представить концепции в виде виртуальных таблиц, распространяющихся на указатели к описателям типов: виртуальная таблица для итератора ввода содержит не только указатели на operator* и operator++, но и указатели на актуальный тип итератора, тип его значения и тип расстояния. А затем можно ввеста указатели и ссылки на концепции!
Обобщенное программирование — это сравнительно молодое направление в программировании. Я счастлив наблюдать, что небольшая попытка, начатая двадцать лет назад Дэйвом Массером, Дипаком Капуром, Аароном Кершенбау- мом и мной» привела к появлению библиотек нового поколения, таких как BGL и MTL. Я должен поздравить Университет Индианы с лучшей командой по обобщенному программированию в мире. Я уверен, что они сотворят и другие чудеса!
Александр Степанов.
Пало-Альто, К^ифорния. Сентябрь, 20011
Я бы хотел поблагодарить Джона Вилкинсона, Марка Манасса Марка Наджор1са и Джереми Сика за многие ценные предложения.
Введение
Графовая абстракция — это мощный инструмент решения задач, используемый для описания отношений между дискретными объектами. Практические задачи могут быть смоделированы в виде графов для различных областей, например таких, как маршрутизация пакетов в Интернете, проектирование телефонной сети, системы сборки программного обеспечения, поисковые машины WWW, молекулярная биология, системы автоматизированного планирования дорожного маршрута, научные вычисления и т. п. Достоинством графовой абстракции является тот факт, что найденное решение проблемы теории графов может быть использовано для решения проблем в широком диапазоне областей. Например, задача нахождения выхода из лабиринта и задача нахождения групп взаимно достижимых веб-страниц могут быть решены с помощью поиска в глубину — важнейшего положения из теории графов. При сосредоточении на сути этих задач, а именно на графовой модели, описывающей дискретные объекты и отношения между ними, специалисты по теории графов нашли решения не просто для «горстки^ отдель* ных проблем, а для целых семейств задач.
Сразу же возникает вопрос. Если теория графов всеобще и широко применяется для произвольных сфер задач, не должно ли программное обеспечение^ реализующее графовые алгоритмы, быть таким же универсальным в применении? Может показаться, что теория графов — это идеальная область для повторного использования программного кода. Однако до сих пор потенциальное повторное использование было далеко от реальности. Графовые задачи редко встречаются в чистой теоретико-графовой форме, они чаще включены в более крупные проблемы, зависящие от области применения. В результате данные, которые можно смоделировать как граф, зачастую явно не представлены как граф, и тогда они закладываются в некоторую структуру данных, специфичную для приложения. Даже в случае, когда данные приложения явно представлены в виде графа, конкретное представление, выбираемое программистом, может не совпадать с представлением, ожидаемым библиотекой. Более того, различные приложения могут накладывать разные ограничения к временной и пространствен ной сложности графовых структур данных.
Эти особенности являются серьезной проблемой для разработчика библиотеки алгоритмов на графах, который хочет предоставить повторно используемое программное обеспечение. Невозможно предусмотреть все возможные структуры данных, которые могут потребоватьсят и написать различные версии графового алгоритма специально для каждой из них. В настоящее время алгоритмы на гра* фах пишутся в терминах той структуры данных, которая наиболее удобна для алгоритма, и пользователи должны преобразовыва1ъ их структуры данных к такому формату, чтобы применить алгоритм. Это неэффективное решение, поглощающее время программиста и вычислительные ресурсы. Часто затраты на преобразования оказываются слишком высокими, и программист переписывает алгоритм в терминах своей собственной структуры данных. Этот подход отнимает время и способствует появлению ошибок, а также имеет тенденцию приводить к недостаточно эффективным решениям, поскольку программист приложения может не быть экспертом в области графовых алгоритмов.
Обобщенное программирование
Стандартная библиотека шаблонов (Standard Template Library, STL) [40] появилась в 1994 году и была сразу принята в стандарт C++. 5九一библиотека взаимозаменяемых компонентов для решения многих фундаментальных задач на поеледовательностях элементов. Отличие библиотеки STL от предлагаемых ранее библиотек состоит в том, что каждый STL-алгоритм может работать с широким набором последовательных структур данных: связные списки, массивы, множества и т. п. Абстракция итератора обеспечила интерфейс между контейнерами и алгоритмами, н шаблонный механизм C++ предоставил нужную гибкость в реализации без потери эффективности. Каждый алгоритм в STL является шаблоном функции, параметризованным по типам итераторов, с которыми он работает. Любой итератор, который удовлетворяет минимальному набору требований, может быть использован независимо от структуры данных, обходимой итератором. Системный подход, использованный в STL для построения абстракций и взаимозаменяемых компонентов, называется обобщенным программированием.
Обобщенное программирование хорошо зарекомендовало себя при решении проблемы повторного использования кода для библиотек алгоритмов на графах. В рамках обобщенного программирования алгоритмы на графах могут быть сделаны более гибкими и легко испол ьзуемыми в большом наборе приложений. Каждый графовый алгоритм пишется не в терминах специфической структуры данных, а для графовой абстракции, которая может быть реализована многими различными структурами данных. Написание обобщенных графовых алгоритмов имеет дополнительное преимущество, являясь более естественным. Абстракция, свойственная псевдокоду описания алгоритма, сохраняется в обобщенной функции.
Библиотека Boost Graph Library (BGL) — это первая библиотека графов С+-, применяющая понятия обобщенного программирования при создании алгоритмов на графах.
Немного из истории 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 Graph Base), не нспользовалсх обобщенный стиль программирования в отличие от STL или MTL. Таким образом, данные библиотеки не удовлетворяли требованиям LSC в гибкости и производительности. Другие исследователи таюке были заинтересованы в создании обобщенной библиотеки алгоритмов на графах для C++. Во время встречи с Бьерком Страуструпом мы познакомились с некоторыми людьми из «AT&T», тоже нуждавшимися в такой библиотеке. Другая ранняя работа в области графовых алгоритмов включала отдельные примеры кодов, написанных Александром Степановым, а также Дитмаром Кюлем в его магистерской диссертации.
Джереми Сик, вдохновленный домашними упражнениями по алгоритмам для своего курса, начал создавать прототипы интерфейса и некоторых графовых классов весной 1998 года с учетом более ранних разработок. Затем Лай'Кван Ли разработал первую версию GGCL, которая стала его магистерским диссертационным проектом.
В следующем году авторы начали сотрудничать с Александром Степановым и Мэтью Остерном. В это время реализация Степанова для компонент связности на основе непересскающихся множеств была добавлена к GGCL, и началась работа по документированию концепций для GGCL модобно документации Остсрна для STL.
В том же году авторам стало известно。Boost, и они были обрадованы тем, что нашли организацию, заинтересованную в создании высококачественных библиотек
С тех лор L5C6bL‘ia преобразована в лабораторию открытых систем (Open Systems Laboratory, OSL). Хотя название и местоположение изменились, программа работы остается прежней. Дополнительная ипформация находится на веб-сайте OSL http://wurw,oslJu.cdu,
C++ с открытыми исходными кодами. В Boost было несколько человек, уделявших внимание обобщенным графовым алгоритмам, и одним из этих людей был Дитмар Кюль. Некоторые обсуждения обобщенных интерфейсов для 1^рафовых структур привели к пересмотру GGCL и появлению в ней новых интерфейсов, очень похожих нате, что есть в 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 LiЬгагу доступна как часть коллекции библиотек Boost. Свежий выпуск библиотек Boost можно загрузить с помощью браузера по следующим адресам: http://www.boost.org/boost_allzip (zip-архив для Windows), http://www.boost.org/boost_alLtar.gz (для Unix), а также по FTP нз каталога ftp://boostsourceforge.net/pub/boost/reLease/.
Zip-архив коллекции библиотек Boost можно разархивировать программой WinZip или ее аналогом. Таг-архив для Unix можно разархивировать с помощью такой команды:
gunzip *cd boost_all.tar.gz | tar xvf -
В результате создается каталог, имя которого состоит из слова boost и номера версии: например, разархивирование версии 1.31.0 создает каталог boo5t_l_31_0. В этом каталоге находятся два важных подкаталога: boost и libs. Подкаталог boost содержит заголовочные файлы для всех библиотек коллекции. Подкаталог libs имеет отдельные подкаталоги для каждой библиотеки в коллекции. Эти подкаталоги содержат файлы исходных кодов и документации к данным библиотекам.
Сориентироваться в архиве можно, открыв в браузере веб-страницу boost_l_31_0/ index.htm.
Заголовочные файлы BGL находятся в каталоге boost/graph/. Однако для BGL нужны и другие заголовочные файлы, так как в библиотеке используются различные компоненты Boost. Гипертекстовая док^ентация находится в каталоге Libs/ graph/doc/, а исходные коды примеров — в libs/graph/example/. В каталоге libs/ graph/test/ содержатся тестовые наборы для BGL. Исходные файлы для реализации анализаторов Graphviz-файлов и программ печати расположены в libs/graph/src/.
Кроме того, что описано палее, для использования BGL не требуется компиляция и сборка. Все, что необходимо, — это добавить каталог заголовочных файлов Boost к пути поиска заголовочных файлов. Например, если в Windows 2000 библиотека версии 1.31,0 разархивирована в корень диска С, для компиляторов Borland, GCC и Metrowerks добавьте -Ic:/boost_l_31_0 к командной строке компилятора, а для Microsoft Visual C++ — /I °c:/boost_l_31_0". Для интегрированных сред разработки (IDE) укажите с: /boost_l_31_0 (или то, во что вы переименовали этот каталог) к пути поиска заголовочных файлов, используя соответствующий диалог. Перед применением В G L-и нтерфейсов к LEDA или Stanford Graph Base последние должны быть установлены согласно инструкциям к ним. Для использования функции read_graphviz() (для чтения Graphviz-файлов «AT&T») необходимо собрать и скомпоновать дополнительную библиотеку из каталога boost_l_31_0/libs/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 предоставляет обширный набор алгоритмов. Студент может сконцентрироваться
на изучении важной теории, на которой основаны графовые алгоритмы, без риска «увязнуть» и отвлечься на слишком большое количество дета-чей реализации.
Для профессиональных программистов 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 flrst.
RandomAccessIterator lastt Compare emp)
Благодарности 21
if (first + 1 < last) {
(Разделить массив пополам и отсортировать каждую половину )
(Слить обе половины )
}
}
Обычно размер каждой части ограничен несколькими строками кода, которые выполняют определенную задачу. Имена для частей выбраны так, чтобы передавать суть этой задачи.
(Разделить массив пополам и отсортировать каждую половину ) к
RandomAccessIterator mid * fiгst + (last - first)/2:
merge_sort(f1rst. mid. cmp):
merge-sort(mid, last, cmp);.
Функция std: : inplace_merge() осуществляет основную работу этого алгоритма, создавая единый отсортированный массив из двух массивов:
《Слить обе половины 〉 в
std::inplacejnergeCfirst, mid, last, cmp);
Иногда для названия используется имя файла. Это означает, что часть записана в отдельный файл. Многие примеры в этой книге записаны в файлах и могут быть найдены в каталоге Libs/graph/exampLe/ в поставке Boost В следующем примере название функции merge_sort() записано в заголовочном файле:
〈meгge^sort.hpp 〉三
#ifndef MERGE_SORT_HPP
#define MERGE_SORT_HPP
(Определение функции сортировки слияниями 〉
#endif // MERGE—SORT—HPP
Благодарности
Мы обязаны поблагодарить множество людей, которые вдохновляли н поддерживали нас в разработке BGL и в написании этой книги.
Особую признательность хотелось бы выразить Александру Степанову и Дэвиду Массеру за их первые шаги в деле обобщенного программирования и постоянную поддержку нашей работы, а также за вклад в библиотеку BGL. Мы особенно благодарим Дэвида Массера за чтение корректуры к этой книге. Работа Мэттью Остерна по документированию концепций STL обеспечила фундамент для создания концепций BGL. Мы благодарим Дитмара К юля за его работу над обобщенными графовыми алгоритмами и паттернами проектирования, особенно за абстракцию ассоциативного свойства. Нужно заметить, что работа была бы невозможна без выразительной мощи языка C++ Бьерна Страуструпа.
Дэвид Абрахамс, Джене Морер, Дитмар Кюль, Бем ан Дэйвс, Гари Пауэл, Грег Колвин и другие участники Boost сделали ценный вклад в интерфейс BGL, вне* ели многочисленные предложения по улучшению, подробно вычитывали эту книгу. Мы также благодарим следующих пользователей BGL, чьи вопросы помогли усовершенствовать BGL (и зту книгу): Гордон Вудхал, Дэйв Лонгхорн, Джоэль Филипс, Эдвард Люк и Стефен Норт.
Мы также благодарим следующих людей за просмотр этой книги во время ее написания: Жана Кристиана ван Винкеля, Дэвид Массера, Бемана Дэйвса и Джеффри Сквайреса.
Большое спасибо нашему редактору Деборе Лафферти и координаторам выпуска Киму Арни Малкахн, Черли Фергюссону и Мерси Барнс, а также остальной часта команды издательства «Addison-Wesley» — работать с вами было одно удовольствие.
В начале работа над BGL была поддержана грантом NSF ACI-998 2205. Части BGL были завершены, когда третий автор был в годичном отпуске в Американской Национальной лаборатории Лоуренса Беркли (где первые два автора были слуяайными гостями). Все рисунки графов в зтсй книге были сделаны с использованием программы dot из пакета Graph viz.
Лицензия
Библиотека BGL выпущена под «художественной» лицензией («artistic» license) программного обеспечения с открытыми исходными кодами. Копия лицензии на BGL приложена к исходному коду в файле LICENSE.
BGL может свободно использоваться как для коммерческого, так и не коммерч ческого использования. Основным ограничением на BGL является то, что модифицированный исходный код может распространяться, только если он отмечен Как нестандартная версия BGL. Для распрострннення BGL и внесения изменений предпочтительным является использование веб-сайта Boost.
От издательства
Ваши замечания, предложения и вопросы отправляйте по адресу электронной почты comp@piter.com (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
Подробную информацию о наших книгах вы найдете на веб-сайте издательства http://www.piter*com.
Руководствопользователя
Введение
В этой главе будут приведены общие положения о некоторых интерфейсах и компонентах библиотеки для работы с графами Boost Graph Library (BGL). Мы начнем с краткого обзора терминолопш теории графов. В качестве примера хорошо моделируемой графом системы будет выступать сеть интернет-маршрутизаторов. Обобщенные интерфейсы, определяемые в BGL. описываются в разделе 1.2. Конкретные графовые классы, реализующие эти интерфейсы, рассматриваются в разделе 1.3. Некоторые сведения об обобщенных алгоритмах на графах приведены в разделе 1.4 данной главы.
Немного терминологии из теории графов
Графовая модель сети интернет-маршрутизаторов показана на рис. 1.1. Маршрутизаторы помечены кружками с буквами, а соединения между ними подписаны средними величинами задержек передачи сигнала (в миллисекундах).
Если придерживаться терминологии теории графов, каждый маршрутизатор в примере сети является вершиной (vertex) (ее еще называют узлом), а каждое со-
единение — ребром (edge) (или дугой). Граф G состоит из множества вершин V и множества ребер £, что можно записать как G = (V, £). Количество элементов множества вершин (число вершин графа) обозначается |V|, а число элементов множества ребер — |Е|. Ребро записывается как пара, состоящая из вершин, которые соединяет ребро. Пара (и,吞)обозначает ребро, соединяющее вершину и с вершиной и.
Сеть маршрутизаторов (рис. 1.1) может быть выражена в нотации теории множеств следующим образом:
U={n, Ь, с, d, е}
Е - {(а, Ь),(4 d), (6, d), (с, 〃),(с, е), (d, с), (& е)}
G=(匕 £)
Граф может быть ориентированным или неориентированным, в зависимости от того, ориентированы или нет ребра из множества ребер. Ребра ориентированного графа обычно называются дугами (хотя далее мы будем пользоваться термином ребро). Ребро ориентированного графа (орграфа) обозначается как упорядоченная пара (и,己),где 〃 一 начальная вершина, а г — конечная вершина ребра. Ребра (以,己)н (a, w) являются различными. В неориентированном графе ребро соединяет вершины в обоих направлениях, поэтому порядок вершин в ребре не имеет значения:(以,г) и (口, и) обозначают одно и то же ребро. Ребро, начало и конец которого совпадают, называется пет.чей. Такие ребра не допускаются в неориентированном графе. Два или более ребра, соединяющие одни и те же вершины, назмваютс只 параллельными, а граф, имеющий такие ребра,—— мультиграфом.
Если граф содержит ребро (w, г), то говорят, что вершина 己 смежна с вершиной и. Для ориентированного графа ребро (у,己)является исходящим для вершины и и входящим для вершины о, В неориентр!рованном графе ребро (w, и) инцидентно вершине и (и вершине и). Множества смежности вершин графа на рис. 1.1 описываются следующим образом:
Adjacent[a] = {瓦 d}
Adjacent[b] = [d\
Adjacent[c] = {a, e}
Adjacent[d]工{c, 4
Adjace?it[e] = {}
Далее записаны исходящие ребра для каждой вершины:
OutEdges[a] = {(白,方),(a d)}
OutEdges[b] = {(b, d)}
OutEdges[c] = {(c, 〃),(c, &)}
OutEdge^d] = {(rf, c), (rf, e)}
OutEdge^e] = {}
Входящие ребра представлены аналогично:
InEdges[a] = {(с, a)}
InEdges[b] = {(a b)}
InEdge^c] == {(& c)}
InEdges[di = Ra, d), (A, d)}
InEdges[e] = {(g c), (d, &)}
12 Графовые концепции
Одна из основных задач обобщенной библиотеки — определить интерфейсы, которые позволят писать алгоритмы, не зависящие от конкретной структуры данных. Заметим, что под интерфейсом подразумевается не только набор прототипов функций, но и наборы синтаксических условий, таких как имена функций и их аргументов, семантических условий (вызываемые функции должны иметь определенные эффекты), гарантии той или иной сложности по времени и памяти.
Пользуясь терминологией из книга «Обобщенное программирование и STL* (см. главу «Дополнение к библиографии), мы используем слово концепция (concept) для обозначения этого более богатого определения интерфейса. Стандартная библиотека шаблонов (Standard Template Library, STL) определяет набор концепций итераторов, которые обеспечивают обобщенный механизм обхода и доступа к последовательностям объектов. Аналогично, рассматриваемая в этой книге BGL определяет набор концепций для изучения 1рафов и манипулирования ими. В данном разделе мы рассмотрим эти концепции. Примеры раздела не относятся к конкретным типам графов. Они написаны как шаблоны функций, где граф является параметром шаблона. Обобщенная функция, написанная с помощью BGL, может применяться к любому типу графа из BGL или даже к типам графов, определенным пользователем* В разделе 1,3 мы обсудим конкретные графовые классы, поставляемые вместе с BGL.
Описатели вершин и ребер
В BGL вершинами н ребрами можно управлять при помощи «непрозрачных» манипуляторов, называемых описателями вершин ( vertex descriptors) и описателями ребер (edge descriptors,). Графовые типы могут нспользовать различные типы для своих описателей. Например, некоторые графовые типы применяют целые числа, а другие — указатели. Типы дескрипторов1 для графового типа всегда доступны через класс gra ph_t га its. Более подробная информация по использовани ю классов свойств приведена в разделе 2*4, а класс д га ph_t га Its обсуждается, в частности, в разделе 14.2.1.
Описатели вершин выполняют очень примитивные функции. Их можно создать только при помощи конструктора, со значениями по умолчанию, затем скопировать при необходимости или сравнить на равенство. Описатели ребер подобны описателям вершин, но предоставляют доступ к связанным с ними вершинам. Следующий шаблон функции демонстрирует2 реализацию обобщенной функции I s_sei f_〕оор(), определяющей, является ли ребро петлей:
template <typename Graph〉
bool is_self_loop(typename graph tralts<Graph>: :edce^descriptor e, const Graphs g+ {
Термины «лшсатель* и «дескриптор» означают одно н то же. — Примеч. ред.
Из эстетических соображений для определения параметров шаблона мы используем typename вместо его аналога class.
typename graph_traits<Gгаph>:;vertex^descriptor u. v; u = sourcete. g): у = target(ef g); return u "== v:
}
Отображение свойств
Графы становятся полезными в виде моделей для задач из специф^гческих областей при помощи ассоциирования объектов н ветчин с вершинами и ребрами. Например, для графа на рис. 1Л каждая вершина имеет имя, состоящее из одного символа, а каждое ребро — величину задержки передачи сигнала. В BGL прикрепленные объекты или величины называются свойствами. Существует много возможностей для реализации, которые могут быть использовады для ассоциирования свойства с вершиной или ребром: члепы структуры (struct); отдельные массивы, индексированные номером вершины или ребра; хэш-таблицы ит. п. Однако для написания обобщенных алгоритмов на графах нам иужен универсальный синтаксис для доступа к свойствам, независимый от того, как они хранятся* Этот универсальный синтаксис определяется концепциями отображения свойства (property map).
Отображение свойства — это объект, обеспечивающий отображение из множества объектов-ключей во множество объектов-значений. Концепции отображений свойств определяют только три футткции:
get (p_map, key) — возвращает объект-значение для ключа key;
put (p_map. key. value) — присваивает значение value объекту-значению, соответствующему ключу key;
p_map[key] — возвращает ссылку на объект-значение.
В следующем примере приведена обобщенная функция print_vertex_name( )t которая выводит имя вершины 口 для данного отображения свойства патентар:
template <typename VertexDescriptor, typeпате VertexNameMap> void pr 1 nt_vertex_name(VertexDescriptor v. VertexNameMap name_map) {
std::cout « get(name_map. v):
}
Аналогично задержка передачи сигнала для ребра может быть напечатана с помощью функции print_trans_delay():
template <typename Graph, typen日呢 TransDeiayMap. typenane VertexNaineMap> void print_tгans_delay(typename graph_tra1ts<Graph>:;edge_de sc ri ptо г e. const Graphs g. TransDelayMap delay^map. VertexNameMap name_map)
std::cout « "trans-delцуС' « get(name_map. sourcete. g)) « "J « get(name_map. targette. g)) « ") = M « get(delay map.色); }
Функции pr1 nt__vertex_name() и phnt__trans_delay() будут использованы в следующем разделе»
Отображения свойств обсуждаются более подробно в главе 15, включая приемы создания отображений свойств, определяемых пользователем. Способы добавления свойств в граф и получения соответствующих отображений описаны в разделе 3.6.
Обход графа
Абстракция «граф» состоит из нескольких видов наборов (collections): вершины и ребра графа, исходящие и входящие ребра, смежные вершины для каждой вершины. По аналогии с STL, BGL использует итераторы для обеспечения доступа к каждому из этих наборов. Имеется пять видов итераторов для фафовт один для каждого набора:
Итератор вершин используется для обхода всех вершин графа. Тип значения итератора вершин — описатель вершины.
Итератор ребер используется для обхода всех ребер фафа. Тип значения итератора ребер — описатель ребра.
Итератор исходящих ребер применяется для доступа ко всем исходящим ребрам данной вершины 〃. Тип значения этого итератора — описатель ребра. Каждый описатель ребра, выдаваемый этим итератором, имеет и в качестве начальной вершины и смежную с 〃 вершину в качестве конечной,
Итератор входящих ребер применяется для доступа ко всем входящим ребрам вершины V. Тип значения — описатель ребра. Каждый описатель ребра выдаваемый этим итератором, имеет п в качестве конечной вершины и вершину, к которой в является смежной, в качестве начальной вершины.
Итератор смежности делает доступными вершины, смежные данной. Тшт значения этого итератора — описатель вершины.
Как и описатели, каждый граф имеет свои собственные типы итераторов, доступные из класса graph_t^1ts. Для каждого из только что описанных итераторов BGL интерфейс определяет функцию, возвращающую std:: pai г объектов-итераторов: первый итератор указывает на первый объект последовательности, а вто^ рой итератор указывает за ее конец. Функция pr1nt_vertex_name()» выводящая на печать имена всех вершин в графе, приведена в листинге 1Л,
Листинг 1.1. Функция для печати имен вершин графа
tempi ate <typename Graph, typename VertexNameMap>
void print_vertex_names(const Graphs g. VertexNameMap name_map)
{
std::cout « "vertices(g) = {
typeduf typename graph_tra1ts<Graph>::vertexJterator iter_t;
for (std: :pair<iter_t/lter_t> p = vertices(g):
p.first != p.second: ++p.first) {
print_vertex_name(*p.first, name^map): std,:cout « ' *;
}
std::cout « ”}" « std::endl;
}
Применение этой функции к объекту-графу, моделирующему сеть маршрутизаторов (см. рис. 1.1), приводит к следующему результату:
vertices(g) = { a b с d е }
Функция pri nt_t^sns__del ay(), которая печатает задержки передачи сигнала, прикрепленные к каждому ребру графа, приведена в листинге L2. В функции используется функция tie() (из boost/tuple/tupie.hрр) для осуществления прямого присваивания из std::paiг в скалярные переменные first и last.
Листинг 1.2. Печать задержек
template <typename Graph, typename TransDeiayMap. typename VertexNameMap>
void print_trans_del ays (const Graph& g. TransDei ayMap tr日 ns_de1dy_ni 叩, VertexNameMap name_map)
{一
typename graph_traits<Graph>: :edge__iterator first* last:
for (tie(first? last) = edges(g); first != last, ++first) {
print_trans_de1ay(*first, g, trans_delay_map. name_map): std::cout << std::endl:
}
Вывод этой функции для графа с рис, 1,1 следующий:
trans-delay(a.b) » 1,2
trans-delay(a.d) - 4.5
trans-delay(b.d) = 1.8
trans-del ay(с.а) = 2.6
trans-delay(c.e) = 5.2
trans-delay(d.c) - 0.4
trans-delay(d.e) - 3.3
В дополнение к функциям vertl ces() и edges О имеются out_edges(), 1 n__edges О и adjacent_vert1 ces(), которые используют в качестве аргументов описатель вершины и объект-граф и возвращают пару итераторов.
Многие алгоритмы не нуждаются во всем разнообразии доступных итераторов, иногда графовые типы не могут предоставить эффективные реализации для всех типов итераторов. Осторожнее применяйте в алгоритмах конкретные типы графов, нельзя требовать от них исполнения неподдерживаемых операций. Если вы попытаетесь использовать графовый тип, который не предоставляет требуемую алгоритмом операцию, возникнет ошибка компиляции> Сопровождающая такую ошибку информация поможет понять, какая операция не реализована. В разделе 2*5 этот материал описан более детально.
Операции, доступные для данного графового типа, приведены в документации к этому тапу. Раздел «Модель для» из справочных глав 12-14 содержит информацию о предоставляемых операциях, в нем перечислены концепции» поддерживаемые данным графовым типом. Операции, требуемые некоторым алгоритмом, даны в документации к алгоритму, где перечислены концепции, требуемые каждым параметром алгоритма.
Создание и модификация графа
Библиотека BGL позволяет определить интерфейсы для добавления или удаления вершин и ребер графа. В этом разделе мы рассмотрим небольшой пример создания графа, моделирующего сеть маршрутизаторов, изображенных на рис. 1*1* Сначала используется функция add_vertex() для добавления к графу пяти узлов, представляющих маршрутизаторы. Функция add_edge() применяется для того, чтобы добавить соединения между маршрутизаторами.
template <t у репаше Graph, typename VertexNameMap. type па me TransDei ayMap> void build_router_network(Graph& g. VertexNameMap namejn冲.
TransDei ayMap dela^map)
Добавить маршрутизаторы к сети )
(Добавить соединения сети )
}
Функция add_vertex() возврадает описатель вершины для новой вершины. Мы используем этот описатель для присваивания вершине имени в отображении свойства имен:
〈Добавить маршрутизаторы к сети )三
typename graph_traits<Graph>::vertex_descriptor a, b. c, d, e:
3 = add__vertex(g); name_niap[a] = *a':
b = add二vert2x(g); name^maptb] = b;
c = add^vertextg): nafnejrapCel - *c*:
d = add^vertexCg): name二map[d] = 'd';
e ■ add_vertex(g); name_map[e] = 'e*:
Функция add_edge() возвращает std:: pai г, где первый член пары — описатель ребра для нового ребра, а второй — логический флаг, показывающий, добавлено ли ребро (в некоторых графовых типах невозможно добавить ребро, если ребро с тем же началом и концом уже есть в графе). Код добавления соединений сети приведен в листинге 13.
Листинг 1.3. Добавление соединений сети
Добавить соединения сети )三
typename graph_trai ts<Graph> : : edge^descri ptor ed; bool inserted;
tie(ed. tie(ed< tided, tieted.tie(ed. tie(ed< tided, tieted.Inserted) inserted) Inserted) inserted)Inserted) inserted) Inserted) inserted)tie(ed, inserted) tie(ed. Inserted) tie(ed, inserted)tie(ed, inserted) tie(ed. Inserted) tie(ed, inserted)add_edge(a. b, g); delay_map[ed] - 1.2 add二 edged d. g); del ay 二mapfed] ^ 4.5 add 二edgu(b, d. g): del ay二mapfedj = 1.8 add[edge(c, a. g); delay~map[ed] = 2.6 add_edge(c* e. g); delay_map[ed] = 5.2 add二edge(d. c, g): delayZmapfed] = 0,4 己dd]dge(d. e, g); delayjnapled] = 33add_edge(a. b, g); delay_map[ed] - 1.2 add二 edged d. g); del ay 二mapfed] ^ 4.5 add 二edgu(b, d. g): del ay二mapfedj = 1.8 add[edge(c, a. g); delay~map[ed] = 2.6 add_edge(c* e. g); delay_map[ed] = 5.2 add二edge(d. c, g): delayZmapfed] = 0,4 己dd]dge(d. e, g); delayjnapled] = 33
tie(ed. tie(ed< tided, tieted.
tie(ed. tie(ed< tided, tieted.
Inserted) inserted) Inserted) inserted)
Inserted) inserted) Inserted) inserted)
tie(ed, inserted) tie(ed. Inserted) tie(ed, inserted)
tie(ed, inserted) tie(ed. Inserted) tie(ed, inserted)
add_edge(a. b, g); delay_map[ed] - 1.2 add二 edged d. g); del ay 二mapfed] ^ 4.5 add 二edgu(b, d. g): del ay二mapfedj = 1.8 add[edge(c, a. g); delay~map[ed] = 2.6 add_edge(c* e. g); delay_map[ed] = 5.2 add二edge(d. c, g): delayZmapfed] = 0,4 己dd]dge(d. e, g); delayjnapled] = 33
add_edge(a. b, g); delay_map[ed] - 1.2 add二 edged d. g); del ay 二mapfed] ^ 4.5 add 二edgu(b, d. g): del ay二mapfedj = 1.8 add[edge(c, a. g); delay~map[ed] = 2.6 add_edge(c* e. g); delay_map[ed] = 5.2 add二edge(d. c, g): delayZmapfed] = 0,4 己dd]dge(d. e, g); delayjnapled] = 33
В некоторых случаях более эффективно добавлять или удалять несколько вершин или ребер одновременно, а не по одной. Интерфейс BGL имеет функцию
и для этого.
Посетители алгоритмов
Многие из алгоритмов STL имеют параметр объект-функцию, предоставляющий механизм для настройки поведения алгоритма в конкретном приложении. Хорошим примером является функция std::sort(), имеющая параметр compare для задания сравнения:
template <typename RdndomAccessIterator, typename В、naryPredicate〉 void sort(RandomAccessIterator flrst, RandomAccessIterator last.
Binarypredicate compare)
Параметр compare является объектом-функцией (иногда используют название 《функтор»). Его применение проиллюстрировано в следующем примере.
Рассмотрим код программы для адресной кннгн. Сортировка массива адресов по фамилии контактного лица может быть осуществлена вызовом функции std : : sort() с соответствующим объектом-функцией. П ример такой функции приведен далее:
struct compare_last_nairie {
bool operatort)(const address_lnfo& к. const addressJnfo& y) const { return xJast_name < yJasCname;
}.
}:
Сортировка массива адресов производится вызовом функции std:: sortO с передачей специализированной функции сравнен ня.
std::vector<address info> addresses:
H~
comparejast^nanie compare:
std::sort(addresses,beginO, addresses.endO. compare):
Библиотека BGL предоставляет механизм, подобный функциональным объектам, для специализации алгоритмов на графах. Эти объекты называются посетителями алгоритмов (algorithm visitors). ГТосетитель BGL — объект из нескольких функций. Вместо одного operatorO для функционального объекта посетитель BGL определяет несколько функций, вызываемых в определенных событийных точках алгоритма (событийные точки изменяются для каждого алгоритма). Несмотря на название, посетители в BGL несколько отличны от паттерна «посетитель», описанного в книге «Паттерны проектирования» [14] авторов Гамма Э,, Хелм Р., Джонсон Р., Влиссидес Дж. («банда четырех»). Посетитель «банды четырех», предоставляет механизм, выполняющий новые операции над объектно!! структурой без модификации классов. Подобно посетителю ^банды четырех» назначение посетителя BGL — обеспечить механизм для расширения. Однако разница заключается в том, что посетитель в BGL расширяет алгоритмы, а не структуры объекта.
В листинге 1.4 приведен код вывода на экран интернет-маршрутизаторов (см. рис. 1.1) путем расширения функции поиска в ширину breadth_fi rst^sea rch() с помощью посетителя. Посетитель печатает имя вершины при наступлении события посещения вершины (см, раздел 4.1.1, где описана процедура поиска в ширину). Класс посетителя определяется в соответствии с интерфейсом, описанным в концепции BFSVisitor.
Листинг 1.4. Пример класса посетителя
tempi ate <typename VertexNaineMap>
class bfs_name_printer : public default_bfs_visitor {
// Наследовать действия 8 событийных точках по умолчанию (пустые) public:
bfs_name_prj nter(VertexNameMap n^map) : rn_name_map(n_map) { } tempi ate <typename Vertex, typenarne Graph>
void discover_vertex(Vertex u, const G%ph& ) const { std::cout << get(m_name_map, u) « *
}■ ■
private:
VertexNameMap in_name_map:
}:
Далее был создав объект-посетитель типа bfs_name_pri nter и передан функции breadth_f1「5t_sea「ch(). Функция vis1tor()t использованная здесь, является частью процедуры именованного параметра, которая описана в разделе 2.7.
bfs_name_printer<VertexNameMap> vi$5dnijm叩);
std::cout « "'Порядок посещения вершин:":
breadth^first_search(g, a. visitor(vis)):
std::cout « std::endl :
Это дает на выходе следующую запись:
Порядок посещения вершин: a b d с е
Ребра дерева поиска в ширину изображены на ряс L2. черными стрелками.
Классы и адаптеры графов
Графовые типы, предоставляемые BGL, можно от нести к одной из двух категорий. К первой относятся графовые классы, которые служат для хранения графов в памяти, ко второй — гра<}ювые адаптеры (adapters), которые создают измененные представления графов (views) или интерфейс к BGL графу, основанному на другом тип巳
Классы「pagB
Библиотека BGL содержит два первичных графовых класса: список смежности adjacencyjist и матрица смежности adjacency__nic!tr1x.
Главный компонент для представления графов — adjacencyjist. Этот класс обобщает традиционное представление графа в виде списка смежности. Граф предстэвляется набором вершин, каждая из которых хранится со своим набором исходящих ребер. Фактическая реализация набора вершин и ребер может подстраиваться под определенные нужды приложения. Класс adjacency_11st имеет несколько параметров шаблона: EdgeList, VertexList, Directed, Vertexproperties, EdgeProperties и Graphproperties.
EdgeH st и VertexLi st предназначены для к.1ассов, используемых для хранения списка вершин и списка ребер графа. Эти параметры позволяют найти компромисс между скоростью обхода и скоростью вставки/удаления, а также выбрать уровень потребления памяти. Кроме того, параметр EdgeList определяет, могут ли добавляться к графу параллельные вершины.
DI rected определяет, является л и граф ориентированным, неориентированным или двунаправленным (bidirectional graph). Двунаправленный граф предоставляет доступ не только к исходящим ребрам, но и к входящим.
Vertexproperties, EdgeProperties и GraphPropertles определяют типы свойств, закрепленных за вершинами, ребрами и самим графом соответственно.
Полная документация по классу ddjdcency_l 1st находится в разделе 14,LL
Для представления плотных графов (таких графов, у которых |Е | ss |У |2) больше подходит класс adjacency^matrl х. В adjacency_matrix доступ к произвольному ребру (г/, о) очень эффективен (это доступ за постоянное время). Этот класс может служить для представления как ориентированных, так и неориентированных графов и обеспечивать механизм для закрепления свойств за вершинами и ребрами. Полная документация по классу adjacency_list находится в разделе 14Л.2.
Стоит заметить, что хотя все приводимые в этой книге примеры относительно невелики (чтобы графическое изображение графа могло поместиться на одной странице), графовые классы BGL являются эффективными по алгоритмам и требуемой памяти. Они могут использоваться для представления графов с миллионами вершин.
Адаптеры графов
Библиотека BGL включает в себя большое количесл во адаптеров графов. Первая группа классов адаптирует любой BGL-граф для реализации нового поведения. Для этого применяются следующие адаптеры графов:
reverse jraph — адаптер, обращающий направления ребер ориентированного графа таким образом, что входящие вершины ведуп* себя как исходящие и наоборот;
filteredjraph — адаптер, создающий представление графа, где два объекта-функции выступают предикатами, контролирующими ввдимость вершнн и ребер в новом графе.
Также BGL обеспечивает поддержку объектов и структур данных, не являющихся графовыми классами BGL. Эта поддержка осуществляется через классы- адаптеры и перегруженные функции (overloaded functions). Ниже приведено описание этих интерфейсов,
edge__l i st — адаптер, который создает BGL-граф из выхода итератора ребер.
Пакет программ Stanford Graph Вазе поддержи вается перегружен ным и функциями из заголовочного файла boost/graph/stanford_graph.hpp. В результате GraphBase-тип Graph* адаптируется к графовому интерфейсу BGL.
LEDA (популярный объектно-ориентированный пакет) включает структуры данных и алгоритмы для графов, Тип GRAPH<vtype. etype> из LEDA адаптируется к графовому интерфейсу BGL с помощью перегруженных функций из файла boost/graph/Leda_graph.hpp.
Тип std:: vector<std: :11 st<1 nt» из STL преобразуется в фаф благодаря перегруженным функциям из файла boost/graph/vector_as_graph<hpp.
Интерфейс BGL более детально описал в справочнике по концепциям в главе 12. Каждый графовый класс реализует некоторые (или все) из этих концепций. Класс adjacency_list можно считать канонической реализацией (моделью) BGL-графа, поскольку он иллюстрирует все базовые идеи и интерфейсы BGL* графов.
Обобщенные алгоритмы на графах
Алгоритмы на графах из BGL являются обобщенными. Они чрезвычайно гибкие по типам структур данных графов, к которым они могут применяться, и по возможностям специализации для решения широкого диапазона задан. Сначала мы рассмотрим функцию topol og1ca1_sort() для двух различных типов графов, а затем продемонстрируем работу обобщенного алгоритма функции depth^f 1 rst_sea rch () применительно к реализации topological_sort().
Обобщенный алгоритм топологической сортировки
Топологическое упорядочение ориентированного графа — это такое хшорядочепие его вершин, при котором если и гра(»е присутствует ребро (w?。),то вершина 以 появляется до вершины г в упорядочении. Шаблон функции topological__sort() имеет два api умента: граф для упорядочения и iгтератор вывода* Алгоритм записывает вершины в итератор вывода в обратном топологическом порядке.
Топологические упорядочения, например, применяются в задачах планирования. На рис. L3 изображен граф, вершинами которого являются подлежащие выполнению задания, а ребра показывают зависимости между заданиями (например, получить деньги в банкомате нужно перед покупкой продуктов).
Рис. 1.3. Граф зависимостей между заданиями
В следующих двух разделах будет показано, как применить алгоритм топологической сортировки к этой задаче. В каждом разделе для иллюстрации обобщенной природы алгоритмов BGL использован свой Tim графа.
Использование топологической сортировки с вектором списков Вначале мы применяем топологическую сортировку к графу, построенному с по^ мощью std: :vector< std: :list<int> >. Код программы тополошческой сортировки графа приведен в листинге L5*
Листинг 1.5. Топологическая сортировка графа. Файл topo-sortl.cpp
〈topo-sortl.cpp 〉三
#1nclude <deque> // для хранения упорядоченных вершин
#1nclude <vector>
#include <11 st>
#1nclude <iostream>
findude <boost/graph/vector_as_graph.hpp>
#include <boost/graph/topological_sort.hpp>
iht malnO
{
using namespace boost;
(Создать метки для каждого задания )
(Создать граф 〉
《Выполнить топологическую сортировку и вывести результат 〉 return EXIT_SUCCESS:
}
Вершины графа предстакленн целыми числами от нуля до шести, поэтому удобно хранить метки вершин в массиве. В листинге 1,6 приведен код для создания соответствующих меток.
Листинг 1.6. Создание меток для заданий
(Создать метки для каждого задания ) в
const char* tasks[ ] - {
иполучить деньги в банкомате",
“забрать детей из школы1'.
"купнть продукты”.
"прив^ти детей на тренировку".
"забрать детей с тренировки".
мприготовить ужин",
"сьесть ужин",
}:
const int n_tasks м sizeof (tasks) / sizeof (char*):
Граф реализован в виде вектора списков. Каждая вершина графа связана ин* дексом вектора. Таким образом, размер вектора определяется количеством вершин в графе. Список по этому же индексу используется для представления ребер, исходящих от данной вершины к другим вершинам графа. Каждое ребро (〃,г) добавляется к графу помещением числа для。в список и, Используемая структура данных проиллюстрирована на рис. 1.4.
Благодаря функциям из boost/g га р h/vector_as_g га ph. h рр вектор списков соответствует BGL-концепции VertexListGraph, а значит, может быть использован в функции topological_sort(). Код создания графа приведен в листинге 1.7
®-нЭ
®-KD &^® G>-KD GXD
Рис. 1.4. Представление графа зависимостей заданий в виде вектора списков
Листинг 1.7. Создание графа
< Создать граф 》=
std::vector< std::list<int> > g(n_tdsks);
g[0].push_back(3):
g[l],push_back(3):
g[l].push_back(4);
g[2].push2back(l);
gL3].push2back(5);
g[4].push3ack(6);
g[5].push_bdck(6);
Чтобы применить topo 1 og 1 с a 1 _s о rt () т необходимо подготовить место для хране- имя результатов, В BGL-алгоритме топологической сортировки вывод записывается в обратгюм тогюлогаческом порядке (так как это можно реализовать эффек* тивнее). Восстановление топологического порядка требует обращения упорядочения, вычисленного алгоритмом. В следующем примере (листинг 1.8) используется std:: dequeue в качестве структуры данных для вывода результатов, так как std: :dequeue может делать вставку в начало за постоянное время, что нужно для получения обратного порядка. Кроме того, вызов topological_sort() требует одного из двух: 1) задания отображения свойства для цветовой окраски вершин; 2) задания отображения из вершин в целые числа, чтобы алгоритм мог создать свое собственное отображение свойства для отметки этапов прохождения по графу. Поскольку в нашем примере вершины уже являются целыми числами, мы просто задаем тождественное отображение свойства 1 dent 1 ty_property_map как отображение индексов вершин. Функция vertex_i ndex__map() используется для задания именованных параметров (см. раздел 2.7). Код топологической сортировки и вывода результата приведен в листинге 1.8.
Лисггинг 1.8. Топологическая сортировка и вывод результата
〈Выполнить топологическую сортировку и вывести результат 》三
std::deque<int> topo_order:
topological_sort(g,
std: :frontjnserter(topo__order).
vertex_index_map(ldent1ty^property_map())k
int n = 1:
for (std::deque<1nt>::1terator 1 = topo_order.begin();
i !« topo_order,end(); ++1. ++n) { std: :cout «"tasks[*i] « std: :endl;
}
В результате показан порядок, при котором задания могли бы быть выполнены с учетом заданных зависимостей:
получить деньги в банкомате
купить продукты приготовить у*мн забрать детей из школы привести детей на тренировку забрать детей с тренировки съесть ужин
Использование топологической сортировки с классом adjacencyjist
Для демонстрации гибкости обобщенного алгоритма topologicd1_sort() будем использовать совершенно другой тип графа: шаблон класса adjacency^llst. Так как функция topol ogi cal_sort() является шаблоном, в нем Moiyr быть использованы графовые структуры произвольного типа. Все, что необходимо, — это чтобы тип удовле* творял концепции, требуемой алгоритмом. Первые два параметра шаблона класса adjacencyjist определяют конкретную используемую внутреннюю структуру. Первый аргумент lists указывает, что std::list используется для каждого списка ис* ходящих ребер. Второй аргумент vecS свидетельствует о том, что std::vector используется как основа списка смежности. Эта версия класса ad jacencyj 1 st аналогична по характеру вектору списков, которым мы пользовались в предыдущем разделе.
〈Создать объект для списка смежности 〉三
adjacency 11st<11stS. vecS. directedS> g(n_tasks):
Функция add_edge() предоставляет интерфейс для вставки ребер в граф класса adjacencyjist (и в любые другие графы, поддерживающие концепцию EdgeMu- tableGraph). Если std:: vector используется в качестве основы списка смежности, тип описателя вершин для adjacency_list будет целым числом. Но использовать целые числа для задания вершин возможно не в。всех графовых типах.
(Добавить ребра к списку смежности )鼻
add_edge(0. 3. g):
add_edge(l. 3. g);
add二edge(L 4, g):
add二edge(2, 1. g):
add二Ege(3, 5. g):
add二Egc<4. 6. g):
add^edgefS. 6, g);
В остальном программа аналогична предыдущему примеру (см. листинг 1.5), за исключением тоголто вместо заголовочного ijjawia vector_as_gгарh.hрр используется файл adjacency_l1st.hpp, В листинге L9 приведен код программы тополоптче-ской сортировки графа (две части из предыдущего раздела использованы повторно). Листинг 1.9. Топологическая сортировка графа. Файл topo-sort2.cpp
〈topo-sort2.cpp 18 > s
продолжение 夕продолжение 夕♦include <vector>
продолжение 夕
продолжение 夕
Листинг 1.9 {продолжениё)
#1nclude <deque>
#include <boost/graph/topologica】_sort.hpp>
#indude <boost/graph/adjacency_lTst.hpp> int malhO {
using namespace boost:
〈Создать метки для каждого задания >
《Создать объект для списка смежности 〉
〈Добавить ребра к списку смежности >
(Выполнить топологическую сортировку и вывести результат ) return EXIT SUCCESS:
1.4.2. Обобщенный алгоритм поиска в глубину
Реализация topolog1cal__sort<) в BGL состоит всего из нескольких строк, так как этот алгоритм может использовать функцию depth_fi rst_search(), как это обычно и делается в учебниках. Реализация состоит из комбинации depth_f 1 rst_sea rch() и посетителя, устанавливающего порядок, в котором вершины проходят через событийIiyю точку «закончить вершину» поиска в глубину. Объяснение того, почему таким образом действительно вычисляется топологическое упорядочение, дано в разделе 3.3.
В листинге 1*10 описывается алгоритмпческий посетитель, который записы - вает вершины при их прохождении через соответствующую событийную точку поиска в глубину. Чтобы сделать этот класс более общим, упорядочение вершин записывается в итераторе вывода, так что пользователь может выбрать метод вывода сам.
Листинг 1.10. Посетитель для записи посещаемых вершин
tempi ate <typename Outputlterator>
class topo_sort_visitor : public default_dfs_visit3r {
〃 наследовать действия по умолчанию (пустые)
public:
topa_sort_visitor(OutputIterator iter) : mJter(iter) { }
template <typename Vertex, typename Graph>~
void finish_vertex(Vertex u, const Graph&) { *m_1ter++ - u: } private:
OutputIterator m_iter:
Итак, topological_sort() реализуется запуском алгоритма поиска в глубину depth_fi rst_search() с topo_sort_vi si tor () в качестве параметра.
template <typename Graph, typename Outputlterator>
void topolog1cal_sort(Graphs g* Outputiterator result_iter) {
topo_sort_vis1tor<OutputIterator> vis(result_iter): depth_first_search(g. visitor(vls));
}
Обобщенное программирование в С++
Введение
Обобщенное программирование (ОП, generic programming, GP) — методология проектирования и реализации программ, которая разделяет структуры данных и алгоритмы через использование абстрактных спецификаций требований. В С+十 для обобщенного программирования характерно использование параметрического полиморфизма шаблонов (templates) с акцентом на про и зводительност ь. Методология обобщенного программирования была использована нами при создании библиотеки BGL Для понимания устройства и структуры BGL читателю необходимо иметь хорошие знания в области обобщенного программирования. В силу своей относительной новизны (по крайней мере, в сообществе пользователей C++) мы решили дать в этой главе введение в обобщенное программирование. Также будут обсуждаться основные приемы ОН в C++, базирующиеся на шаблонах. Эти приемы — не просто набор «фокусов»-: совместно они образуют новый подъязык в рамках C++.
Абстрактные спецификации требований в обобщенном программировании подобны прежнему понятию абстрактных типов данных. Абстрактный тип данных —это спецификация тапа. Она состоит из описания подходящих операций и задает семантику этих операций, зачастую включая пред- и постусловия и аксиомы (или инварианты) [30]. Классический пример абстрактного типа данных - стек с методами записи («push») и извлечения («рор^) данных. Есть много способов реализации стека: массив переменного размера, связный список н другие, но детали реализации не важны для пользователя стека до тех пор, пока реализация удовлетворяет спецификации абстрактного типа данных.
В обобщенном программировании понятие абстрактного типа данных расширяется. Вместо определения спецификации для отдельного типа мы описываем семейство типов, которые имеют общий интерфейс и семантическое поведение (semantic behavior). Набор требований» описывающий интерфейс н семантическое поведение, называется концепцией (concept). Алгоритмы, сконструированные
в обобщенном стиле, можно применить к любым типам» удовлетворяющим требованиям этого ал горитма. Эта способность использовать различные типы с одной и той же переменной (или параметром функции) называется полиморфизмом.
Полиморфизм в объектно-ориентированном программировании
В объектно-ориентированном программировании (ООП) полиморфизм реализуется посредством виртуальных функций и наследования, что называется полиморфизмом подтипов. Требования концепции к интерфейсу могут быть записаны как виртуальные функции в абстрактном базовом классе. Предусловия и инварианты соответствуют утверждениям (assertions), когда это возможно. Конкретные классы наследуют из абстрактного базового класса и определяют реализации этих функций. Говорят, что конкретные классы являются подтипами (или производи ными классами) базового класса. Обобщенные функции пишутся в терминах абстрактного класса, а во время выполнения вызовы функций осуществляются для конкретного типа объекта (в C++ через таблицы виртуальных функций). Любой подтип абстрактного базового класса взаимозаменяем н может быть использован в обобщенной функции.
Классическим примерам концепции нз математики является аддитивная абелева группа — это множество элементов с ассоциативной операцией сложения, имею* щее обратный элемент и нуль [45]. Мы можем представить эту концепцию в C++, определив абс^грактный базовый класс следующим образом:
// Концепция AdditiveAbelianGroup как абстрактный базовый класс class AdditiveAbe11anGroup { public:
virtual void add(AdditiveAbelianGroup* y) = 0;
virtual AddltiveAbelianGroup* Inversed = 0:
virtual AdditiveAbel 1 anGroup* zeroO * 0;
}:
Используя этот абстрактный базовый класс, мы можем написать универсальную 巾 ункцнк? su(n():
AdditiveAbelianGroup* sum(array<AdditiveAbelianGroup*> v)
AdditiveAbelianGroup* total ^ v[0]->zero():
for (int i = 0: i < v.sizeO; ++1)
total->add(v[1]);
return total;
}
Функция sum() будет работать с любым массивом, тип элемента которого производен от Add i 11 veAbel 1 anGroup. Примерами таких типов могут служить числа и векторы,
class Real : public Addi11veAbel1anGroup {
}:
class Vector : public AdditiveAbelianGroup {
};
2.1. Введение 41
Полиморфизм в обобщенном программировании
В обобщенном программировании полиморфизм реализуется с помощью шаблонов классов или функций. Шаблоны обеспечивают параметрический полиморфизм. Ниже функция sum() записана как шаблон. Наличие базового класса Additive- AbelianGroup уже не нужно, хотя мы оставили его в качестве имени параметра шаблона для удобства и документирования.
template <typenafne AdditiveAbel 1 anGroup>
AddltiveAbelianGroup sum(array<Adci1tiveAbel 1anGroup> v)
Addi ti veAbel ianGroup total - v[0Lzero();
for Ci nt i = 0: 1 < v.sizeO: ++i)
total.add(v[i]):
return total:
}
В C++ концепция выражается в наборе требований к аргументу шаблона, чтобы шаблон класса или функции мог быть успешно скомпилирован н исполнен.
Хотя концепции присутствуют в обобщенном программировании неявно, они очень важны и должны быть тщательно документированы. В настоящее время такая документация обычно появляется в комментариях к коду или в книгах» таких как «Обобщенное программирование и STL» (см. главу «Дополнение к библиографии»). Вернемся к примеру с Addi ti veAbel ianGroup, но уже в качестве концепции AdditiveAbelianGroup.
// концепция AdditiveAbelianGroup (аддитивная абелева группа)
// правмньнме выражения:
//
x.add(y)
//
добавить
//
у 二 х.inverseO
//
обратить
〃
у = x.zeroO
//
ноль
//
семантика:
Конкретные типы, удовлетворяющие требованиям Additi veAbelianGroup, не обязательно должны наследовать от некоторого базового класса. Типы аргументов шаблона подставляются в шаблон функции при инстанпировании (во время компиляции). Для описания отношения между конкретными типами и концепциями» которым они удовлетворяют, используется термин модель. Например, Real и Vector являются моделями концепции AdditiveAbelianGroup.
struct Real { 〃 нет наследования
}:
struct Vector { 〃 нет наследования
};
Сравнение ОП и ООП
До сих пор мы не совсем точно описывали обобщенное программирование как «программирование с шаблонами», а объектно^ориентированное — как «программирование с наследованием». Это может быть несколько неправильно истолковано, так как суть этих методологий только косвенно связана с шаблонами и наследованием.
Еслп говорить более формально, обобщенное программирование основано на параметрическом полиморфизме, тогда как объектно-ориентированное — на поли* морфизме подтипов. В C++ эти полиморфизмы реализованы в виде шаблонов и наследования, но другие языки могут предлагать для этого иные механизмы Например, расширение сигнатур (signatures extension) в GNU C++ [4] обеспечь вает альтернативную форму полиморфизма подтипов* Мультиметоды (в таких языках, как CLOS [21]) предлагают семантику, более близкую к параметрическому полиморфизму, но с диспетчеризацией вызовов времени выполнения (в отли* чие от дислетчеризации во время компиляции шаблонов).
Тем не менее, поскольку мы выбрали стандартный C++, было бы полезно сопоставить ОП п ООП, сравнив наследование (и виртуальные функции) с шабло- налш в контексте C++.
Виртуальные функции медленнее шаблонных функций
Вызов виртуальной функции выполняется медленнее, чем вызов шаблонной функции (последний столь же быстр, как и вызов обычной функции), так как вызов виртуальной функции включает в себя лишнее разыменование указателя для на- хоадешш метода в таблице виртуальных функций. Сами по себе эти «накладные расходы» могут быть незначительными, но они способны косвенно повлиять на скомпилированный код: например, не позволить оптимизирующему компилятору встроить функцию в код (как встраиваемую функцию, inline-функцию) и применить дальнейшие оптимизации полученного кода.
Конечно, размер издержек на виртуальные функции всецело зависит от объема производимых в функции вычислений. Для компонентов уровня итераторов и контейнеров STL или итераторов графов BGL потери при вызове функции могут оказаться значительными. Производительность на этом уровне сильно зависит от того, являются ли функции вроде operator++() встраиваемыми. По этой причине шаблоны являются единственным выпаром для реализации эффективных, низкоуровневых, повторно используемых компонентов, подобных тем, что можно найти в STL или BGL.
Диспетчеризация времени выполнения в сравнении с диспетчеризацией во время компиляции
Диспетчеризация времени выполнения виртуальных функций н наследование являются, вне всякого сомнения, одними из лучших свойств объектно-ориентированного программирования. Для некоторых видов компонентов диспетчеризация времени выполнения является безусловным требованием, так как решения могут приниматься на основании информации, доступной только во время выполнения. В этом случае виртуальные функции и наследование необходимы.
Шаблоны не могут предложить диспетчеризацию времени выполнения, но они обеспечивают значительную гибкость во время компиляции* Фактически, если диспетчеризация может быть выполнена во время компиляции, шаблоны обеспечивают большую гибкость, чем наследование, так как не ограничивают типы аргументов шаблона родством с определенным базовым классом (но об этом мы поговорим позже).
Размер кода: виртуальные функции малы, шаблоны велики
Распространенной проблемой в шаблонном программирование является «разбухание» кода из-за неправильного использования шаблонов. Тщательно спроектированные компоненты на основе шаблонов не увеличивают объем кода по сравнению с их аналогами, построенными па наследовании. Основной способ управлять размером кода — отделить функционш1ыюсть, зависящую от типов шаблона, от функциональности, от них не зависящей< Примером этого может служить реализация std: :11st в библиотеке STL от фирмы SGL
Проблема бинарного метода
При использовании подтипов (наследования и виртуальных функций) для реализации операций, работающих с двумя или более объектами, возникает серьезная проблема. Зта проблема известна как «проблема бинарного метода» [8]. Классическим примером (приведен далее) этой проблемы является интерфейс класса Point (точка, заданная координатами на плоскости), имеющий функщпо-метод класса equal (). Эта проблема особенно актуальна для BGL, поскольку многие из определяемых в BGL типов (дескрипторы вершин, ребер и итераторы) требуют наличия операции operator₽=() подобно equal () для класса Poi nt.
Следующий абстрактный базовый класс описывает интерфейс класса Point.
class Point {
public:
vlrtual bool equal(const Point* p) const 二 0:
}:
Используя этот интерфейс, разработчик библиотеки может написать «обобщенную» функцию, которая получает аргументы любого производного от Point типа и выводит, являются ли они равными.
void print equal (const Point* a. const Point* b) {
std: :cout « std: :boo1 alpha « a->equaKb) « std: :endl :
}
Рассмотрим теперь реализацию некоторого «точечного типа, скажем, класса цветных точек ColorPolnt. Предположим, что в программе это будет единствен* ный «точечный» класс, которьп! мы будем использовать. В этом случае достаточно определить равенство между двумя объектами ColorPolnt, не определяя равенства между цветными точками и точками других видов.
class Colorpoint : public Point {
public:
ColorPolnt(float x. float y. std::string c) : x(x). y(y). color(c) { } virtual bool equal(const ColorPoint* p) const
{ return color == p->color && x = p->x && у -™ p->y: } protected: float x. у: std::st ri ng color:
}:
При попытке использовать этот класс выяспяегся, что функция ColorPol nt:: equal О не заместила Poi nt:: equal (). При создании экземпляра Col orPoi nt возникает следующая ошибка:
error: object of abstract class type "ColorPoint" is not allowed:
pure virtual function "Point:;equal'* has no overrider
ошибка: объект абстрактного типа класса "ColorPoint" не разрешается создавать:
чистая виртуальная функция "Point::equalг, не замещена
Получается, что по правилу типизации тип параметра функции-метода класса в производных классах должен быть либо тот же, либо базовым классом такого же типа, как у параметра в базовом классе. В случае с классом Col orPoi nt параметр для equal () должен быть Point, а не ColorPoint* Однако это изменение приводит к другой проблеме. Внутри функции equal О аргумент класса Point должен быть приведен к типу Cel orPoint для выполнения сравнения. Добавление этого приведения означает, что на этапе компиляции остается неизвестным, является ли программа, использующая ColorPoInt, типобезопасной. Методу equal О по ошибке может был» передан объект другого «точечного^ класса» вызвав исключение времени выполнения. Ниже описан класс ColorPoint2, в котором параметр equal С) изменен на Point, а также добавлено приведение к нужному типу:
class ColorPolnt2 : public Point {
public:
Col orPoint2(fl oat x. float y. std::string s) : x(x). y{y). color(s) {]
v1rtual bool equal(const Point* p) const {
const ColorPoint2* cp = dynamic_cast<const ColorPoint2*>(p);
return color =■ cp->color && x " cp->x && у — cp->y:
}
protected:
float x. y:
std::string color:
};
Теперь предположим, что вместо виртуальных функций мы бы использовали шаблоны функций для выражения полиморфизма. Тогда функция print^equal I) могла бы быть написана так:
template Vypend肥 PointType>
void print_equa!2(const PointType* a. const PointType* b) { std::cout « std::bool alpha « a->equal(b) « std::endl :
}
Для использования этой функции класс цветных точек не нуждается в наследовании из класса точек, и проблемы с приведением типов не возникает. При вызове print_equal 2() с двумя объектами типа ColorPol nt параметр PointType принимает значение ColorPoint, что приводит только к вызову ColorPoint::equal (). Тем самым поддерживается безопасность типов.
ColorPolnt* а 口 new ColorPoint(0.0. 0.0. "blue");
ColorPoint* b - new ColorPoint(0.0, 0.0. "green");
printjequal2(d, b);
Так как BGL реализована на шаблонах функций, проблема бинарного метода нам не угрожает. И наоборот, эта проблема возникала бы на каждом шаге, если BGL была основана на виртуальных функциях.
22 Обобщенное программирование и STL
Предметная область STL затрагивает основные алгоритмы из информатики (например, структуры массивов и списков, алгоритмы поиска и сортировки). Было
много «фундаментальных» библиотек, которые пытались предоставить всеобъ- ешЕющии набор структур данных и алгоритмов. От них STL отличается применением обобщенного программирования (процессом и практикой).
Как объясняют в своей книге [35] Массер и Степанов, процесс ОП в применении к некоторой задаче состоит из следующих основных шагов:
Обнаружение полезных и эффективных алгоритмов.
Определение их обобщенного представления (то есть параметризация каждого алгоритма таким образом, чтобы требований к обрабатываемым данным было как можно меньше).
Описание набора (минимальных) требований, при удовлетворении которых эти алгоритмы могут быть выполнены эффективно.
Создание среды разработки (framework), основанной на классификации требований.
Этот процесс отражен в структуре и устройстве компонентов STL.
На практике процесс минимизации и проектирования среды разработки подразумевает такую структуру, где алгоритмы не зависят от конкретных типов данных, с которыми работают. Вернее, алгоритмы пшвутсх для обобщенных специфика* цийт которые были установлены в ходе анализа потребностей этих алгоритмов.
В алгоритмах обычно требуется выполнять обход стругстуры данных и получать доступ к ее элементам. Если структуры данных предоставляют стандартный интерфейс для обхода и доступа, обобщенные алгоритмы могут быть смело смешаны и согласованы со структурами данных (в терминологии STL — контейнерами).
Главным помощником в задаче разделения алгоритмов и контейнеров в STL является итератор (иногда его называют обобщенным указателем), Итераторы предоставляют механизм для обхода контейнеров и доступа к элементам. Интерфейс между алгоритмом и контейнером строится на основе требований к итератору, которым должен соответствовать тип итераторов данного контейнера. О6о6・ щенные алгоритмы наиболее гибки, когда они написаны в терминах итераторов и не полагаются на конкретный вид контейнера.
Итераторы п одраздел я ются на несколько категорий (видов), например: InputIterator, Forwarditerator, Random Accessiterator. Взаимосвязь между контейнерами, алгоритмами и итераторами представлена на рис. 2.1.
Рис. 2.1. Отделение контейнеров и алгоритмов с помощью итераторов
Библиотека STL определяет набор требований для каждого вида итераторов. В требованиях говорится, какие операции (допустимые выражения) определены для данного итератора и каков смысл каждой операции. Для примера рассмотрим некоторые требования к итератору произвольного доступа из STL (он включает в себя требования к Forwarditerator), приведенные в табл. 2.1. Тип X означает
итераторный тип, Т — указываемый тип, U — тип члена Т. Объекты at Ь, и г — ите* раторы, m — член Т, п — целое число.
Таблица 2.1. Некоторые требования к итератору произвольного доступа из STL
Выражение
Тип результата
Примечание
а к b
bool
*а == *ь
a I- b
bool
Ка — Ь)
а < b
bool
b - а > 0
*а
Т&
Разыменование а
a->ni
U&
(*а).т
++г
Х&
Из г »■ s следует ++г
==F
—г
Х&
Из「з $ следует —г
™ -s
г +» п
х&
Такой же, как п от ++г
а + п
X
{tmp = a: return tmp
*=门:}
b - а
Расстояние
(а < b) ? distanceta.
b):-
distance(bf а)
а[п]
Приводимый к Т
*(а + n)
Пример вычислений с накоплением
В качестве кошсретиого примера рассмотрим алгоритм accumulateO, который последовательно применяет бинарную операцию к начальному значению и каждому элементу контейнера. Произведем суммирование элементов контейнера, используя операцию сложения. Следующий код показывает, как можно реализовать accumulate() на C++. Аргументы first и last — это итераторы, отмечающие начальный и запредельный элементы последовательности. Все аргументы функции параме-гризованы по типу, таким образом, алгоритм может быть использован с любым контейнером, который моделирует концепцию Inputiterator. При прохождении последовательности итератор применяет тот же синтаксис, что и указатели; в частности, operator++() переводит итератор на следующую позицию в последовательности. В табл* 2.1 перечислено несколько других способов продвижения итераторов (главным образом для итератора произвольного доступа). Для доступа к элементу контейнера, на который указывает итератор, можно использовать операцию разыменования operator*() или операцию индексирования operator[]()— для доступа по смещению от текущей позиции итератора.
template <typenaine Input Iterator, typename「typenan)e 81naryOperator>
T accumulate(Inputiterator fir5t. InputIterator last. T Init.
Bi nary Operator binarycp)
{一
for (: first != last: ++first)
init » binary_op(1nit, *first):
return Init:
}
Для демонстрации гибкости интерфейса, п редоста в ля ем ого итератором, мы используем шаблон функции accumul ate() с вектором и со связным списком (и то и другое из STL) (листинг 2.1).
2*3, Концепции и модели 47
Листинг 2.1. Демонстрация интерфейса итератора
〃 использование accumulated с вектором
std::vector<doublе> х(10. 1.0):
doublе smul:
sunil = std: :accuniulate(x.begin() t x.endO. 0.0. std. :p1us<double>());
// использование accumulate() co связным списком
std::list<dauble> y;
double sum2:
// копирование значения вектора в список
std::copy(x.begin(), x.end(), std::back inserter(y));
sum2 = std::accumulate(y.begin(). у.end(). 0.0. std::plus<double>()):
assertCsuml == $um2); 〃 они должны быть равны
Концепции и модели
В предыдущем разделе был приведен пример требований к RandomAccessIterator. Мы также наблюдали, как Inputiterator был применен в качестве требования для функции accumulate() и как были использованы зто£; функцией std : : 11 st : : 1 terator и std:: vector:: iterator. В этом разделе будут определены термины для описания отношений между наборами требований, функций н типов.
В контексте обобщенного программирования термин «концепциям употребляется для описания набора требований, которым должен удовлетворять аргумент шаблона, чтобы функция или класс работали правильно. В тексте использо' ван специальный шрифт для того, чтобы отличать имена концепций.
Примеры определений концепций можно найти в стандарте C++. Многие из них относятся к итераторам. Кроме того, полную покумвнтаиню по концепциям, использованным в STL, можно найти в книге Мэттью Остерна «Обобщенное пр。- граммирование и STL* (см. главу «Дополнение к библиографии») и на веб-сайте, посвященном SGI STL http://www*sgi.com/tech/stl/. Эти концепции часто применяются в определениях концепций BGL*
Наборы требований
Требования к концепциям состоят из набора допустимых выражений, ассоциированных типов, ит(вариантов и гарантии сложности. Тип моделирует концепцию, если он удовлетворяет набору ее требований. Концепция может дополнять требования другой концепции. Это называется уточнением (refinement) концепции.
Допусти.мые выражения — это выражения C++, которые должны успешно компилироваться для тмнов, заданных в выражении, чтобы они считались ^foдeлиpyющимн конце ггцию.
Ассоциированные типы — вспомогател ьные типы, имеющие некоторое отно- шеняе к типу Т, моделируюшему концепцию. Требования концепции обьпно содержат утверждения об ассоциированных типах. Например, требования к итератору обычно включают ассоциированный тип value_typ^ н необходимость того, чтобы об7>екты, возвращаемые операцией разыменования итератора, были имешю данного типа. В C+ + принято использовать класс свойств (traits class) для отображения типа Т в ассоциированные типы концепции.
Инварианты — такие характеристики типов, которые должны быть постоянно верны на этапе выполнения программы. Инварианты часто принимают форму пред- и постусловий. Когда предусловие не выполняется, поведение операции не определено и может приводить к ошибкам. Такая ситуация возникает в библиотеке BGL. Некоторые библиотеки предоставляют отладочные версии, которые используют утверждения или генерируют исключения при нарушении предусловия. Будущие версии BGL, возможно, будут делать так же.
Гарантии сложности — это максимальные пределы того, как долго может происходить выполнение одного из допустимых выражений или сколько разлтных ресурсов может потребовать это вычисление.
Пример: Inputiterator
В этом разделе мы рассмотрим Inputiterator как пример концепции. Во-первых, концепция Inputiterator является уточнением Trivia Ute га to г, которая, в свою очередь уточняет Assignable и EqualityComparable. Таким образом, Inputiterator удов* летворяет всем требованиям Triviallterator (который удовлетворяет требованиям Assignable и EqualityComparable).
Таким образом тип, моделируюи1ий Inputiterator, будет иметь оператор разыменования, он може^г быть скопирован, присвоен, значения этого типа можно сравнивать с друпши объектами-итераторами с помощью операций == и !=.
Концепция Inputiterator требует наличия операций преинкремента и постинкремента. Эти требования обозначены следующими допустимыми выражениями. Объекты i и j являются экземплярами типа Т, моделирующего InputIterator.
…j " t,⑶: // •I к j // 1 !- j " *1 〃 ++i U H+ "…j " t,⑶: // •I к j // 1 !- j " *1 〃 ++i U H+ "присваивание (из Assignable) копирование (из Assignable) проверка на равенство (из Equal incomparable) проверка на неравенство (из EqualityComparable) разыменование (из Trivial Iterator) преинкремент постинкреиент
…j " t,⑶: // •I к j // 1 !- j " *1 〃 ++i U H+ "
…j " t,⑶: // •I к j // 1 !- j " *1 〃 ++i U H+ "
Класс std: :iterator_traits предоставляет доступ к ассоциированным типам итераторного типа. Тип объекта, на который указывает итератор (назовем его X), может быть определен через vdluejype класса свойств. Другими ассоциированными типами являются reference, pointer, difference_typc и iterstor_category. Ассоциированные типы и классы свойств обсуждаются более детально в разделе 2.4. В следующем шаблоне функции мы используем класс 1terator_traits для получения vdlue_type итератора н разыменовываем! итератор:
tempi ate <typename Iterator void dereference example(Iterator i)
{
typename iterator_trajts<Iterator>::value_type t; t - *i;
}
Что касается гарантий сложности, все действия Inputiterator должны выполняться за постоянное время. Примерами типов, моделирующих Inputiterator, являются std::11st<int>::1terator,double*и std::1$tream 1terator<char>.
Цель определения концепций становится яснее при рассмотрении реализаций обобщенных алгоритмов. Ниже следует реализация функции std: ifor_each(). Внутри функции к объектам - итераторам first и last применяются ровно четыре операции: сравнение с помощью operator !=( * инкремент operator++()s разымено- вывание operator*() и создание копии. Чтобы эта функция успешно откомпилировалась и правильно работала, аргументы-итераторы должны поддерживать как минимум зтм четыре операции. Концепция Inputiterator включает данные операции (и еще некоторые), так что это разумный выбор для лаконичного описания требований к for_each().
template <typename Inputiterator, typename Function〉
Function for_each(Input Iterator first. Inputiterator last. Function f )
{
for《:first !■= last: ++fi「st) f (*first):
return f :
Ассоциированные типы и классы свойств
Одним из наиболее важных механизмов, используемых в обобщенном программировании, является класс свойств (traits class), который был предложен Натаном Мэйерсом [36]. Механизм класса свойств может показаться немного неестественным при первом знакомстве (из-за синтаксиса), но суть этой идеи проста. Очень важно научиться использовать классы свойств, так как они постоянна применяются в обобщенных библиотеках вроде STL и BGL.
Ассоциированные типы в шаблонах функций
Класс свойств — это просто способ определения шк^ормации о типе, без которого мы бы о нем вообще ничего не узнали. Например, рассмотрим обобщенную функцию sum():
tempi ate <typendme Array>
X sum(const Arrays v. int n)
X total = 0:
for (Int i - 0: i < n; ++j)
total +- v[1];
return total:
}
С точки зрения этой шаблонной функции о шаблонном типе Array известно немногое* Например, тип элементов этого массива не задан. Однако эта информация необходима для объявления локальной переменной total, которая должна быть того же типа, что и элементы Array. Поэтому имя X здесь только макропеременная» которую нужно заменить на что-то другое, чтобы получить корректную функцию sum().
Определители типов, вложенные в классах
Для того чтобы получить информацию о типе, можно применить операцию разрешения области видимости : : для доступа к операторам typedef, в刀о*еннь1м внутрь класса. Напрнмер, класс массива может выглядеть так:
class rpy^array {
publ1с:
typedef double value_type; // тип элемента массива
doubles operator[ ](Tnt i) {
return m_data[1];
private:
double* m_data:
}:
Тип элемента массива можно получить через array::vmlujtype. Обобщенная функция sum() реализуется с использованием этого приема следующим образом (заметьте, что X были заменены на typename Array : : val ие_1уре3):
tempi ate <typenane Array>
typename Array::vslue__type sum(const Arrays v. int n)
{一
typename Array::va1ue_type total = 0:
for (int 1 = 0: 1 < n: ++i)
total += v[U; return total; }
В этой функции surnO применение вложенного typedef целесообразно, пока Array является классом, имеющим внутри себя typedef. Однако существуют случаи, при которых иметь вложенный typedef непра!<тично или просто невозможно. Допустим, нам захочется использовать обобщенную функцию sum() с классом сторонних производителей, который не предоставляет треб)гемый typedef. Или понадобится функция sum() со встроенным типом вроде doubl е*:
int п = 100:
double* х = new double[n]; sum(x. n);
В обоих случаях это вполне возможно, так как функциональные требования для использования выполнены: оператор operator[]() работает и с double*, и с воображаемым классом сторонних производителей. Ограничением для применения является необходимость передачи информации от классов» которые мы хотим использовать, в функцию sum().
Определение класса свойств
Решением этой задачи является класс свойств — шаблон класса, единственое назначение которого — обеспечение отображения из одного типа в другие типы, функции. константы. Механизм языка C++, позволяющий шаблону класса создавать такое отображение» называется специализацией шаблона (template specialization). Отображение достигается созданием различных версий класса свойств для определенных параметров'типов. Мы покажем, как это работает, создав класс а гray_tга^^s для использования функции 5um().
Шаблонный класс a rray_t га its строится на основе типа Array и позволяет определить valujtype (тип элемента) массива. По умолчанию (полностью ша-
Когда тип слева от операции разрешения области видимости:: каким-либо образом зависит от аргумента шаблона, используйте ключевое слово typename перед типом.
блонный) п ред п ола гается, что массив — тип со вложенным typedef, такой как my_aггау:
template <typename Array> struct array_traits { typedef typename Array:zvalue type value^type:
}:
Теперь возможно создать специализацию шаблона array_tra^s Д-^ обработки ситуации, когда аргумент шаблона является встроенны\! типом, например doubl е*:
tempi ate о struct array_tra1ts<doubl е*> { typedef double value type:
};
Классы сторонних производителей, скажем, johns_int_array, могут быть приспособлены к нашим нуждам аналогично:
template о struct array_traits<jQhns_int_array> {
typedef int value typS
}:
Функция sum(), написанная с использованием класса array_tra^si показана ниже. Чтобы осуществить доступ к типу для переменной total, мы извлекаем value_type из array_traits.
template <typena(ne Array>
typename array_traits<Array>:;value_type sumCconst Arrays v. int n)
typename array_tra^s<^rray>: :value_type total 口 0: for (int 1=0; i < n; ++i)
total +h v[H: return total:
}
Частичная специализация
Писать отдельный класс свойств для каждого указательного типа непрактично и нежелательно. Ниже показано, как использовать частичную специализацию для организации array_traits для всех типов указателей. Компилятор C++ сопоставит аргумент шаблона, заданный при инстанцировании t「己its_cldss, и все определенные специализации для Т* н выберет наиболее подходящую специализацию. Частичная специализация для Т* подойдет для любого типа-указателя. Для doubl е* компилятором будет выбрана полная специализация (приведенная в разделе 2.4.3.) как более подходящая для конкретного типа-указателя.
tempi ate <typename Т>
struct array_traits<T*> {
typedef T value_type:
}:
Частичная специализация может также быть использована для создания версии array_traits для шаблона класса стороннего производителя.
template <typename Т>
struct а гray_t га 11s<john s_a ггау<Т> > {
typedef Т value_type:
Наиболее известным применяемым классом из класса свойств является Пега- tor_traits для STL. Библиотека BGL также использует классы свойств, такие как graph_traits 11 property_traits. Обычно класс свойств применяется с конкретной концепцией или семейством концепций. Класс 1terator_tr^its работает с семейством итераторных концепций, класс graph_traits работает с семейством графовых концепций BGL.
Диспетчеризация тегов
Диспетчеризация тегов (tag dispatching) — это прием, часто работающий «рука об руку》с классами свойств. В нем используется перегрузка функций для диспетчеризации, основанной на свойствах типа. Хорошим примером является реализация (функции std: :advance() в STL, которая по умолчанию осуществляет нн- кремент итератора п раз. В зависимости от вида итератора могут применяться различные оптимизации в реализации дашюй функции. Если итератор обеспечивает произвольный доступ, функция advanceO может быть легко и очень эффективно реализована с помощью i += п. Если итератор двунаправленный, то возможен вариант с отрицательным п, поэтому необходимо осуществить декремент итератора п раз. Отношение между внешним полиморфизмом и классами свойств состоит в том, что свойство, которое будет использовано для диспетчеризации (в нашем случае iterato^category), доступно через traits_class.
В следующем примере (листинг 2.2) функция advance() использует класс iterator Jrai ts для определения 1terator_category. Затем она выполняет вызов перегру* жениой функции advance_d1spatch(). Подходящал функция advance_dispatch() выбирается компилятором на основе того, к какому типу (теговому классу в листинге ниже) будет отнесен iterator^category. Тег — это простой класс, основным назначением которого является передача определенного свойства для использования в те- го вой диспетчеризации. Принято давать теговым классам имена, оканчивающиеся Ha_tdg. Мы не определяем перегруженную функцию для forwa rd J terator_tagj так как этот случай обрабатывается перегруженной ф^^нкцией из 1 nput^l terator_t^9-
Листинг 2.2. Пример диспетчеризации тегов
struct inputjterator_tag {}; struct outputjterator^tag {}: struct forward^iteratortag : public inputlteratortag {}; struct bidirectional_iterdtor_tag : public forward_iterator_tag {}: struct random_access2iterator_t3g : public bi di recti onal J teratortag {}:
tempi ate <typename Input Iterator, typename Distance>
void advance_d1spatch(lnputlterator& 1. Distance nT input_1 terator__tag)
{ while fn—) f: }
tempi ate <typename Bidi recti onalIterator. typename Distance〉 void advance__dispatch(Bid1rectionalIterator^ 1. Distance n, bidirectional__iterator_tag)
{一
if (n >- 0)
while (n--) ++i: else
while (n++)—1:
template <typename RandomAccessIterator, typename Distance> void advance_dispatchfRandcmAccessIterators 1, D1stance n, random_access_i terator^tag) { i +e n: }
template <typename Input Iterator, typename Distance> void advance(Input Iterators i. Distance n)
typedef typename iterator^traits<InputIterator>::1terdtor_category Cat: advance_dispatchti. n, Cat());
}
Класс g「dph_trai ts из BGL включает три категории: di rected^category, edge_pa - rail el—Category и traversal _category. Теги для этих категорий могут быть использованы в диспетчеризации аналогично Iterator^category.
Проверка концепции
Важным аспектом использования обобщенной библиотеки является применение под* ходящих классов в качестве аргументов шаблона к алгоритмам. То есть необходимо, чтобы применяемые классы моделирова-чи концепции, указанные в требованиях алгоритма. Если используется неподходящий класс, компилятор выдаст сообщения об ошибке, но расшифровать эти сообщения пользователю библиотеки шаблонов может быть достаточно сложно [2,41]. Компилятор может выдать буквально страницы трудночитаемых сообщений, даже если допущена совсем небольшая ошибка.
Ниже, в листинге 2.3, допущена типичная ошибка, где функция std : :sort() применяется к массиву объектов. В этом спунае operator() не реализован для типа foo — это означает, что foo нарушает требования LessThanComparabLe (которые даны в документации к std : : sort ()).
Листинг 2.3. Ошибка: невыполнение требований концепции
#i nclude algorithm class foo { }: int maindnt. cha「*[]) { foo array_of_foo[10]: std::sort?array_of_foo. array_of_foo + 10); return 0:
Сообщение об ошибке, возникающее в результате, нелегко понять, и нее, за исключением наиболее опытных программистов C+十,попадут в очень непростое положение, пытаясь найти источник возникновения ошибки из данного сообще* ния. В сообщении не упоминается концепция LessT han Com parable, требования которой были нарушены» но показаны многие внутренние функции, вызываемые из std:: sort (). Помимо этого, нет указания на строку, в которой произошла ошибка. В нашем случае это вызов std:: sort (). Вот как выглядит сообщение об ошибке:
stl_heap.h: In function void_adjust_heap<foo*.int,foo>(foo*.1 nt,1 nt.foo):
$tl[h2dp.h:214: instantiated from- _make_heap<foo*.foo,ptrdlff_t>(foo*.
foo*.foo*.ptrdlff_t*)
stl_heap.h:225:instantiated from rnake_heap<foo*>(foo*,foo*)
stl二d】gohl562:instantiated from _part 1 al_sort<foo*,foo>(foo*,foo*.
foo*. foo*)
st1_a1go.h:1574: instantiated from partial_sort<foo*>(foo*4foo*.too*) $tl[dlgo.h:1279: instantiated from —introsort_loop<foo*.foo.1nt>(foo*t foo*.foo*,int)
stl__algo.h: 1320: instantiated from here
stl2heap.h:115: no match for foo. & < foo &
Классы для проверки концепций
Для преодоления этой проблемы мы придумали идиому C++ для принудительной проверки на соответствие концепции, которую мы назвали проверкой концепции [39]. Соответствующий код находится в библиотеке проверки концепций BCCL (Boost Concept Checking Library) [6]. Для каждой концепции BCCL предоставляет класс проверки концепции, такой как, например, следующий класс для lessThanComparable. Требуемые допустимые выражения для концепции проверяются в функции-методе класса const га 1nts().
tempi ate <typename T>
struct LessThanComparableConcept {
void constraints^ {
(bool Ha < b):
T a. b;
}:
По заданным пользователем аргументам шаблона в начале обобщенного алгоритма с использованием funct1on_requires() из BCCL инстанцируется класс проверки концепции.
#i ncl ude <boo$t/concupt_check, hpp>
template <typename Iterator
void safe_sort(Iterator first, Iterator last)
{
typedef typename std: ?1terator_traits<Iterator>: :7а1иеЗуре T: functior»_requires< LessThanComparabl eConcept<T> >(); ' // другие требования std::sort(first. last);
}
Теперь, если safe_sort() будет использован неправильно, сообщение об ошибке (приведенное далее) будет намного понятнее: оно короче, обозначено место ошибки, приведено имя нарушенной концепции, внутренние функции алгоритма не фигурируют в сообщении.
boost/concept_check.hpp: In method
void boost: :LessThanCornparableConcept<foo>::constralnts(): boost/concept_check.hpp:31:instantiated from
boost::funct1on__requires<boost::LessThdnComparableConcept<foo> >() sort_eg,cpp:ll: instantiated from safe_sort<foa*>(foo*, foo*) sort_eg.cpp:21: instantiated from here
boost/concept_check.hpp:260: no match for foo & < foo &
В библиотеке BGL применяются проверки концепций для обеспечения пользователей более правильными сообщениями об ошибках. Для каждой графовой концепии создай соответствующий класс проверки концепции, определенный в заголовочном файле boost/graph/graph_concepts.hpp, В начале каждого алгоритма в BGL проверяются концепции каждого аргумента. Сообщения об ошибках из graph_concepts.hpp, скорее всего, свидетельствуют о том, что какой-нибудь из
типов одного из аргументов некоторого алгоритма нарушает требования этого алгоритма к концепции.
Прототипы концепций
Дополнительной к проверке концепций задачей является верификация того, что за документированные требования обобщенного алгоритма действительно реализуются алгоритмов,Эту задачу мы называем покрытием концепции (concept covering)* Обычно авч'оры библиотек проверяют покрытие вручную, что приводит к ошибкам. Мы же разработали идиому для C++, которая использует проверку типов компилятора C++ [39] для автоматизации этой задачи. Код для покрытия концепций также находится в BCCL. Библиотека BCCL имеет прототип-класс дл* каждой концепции, применяемой в стандартной библиотеке. Прототип-класс представляет собой минимальную реализацию концепции. Для проверки того, покрывает ли концепция алгоритм, создается объект ирототип-класса и передается алгоритму.
В следующем примере программа пытается проверить, покрываются ли требова* ния std: :sort() итератором, моделирующим RandomAccessIterator, который имеет тип значения LessThanComparable,
#indude «algorithm
#i nd ude <boost/concept^rchetype ,hpp> 1 nt main()
{
using namespace boost:
typedef 1 ess_than_conip3rable_archetype<> T: random_access_iterator_archet7pe<T> ri; std::sort(ri.~ri);
}
На самом деле эта программа не будет успешно скомпилирована, так как те концепции не покрывают требования std::sortO к аргументам своего шаблона. Результирующее сообщение об ошибке показывает, что алгоритм также требует, чтобы тип значения был CopyConstructible.
nul 1_аrchetype(const null^rchetype^lnt> &) Is private
Алгоритму требуется не только конструктор копирования, но и операция присваивания. Эти требования объединяются в концепции Assignable. Следующий код показывает реалнзапию прототип -класса для Assignable. Прототип-класс снабжен параметром шаблона Base для того, чтобы можно было комбинировать прототипы. Для проверки std: :sort() нам нужно соединить прототип-классы для концепций Assignable и LessThanComparable.
tempi ate <typename Base = null_archetype<> > class assignable_archetype : public Base { typedef assignable_archetype self :
public:
assign ablejairchetype (const self &) { }
self & operator^(const self &) { return *this: }
};
Библиотека BGL содержит прототип-классы для каждой графовой концепции в заголовочном файле boost/graph/graph_archetypes*hpp. Тестовые программы ве- рификаци и спецификаций для всех алгоритмов BGL с использованием графовых прототипов находятся в каталоге Libs/graph/test/.
Пространство имен
Как и с других Воо51-библш)теках, каждый компонент Boost Graph Library определен в пространстве имен boost для того, чтобы избежать конфликта имен с другими библиотеками и приложениями. В этом разделе мы покажем, как получить доступ к классам н функциям BGL в пространстве имен boost.
Классы
Пол^^чить доступ к классам BGL можно несколькими способами. В следующем коде показаны три способа доступа к классу adjacencyjist из пространства имен boost.
{ // Применить префикс пространства имен •
boost::adjacency_l i st<> g:
}~
// Внести класс в текущую область видимости операторон using
using boost;:adjacencyJist;
adjacency Jis t<> g:
}
〃 Внести все компоненты Boost в текущую область видимости using namespaсе boost;
adjacency!1st<> g:
}
Для краткости н ясности представления в примерах кода в этой книге опущен префикс boost: :, как если бы оператор using namespace boost; был задан в объемлющей области видимости. Для кода, в котором применяются Boost-бнб- л иотеки, мы рекомендуем использовать префикс boost : : в заголовочных файлах и либо указывать его же в исходных файлах, либо применять там операторы using с явным указанием классов. Старайтесь не использовать просто using namespace boost:, так как это может привести к конфлнктам имен. Однако внутри функций (то есть в ограниченной области видимости) опасность появления конфликтов мала и using namespace можно использовать.
Поиск Кенига
Операции на графах
Интерфейс BGL состоит из перегруженных функций, определенных для каждого графового типа. Например, функция num_vert1ces() имеет один аргумент, объект граф, н возвращает количество вершин. Эта функция перегружается для каждого графового класса BGL Интересно (и, как мы увидим, к счастью), перегруженные функции могут вызываться без уточнения имени функции пространством имен. Используя процесс, называемый поиском Кенига, компилятор C++ рассматривает тип аргумента для перегруженных функций и ищет перегруженные функцш: в пространстве имел аргумента1.
Поиск Кенига назван так в честь его первооткрывателя Эндрю Кенига (Andrew Koenig). Иногда его называют поиском, зависимым от аргумента.
В следующем примере приведена демонстрация поиска Кенига. Рассмотрим случай, когда используются графовые классы из двух различных графовых биб^ лиотек. Каждая библиотека имеет свое собственное пространство имея, внутри которого определен графовый класс и функция num_vert1ces().
namespace Hb jack {
class graph { /* ... */ }:
int numvertices (const graph&J {/*...*/}
}
namespace lib_ji11 {
class graph~{ /* …*/ };
int num_verttces(const graphs) {/*.,.*/)
}
Предположим, что пользователь хочет применить некоторый обобщенный графовый алгоритм, скажем, boost: :pail (), к обоим графовым типам.
int maiM)
{
lib jack::graph gl;
boost: : pall (gl):
libjill: :graph g2;
boost::pail(g2);
}
Внутри boost: :pail О присутствует вызов num_verticesО. В этой ситуации было бы желательно, чтобы при использовании графа из 11 bjack вызывалась функция 11 b_jack:: num vertices(), а если граф из 1 i b_J111 — то 11 b_jill:: num_vert1ces(). Поиск Кенига, как свойство языка C++, позволяет это сделать. Если вызов функции не уточнен указанием пространства имен, компилятор C++ будет осуществлять поиск в пространстве имен аргументов с целью определения правильной функции для вызова*
namespace boost {
template <typename Graph>
void pail(Graphs g) {
typenamc graph_tга 11s<Graph> : : verti ces__si ze__type
N = num_vertices(g): // Разрешится в поиске Кенига
} // namespace boost
Графовые алгоритмы
Графовые алгоритмы из BGL отличаются от операций над графами тем, что они являются шаблонами функций, а не перегруженными функциями. Тем самым поиск Кенига неприменим к графовым алгоритмам BGL и получить к ним доступ можно только посредством префикса пространства имен boost : : или с использованием других методов, описанных в разделе 2.6.1. Например, для вызова алгоритма breadth_fi rst_search() префикс boost: : необходим:
boost: :breadth_first_search(g. start. visitor(vis)):
Именованные параметры функций
Многие алгоритмы BGL имеют длинный список параметров, чтобы обеспечить максимальную гибкость. Однако часто такая гибкость не требуется и было бы достаточным использовать значения по умолчанию для большинства параметров. Например, рассмотрим следующий шаблон функции, имеющий три аргумента.
tempi ate <typename X, typename Y. typename Z> void f (X x. Y у. Z z);
11ользователь должен иметь возможность передачи любого числа аргументов (и даже ни одного), а не указанные параметры должны получить значения по умолчанию, Также необходима возможность передачи аргумента для параметра у, но не для х или z. Некоторые языки программирования имеют прямую поддержку этих возможностей, называемых именованными параметрами (named parameters млн keyword parameters). При применении именованных параметров в качестве признака для привязки параметров аргументам используется непорядок параметров (как это принято), а имя, свое у каждого аргумента.
〃 Если бы C++ поддерживал именованные параметры, мы могли бы написать:
int д: int b: f (z»b. хТ: 〃 связать b с параметром z. а с параметром х // у получает значение аргумента по умолчанию
Конечно. C++ не поддерживает именованные параметры, но это свойство может быть реализовано с помощью небольшой хнтростн. Библиотека BGL включает класс, называемый bgl_named_params, который имитирует именованные параметры, позволяя строить списки параметров1. В следующем коде приведен пример вызова функции be〕lman_ford_shortest_path() с использованием техники именованных параметров. Каждый из аргументов передается функции, чье имя показывает, с каким параметром связать аргумент. Заметьте, что именованные параметры разделены не запятой, а тачкой. На класс bgl_named_parafns не ссылаются явно — он создается неявно при вызове weight_map(), а затем список аргументов расширяется путем вызовов к di stance_map() и predecessor_map().
bool г = boost::bellman_ford_shortest_paths(g* 1nt(N).
boost::weight_map(weight).
di stancejnap(&distance[0]).
predecessor^map(&pa rent[0])):
Порядок, в котором следуют аргументы, неважен, пока каждому аргументу соответствует правильная функция. Приведенный ниже вызов функции bellman- ford_shortest_paths() эквивалентен приведенное [у выше.
bool г = boost::bellman_ford__shortest_paths(gt int(N).
boost: :predecessorjnap(&parent[OH. d1stance_map(&distance[0]).
weight_map(weight)):
Это обобщение идиомы, описанной в |41].
Изучаем BGLИзучаем BGL
Изучаем BGL
Изучаем BGL
Как уже было сказано, концепции играют центральную роль в обобщенном программировании. Концепции — это определения интерфейсов, позволяющие алгоритму использовать много различных компонентов. Библиотека алгоритмов на графах (BGL, Boost Graph Library) определяет большой набор концепций, затрагивающий различные аспекты работы с графом, такие как обход графа или изменение его структуры. В данной главе мы познакомим читателя с этими концепциями, а также дадим мотивацию к выбору той или иной концепции BGL.
Из описания процесса обобщенного п ро 1*рам м про ван ия (см. главу 2 в начале) мы знаем, что концепции выявляются при анализе алгоритмов, используем ых для решения проблем в определенных областях. В данной главе мы изучим проблему отслеживания зависимостей файлов в системе сборки. Для каждой подзадачи будут рассмотрены обобщенны^, универсальные решения. В результате мы получим обобщенный алгоритм иа графе и его приложение к задаче нахождения зависимостей между файлами,
«По пути» мы также рассмотрим более «земные», но необходимые темы, такие как создание графового объекта и наполнение его вершинами и ребрами.
Зависимости между файлами
Обычным применением для графов является представление зависимостей. Программисты сталкиваются с зависимостями между файлами каждый день при компиляции программ. Информация об этих зависимостях используется такими программами, как make, или средами разработки вроде Visual С+十 для определения того, какие файлы должны быть перекомпилированы при генерации новой версии программы (или, в общем случае, для выполнения некоторой цели) после изменений в файлах с исходным кодом.
На рис. 3.1 изображен граф, у которого имеется отдельная вершина для каждого файла с исходным кодом, объектного файла н библиотеки, использованных
в программе killerapp. Ребро этого графа показывает, что целевой объект некоторым образом зависит от другого (например, заголовочный файл включается в исходный файл, а объектный файл компилируется из исходного).
Ответы на многие вопросы, возникающие при разработке системы сборки» такой как make, могут быть сформулированы в терминах гра巾а зависимостей. Нас может интересовать следующее:
Если необходимо собрать все цели, в каком порядке это должно быть сделано?
Присутствуют ли в зависимостях циклы? Цикл в зависимостях является ошибкой, и должно быть выдано соответств^чощее сообщение об ошибке.
Сколько шагов нужно сделать для сборки всех целей? Сколько шагов необходимо осуществить для сборки всех целей, если независимые цели собирать параллельно (например, на сети рабочих станций или на многопроцессорном компьютере)?
В следующих разделах зтн вопросы ставятся в терминах графов и разрабатываются Графовне алгоритмы для их решения. Граф, представленный на рис. 3.1, используется во всех примерах,
Подготовка графа
Перед тем как мы сможем решать поставленные выше вопросы, нам необходимо иметь представление графа зависимостей между файлами в памяти. То есть нам нужно, используя BGL, построить графовый объект.
Решаем/ какой「рафовы订 класс использовать
В BGL имеется несколько графовых классов, из которых можно выбирать. Хотя в силу обобщенного характера алгоритмов BGL можно использовать любой определенный пользователем графовый класс, мы ограничимся только классами из BGL. Основными классами в BGL являются adjacency_11st и adjacency matrix. Первый целесообразно использовать во многих случаях, особенно для представле* ния разреженных графов. Граф файловых зависимостей содержит очень малое число ребер для одной вершины, то есть он является разреженным. Класс adjacency _ma tri х подходит для представления плотных графов, но очень невыгоден для разреженных.
В этой главе используется исключительно ad jacency l i st, хотя многое из представленного можно без изменений использовать с классом adjacency_matr1x» так как он имеет почти идентичный интерфейс с adjacency_li st. Здесь мы используем тот же вариант adjacencyjlst, как и в разделе 1.4.1,
typudef adjacency_list<
lists, // Хранить исходящие ребра каждой вершины в std::11st vecS* // Хранить набор вершин в std::vector
directedS // Граф файловых зависимостей ориентированный > f11e_dep_graph:
Строим граф с помощью итераторов ребер
В разделе 124 мы показали, как можно использовать функции add__vertex() и add_edge() для создания графа. Эти функции добавляют вершины и ребра по одному за вызов, но во многих случаях необходимо добавить несколько ребер за один вызов. Для этого класс adjacencyj 1st имеет конструктор, который принимает на входе два итератора, определяющих блок ребер. Итератором ребер может быть любой Inputiterator, который разыменовывастся в std: :ра1г целых чисел (ij), представляющих ребро г*ра巾а. Целые числа i и j представляют вершины, где О 4 i V |Р| и 0 4/< |У|. Параметры п и m показывают соответственно количество вершин и ребер в графе. Эти параметры необязательны, но их примелелие увеличивает скорость создания графа. Параметр свойств графа р присоединяется к графовому объекту. Прототип конструктора, использующего итераторы ребер, записывается следующим образом:
tempi ate <typename EdgeIterator^
adjacency !1 st(Edgeiterator first. EdgeIterator last, vert!ces_s 1 ze type n = 0. edges_size_type m = 0. const Gf3phPropert1es& p = GraphPropertlesO)
В следующем коде показано применение «реберного» конструктора для создания графа. Итератор std:: i stream_1 terator служит основой итератора ввода, читающего данные о ребрах из файла. В файле задано число вершин графа, после чего приведены пары чисел, обозначающие ребра. Второй итератор ввода (создаваемый со значениями по умолчанию) является «заглушкой» для конца ввода. Ите* ратор std: :istreani_1 terator передается напрямую конструктору графа.
std:: i fstream fi le_1n(Mniakefi 1 e-dependencies. dat"):
typedef graph^tral ts<f 11 e_dep_graph>; :vertices_size_type sizjtype; size_t^pe n^vertlces:
fi1e_1n » nvertices: // прочесть число вершин
std:~istream^lterator<std::palr<size_type. size_type> > 1nput_begin(fiTe_in), input_end:
fl 1 e_dep_graph gdnput^begirT input_end7 n^vertlces::
Поскольку тип значения std: : 1 stream_1 terator — это std: ipalr, итератор ввода нужно определить для std: :pa1r.
namespace std {
template <typename T>
std: :istream& Qperator»(std: :istream& In. std: :pair<TJ>& p) { in » p.first » p.second: return Tn:
}
Порядок компиляции
Первый вопрос касался определения порядка сборки всех целевых объектов. Основной заботой здесь является обеспечение того, чтобы до сборки данной цели были построены все цели, от которых она зависит. Это та же самая задача, которую мы рассматривали в разделе 1.4.1, когда планировали выполнение заданий.
Топологическая сортировка через поиск в глубину
Как уже упоминалось в разделе 1.4.2, топологическое упорядочение может быть получено с использованием алгоритма поиска в глубину (depth-first search). Напомним, что при поиске в глубину посещаются все вершины в графе, начиная с любой вершины, и затем выбирается новое ребро. В следующей вершине выбн- рается другое ребро для нового шага. Этот прогресс продолжается, пока не заходит в тупик (вершина без исходящих ребер до еще не посещенных вершин)- Затем алгоритм возвращается на последнюю посещенную вершину, у которой есть другая, еще не пройденная, смежная вершина. После того как будут посещены все вершины» которых можно достичь из выбранной в начале вершины, выбирается еще одна из нейройденных вершин, и поиск продолжается. Ребра, которые ал го- ритм обошел в каждом из этих поисков, образуют дерево поиска в глубину, а все такие поиски дают лес поиска з глубину. Лес поиска в глубину не уникален для данного графа. Обычно можно найти несколько допустимых лесов для данного графа, так как порядок посещения вершин не фиксирован. Каждое уникальное упорядочение имеет свое дерево поиска в глубину.
Существуют две полезные метрики при поиске в глубину — порядок посеще^ ния и порядок окончания обработки вершины. Представим целочисленный счетчик, значение которого в начале равно нулю. Каждый раз при первом посещении вершины значение счетчика записывается как момент посещения для данной вершины и увеличивается на единицу. Аналогично, когда произведен обход всех вершин, достижимых из данной, значение счетчика записывается как момент окон- чан ия данной верят ины. Значение счетчика посещения родительской вершины всегда меньше, чем у дочерней. И наоборот, значение счетчика окончания обработки меньше у дочерней вершины, чем у родительской. Поиск в глубину на графе файловых зависимостей показан на рис. 3,2. Ребра деревьев поиска в глубину
отмечены черными стрелками, а у вершин подписаны значения их счетчиков посещения и окончания обработки (через косую черту).
Связь между топологическим упорядочением и поиском в глубину можно объяс- ннть, рассмотрев три различных случая в некоторой точке алгоритма поиска в глубину, где проверяется ребро (以,г). В каждом случае значение счетчика в момент окончания обработки V всегда меньше того же значения для 〃. Таким образом, моменты окончания обработки задают топологическое рторядочение (в обратном порядке).
Вершина о еще не посещена. Это означает, что 口 будет потомком и н значение счетчика окончания обработки вершины будет меньше, чем у wT так как при поиске в глубину потомки и обрабатываются прежде, чем и.
Вершина п была посещена ранее в другом дереве поиска в глубину. Значит, значение счетчика окончания обработки 吞 меньше, чем 〃.
Вершина г была посещена ранее в этом же дереве нонска в глубину. Если это случилось, граф содержит цикл и его топологическое упорядочение невозможно. Цикл — это путь из ребер, такой, что первая и последняя вершина пути — одна н та же.
Главно比 частью поиска в глубину является рекурсивный алгоритм, который вызывает самого себя для каждой смежной вершины. Мы создадим функцию topo_sort_dfs(), которая реализует поиск в глубину, модифицированный для вычисления топологического упорядочения. Первая версия этой функции будет необобщенной функцией, решающей задачу «в лоб». В следующих разделах мы внесем изменения, которые позволят создать обобщенный алгоритм.
Параметры topo_sort_dfs() включают в себя граф, стартовую вершину, указатель на массив для записи топологического порядка, а также массив для записи посещенных вершин. Указатель topo_order устанавливается на конец массива и затем уменьшается для получения прямого топологического порядка из обратного.
Заметьте, что topo_order передается по ссылке, поэтому его декремент модифицирует оригиншх объекта при каждом рекурсивном вызове (если бы topo^order передавался по значению, декремент влиял бы только на копию исходного объекта). В листинге 3.1 приведена реализация функции topo_sort_dfsО.
Листинг 3.1. Функция topo_sort_dfs()
void topo_sort_dfs(const file_dep_graph& g, vertex_t u. vertex_t*& topo^order. 1nt* mark) {
mark[u] - 1; // 1 означает ,,пoceщeннaя,^, 0 - "еще не посещенная"
《Для каждой смежной вершины сделать рекурсивный вызов 》 *^-topo_order - и:
}
Типы vertex_t и edgc t являются соответственно дескрипторами вершин и ребер для fi 】e_dep_graph.
typedef graph__tгаi ts<f 11 e_dep_graph>: :vertex__descriptor vertex_t:
typedef graptT trai ts<fi1e^depT_graph>::edge_descri ptor edge_t Г
Маркировка вершин с использованием внешних свойств
Каждая вершина во время поиска должна быть посещена только один раз. Для записи факта посещения верившы мы можем пометить ее в массиве, содержащем пометки для всех вершин. В общем случае будет использоваться термин внешнее хранилище свойств (external property storage) в качестве названия при сохранении свойств вершин или ребер в структуре данных вроде массива или хэш-таблицы, отдельной от графового объекта. Пометки — только одно из свойств, которые можио хранить в структурах данных, внешннх по отношению к графу. Значения свойств ищутся по ключу, который может быть легко получен из дескриптора вершины или ребра. В нашем примере мы используем версию adjacency_list, где дескрипторы вершин — целые числа от нуля до num^verti ces(g) - 1. Поэтому дескрипторы вер- шип сами могут быть использоваР1Ы в качестве индексов к массиву пометок.
Досг/п к смежным вершинам
В функции tcpo_sort_dfs() нам необходимо получить доступ к вершинад, смежным с вершиной 〃. Концепция AdjacencyGraph определяет интерфейс для доступа к см еж* ным вершинам. Функция ad jacent_vert1 ces () получает вершину и граф в качестве аргументов и возвращает пару итераторов, чей тип значения — дескриптор вершин. Первый итератор указывает на первую смежнун) верпшну, а второй — за конец последовательности смежных вершин. Смежные вершины выдаются итераторами в произвольном порядке. Оба этих итератора шиеюттап adjacencyjterator из класса grdph—trdits. В справочном разделе по adjacency_l1st (раздел 14.1.1) написано, что тип adjacencyj 1 st моделирует концепцию AdjacencyGraph, а значит, мы можем корректно использовать функцию acijacent_vertices() с нашим графом файловых зависимостей. Код для обхода смежных вершин в topo__sort_dfs() показан ниже:
< Для каждой смежной вершины сделать рекурсивный вызов > = graph_trai ts<f11e_dep_graph>::adjacency_i terator vi, vi_end;
for (tie(v11 v1_end) = adjacent_vertices(ut g): vi != vi_end; ++vi) if (markE^vi]-— 0)
topo_sort_dfs(g, *vi. topo_order. mark):
Обход всех вершин
Один из способов гарантировать, что упорядочение будет получено для каждой вершины графа (а не только для вершины» достигаемой из некоторой стартовой вершины), — поместить вызов topo_sort_dfs() в цикл по всем вершинам графа. Интерфейс для обхода всех вершин графа определен в концепции VertexListGraph. Функция verti ces О получает графовый объект н возвращает пару итераторов вершин, Цикл по всем вершинам и создание массива пометок приведены в функции topo_sort() (листинг 3.2),
Листинг 3・2. Функция topo_sort()
veld topo^sort(const file_dep_graph& g, vertex t* topojorder)
std::vector<int> mark(num^vertices(g). 0):
graph_traits<file_cfep_graph> : :vertex_iterator vi. vi_end:
for (tie(vi, viend) = vertices(g); vi != viend: -н-vi)
if (mark[*vi] =a 0)
topo_sart_dfs(2. *vi, topo^order. &пагк[0]};
}--
Для удобства нам нужно преобразовать верш ин ы - числа в ассоциированн ые с ними имена целей. Список имен целей (в порядке, соответствующем номеру вер- шшты) храиится в файле, так что возможно прочитать этот файл и сохранить имена в массиве, который затем будет использован для печати имен вершин.
std: :vector<std: :string> nanie(num_vertices(g));
std::1fstream namejn("makefi1e-target-names,dat"):
graph_trait$<fi1e_dep_greph>:;vertex一iterator vi. vi_end:
for (tie(vi. vi_end) = vertices(g): Ui !=• vi_end: ++v1)
name_in » name[*vi]:
Теперь мы создадим массив order для хранения результатов и применим функцию топологической сортировки.
std::vector<vertex_t> order(num_vertices(g));
topo_sort(g. &order[0] + num^vertices(g)):
for Tint i = 0; i v nuin_vertices(g): ++i)
std: :cout « name Eorder [i J] « std: :endl:
В результате получится следующий список:
zag.cpp
zig.cpp
foo.cpp
bar.cpp
zow.h
boz.h
zig.o
yow.h
dax.h
zag.。
foo.o
bar.o
3 Зак 375
1ibfOQbar.d
libzigzag.a
killerapp
Циклические зависимости
Одним из важных предположений в предыдущем разделе было то, что граф файловых зависимостей не имеет циклов. Как установлено в разделе 3.3.1, граф с циклами не имеет топологического упорядочения. Формально верный Makefile не будет иметь циклов, но наша система сборки должна обнаруживать такие ошибки и сообщать о них.
Поиск в глубину может быть использован и для задачи выявления циклов. Если он будет применен к графу с циклом» одна из ветвей дерева поиска замкнется на себе, то есть, найдется ребро из вершины к одному из ее предков по дереву* Назо* вем такое ребро черным. Появление черного ребра можно обнаружить, если мы изменим схему отметки вершин. Вместо того чтобы отмечать каждую вершину как посещенную или не посещенную, мы будем использовать трехцветную схему: белый цвет будет обозначать не посещенную вершину, серый — посещенную, но не обработанную (в процессе поиска потомков), и черный — вершину, посещенную вместе со всеми потомками. Трехцветная схема полезна для некоторых графовых алгоритмов, поэтому заголовочный файл boost/graph/properties.hpp определяет следующий перечислимый тип:
enum defaul t_col or_type { whlte_color, gray__color. black_color }:
Цикл в графе вдентифицируется по серой смежной вершине — это означает, что ребро замыкается на предке. Код в листинге 3.3 представляет версию поиска в глубину, модифицированную для обнаружения циклов.
Листинг 3.3. Поиск в глубину с обнаружением циклов
bool ha s_cyc1e_d fs(con st file_dep_graph& g, vertex_t u, default_coior^ype* color)
{
color[u] a gray color;
graph_tra i ts<fiTe_dep__graph>: : adjacenc.y_i terator №, vi_end;
for (tie(v1. vi_end) = adjacent_vertices(u. g); vi != vi_end; 卄vi)
if (colort*vi] — whlte_colorT
if (has_cycle_dfs(gt *vi. color))
return true: // если обнаружен цикл, то немедленный возврат else if (color[*vi] 一 gray_color) // *vi является предком return true:
cqIoMW = bl ack_col or; return false:
Как и в тополоппеской сортаровке, в функции has_cycle() (листингЗЛ) рекурсивный вызов помещен внутрь цикла по всем вершинам, так что возможно охватить при проверке все деревья поиска в графе.
Листинг 3.4. Функция has_cycle()
bool has_cycle(const file dep_graph& g)
std::vector<default__color_type> col or(num_verti ces(g)* white_color):
《На пути» к обобщенному поиску в глубину: посетители 67
graph_tra1ts<file__dep_graph>: :vertexjterator vi. vi_end;
for (tie(*. vi_end) = vertices(g); vi != vi end; ++vi)
If (color[*vi] — white^color)
if (hds cyc】 jdfs(g, *vi, &color[0])) return false:
«На пути» к обобщенному поиску в глубину: посетители
На данный момент мы ршеем две законченные функции: topo_sort() и has_cycle(), каждая из которых реализована в виде поиска в глубину, хотя и немного по-разному. Однако их существенная схожесть дает прекрасную возможность для повторного использования кода. Было бы намного лучше» если бы мы имели один алгоритм для поиска в глубину, который являлся обобщением topo_sort() и has_cyclе() и использовал параметры для специализации поиска в глубину в каждой из задач.
Дизайн библиотеки STL подсказывает нам, как можно создать подходящим образом параметризованный алгоритм поиска в глубину. Многие из алгоритмов STL могут быть специализированы параметром в виде определенного пользователем объекта-функции. Нам хотелось бы параметризовать поиск таким же способом, реализовав в topo_sort() н has_cycle() передачу объекта-функции.
К сожалению, в нашем случае ситуация несколько более сложная, чем в типичном алгоритме STL. В частности, есть несколько различных мест, где должны происходить специализированные действия. Например, функция topo_sort() записывает упорядочение в самом конце рекурсивной функции topo_sort_dfs 0,тогда как hd$_cyc1e() требует включения действия внутри цикла по смежным вершинам.
Решением этой проблемы является применение объекта-функции с более чем одним членом для обратного вызова. Вместо единственной функции operatorO мы используем класс с несколькими функциями-методами класса, вызываемыми из различных мест (событийных точек). Такой объект-функция называется посетителем алгоритма (algorithm visitor). Посетитель для алгоритма поиска в глубину будет иметь пять функций: discover_vertex()T tree_edge{), back_edge(), forward_or_cross_edge() и finish_vertex(). Также вместо итерации по смежным вершинам мы будем обходить исходящие ребра. Это позволит передавать дескрипторы ребер функциям посетителя и тем самым предоставлять больше информации посетителю, определенному пользователем. Следующий код функции поиска в глубину, приведенный в листинге 3.5, имеет параметр шаблона для посетите刀я.
Листинг 3.5. Функция поиска в глубину с посетителем
tempi ate <typename Visitor>
void dfs_vl(const file_dep_graph& g, vertex_t u. default_coTor_type* color, Visitor vis) {-
color[u] - gray__co1or:
vi s.discover_vertex(u. g):
graph_traits<fi le_dep_graph> : :out__edge_i terator ei. ei_end;
for (t1e(ei. el^end) = out_edges(ij. g)7 ei !- e1_end; ++ei) {
If (color[target(*ei . g)j — white_color) { vis.treeledge(*ei. g):
продолжение W
Листинг 3.5 {продолжений
dfs_vl(g. target(*ei. g). color, vis):
} else if (color[target(*ei, g)] == gray col or) vis.back_edge(*e1. g);
else
vis.forward_or_cross_edge(*ei, g): }
co1or[u] e black_color: vls.finish vertex(u. g); }-
template <typename V1sitor>
void generic dfs vl(const file_dep_graph& g. Visitor vis) {--
std::vector<default_color_type> color(num_vert1ces £ g). white__color): graph_traits<f11e_dep_graph>::vertex_iterator vi. vi_end;
for (tie(vi. vi_end) - vertlces(g): Ui != vi_end: ++?i) { if <color[*vi] — white_color)
df$_vl(g, *vK &со1ог[0], vis):
} )
Пять функций-методов класса посетителя обеспечивают необходимую гибкость, но пользователь, которому нужно, скажем, только одно действие, не должен писать четыре пустые функции-метода класса. Эта проблема может быть легко решена созданием посетителя по умолчанию, из которого могут быть созданы посетители, определяемые пользователем (листинг 3.6).
Листинг 3.6. Структура посетителя со значениями по умолчанию
struct default_dfs_visitor { 〃 посещение вершины template <t>pename V. typename G> void discover^vertexCV- const G&) { } // встретилось ребро дерева template <typename E, typename G> void tree_edge(E, const G&) { } // встретилось обратное ребро template <typenaroe E. typename G> void back_edge(E. const G&) { } 〃 встретилось прямое или поперечное ребро template <typename Е, typename G> void forward_or_cross^edge(E. const G&) { } // окончание обработки tempi ate <typename V. typename G> void finish vertexCV, const G&) { }
}:-
Для демонстрации того, что обобщенный алгоритм поиска в глубину может решить наши проблемы, мы переделаем функции topo_$ort() и has_cycle(). Во-первых, нам нужно создать посетителя, записывающего топологическое упорядочение в событийной точке ^окончание обработки вершины». Код этого посетителя выглядит так:
struct topo^visltor : public default_dfs_v1s1tor { topo^vlsitor(vertex_t*& order) : topo_order(order) { } void finish_vertex(vertex^t u, const file_dep_graph&) {
*'-topo order » u:
}
vertex t*& topo order;
}: ~-
Только две строки содержатся в коде функции topo^sortO при ее реализации с использованием обобщенного поиска в глубину. В первой строке создается объект-посетитель, а во второй происходит вызов обобщенного поиска,
void topo sort(const file dep graphs g. vertex_t* topo_order)
{''-
topovisitor vis(topo_order);
generic_dfs vl(g, visT;
}
Для реализации функции has_cyc1e() мы используем посетителя» который записывает наличие цикла, когда встречается обратное ребро (back edge).
struct cycle_detector : public 6ef aul t_dfs_vl si tor { cycle_detector(boolS cycle) : has_cycle(cycle) { } void back_edge(edge t. const file2dep_graph&) { has_cycTe = true:
bool& hasjrycle:
};
Новая функция hd5_cycle() создает объект обнаружения цикла и передает его обобщенному алгоритму поиска в глубину.
bool has_cycle(const file_dep graphs g)
bool has_cycle = false:
cycle_detector vis(has_cycle);
generic_dfs_vl(g. vis):
return has_cycle;
Подготовка графа: внутренние свойства
Прежде чем перейти к следующему вопросу по файловым зависимостям, уделим немного внимания другому типу графа. В предыдущих разделах мы использовали массивы для хранения такой информации, как имена вершин. Когда свойства вершин и ребер имеют то же время жизни, что и сам граф, может быть более удобным включить нх непосредственно в графовый объект (назовем такие свойства внутренними). При написании собственного графового класса можно включить поля-методы класса дая этих свойств в структуру вершины или ребра.
Класс adjacency」1 st имеет параметры шаблона VertexProperti es и EdgeProperties, позволяющие присоединять (приписывать) произвольные свойства к вершинам и ребрам. Эти параметры шаблона предполагают класс property<Tag. Т> в качестве типа аргумента. Здесь Тад — тип( задающий свойство, аТ устанавливает тип объекта- свойства. Существует некоторое количество предопределенных свойств (см. раздел 15*2.3), таких как vertex_name_t н edge_weight_t. Например, для присоединения std:: string к каждой вершине можно использовать следующий тип-свойство:
рroperty<vertex_n ame_t. std::string>
Если предопределенных тегов свойств недостаточно, можно создать новый тег. Один из способов — опредетпггь тип-перечисление с именем vertex_xxx_t или edge_xxx_t, который содержит перечисление с тем же именем (без J). Затем можно использовать BOOST_INSTALL_PROPERTY для специализации классов свойств property_kind и property^um. Зададим свойство периода компиляции в виде стоимости (cost), которое мы будем испо-тьзовать в следующем разделе дая вычисления полного времени компиляции.
namespace boost {
enum vertex_compi1e_cost_t { vertex_comp1le^cost = 111 }: // уникальный
номер
BOOST_INSTALL_PROPERTY(vertex t compile_cost):
} ~'
Класс property имеет необязательный третий параметр. Он нужен для вложения нескольких классов property с добавлением множества свойств каждой вершине или ребру, В листинге 3.7 приведен код созданнх нового typedef для графа, при этом одновременно добавляются свойства вершин и ребер.
Листинг 3.7. Определение типа для графа со свойствами
typedef adjacency_list<
lists, lists.di rectedS, // свойстваlists, lists.di rectedS, // свойства// Хранить исходящие ребра в std::1ist
lists, lists.
di rectedS, // свойства
lists, lists.
di rectedS, // свойства
// Хранить набор вершин в std::11 st
// Граф файловых зависимостей ориентированный вершин
pгqperty<vertex_name_t. std::string,
property<vertex_coriipi 1 e_cost_t. fl oat.
property<vertex_di stance_t~ 门 oat.
property<vertex_color_tt default^color_type> > > >.
// свойства ребер
property<edge_wei ght t * fl oat>
> fi le_dep__graph2;
Мы также изменили второй аргумент шаблона adjacency_Hst с vecS на lists. Это приводит к важным последствиям. Удаление вершины из графа теперь выполняется за постоянное время (с vecS удаление вершины линейно по числу вертин и ребер). С другой стороны, тип дескриптора вершины больше не является целым числом, поэтому хранить свойства в массивах и использовать вершину как смещение больше нельзя. Однако отдельное хранилище теперь не требуется, так как мы сохраняем свойства вершин в графе.
В разделе 1*2*2 было введено понятие отображения свойств. Напомним, что отображение свойств — это объект, который может быть использован для преобразования ключа (например, вершины) в значение (например» название вершины). Когда были заданы свойства для adjacency_l 1st (как мы только что сделатш), отображения для этих свойств могут быть получены с применением интерфейса PropertyGraph. Следующий код показывает пример получения двух отображений свойств: одного — для названий вершин и другого — для времени компиляции. Класс свойств property_map предоставляет тип отображения свойств.
typedef property_map<fi le_dep_graph2. vertex__name_t>: :type name_map_t ;
typedef property2nidp<f11e^de|T_graph2. vertex^conpi' e_cost_t>: : type
comp 1 1 e__cost_map__t :
typedef property_map<fi 1 e_dep_graph2. vertex di $tance_t>:: type
distance_fnap_t:
typedef propertyjnap<fi1e_dep_graph2. vertex_color_t>::type colоr_map_t:
Функция get() возвращает объект отображения свойств:
name_map_t патентар = get(vertex_name. g);
compi 1 e_cost_map_t conipl le_costjnap = get(vertex_compi 1 e_cost. g): distance_map_t distance_map = get(vertex_distance, g); colorjnap_t cblorjnap -~get(vertex_color."g):
Данные no оценке времени компиляции для каждой цели в make-файле будут храниться в отдельном файле. Файл читается при помощи std: :1 fst ream, и свойства записываются в граф с использованием отображения свойств name^map и сош- pile_cost_map. Эти отображения свойств моделируют LvaluePropertyMap, а значит, имеют операцию operator[](), которая отображает из дескрипторов вершин в ссылки на соответствующие объекты свойств вершин.
std: :ifstream nameJnC'makefile-target-nanies(dat'');
std::i fstream compi1e_co st_1n("t a rge t ^ comp11e-co st s.dstи): graph_tга1ts<fi1e_dep_graph2>::vertex_i terator vi. vl end; for 压leWL vi_end) = vertices(g); vT != vi^end; ++vT) { namjin » name_map[*vi ]: compile一cost in » compile_cost__map[*vi];
}- -
В следующих разделах мы изменим функции топологической сортировки и поиска в глубину, чтобы использовать интерфейс отображений свойств для доступа к свойствам вершин вместо жесткого прописывания доступа через указатель на массив.
Время компиляции
Следующие вопросы, на которые нам нужно ответить: «Сколько времени займет компиляция?» и «Сколько времени займет компиляция на параллельном компьютере?» На первый вопрос ответить легко. Мы просто суммируем время компиляции для всех вершин графа. Ради интереса выполним это вычисление с помощью функции std: accumulate. Чтобы использовать данную функцию, нужны итераторы, которые при разыменовании дают стоимость компиляции для вершины. Итераторы вершин графа не предоставляют подобной возможности, так как при разыменовании выдают дескрипторы вершин. Вместо них мы применим класс graph_property_i ter_range (см. раздел 16.8) для генерации подходящих итераторов.
graphj)roperty_iter_range<fi1e_dep_graph2r
vertex__compi 1 e_cost_t>: :iterator ci. ci_end; tiefci. c1_end) = get_property_iter_range(g. vertex_comp1le_cost)7 std::cout << "полное время последовательной компиляции: ”
« std:accumulate(ci, ci_end, 0.0〉 « std::end1:
Вывод этого участка кода будет таким:
полное время последовательной компиляции; 21.3
Теперь предположим, что мы имеем параллельный супер комп ьютер с сотня* ми процессоров. При наличии независимых друг от друга целей они могут компилироваться одновременно на разных процессорах* Сколько времени будет занимать компиляция? Для ответа на этот вопрос необходимо найти критический путь через граф файловых зависимостей. Иначе говоря, нам нужно найти самый длинный путь через граф.
Вклады различных целей во время компиляции файла libfoobar.a изображены на рис, 3.3. Черными стрелками обозначены зависимости файла libfoobar.a. Предположим, что мы уже определили, когда закончится компиляция Ьаг.о и foo.o. Тогда время компиляции для Libfoobar.a будет максимумом времен компиляции для Ьаг.о н foo.o плюс время их компоновки в файл библиотеки.
Теперь, когда известно, как вычислить «расстояние» («distance») для каждой вершины, возникает вопрос: в каком порядке мы должны обойти вершины? Понятно, что если есть ребро (〃,и), принадлежащее графу, то нужно вычислить расстояние для и перед г\ так как вычисление расстояния для г требует знания расстояния до и. Теперь необходимо просмотреть вершины в топологическом порядке.
Рис. 3.3. Вклады различных целей во время компиляции libfoobar.a
Обобщенная топологическая сортировка и поиск в глубину
Так как был заменен тип графа с fl 】e_dep_graph на fi 1 e_dep_graph2, стало невозможным использовать функцию topo_sort(), разработанную в разделе 3,4. Несоответствие касается не только графового типа, но и массива col or, использов^ного в де- neric_df5_vl(), который показывает, “то дескрипторы вершин являются целыми числами (это неверно для file_dep_graph2). Эти проблемы ведут к созданию даже более общей версии топологической сортировки и лежащего в ее основе поиска в глубтшу. Параметризация функции topo_so「t() осуществляется в несколько этапов:
Отдельный тип f 11 e_dep_graph заменяется н а параметр шаблона Graph. Простая смена параметра шаблона ничего не даст, если нет стандартного интерфейса всех типов фафов, которые мы хотим использовать с алгоритмом. Здесь приходят на помощь концепции обхода графов из BGL* Для topo__sort() нам нужен тип графа, который моделирует коьщепции VertexListGraph и IncidenceGraph.
Использование vertex_t* для вывода упорядочения является слишком жестким ограничением. Общий способ вывода последовательности элементов предполагает применение итератора вывода, как это происходит в алгоритмах STL. Это дает пользователю намного больше вариантов для хранения результатов.
Нам нужно добавить параметр для отображения цветов. Необходимо учесть только самые существенные моменты. Функция topo_sort() должна иметь возможность отображать дескриптор вершины в объект-маркер для данной вершины. Библиотека Boost Property Map Library (см. главу 15) определяет минимальный интерфейс для осуществления этого отображения. В нашем случае используется интерфейс Lvalue Property Мар. Внутреннее отображение col or_map, полученное из графа в разделе 3.6, реализует интерфейс Lvalue Property Мар, так же как и массив цветов из раздела 3.3.4. Указатель на массив цветовых маркеров может быть применен как отображение свойств благодаря перегруженным функциям из boost/property_map*hpp, которые адаптируют указатели под интерфейс LvaluePropertyMap.
В листинге 3.8 приведена реализация обобщенного topo_sort()* Далее будут обсуждаться обобщенные topo_vis1tor и generic_dfs_v2().
Листинг 3.8. Функция topo_sort(), вторая версия
tempi ate <typename Graph, typename OutputIterator, typename ColorMap> void topo sort(const Graph& g, Outputiterator topo_order, ColcrMap color) {
topo_visitor<0utputlterator> vis(topo_order): generic dfs v2(g. vis. color):
}
Сейчас класс topo_visitor является шаблоном класса (листинг 3.9)f который нужно приспособить под итератор вывода. Вместо декремента вставляется инкремент итератора вывода (декремент итератора вывода запрещен). Для получения того же самого обращения, как в первой версии topo^sortO, пользователь может передать внутрь обратный итератор или, например, итератор вставки в начало списка.
Листинг 3.9. Класс topo_visitor
template <typename Outputlterator>
struct top。一visitor : public default_dfs_vis1tor {
topo_visTtor(OutputIterator& order) :~topo_order(order) { }
template <typename Graph〉
void finish_vertex(typename graph_traits<Graph>::vertex_descriptor u. const Graph&)
{ *topo_order++ - u: }
OutputlteratorS topo_order:
}:
Обобщенный поиск в глубину (листинг 3.10) изменяется через параметризацию типа графа и отображение цветов. Так как неизвестен тип цвета, необходимо запросить тип его значений (с помощью класса свойств property_t「dits) у ColorMap. Вместо использования констант, таких как white_color, мы применяем цветовые функции, определенные в col о r_t га Its.
Листинг 3.10. Обобщенная функция gene「i jdfs_v2()
tempi ate <t^pename Graph. typename Visitor, typename ColorMap>
void generic dfs v2(const Grdph& g, Visitor vis. ColorMap color)入
продолжение 夕
Листинг 3.10 {продолжений
{
typedef color_trai ts<typename
property_tra i ts<ColorMap>::value_type> ColorT;
typename graph_tra1ts<Graph>:7vertex_iterator vi, vi_end;
for (tie(vi. vf end) = vertices(g); vi != vi_end: ++vi)
color[*vi] - ColorT::white();
for (tieCvlt vi_end) - vertlces(g): vi != vi_end; ++v1)
if (color[*vi] ■= ColorT::white())
dfs_v2(g. *vi. color, vis):
}
Логическое содержание df$_vl не требует изменений, однако нужно произвести небольшие модификации из-за параметризации типа графа. Вместо жесткого задания ve「tex_t в качестве дескриптора вершины мы извлекаем соответствующий дескриптор вершины из графового типа, используя класс свойств graph__traits. Полностью обобщенная версия функции поиска в глубину приведена в листинге З.П. Она, в сущности, аналогична depth^fi rst^vl si t () из BGL
Листинг 3.11. Функция dfs_v2()
template <typename Graph, typename Colo「Map, typename Visitor>
void dfs_v2(const Graphs g.
typename graph_traits<Grapli> : : vertex^descrl pt о г u.
ColorMdp color. Visitor vis)
{
typedef typename property_tralts<Co1оrMap>::va1ue^type color_type:
typedef color_traits<color_type> ColorT:
color[u] ■ ColorT: :Qrayp:
v1s.dl5cove「_ve「tex《u. g):
typename graph__tгаits<Graph>: ;out_edgeJterator ei. ei_end:
for (tie(ei. e,_end) = out_edges(I g)7 ei != ei_end: Ki)
if (color[target(*e1. g汀==ColorT::white()) {
vis.tree_edge(*ei, g);
dfs v2(g? tdrget(*ei. g). color, vis):
} else if (color[target(*ei. g)] — CotorT::gray()) vis.back_edge(*ei. g);
else
vis.fоrward^or_cross_edge(*e11 g):
co1or[u] - ColorT: iblackO:
vis.finish_vertex(u, g):
Реальные функции BGL depth_fi rst_search() и topological_sort() очень похожи на обобщенные функции, которые мы разработали в этом разделе. Детальный пример использования функции depth_first_seerch() рассмотрен в разделе 4.2, а полное описание depth^f 1 rst_search() приведено в разделе 13.2.3. Документация по topological_sort() дана в разделе 1325.
Время параллельной компиляции
Теперь, когда мы имеем обобщенные топологическую сортировку и поиск в глубину, можно решать задачу определения полного времени компиляции на параллель* ном компьютере. Во-первых, проведем топологическую сортировку, сохранив результаты в векторе topo_order. Затем мы передадим обратный итератор в topo.sort () для того, чтобы получить прямой топологический порядок (а не обратный)*
std::vector<vertex_t> topo_order(num_vert1ces(g)):
topo_sort(g, topo_order.rbegin()P color_map):
Перед вычислением времен компиляции необходимо подготовить отображение расстояний (его мы будем использовать для хранения результатов вычислений времени компиляции) (листинг 3.12). Для вершин, которые не имеют входящих ребер (будем считать их исходными вершинами), инициализируем их расстояние нулем, так как компиляция этих целей в make-файле может начаться сразу. Всем другим вершинам присваивается бесконечное значение расстояния. Найдем исходные вершины, помечая все вершины с входящими в них ребрами.
Листинг 3.12. Подготовка к вычислению времен компиляции
graph_tralts<file_dep_g гa ph2>::ve гtе x_i t ег a to г i, i_end; graph2tra1ts<file^de[rgraph2>: :adjacency_1terator Gi, vi_end;
// найти исходные вершины путем пометки всех вершин с входящими ребрами
for (tied. Lend) = vertices(g): i 口 i_end: ++i)
co)or__map[*T] = white_color:
for (tied, i_end) - vertices(g); i !■ i_end;卄i)
for (tie(vi7 vi_end) = adjacent_vertices(*i. g): vi !- vi_end: ++vi) color_map[*vil = black^color:-
// инициализируем расстояния в 0. а для исходных вершин прмсванваем время компиляции
for (tied. i_end) - vertices(g); 1 仁 i_end: ++i)
If (co1or_map[*i]工=white_color)
distance_map[*i] = compiTe_cost_map[*i]; else
distance_rnap[*i]二 0;
Теперь все гатово для вычисления расстояния. Мы проходим через все вершины, записанные в topo_order, и для каждой из них обновляем расстояние (полное время компиляции) до каждой смежной вершины. Это несколько отличается от того, что описано выше. До этого мы говорили о том, что от каждой вершины мы смотрели «вверх»1 по графу для вычисления расстояния до этой верппшы. Сейчас же мы пе- реформул ировали вычисления так, что мы проталкиваем расстояния «вниз» по графу. Причина такого изменения состоит в том, что для прохождения 嘛вверх方 требуется доступ к входящим ребрам вершины, а этого наш тип графа не предоставляет.
$td: :ve€torvyertexJ:A: iterator ui;
for (ui = topo_order.begin(): ui I- topo_order.end(); ++ui)(
vertex_t u〕*ui;
for (t7e(vi. v1_end) = adjacent_vertices(и. g): vl != vLend; ++vi) if (dlstance_map[*vi] < dlstance^mapCu] + compi1e_cost_map[*vi]) distance_map[*vi] - distance_map[u] + compile_cost__mapL*vi ];
}
Максимальное значение расстояния среди всех вершин дает нам искомое полное время параллельной компиляции. Для создания итераторов свойств по рас- стояшгям вершин вновь применяется graph_propertyj ter_range. Функция std: :тех_е! е- mentO используется для нахождения максимума.
g га ph_p rope rty_i t ег_га nge< f 11 e_dep_graph2, vertex_distance_t>::iterator ci. ci_end;
tie《ci. ci_end) - get_property_iter_range(g» vertex_distance);
Против направления стрелою — Примеч. перев.
std::cout « "полное время параллельной компиляции:" « ^std: :rnax_elenient(c1, ci_end) « std: :erdl ;
В результате будет выведена такая запись:
полное время параллельной компиляции: 11.9
Для каждой цели в make-файле, изображенном на рис. 3.4, показаны два числа, означающие время его собственной компиляции и время, за которое он будет скомпилирован при параллельной компиляции. Критический путь обозначен черными стрелками.
Рис. 3.4. Время компиляции и аккумулированное время компиляции для каждой вершины
Итоги
В этой главе мы применили BGL для получения ответов на вопросы, которые могут возникнуть при создании системы сборки программного обеспечения: в каком порядке нужно собирать цели make-файла? Нет ли циклических зависимостей? Сколько времени займет компиляция? Ответы на эти вопросы были получены при рассмотреннн топологического упорядочения ориентированного графа и его вычислении алгоритмом поиска в глубину.
При реализации решений для представления графа файловых зависимостей мы ис11о.тьзовали класс adjacency^! 1 st из BGL. Была написана простая реализация топологической сортировки и обнаружения циклов. После определения общих мест кода он был преобразован в обобщенную реализацию поиска в глубину. Мы использовали посетителей алгоритма для параметризации поиска в глубину н затем написали конкретных посетителей для реализации топологической сортировки н обнаружения циклов.
Мы также рассмотрели различные варианты класса adjacencyjist, которые позволили приписать свойства (имя вершины и время компиляции) к вершинам графа. После этого поиск в глубину стал более обобщенным благодаря параметризации графового типа и метода доступа к свойствам. Наконец, были прнмене- нн топологическая сортировка и поиск в глубину для вычисления времени компиляции всех файлов на параллельном компьютере.
Основные алгоритмы на графах
Поиск в ширину
Поиск в ширину (breadth-first search, BFS) — один из основных способов получения информации о графе, который можно применить при решении множества различных задач. Библиотека BGL имеет обобщенную реализацию поиска в ширину в виде алгоритма breddth_fi「st_sedrch(). Этот функциональный шаблон параметризован, поэтому может быть использован во многих ситуациях. В данном разделе будет описан и применен для вычисления чисел Бэкона алгоритм поиска в ширину.
Определения
Поиск в ширину — обход гра。% который посещает все вершины» достижимые из данной исходной вершины. Порядок посещения вершин определяется рассто- 只ннем от исходной вершины до каждой вершины графа. Ближние вершины посещаются раньше, чем более удаленные.
Алгоритм поиска в ширину можно представить себе в виде волны, которая распространяется от камня, брошенного в воду. Вершины на гребне одной волны находятся на одинаковом расстоянии от исходной вершины. Поиск в ширину для простого графа показан на рис. 4.1, Порядок посещения вершин имеет последовательность {d}, {/,g}, {с, Л, Ь, е}, {〃} (вершины ст*рупппровалы по их расстоянию от вершины d).
При посещении вершины и ребро (и,己),которое привело к ее посещению, называется древесным ребром. Все вместе древесные ребра образуют дерево поиска в ширину с корнем в исходной вершине. Для данного древесного ребра (и, и) вершина и называется предком, или родителем, для V. Древесные ребра на рис. 4.1. обозначены черными линиями, а все остальные — серыми.
Вершины графа помечены кратчайшим расстоянием от исходной вершины d. Кратчайшее расстояние 5($,己)от некоторой вершины s до вершины 口 一 минимальное количество ребер в любом пути из s в г. Кратчайший путь — путь, длина
которого равна S(5,。). Понятно, что кратчайший путь может быть не единственным. Главной особенностью поиска в ширину является то, что вершины с более короткими кратчайшими путями посещаются прежде вершин с большими.
Рис. 4.1. Распространение поиска в ширину по графу
В главе 5 мы займемся вычислениями кратчайших путей, в которых длина пути определяется суммой весов» приписанных ребрам пуги, а непросто числом ребер пути.
Шесть степеней Кевина Бэкона
Интересное приложение поиска в ширину возникает в популярной игре «Шесть степеней Кевина Бэкона». Идея игры состоит в том, чтобы связать некоего актера1 с Кевином Бэконом через цепочку актеров, которые снимались в кино вместе, менее чем за шесть шагов. Например, Теодор Хесбург (заслуженный президент Университета Нотр-Дам в отставке) снимался в фильме «Rudy» с актером Герри Веккером, который снимался в фильме «Sleepers» вместе с Кевином Бэконом. По каким-то неизвестным нам прнчннам три студента, Майк Джинелли, Крэй Фасс и Брайан Тертл, решили, что Кевин Бэкон является центральной фигурой индустрии развлечений. Математики играют в похожую игру, вычисляя свое число Эрдеша, которое является числом работ, написанных в соавторстве, — это число отделяет их от известного Пауля Эрдеша.
Игра «Шесть степеней Кевина Бэкона» на самом деле является задачей на графе. Для того чтобы представить эту задачу в виде графа, нужно назначить вершину каждому актеру и создать ребра между двумя вершинами в том случае, если актеры снимались в одном фильме. Поскольку отношения между актерами симметричны, ребра не имеют направлений и граф получается неориентированным.
Проблема нахождения череды актеров до Кевина Бэкона становится традиционной графовой задачей нахождения пути между двумя вершинами. Поскольку
Под актерами мы будем понимать и актрис.
мы хотим найти путь с длиной менее шести шагов, в идеале было бы неплохо найти кратчайший путь между вершинами. Как было показано в предыдущем разделе, поиск в ширину может быть использован для нахождения кратчайших путей. Подобно числу Эрдеша, мы будем называть длину кратчайшего пути от данного актера к Кевину Бэкону числом Бэкона. В следующем примере будет показано, как использовать функцию breadth_fi rst_search(), чтобы вычислить числа Бэкона для группы актеров.
Входной файл и создание графа
В данном примере будет использована небольшая часть фильмов и актеров нз базы данных по кинофильмам Internet Movie Database1. Файл example/kevin^bacon.txt содержит список пар актеров, которые снимались в одном и том же фильме. Как показано в следующем фрагменте, каждая строка файла содержит имя актера, название фильма и имя другого актера из этого же фильма. В качестве разделителя используется точка с запятой.
Patrick Stewart:Prince of Egypt* The (1998):Steve Martin
Для начала прочитаем файл с помощью std: zifstreaTTi и создадим граф на его основе.
std::ifstream datafile(M./kevln-b3con.dat"):
if (! datafile) {
std::cerr « "No ./kevin-bacon.dat file" « std:;end1:
return EXIT_FAILURE:
}~
Для представления графа используется adjacency_1ist, a undirectedS служит признаком того, что граф неориентированный. Как и в разделе 3.6, для присваивания вершинам имен актеров, а ребрам — названия фильмов класс property используется для добавления этих свойств вершинам и ребрам*
typedef adjacency_Hst<vecS. vecS, undirectedS, property<vertex~name_t. std::string>, property<edge_name_t. std::string> > Graph;
Graph g;
Для доступа к свойствам объекты отображений свойств должны быть получены из графа. Следующий код готовит эти отображенияf которые позднее используются дескрипторами вершин и ребер для доступа к связанным с ними именам.
typedef property jrmp<G га ph * vertex_name_t> :: type actor_name_map_t:
actоr_name__map_t actor_name = get(vertex_name( g);
typedef property_niap<Graph. edge_name_t>: :type movi e_name_iriap_t:
movieMname_map_t'connect!ng_mov1 e - get(edge_nanie. g):
Файл читается построчно и разбирается в список лексем, разделенных точками с запятой. Для создания «виртуального» контейнера лексем используется библиотека лексического разбора Boost Tokenizer Library. Код для разбора файла данным и приведен в листинге 4Л.
Internet Movie Database используется факультетом ин4юрматики Университета Вирджнннн для гра* 巾а к их «Оракулу Бэкона>.
Листинг 4.1* Разбор файла с данными
for (std: :string line; std: :getline(datafileJlne):)( char deliniiters_separator<char> sep(false. u"\ ":"): tokenizero line_toks(line. sep):
tokenizero: :iterator i = line_toks.begin():
《Взять имя первого актера и добавить вершину к 「口己0y )
《Сохранить название фильма в переменной )
《Взять имя второго актера и добавить к графу 〉
《Добавить ребро. связывающее двух актеров, к графу 〉
}
Каждая строка ввода соответствует ребру графа, инцидентному двум вершинам, которые определяются именами актеров. Название фильма приписывается к ребру как свойство. Одной из проблем в создании этого графа нз такого формата файла является то, что он представляет собой поток ребер. Хотя добавить ребро к файлу несложно, проще добавлять к нему вершины. Вершины появляются только в контексте соединяющих их ребер, причем во входном потоке вершина может появиться несколько раз. Для обеспечения однократного включения вершины в граф используется отображение имен актеров и верцгины. По мере добавления вершин к графу последующее появление той же вершины (уже в составе другого ребра) может быть связано с правильной вершиной, уже находящейся в графе. Это легко достигается использованием std: :map.
typedef graph tгаits<Graph>; :vertex_descriptor Vertex:
typedef std::nap<std::string, Vertex> NameVertexMap:
NameVertexMap setons;
Первая лексема каждой строки — это имя актера. Если актера еще нет в отображении, вершина добавляется к графу, свойству вершины паше присваивается имя актера, а дескриптор вершины записывается в отображение (листинг 4.2). Если актер уже находится в отображении, функция std: :map:: Insert О возвращает итератор, указывающий на позицию соответствующей вершины в графе.
Листинг 4.2. Добавление имени первого актера как вершины в графе
《Взять имя первого актера и добавить вершину к графу 〉三
std:;stri ng actors_name - *i++;
NameVertexMap: iterator pos:
bool Inserted:
Vertex u, v:
tieCpos. inserted) = actors insert(std; :make_pair(actors_narrte. VertexO));
if (inserted) {
u - addvertex(g);
actor_name[u] - actors_name; pos->second - u:
} else {
u = pos->second:
}
Вторая лексема (название фильма) закрепляется за ребром, соединяющим двух актеров. Однако ребро не может быть создано, пока нет дескрипторов вершин для обоих актеров. Поэтому название фильма сохраняется для последующего исполь^ зования.
(Сохранить название фильма в переиенной > 三
std::string movie name = *j++;
Третьей лексемой является имя второго актера. Для включения в граф соответствующей вершины используется тот же способ, что и для первого актера.
〈Взять имя второго актера и добавить к 「pwifty 〉建
tie(po5. inserted) = actors.insert(std: :make_pair(*i. VertexO));
if (inserted) {
v = add_vertex(g):
actdr_naine[v] = *i:
pos->second - v;
} else v - pos^>second:
На последнем шаге добавляем ребро между актерами и записываем название объединяющего их фильма. Так как для параметра EdgeLi 5t используется 5€tS, то параллельные ребра не попадают в граф.
(Добавить ребро, связывающее двух актеров, к графу )三
graph_traits<Graph>::edge_descriptor е:
tie(e, inserted) - addredge(u, v. g):
if (inserted) connecting_movie[eJ = movie_name;
Вычисление чисел Бэкона с помощью поиска в ширину
В нашем подходе для вычисления чисел Бэкона с помощью поиска в ширину числа Бэкона вычисляются для всех вершин графа и поэтому необходимо где-нибудь их хранить. Так как мы используем adjacencyj i st с параметром VertexLi st=vecS, дескрипторы вершин являются целыми числами из диапазона [О, |V|). Числа Бэкона могут быть записаны в std:: vector, индексом которого будет дескриптор вершины.
std::vector^!nt> bacon_number(num_vertices(g)):
Алгоритм breadth_first_search() принимает три аргумента: граф, исходную вершину и именованные параметры. Исходная вершина должна быть вершиной, соответствующей Кевину Бэкону, и может быть получена из отображения actors (имя-вершина). Число Бэкона для самого Кевина Бэкона равно, разумеется, нулю.
Vertex src - actors[нKevin Bacon"];
bacon^numberCsrc] = 0;
Для вычисления чисел Бэкона фиксируются расстояния вдоль кратчайших путей. В частности, когда поиск в ширину находит древесную дугу (/ о), расстоя* ние для 己 может быть вычислено как d®J j cl[u] + 1. Для вставки этого действия в алгоритм поиска в ширину определим класс*посетитель bdcon_numb色「recorder, моделирующий концепцию ВFSVisitor. Вычисление расстояния будет осуществляться в функции-методе класса tree_edge() в событийной точке «встретилось древесное ребро». Класс bacon_number__recorder унаследован от default_bfs_visitor с реализацией по умолчанию (пустой) остхпьных функций-методов класса для оставшихся событийных точек (листинг 4. 3). Чтобы сделать посетителя более универсальным, для доступа к расстоянию до вершины используется обобщенный интерфейс LvaLuePropertyMap.
Листинг 4.3. Класс bacon_number_recorder
tempi ate Уурепшпе D1stanceMap>
class bacon__number_recоrder : public default_bfs_visitor { public:
продолжение 夕продолжение 夕bacon_number_recorder(D1stanceMap dist) : d(dist) { }
продолжение 夕
продолжение 夕
Листинг 4.3 {продолжений
tempi ate <typename Edge, typename Graph〉
void tree_edge(Edge e. const Graph& g) const { typename graph_traits<Graph>::vertex_descriptor u = source(e7 g), v - target(e, g)7
d[v] = d[u] + 1:
}
private:
DistanceMap d:
}:
// Функция для удобства
template <typename DistanceMap>
bacon_number_recorder<D1stanceMap>
record_bacon2number(DistanceMap d)
{
return baconjwmber_reco「du「vDistanceM叩〉(d):
}
Теперь все готова для вызова breadth_f1 rst_search(). Аргумент посетителя является именованным параметром, поэтому его необходимо передать, применив функцию visitorf). В качестве отображения расстояний здесь используется указатель на начало массива bacon_number.
breadth_first_search(g, src,
vi sltorC record_bacon_number(&ba:on_number[0]))):
Число Бэкона для каждого актера выводится в цикле по вершинам графа.
graph_tга 11s<Graph>::vertexJterator i. end;
for (tied. end) - verticesTg); i !- end: ri) {
std: :cout « actor_nan)e[*i] « ” имеет число бзкона " « bacon二number[*i] « std::endl:
}-
Ниже представлен фрагмент вывода полученной программы.
Willidm Shatner имеет число Бэкона 2
Denise Richards имеет число Бэкона 1
Kevin Bacon имеет число Бэкона О
Patrick Stewart имеет число Бэконд 2
Steve Martin имеет число Бэкона 1
Поиск в глубину
Поиск в глубину является основной частью многих графовых алгоритмов- Поиск в глубину использует и алгоритм нахождения сильно связной компоненты (см. раздел 13.5.2), и алгоритм топологической сортировки (см. раздел 13,2.5), Также поиск в глубину полезен и сам по себе. Например, его можно использовать для вычисления достижимости и обнаружения циклов в графе (см. раздел 3.4).
Последняя возможность делает поиск в глубину полезным и для оптимизирующего компилятора, которому необходимо выявлять циклы в графе потока уп* равления программы. В данном разделе описывается, как использовать функции depth^fl rst_search() и depth_f1rst_vis1t() при рассмотрении примера обнаружения и определения границ циклов в графе потока управления.
Определения
Поиск в глубину посещает каждую вершину графа ровно один раз. При выборе следующего изучаемого ребра поиск в глубину всегда выбирает ход «глубже» в граф (откуда и происходит его название). То есть поиск в глубину выбирает следующую смежную еще не посещенную вершину, пока не дойдет до вершины, у которой нет смежных не посещенных вершин. Затем алгоритм возвращается к предыдущей вершине и продолжает идти вдоль еще не исследованных ребер, выходящих из этой вершины. После того как поиск в глубину посетил все достижимые вершины (из конкретной исходной вершины), он выбирает одну из еще не посещенных вершин и продолясает работу. При этом процессе создается множество деревьев поиска в глубину, которые вместе образуют лес поиска в глубину. Распространение поиска в глубину на неориентированном графе показано на рис. 4.2* Для каждого ребра обозначен порядок его прохождения.
Подобно алгоритму поиска в ширину, алгоритм поиска в глубину помечает вершины цветами для отслеживания продвижения поиска по графу. Первоначаль' но все вершины имеют белый цвет. Когда алгоритм посещает вершину, он окрашивает ее в серый цвет. После посещения всех потомков вершины она окрашивается в черный цвет.
Поиск в глубину присваивает ребрам графа три категории: древесное ребро (tree edge), обратное ребро (back edge) и прямое или поперечное ребро (forward or cross edge)* Древесное ребро — это ребро из леса поиска в глубину, который строится (явно или неявно) в процессе обхода графа. Более точно, ребро (и,己)является древесным, если и была впервые посещена после прохождения ребра (以,г). Во время поиска в глубину древесные ребра могут быть идентифицировавь( по белому цвету вершины ^, Вершина и называется предком, или родителем, вершины о в дереве поиска, если (以,己)является древесным ребром. Обратное ребро соединяет вершину с одним из его потомков в дереве поиска.
Этот тип ребра выявляется, если конечная вершина г исследуемого ребра (м, V) имеет серый цвет» Петли считаются обратными ребрами. Прямое ребро — ребро (以,и), не принадлежащее дереву поиску которое соединяет вершину 以 с потомком V в дереве поиска. К поперечным относятся ребра, не входящие в предыдущие три категории. Если конечная вершина и изучаемого ребра окрашена черным, то это либо прямое ребро, либо поперечное ребро (хотя мы не знаем, какое именно).
Существует много лесов поиска в глубину для данного графа, а значит, много различных (и одинаково правильных) способов классифицировать ребра. Одним из вариантов реализовать поиск в глубину является использование стека. Поиск в глубину во время обработки вершины помещает в стек смежные вершины и извлекает одну вершину в качестве следующей для обработки. Другим (эквивалентным этому) способом является использование рекурсивных функций.
Одним из интересных свойств поиска в глубину является то, что моменты по сещения и окончания обработки вершин образуют структуру скобок. Если мы будем писать открывающую скобку при посещении вершины, а закрывающую — при окончании обработки вершины, результатом будет набор правильно вложенных скобок. Здесь мы приведем скобочную структуру графа с рис. 4.2 после при менения поиска в глубину. Поиск в глубину является ядром других графовых алгоритмов, включая топологическую сортировку и два алгоритма поиска связных компонент. Он может быть использован и для нахождения циклов (см. раздел 3.4).
(а (с (f (g (d (b (e e) b) d) g)(h h) f) с) a) (1 (j j) t)
Нахождение циклов в графах потоков управления программы
Нашей задачей в этом разделе является использование поиска в глубину для нахождения циклов в графе потока управления программы. Пример графа потоков показан на рис. 4.3. Каждый прямоугольник представляет базовый блок, содержащий последовательность инструкций с одной точкой входа и одной точкой выхода. Если между двумя блоками есть ребро как (6口 В6), то В, — предшественник В5 и Ве — последователь для В「Цикл определяется как набор блоков, где все блоки достижимы один из другого вдоль некоторого пути графа потоков [32].
Нахождение циклов в графе потоков управления состоит из двух шагов. На первом шаге ищутся все обратные ребра в графе. Каждое обратное ребро (/ и) идентифицирует цикл» поскольку v — предшественник м в дереве поиска в глубрь ну, и добавление (% 單)завершает цикл. Вершина о называется головой цикла. Поиск в глубину используется для нахождения обратных ребер графа. Ребро (В7, В{) (рис. 4.3) является примером обратного ребра. Второй шаг состоит в определении того, какие вершины какому щ«слу принадлежат.
Эти два шага объединены в шаблоне функции f1nd_loops() (листинг 4.4). Эта функция имеет три параметра: вершину entry, граф g и контейнер для хранения вершин каждого цикла. Тип Graph должен моделировать BidirectionalGraph, чтобы можно было иметь доступ как к входящим, так и к исходящим ребрам графа (листинг 4.7). Тип Loop является контейнером, элементы которого — наборы вершин.
Обратные ребра из первого шага хранятся в векторе back_edges, a col or_map применяется для отслеживания продвижения поиска в глубину.
Рис. 4.3. Граф потоков управления программы
Листинг 4.4, Шаблон функции findjoops()
《Шаблон функции flnd_loops 〉=
template <typensnie Graph, typename Loops>
void findjoops(
typename graph_traits<Graph>::vertex_descriptor entry.
const Graphs g「
Loop$& 1 oops) 〃 Контейнер с набором вершин
{
function_requiresv Bld1 rect 1 onalGraphConcept<Grapti> >();
typedef typename graph_traits<Graph>::edge_descriptor Edge:
typedef typename g「aph:t「dits<Graph>::ve「tex_de£criptor Vertex;
std::vector<Edge> back^edges:
std::vector<default_coTor_type> color_map(num_vertices(g)):
〈Найти все обратные*ребра" графа >
(Найти все вершины каждого цикла 〉
На первом шаге создается back_edge_recorder из DFSVisitor, который будет записывать обратные ребра во время поиска в глубину. Чтобы сделать этот класс повторно используемым, механизм хранения обратных ребер не задан, а параметризован как Outputlteratoc Класс back_edge__recorder, как обычно, наследует из defaul t_dfs_vi sltor, с тем чтобы иметь пустые заглушки для функций событийных точек, которые back_edge_recorder не определяет. Реализовать нужно только функцию-метод класса 6dck_edge(). В листинге 4.5 приведены коды шаблона класса back_edge_recorder и функции для порождения объектов этого класса.
Листинг 4.5. Класс регистратора обратных ребер
《Класс регистратора обратных ребер 〉 ■
tempi ate <Vpename Outputlterator>
class back_edge_recorder : public cfefault_dfs_visitor {
public:
back_edge_recorder(0utputlterator out) : m_out(out) { }
template <typename Edge, typename Graph〉
void back_edge(Edge e, const Graphs) { *m_outx ° e:)
private:
Outputiterator m out:
// Функция для порождения объектов
tempi ate <typename Outputlterator> t)ack_edge_recorder<Output Iterator nake~bacCedge_recorder( Output Iterator out)(
return back_edge_recorder<Output!terator>(out);
}
Теперь все готово для вызова функции поиска в глубину. Мы выбираем depth_f 1 rst_vi sit() вместо depth^f 1 rst^search(), так как все вершины графа потоков достижимы из исходной вершины. Поэтому вершина entry передаете只 внутрь как начальная точка поиска. Третий аргумент — посетитель» который будет регистрировать обратные ребра. Аргумент для make_back_edge_recorder() должен быть итератором вывода, адаптер std: :back_1nsert_1 terator используется для записи в вектор обратных ребер. Последний параметр — depth.fi rst_visit() — предназначен для отображения цветового свойства, которое поиск в глубину будет нс“ пользовать при отслеживании продвижения по 】рафу. Это отображение создается итератором вектора color_map (см. раздел 15.2.2),
《Найти все обратные ребра графа 〉三
depth_first_v1sit(g, entry, make_back__edge_recоrder(std: :back_inserter(back_edges)). make] terator_property_niap( col or_map. beglnO, get (vertexJ ndex, g)));
На втором шаге процесса определения циклов мы устанавливаем, какие вершины входят в каждый цикл (листинг 4.6), Для каждого обратного ребра, найденного на первом шаге, вызывается функция compute一loop_extent(), которая находит все веришны, принадлежащие циклу.
Листинг 4.6. Нахождение всех вершин каждого цикла
〈Найти все вершины каждого цикла 〉三
for (std: :vector<Edge>::size^type i - 0: i < back^edges.sizeO: ++i) {
1 oops.push_back(typename Ldops::value_type());— co<iipute_loop_extent(back edges[i], g. loops.bacM));
} 一 -
Чтобы вершина v принадлежала циклу обратного ребра (玄 й), и должна быть достижима из Л и £ должна быть достижима из v. Поэтому функция compute^ Ioop_extent() (листинг 4.7) состоит из трех шагов: вычисление всех вершин, до стижимых из головной вершины, вычисление всех вершин, из которых достижим хвост цикла, и пересечение этих двух множеств вершин.
Листинг 4.7. Вычисление границ цикла
〈Вычислить границы цикла )三
tempi ate 〈typename Graph, typename Set>
vol d computej oop_extent (
typename graph_traits<Graph>: :edge descriptor back_edge, const Graph& g: Set& loop set) {
funct1on_requires< BidirectionalGraphConcept<Graph> >();
typedef typenatne graph_trait$<Graph>::vertex descriptor Vertex: typedef col or_tra i ts<defaul t_color_type> Color:
Vertex loop head. loop^tail ; loop tail =~source(back_edge, g): 1oop~head = target(back二 edge, g);
《Вычислить 《Вычислить (Вычислить《Вычислить 《Вычислить (Вычислитьграницы цикла: достижины из головы 〉
《Вычислить 《Вычислить (Вычислить
《Вычислить 《Вычислить (Вычислить
границы цикла: достижимы к хвосту 〉
границы цикла: пересечение наборов достижимости 》
Для вычисления всех вершин, достижимых из головной вершины цикла, снова используется depth_f1 rst_vi si t( )* В этом случае не требуется определять нового посетителя, поскольку нужно только знать, какие вершины были посещены, а это может быть определено по цвету уже после запуска поиска в глубину. Вершины, окрашенные в черный или серый (но не в белый) цвет, были посещены в ходе поиска в глубину. Цветовые свойства хранятся в векторе reachabl e_from_head. Все BepmHHbit достижимые из блока Вр показаны на рис. 4.4.
Рис. 4.4. Вершины, доступные из блока 旦Рис. 4.4. Вершины, доступные из блока 旦
Рис. 4.4. Вершины, доступные из блока 旦
Рис. 4.4. Вершины, доступные из блока 旦
《Вычислить границы цикла: достижимы из головы >三 std: :vector<clefaul t_col or_type>
reachable_from_head(num_verticesCg)( Color::white());
depth_fir$t二vising. 1 oop^head, defaul t_dfs__v1 si tor ().
make_i terator_property_map(reachable^from^head.begi n(), get(vertex index, g)));
На втором шаге необходимо вычислить все вершины, из которых достижим блок В?, Для этого выполним поиск в глубину《протнв течения». То есть вместо обхода по исходящим ребрам каждой вершины обходятся входящие ребра. Функция depth_fi rst_visit() из BGL применяет функциюout_edgesО для доступа к еле* дующим вершинам, однако она может быть применена к нашей ситуации, если мы используем адаптер reverse_graph. Этот адаптер берет BidirectionalGraph и выдает представление графа, в котором исходящие и входящие ребра поменялись местами.
Следующий код показывает» как это может быть сделано. Все вершины, из КО' торых доступен блок J3* отображены на рис. 4,5.
(Вычислить границы цикла: достижимы к хвосту 》-
std::vector<default_color_type> reaGhable_to_ta11(num_vertices(g)): reverse_graph<Graph> reverse_g(g):
depth_fTrst_visit(reverse_g.~1oop^tal1» default_dfs_vTs1tor().
makejterator_property_map(reachable_to3ail .beginO.
get(vertex^index. g)B:
На последнем шаге вычисления вершин, принадлежащих циклу, осуществляется пересечение двух полученных ранее множеств достижимости. Вершина вносится в 1 oop_set в случае, если она достижима из головной вершины и если《хвост* достижим из нее.
〈Вычислить границы цикла: пересечение наборов достижиности > 三
typename graph_trai ts<Graph>::vertexjterator vi, vi_end;
for (tie(vi, vi^end) - vertices(g): vi != vi_end; ++vi)
if (reactiable^froin_head[*v1 ] !■ Color: :white()
&& reachable_to_tai1[*vi] != Color::white())
loop_set.insert7*vi);
Рис* 4.5. Вершины^ из которых доступен блок 8?
Задачи нахождения кратчайших путей
В этой главе мы решаем некоторые задачи маршрутизации пакетов с использованием алгоритмов нахождения кратчайших путей. В первом разделе проблема кратчайшего пути объясняется в общем виде и напоминаются некоторые определения. Второй раздел является кратким введением в пакетную маршрутизацию. Третий и четвертый разделы описывают два наиболее часто используемых протокола маршрутизации пакетов и представляют реализацию их основных алго* ритмов с помощью BGL.
Определения
Путем называется последовательность вершин <гп, □[,..” d*> в графе G=(匕 £), такая, что каждое из ребер (5, q + J находится в наборе Е (каждая вершина соединена со следующей вершиной последовательности). В задаче нахождения кратчайшего пути каждое ребро (и, п) имеет вес 侬(/ 口). Вес пути (или длина пути) определяется как сумма весов каждого ребра пути:
械Р)=z加(巧,%4)・
i-0
Вес В(м г) кратчайшего пути из вершины 以 в вершину 己—это минимум весов всех возможных путей нз 以 в v. Если пути из м в и не существует, 5(% v) = х:
f minСр) :〃->?}
5(%。)= 1*
00
Кратчайшим путем называется любой путь, вес которого равен весу кратчайшего пути. Пример кратчайшего пути показан на рис. 5.1.
Задача кратчайшего пути между д&улш вершинами состоит в нахождении кратчайшего пути, соединяющего две данные вершины. Задача кратчайше путей из
одной вершины заключается в нахождении кратчайшего пути от заданной вершины до всех других вершин графа. Набор кратчайших путей, исходящих из одной вершины, называется деревом кратчайших путей. Задача кратчайшего пути между всеми парами вершин графа состоит в нахождении кратчайших путей из каж дой вершины графа в каждую другую.
Рис. 5・1. Кратчайший путь от вершины а до е обозначен черными стрелками
Как оказалось, для решения задачи кратчайшего пути между двумя вершинами не существует алгоритмов, которые были бы асимптотически более быстрыми, чем алгоритмы для решения задачи кратчайших путей из одной вершины. Библиотека BGL включает в себя два классических метода для решения задачи кратчайшего пути из одной вершины: алгоритм Дейкстры и алгоритм Веллмана- борда. Также BGL вк.оючает алгоритм Джонсона для нахождения кратчайших путей между всеми парами вершин.
Алгоритмы нахождения кратчайших путей широко применяются во многих областях. Одним из важных современных применений является маршрутизация пакетов в Интернете. Протоколы, которые управляют информационными пакетами при передаче через Интернет, используют алгоритмы кратчайших путей для сокращения времени прохождения пакета до его назначения.
Маршрутизация в Интернете
Когда компьютер отправляет сообщение другому компьютеру с использованием интернет-протокола (IP), содержимое сообщения укладывается в пакет. Каждый пакет помимо данных сообщения (полезная составляющая) включает метаданные такие так адреса источника и приемника, длина данных, порядковый номер и т. п Если сообщение большое, данные разбиваются на меньшие частиf каждая из которых пакуется отдельно. Индивидуальные части снабжены порядковыми номерами, так что исходное сообщение может быть собрано на принимающей стороне.
Если адрес назначения для пакета находится вне локальной сети, пакет отправляется с исходной машины на интернет-маршрутизатор (internet router). Маршрутизатор направляет получаемые пакеты другим маршрутизаторам, исходя из таблицы маршрутизации, которая создается на основании протоколов маршрутизации. Путешествуя с одного маршрутизатора на следующий (то есть совершая переход, hop), пакеты прибывают на место назначения. Если сеть перегружена, некоторые пакеты могут быть потеряны. Для н;ще*ной доставки применяются протоколы более высокого уровня, например протокол управления передачей (Transmission Control Protocol, TCP). Он использует квитирование (установление связи) между отправителем и получателем, чтобы потерянные пакеты передавались
вновь. Программа системы Unix traceroute (или ее Windows-аналor tracert) может быть использована для прослеживания пути от вашего компьютера к другим узлам Интернета.
Конечная цель процесса маршрутизации состоит в доставке пакетов до пункта назначения как можно быстрее. На продолжительность доставки пакета влияет множество факторов, например количество переходов (hops), задержки внутри маршрутизаторов, задержки передачи между маршрутизаторами, пропускная способность сети и т. д. Протокол маршрутизации должен выбирать наилучшие пути между маршрутизаторам и — эта информация хранится в таблице маршрутизации.
Задача маршрутизации может быть смоделирована в виде графа, в котором каждая вершина является маршрутизатором, а каждое прямое соединение между маршрутизаторами — ребром. Ребру приписаны такие данные, как задержка и пропускная способность. Граф для простой сети маршрутизаторов представлен на рис. 52 У соединений обозначены средние задержки передачи. Теперь задача маршрутизации сведена к задаче кратчайших путей.
Рис. 5.2. Интернет-маршрутизаторы, соединенные друг с другом
Алгоритм Беллмана-Форда и маршрутизация с помощью вектора расстояний
Некоторые из первых интернет-протоколов маршрутизации, например протокол маршрутной информации (Routing Information Protocol, RIP) [19], использовали вектор расстояний. Основная идея RIP — поддерживать на каждом маршрутизаторе оценку расстояния до всех других маршрутизаторов и периодически сравнивать
эти записи со своими соседями. Если маршрутизатор узнает о более коротком пути до некоторого пункта назначения от одного из своих соседей, он обновляет запись о расстоянии до этого пункта и изменяет свою таблицу маршрутизации так, чтобы пакеты в этот пункт шли через данного соседа. По прошествии некоторого времени оцененные расстояния, хранимые таким распределенньш способом, гарантированно сходятся к истинным расстояниям, давая маршрутизаторам точную информацию о наилучшем пути.
Алгоритм протокола RIP является распределенной формой алгоритма Беллмана-Форда для нахождения кратчайшего пути из одной вершины [5,13]. Основной шаг алгоритма Беллмана-Форда называется релаксацией ребра и соответствует сравнению своих записей с соседями. Операция релаксации, примененная к ребру (м, г), выполняет следующее обновление:
d\v] = miii(砧(弘。)+ d同,小可).
Алгоритм Беллмана-Форда содержит цикл по всем ребрам графа, в котором релаксация применяется к каждому ребру. Алгоритм повторяет цикл | V | раз, после чего расстояния гарантированно сокращаются до минимально возможных (еслг:., конечно, в графе нет цикла с отрицательным весом). Если в графе есть отрицательный цикл, то в нем будут неправн 刀 ьно м и н им из иро в анн ы е ребра, то есть найдется ребро (% 偉,такое что ⑬(и, г) + d[u] < d[矶,где w — вес, ad— расстояние. Для проверки того, что минимизация прошла успешно, алгоритм делает дополнительный проход и возвращает истину, если это так, и ложь — в противном случае.
Функция bellman_ford_shortest_paths() реализует алгоритм Беллмана-Форда, В следующем разделе будет показано, как использовать ее для решения задач и маршрутизации. Схема файла bellman-ford-internetxpp программы приведена в листинге 5.1.
Листинг 5.1. Файл bellman-forddnternet.cpp
〈bel1man-ford-1nternet.cpp 〉=
#i nd ude <iostream>
#1nclude <boost/array,hpp>
#include <boost/graph/edge_list.hpp>
^include <boost/graph/bellnian_ford_shortest_paths.hpp>
int ma1n()
{
using namespace boost:
(Подготовить сеть наршрутизаторов 〉
《Присвоить веса ребрам 〉
< Создать хранилище для свойств вершин )
(Вызвать алгоритм Беллмана-Форда 〉
(Вывести расстояния и родителей ) return EXITJUCCESS:
}-
Первый аргумент функции bel 1 man_ford_shortest_paths() является графовым объектом. Тип этого графового объекта должен моделировать концепцию Edge* ListGraph. Многие из графовых классов BGL моделируют EdgeListGraph н, следовательно, могут быть использованы с этим алгоритмом. Здесь мы применим шаблон класса edge_li£t, который является адаптером. Он позволяет представлять множество значений итератора в виде графа. Тип значений итератора должен быть
std:: pal г — пара дескрипторов вершин. Дескрипторы вершин могут быть практически любого типа, хотя мы используем целые числа для удобства применения их в качестве индексов в массивах*
В нашем случае ребра хранятся в boost:: array, где каждое ребро есть std: :pai г. Каждой вершине приписан идентификационный номер, заданный с помощью перечисления. Параметры шаблона для ®dge_list содержат тип итератора, тип значения итератора и тип разности итераторов1. Код по подготовке сети маршрутизаторов приведен в листинге 52
Листинг 5.2. Подготовка сети маршрутизаторов
(Подготовить сеть маршрутизаторов 〉三
〃 Идентификационные номера для маршрутизаторов (вершин)
enum { А, В. С, D. Е, F, G. Н. n_vertices };
const int n_edges =11:
typedef std::pair<intt int> Edge;
〃 Список соединений между м叩山рутмзморами. сохраненный в массиее array<Edge. n_edges> edges 口 { { EdgeCA. В). Edge(A. С).
Edge(B. D)「Edge(B. E). Edge《C. E), Edge(C, F). Edge(D. H),
Edge(O. E), Edge(E, H)t Edg2(F. G), Edge(G. H) } }:
〃 Указание типа графа и определение графового объекта
typedef edgeJ1st<array<Edge. n_edges>::iterator> Graph:
Graph gtedgef,begin(). edges.end()):
Для передачи веса ребра (задержек передачи) в алгоритм нужно определить отображение весового свойства ребра, моделирующее концепцию ReadablePropertyMap. По умолчанию параметр we1ght_mdp() является внутренним отображением свойства веса ребра графа, которое может быть получено с помощью get (edge_wei ght ♦ g}. Поскольку класс edge_list не поддерживает определенные пользователем внутренние отображения свойств, веса ребер долисны храниться не в нем и аргумент отображения свойства должен быть передан в функцию явно. Класс edge_l i st предоставляет отображение свойства ребро-индекс, так что индексы ребер могут быть использованы как смещения в массиве свойств ребер. В нашем случае там хранятся задержки передачи. Приведенный ниже код создает массив значений задержек передачи.
(Присвоить веса ребрам 〉三
// Значения задержек передачи для каждого ребрз array<float. n_edges> delay =
{ { 5.0, L0「L3, 3.0. 10.0t 2.0. 6.3, 0,4. 1.3. 1.2. 0.5 } }:
Массив del ay обеспечивает хранение весов ребер, но не предоставляет интерфейс отображения свойств, требуемый алгоритмом для отображения дескрипторов ребер в вес. Необходимый интерфейс отображения свойств предоставляется классом - адаптером 1terator_property_map из библиотеки Boost Property Map Library. Этот класс преобразует итератор (такой, как итератор для массива значений задержек) в Lvalue Property Мар. Удобным средством для создания адаптера является вспомогательная функция make_1teratorj}roperty_map(). Первый аргумент —
Для компиляторов с работающей версией std::iterator_traits шаблонные параметры edge_list типа значения и типа разности не нужны, так как имеются правильные параметры но умолчанию.
итератор, второй — отображение из ребер в индексы ребер, и третий аргумент — объект типа значений итератора, который нужен только для вывода типа. Вызов функции make__1teпаtor_property_map() может быть таким:
《Создать отображение свойстаа для задержек )=
makej terator_property_map(del ay. begin (). get(edgejndex. g). del ay [0])
В качестве значения, возвращаемого функцией, служит созданный объект- адаптер, который затем передается функции Беллмана-Форда. Функция дет() извлекает отображение edge_index из объекта-графа н является частью интерфейса PropertyGraph.
Вершинам графа приписано несколько cboiictb. Как н на рис, 52 вершины обозначены буквами (это их имена). Метки расстояний требуются для записи длин кратчайших путей. Наконец, отображение предков parent используется для записи дерева кратчайших путей. Для каждой вершины в графе отображение предков записывает родителя данной вершины в соответствии с деревом кратчайших путей, то есть каждое ребро (parent[u], и) является ребром в дереве кратчайших путей.
Класс edge」1 st не обеспечивает методы для задания свойств вершин (вершины —только целые числа). Свойства хранятся в отдельных массивах с номером вершины в качестве индекса (листинг 5.3). Начальным значением расстояний является бесконечностьт и родителем каждой вершины первоначально устанавливается она сама.
Листинг 5.3. Создание хранилища свойств вершин
(Создать хранилище для свойств вершин ) в
// Определение хранилищ для некоторых 0внешних" свойств вершин
chsr name[ ] = "ABCDEFGH";
array<int, n_vertices> parent;
for (Int 1 =飞;1 < n^vertlces: ++i)
pa rentet j - i;
arrdyvfloat, n_vertices> distance:
di stance.assign(std::numeriej imlts<float>::nax()):
// Обозначим А как исходную вершину
distance[A]二 0:
Поскольку описатели вершин графа edgjl 1 st являются целыми числами, указатели на массивы свойств подходят в качестве отображений свойств» так как Boost Property Map Library включает специализации для встроенных типов указателе" (см. раздел 1521).
Ниже показан вызов bel lman_ford_shortest_paths(). Расстояния кратчайших путей записаны в векторе расстояний, а родитель каждой вершины (в соответствии с деревом кратчайших путей) записан в вектор родителей.
《Вызвать алгоритп Беллмана-Форда 〉三
bool г - be11inan_ford_shortest_paths(g. int(n_vert1ces).
weightjnap( ( Создать отображение свойства для задержек〉).
di stance_map(&di stance[0]).
predecessor_map(&parent[0])):
Программа завершается выводо\г предков и расстояний для каждого маршрутизатора в сети или выводит предупреждение пользователю об отрицательном цикле в сети.
《Вывести расстояния и родителей 》三
if (г)
for (int 1 ■ 0: i < n_vertices: "i)
std::cout « name[1] « °: ” « distance[i]
« “ “ « name[parent[i]] « std::endl: else
std::cout « °отрицательный цикл0 « std::end1;
Для нашего примера программа выдаст следующее:
3 7 2 7 051663443 7 2 7 05166344A A A в D c F G_A A A в D c F G_
3 7 2 7 05166344
3 7 2 7 05166344
A A A в D c F G_
A A A в D c F G_
Таким образом, работая в обратную сторону через предков, мы можем видеть, что кратчайший путь от марпфутизатора Л до маршрутизатор Несть {AtC,F,G,H〉•
Маршрутизация с учетом состояния линии и алгоритм Дейкстры
Уже к началу 1980-х годов появились сомнения в масштабируемости маршрутизации по вектору расстояний. Проблемы вызывали следующие два аспекта:
В среде, где топология сети часто изменяется, маршрутизация по вектору расстояний сходилась слишком медленно, чтобы поддерживать точную информацию о расстояниях.
Сообщения с обновлениями содержали расстояния до всех узлов, так что размер сообщения рос вместе с размером всей сети.
Для решения этих проблем была разработана маршрутизация с учетом состояния линии (Link-State Routing) [28, 37]. При такой маршрутизации каждый маршрутизатор хранит графовое представление топологии всей сети и вычисляет свою таблицу маршрутизации по этому графу, используя гыгоритм Дейкстры. Для поддержания графа в актуальном состоянии маршрутизаторы совместно используют информацию о состоянии линий: какие линии открыты (《up》),а какие нет (《down》). Когда возможности связи изменяются, по всей сети распространяется информация в виде объявления состояния линии.
Поскольку совместно использовать нужно только локальную информацию (связь с соседями), маршрутизация с учетом состояния линии не страдает от проблем с большим размером сообщения, как это происходит при маршрутизации по вектору расстояний. Кроме того, так как каждый маршрутизатор вычисляет свои собственные кратчайшие расстояния, реакция на изменения в сети и перевычисление точных таблиц маршрутизации занимает намного меньше времени. Недостатком маршрутизации с учетом состояния линии является то, что она требует от маршрутизатора больше вычислений и памяти. Но даже с учетом этого данный вид маршрутизации признан эффективным и формализован в виде протекала маршрутизации с определением кратчайшего маршрута (Open Shortest Path First
protocol, OSPF) [33], Сейчас он является одним из предпочитаемых протоколов для внутренней маршрутизации между шлюзами.
Алгоритм Дейкстры находит все кратчайшие пути из исходной вершины до каждой вершины графа, последовательно наращивая множество вершин 5Т для которых известен кратчайший путь. На каждом шаге алгоритма вершина из мно* жества V-Sc наименьшей меткой расстояния добавляется к S Затем исходящие ребра вершины релаксируются с использованием того же способа, что и в алгоритме Белл мана- Форда, по формуле d[硏=min(⑬(必 口)+ d[u],d[v]). Затем алгоритм повторяет цикл, обрабатывая следующую вершину из,一 S с наименьшей меткой расстояния. Алгоритм завершается, когда в 5 оказываются все вершины, достижимые из исходной.
В оставшейся части этого раздела мы покажем, как использовать функцию di jkstra_shortest jaths() из BGL при решении задачи поиска кратчайшего пути из одной вершины в сети маршрутизаторов и как вычислять таблицу маршрутизации. Пример сети, описанной в RFC 1583, изображен на рис. 5.3. Интернет-мар* шрутизаторы используют единый протокол марш рут и заци и. RT обозначает маршрутизатор (router), N — сеть (network), под которой понимается блок адресов, трактуемых как единый пункт назначения, Н — хост. Веса ребер указывают стоимость передачи.
Для демонстрации алгоритма Дейкстры мы вычислим дерево кратчайших путей для маршрутизатора RT6. Основные шаги программы показаны в листинге 5.4.
Листинг 5.4. Вычисление дерева кратчайших путей для маршрутизатора RT6
(ospf-example.cpp )三
include <fstream>// для файлового ввода-вывода
1 ndude <boost/graph/graphviz,hpp> // для read/'Airite_graphv1z()
#iMlude <boost/graph/dijkstra_shortest_paths.hpp>
1 nd ude <boost/1 exi cal ca st. hpp>
int ma1n()
{
using namespace boost;
〈Читать орграф из dot-файла Graphvlz 〉
Копировать орграф, преобразуя строковые метки & целые веса >
(Найти шестой маршрутизатор >
(Подготовить отображение свойства родителей для записи дерева кратчайших путей )
《Выполнить алгоритм Дейкстры >
(Покрасить все ребра дерева кратчайших путей черный )
《Записать новый граф в dot-файл Graphviz )
〈Записать таблицу маршрутизации для шестого маршрутизатора 〉 return EXП一SUCCESS:
)-
На первом шаге создается граф. Граф на рнс. 5.3 представлен как dot-файл Graphviz. Пакет Graphviz предоставляет инструменты для автоматической компоновки н рисования графов. Он доступен на сайте www.graphviz.org. Программы Graphviz используют специальный формат файла для графов, называемый dot-файлами. Библиотека BGL включает в себя анализатор для чтения этого формата в граф BGL. Анализатор можно использовать с помощью функтш read_graphviz(), опреде* ленной в boost/graph/graphviz.hpp. Так как граф ориентированный, мы используем тип GraphvizDigraph. Для неориентированного гра(|?а применяется тип Graphvi zGraph,
飞
Тип GraphvizDigraph хранит свойства вершин и ребер как строки. Хотя строкя могут быть удобны для файлового ввода-вывода и печати, веса ребер должны быть целыми числами, чтобы ими было легко манипулировать внутри алгоритма Дейкстры. Таким образом, g_dot копируется в новый граф. Каждое ребро в типе GraphvizDi graph обладает рядом атрибутов, которые записаны в std :: map<std : :str1 ng. std:: st ri ng>. Веса ребер хранятся в атрибуте «label» каждого ребра. Эта метка преобразуется в int при помощи boost : :lexlcal_cast, и затем ребро вставляется в новый граф. Поскольку типы Graph и GraphvizDigraph основаны iiaadjacencyjist с параметром VertexLis^vecS, типы дескрипторов вершин у обоих графов — целые числа. Результат source(*ei. g dot) может, таким образом быть непосредственно использован в вызове add_edge() для графа д. Код копирования орграфа приведен в листинге 55
Листинг 5.5. Копирование орграфа
〈Копировать орграф, преобразуя строковые метки в целые веса 〉=
typedef adjacencyjlst < vecS. vecS. directedS. njproperty.
property < edge_weight_t. int > > Graph;
typedef graph_t「dit5 < Graph >::vertex_descriptor vertex^descriptor:
Graph g(num_vertices(g_dot));
property^map < GraphvIzDigraph. edge_attribute_t 光:type
edgj/trjn叩 » get(edge_attnbute1 g_dot):
graph_traits < GraphvizDigraph >: :edge_Tterator ei. 5_end:
for (tielei. ei_end) = edges(g_dot); ei != ei^end; ++e 彳){
int weight - Tex1cal_cast < 7nt >{edgjdttr3^p[*ei][”1db3”]): property < edge_weight_t. int >edge_property(weight):
add edge(source(*eir g_dot)T target?*ei, g_dot), edge_property, g):
}"『-
Для того чтобы шестой маршрутизатор стал исходной вершиной поиска кратчайших путей, нужно определить его дескриптор. Программа ищет вершину с меткой атрибута «RT6m
(Найти шестой маршрутизатор 〉=
vertex_descriptor router six;
property^map < GraphvizOigraph, vertex^ttribute」>::type vertex^attr^map = get(vertex_attri bute. g_dot);
graph_traits < GraphvizDigraph >::vertex_iterator vit vi_end:
for (tie(vi. vi end) = v^rt1ces(§ dot): vl != vi end; +ПИ)
if ("RT6” == 7ertex_attr_map[*vi][Иabel"]) {— router_six = *vi; break;
Совместно кратчайшие пути от шестого маршрутизатора до других маршрутизаторов образуют дерево кратчайших путей* Эффективным способом для представления такого дерева является запись родителей каждого узла в дереве. Здесь мы просто используем std: :vector для записи родителей.
(Подготовить отображение свойства родителей дпя записи дерева кратчайших путей 〉 а
std:sector < vertex_descriptor > parent(num_vertices(g));
// Все вершины вначале являются своими родителями
typedef graph_traits < Graph >: : vertices_size_typ£ size type;
for (size_type р = 0: р < num_vertices(g): ++р)
parent[p] = р;
Теперь все готово для вызова алгоритма Дейкстры. Мы передаем ему массив родителей через именованный параметр predecessor_rnap().
〈Выполнить алгоритм Дейкстры 〉 «
dijkstra shortest^paths(g. router_six. predecessor_map(&parent[0]));
Для подготовки к выводу графа в dot-файл Graphviz мы «красим» цвета древесных ребер в черный цвет. Древесные ребра были записаны в массив parent. Для каждой вершины 1 ребро (parentti], i) является древесным ребром, если не выполняется равенство parents ] = 1. В противном случае i — корневая или недо* стижимая вершина.
(Покрасить все ребра дерева кратчайших путей в черный 〉 ^
graph_traits < GraphvizDigraph >::edge_descriptor e;
for (size_type 1 = 0; i < nuni_vertices?g); ++1)
if (parent[i] != i) {
e 二 edge(parent[i]. 1. g_dot).first:
edge^attraiapEelfcolor0] = " black":
} --
Теперь мы можем записать граф в dot-файл. Для ребер цветом по умолчанию выберем серый (для недревесных ребер). Вычисленное дерево кратчайших путей для шестого маршрутизатора представлено на рис. 5d.
《Записать новый граф в dot-файл Graphviz ) *
gгаph_property < GraphvIzDIgraph. graph_edge_attr1bute_t > type &
graph_e<ige_attгjnap - get_property(g_dot. graph_edge~attribute):
graph_edge_attr_map[HcolorH] = "grey";~
wr i te^graphvi z (,r fi gs/ospf - spt ree. dot". g_dot);
На последнем шаге вычисляется таблица маршрутизации для шестого маршрутизатора. Табльща маршрутизации HNfeeT три колонки: пункт назначения (destination), следующий переход (hop) в сторону узла назначения и полное расстояние до узла назначения. Для заполнения таблицы маршрутизации создаются записи для каждого пункта назначения и сети. Информация для каждого узла может быть получена обратным прохождением кратчайшего пути от назначения до шестого маршрутизатора по отображению родителе/!. Узлы, являющиеся своими собственными родителями, пропускаются, так как это либо сам шестой маршрутизатор, либо недостижимый узел.
< Записать таблицу мар|друтизации для шестого маршрутизатора > 三
std: : of st ream rtabl e( ° rout 1 ng-tabi e. daf);
rtable « "Dest Next Hop Total CostM « std::endl;
for (tie(vi. vi_end) - vertices(g_dot); vi != vi_end: ++vi)
if (parent[*v7] !« *vi) {
rtable « vertex_attr_map[*vi][ПabelH] « ””;
(Следовать no пути назад к шестому маршрутизатору по родителям 〉 }
Во время следования по пути из конечного пункта к шестому маршрутизатору веса путей суммируются в path_cost( Мы также записываем дочерний узел текущей вершины, так как по завершении цикла эта вершина будет использоваться в качестве следующего перехода.
RT10RT10RT12RT12Рис. 5.4, Дерево кратчайших путей для шестого маршрутизатораРис. 5.4, Дерево кратчайших путей для шестого маршрутизатора
RT10
RT10
RT12
RT12
Рис. 5.4, Дерево кратчайших путей для шестого маршрутизатора
Рис. 5.4, Дерево кратчайших путей для шестого маршрутизатора
〈Следовать по пути назад к шестому маршрутизатору по родителям 》в vertex__descriptor v = *vi, child;
int path_cost = 0:
property_map < Graph. edge_wuight_t >::type
weight_map = get(edge_weight. g);
do {
path^cost += get(weight_map. edge(parent[v], v. g).first); child = v:
v - parent[v]:
} while (v !- parent[v]):
rtable « vertex_attr_map[child][nlaber] « w
rtable « path_cost <工 std::endl;
Результирующая таблица маршрутизации выглядит так:
ЧеesT1T2T3T4T5T7T8T9T1T1T1123467891012131415ldDRRRRRRRRRRRNNNNNNNNNNNNNHЧеesT1T2T3T4T5T7T8T9T1T1T1123467891012131415ldDRRRRRRRRRRRNNNNNNNNNNNNNHto о о о о о о о о Ли о о о о 北 Х33335111 11133331111115511 bMeRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTto о о о о о о о о Ли о о о о 北 Х33335111 11133331111115511 bMeRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTПолная стоимостьTotal Cost7767.68811710111010788121011131014141721Полная стоимостьTotal Cost7767.68811710111010788121011131014141721
ЧеesT1T2T3T4T5T7T8T9T1T1T1123467891012131415ldDRRRRRRRRRRRNNNNNNNNNNNNNH
ЧеesT1T2T3T4T5T7T8T9T1T1T1123467891012131415ldDRRRRRRRRRRRNNNNNNNNNNNNNH
to о о о о о о о о Ли о о о о 北 Х33335111 11133331111115511 bMeRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRT
to о о о о о о о о Ли о о о о 北 Х33335111 11133331111115511 bMeRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRTRT
Полная стоимость
Total Cost
7
7
6
7
.6
8
8
11
7
10
11
10
10
7
8
8
12
10
11
13
10
14
14
17
21
Полная стоимость
Total Cost
7
7
6
7
.6
8
8
11
7
10
11
10
10
7
8
8
12
10
11
13
10
14
14
17
21
Задача минимального осговного дерева
Библиотека Boost Graph Library реализует два классических алгоритма для нахождения минимоьного остовного дерева: алгоритм Краскала [23] и алгоритм Прима [38]. Задача нахождения минимального остовного дерева появляется во многих приложениях, таких как планирование телефонной сети, построение монтажяо!! электрической схемы, упаковка данных. В этой главе мы применяем алгоритмы BGL к задаче планирования телефонной сети.
Определения
Задачу минимального остовного дерева можно определить следующим образом* Для дашюго неориентироваьшого графа G = (К Е) необходимо найти ациклическое подмножество его ребер Тс £, которое соединяет все вершины в графе и обицЗ вес которого минимален. Общий вес остовного дерева — это сумма весов ребер из 7:
劭(Т) = £ v(〃, г)*(鶴 0)EТ
Ациклическое подмножество ребер, которое соединяет все вершины графа, называется остовнъш деревом. Минимальным остовным деревом называется ос- товлое дерево Тс минимальным полным весом.
62 Планирование телефонной сети
Предположим, что в ваши обязанности входит проведетгие телефонных линий для некоторого отдаленного региона. Регион состоит из нескольких городов и дор。*- ной сети. Проведение телефонной линии требует возможности подъезда на грузовике, а значит, необходимости дороги вдоль всего маршрута линии. Ваш бюд-
жет достаточно невелик н строительства новых дорог не предусматривает: телефонные линии должны идти вдоль существующих дорог. Также желательно минимизировать общую длину телефонного кабеля, который потребуется для соединения всех населенных пунктов региона.
Поскольку регион малонаселен, такие факторы, как пропускная способность, не рассматриваются. Пример отдаленного региона с городами, соединенными между собой дорогами, в виде взвешенного графа представлен па рис. 6.1. Длины дорог обозначены в милях. Нашей целью является нахождение оптимального расположения телефонных линий. Сначала мы решим эту задачу, используя алгоритм Краскала, а затем с помощью алгоритма Прима.
Рис. 6Л.「орода, соединенные сетью дорос в виде взвешенного графа
Алгоритм Краскала
Алгоритм Краскала стартует с того, что каждая вершина является сама по себе деревом, без ребер из множества 7; которое н составит минимальное остовное дерево. Затем алгоритм проверяет каждое ребро графа в порядке увеличения веса ребра. Если ребро соединяет две вершины в разных деревьях, алгоритм сливает эти деревья в одно н добавляет ребро к множеству Т. После того как все ребра будут просмотрены, дерево Тпокроет граф (если он связный) и станет минимальным остовным деревом |~рафа.
Схема файла kruskal-telephonexpp, в котором применяется функция kruskal_ mi п I mum_s pa nn 1 ng_t г ее () для вычисления наилучшего расположения телефонных линий, представлена в листинге 6*1.
Листинг 6.1. Файл kruskal-telephone.cpp
(kruskal-telephone.срр )-
#include <boost/config.hpp> include <iostream>
#include <fstrea(n>
知 nd Me <boost/l exi cal_cast.hpp>
indude <boost/grapn/graphv1z.hpp>
indude <boost/grapti/kruskaIjiri口一叫日口「1 ng_tree;hpp>
Int
main。
using namespace boost:
(Ввести неориентированный граф из dot-файла Graphviz >
(Копировать неориентированный граф.
преобразуя строковые метки в целочисленные веса )
〈Вызвать ал「0р»пм Краскала и сохранить минимальное остовное дерево )
《Вычислить вес остовного дерева )
Пометить древесные ребра черными линиями и вывести в dot-файл } return EX1T—SUCCESS;
}-
Граф, изображенный на рис. 6.1, хранится в dot-файле пакета Graphviz и считывается в память с помощью функции readjraphvizO из boost/graph/graphviz.hpp. Так как в этом примере граф неориентированный, используется тип Graphvl zGreph.
Ввести неориентированный граф из dot-файла Graphviz )三 Graphvl zGraph g/ot; read^graphviz(Hfigs/telephone-network,dot". g_dot):
Как n в разделе 5.4, метки ребер необходимо преобразовать из строк в целые числа. Для этого нужно скопировать Graphvl zGraph в новый граф и применить lexical__cast для выполнения преобразования из строк в целые числа (листинг 6.2),
Листинг 6.2. Копирование неориентированного графа с преобразованием меток
〈Копировать неориентированный граф.
преобразуя строковые метки в целочисленные веса 》三
typedef adjacencyjlst < vecS. vecS. unciirectedS, rio_prop日rty.
property < edge\eight__tr int > > Graph;
Graph g(nuHi_vertices(g__dot));
property^map < GraphvlzGraph; edge_attribute_t >::type
edge_attr_map • get(edge^attгi bute. g_dot):
graph^traits 《GraphvizGraph >::edge_1terator el. ei_end:
for (tie(ei. e1_end) ^ edges(g_dot): ei != ei_end: ++e1) {
int weight ^ Texlcal^cast < Tnt >(edge_attr2inap[*ei][" 1 abelM]):
property < edge_weigRt_t. int >edge_property(weight);
add edge(sourceT*ei. g_dQt). target?*ei. g dot). edge property. g):
} ---
Вызов алгоритма Краскала требует, чтобы тип графа был одновременно VertexListGraph и EdgeListGraph. В разделе 14.1.1 в описании adjacency_11st показывает, что выбранный нами тип Graph прекрасно подходит. Для хранения вывода алгоритма (ребер минимального остовного дерева) мы используем std::vector mst и применяем std : : back_1 nserter () для создания из него итератора вывода. Алго* ритм Краскала имеет несколько именованных параметров. В нашем примере мы наш не пользуемся, поэтому все они принимают значения по умолчанию. Отображение весов и отображение индексов вершин по умолчанию извлекаются из графа (это внутренние свойства). Имя edge_we1ght_t объявлено как свойство типа Graph, и отображение индексов вершин уже есть в adj.acencyjTSt с VertexLi st=vecS. Отображения предшественников и рангов (используются только в пределах алгоритма Краскала) ио умолчанию создаются внутри алгоритма.
Вызвать алгоритм Краскала и сохранить минимальное остовное дерево 〉三 std: :vector v graph traits < Graph >: :edge_descriptor > mst;
kruskal_minimum_s pa nni ng_t г ее (g. std: :back_inserter(Tnst));“
Когда алгоритм завершил свою работу, минимальное остовное дерево хранится в mst. Полный вес этого дерева вычисляется сложением весов ребер в mst. В нашем примере полный вес для минимального остовного дерева составляет 145 миль.
《Вычислить вес остовного дерева }三
property_niap < Graph. edge_weight_t >::type_weight = get(edge_weight. g}:
int totaT_weight » 0:
for (int e = 0; e < mst.sizeO; ++e)
total^weight += get(weight, mst[e]);
std: :cout « ''полный вес: ” « total_weight « std: :endl:
Древесные ребра затем окрашиваются в черный цвет, и граф сохраняется в dot- файле (листинг 6.3).
Листинг 6.3. Отмечаем древесные ребра для вывода
〈Пометить древесные ребра черными линиями и вывести в dot-файл)= typedef graph__traits < Graph >; :vertex_ciescriptor Vertex: for (int 1 = 0: 1 < mst.sizeO: —i) {
Vertex и = source(mstCi]. g). v = target(mst[i], g):
edge^attr map[edge(u. v. g__dot) Л1 rst]['^color"] = "black": // черный цвет
}
std: : of st ream out("fjgs/telephone-nist-kruskal .dot1');
graph_property < GraphvizGraph, graph_edge_attribute_t >::type & graph_edge_attr_map = get_property(g_dot" graph_edge_attribute);
graph edge_attrjnapCncolor0] = “gray": 〃 серый ивет graph^edge^attr^map["style"] - "bold": // полужирный стиль write_grapfiviz(out. g_dot);
Результирующее минимальное остовное дерево показано на рис. 62 Оптимальное расположение телефонных линий отображается черными литшями.
Алгоритм Прима
Алгоритм Прима наращивает минимальное остовное дерево по одной вершине за раз (а не одно ребро за раз, как в алгоритме Краскала). Основная идея алгоритма Прима состоит в последовательном добавлении вершин к минимальному остов- ному дереву. Очередная вершина должна иметь общее ребро с минимальным весом, соединяющее ее с уже находящейся в дереве вершиной. Алгоритм Прима очень похож на алгоритм Дейкстры. (На самом деле реализация алгоритма Прима в BGL есть просто вызов алгоритма Дейкстры со специально подобранными функциями сравнения расстояний и объединения.)
В этом разделе алгоритм рг 1 m_m1 ni mum_spannl ng_t гее() применяется к той же самой задаче планирования телефонной сети (см. рис. 6.1). Схема файла prim-tele- phone.cpp (листинг 6.4) подобна использованной в предыдущем разделе, хотя имеется некоторая разница в том, как алгоритм Прима выводит ребра остовного дерева.
Листинг 6.4. Файл prim-telephone.cpp
(prim-telephone.срр 》三
#include <iostream>
#include <fstream> include <vector>
#indude <boost/lexical_cast,hpp>
#indude <boost/graph/graphviz.hpp>
#1 ncl ude <boost/graph/pri 叱加 n ? muni_ s pdnn 1 ng_t г ее. h pp>
int niainO
{
using namespace boost:
《Ввести неориентированный граф из dot^awna Graphviz )
《Копировать неориентированный граф.
преобразуя строковые метки в целочисленные веса 〉
(Вызвать алгоритм Прима
и сохранить минимальное остовное дерево в 叩едшественннках )
(Вычислить вес остовного дерева )
《Пометить древесные ребра черными линиями и вывести s dot-файл 〉 return EXIT SUCCESS;
}'
Первые два шага (чтение из dot-файла н копировапме графа) те же самые, что и в предыдущем примере. В вызове алгоритма Прима первый параметр — трафт а второй —отображение предшесоешшков. Минимальное остовноедерево записывается алгоритмом Прима в виде отображения п редшествен н и ков. Для каждой вершины v в графе pa「cnt[v] является родителем v в минимальном 口стовном дереве. Внутри алгоритма parent[v] может быть многократно присвоен, но последнее присваивание гарантированно устанавливает правильного родителя. Для точной настройки функции prim_mi пi mum_spa nn 1 ng_t гее () может быть применен ряд нгиенованшях параметров (мы оставляем значения по умолчанию). В нашем случае используются отобра- жеиия весов ребер и индексов вершин, внутренние для типа Graph, а вспомогательное отображения цветов и расстояний создаю1ся внуфи алгоритма. Значением по умолчанию для корневой вершины является *vertices(g). f 1 rst, что допустимо, так как корень минимального остовного дерева произволен.
(Вызвать алгоритм Прина
и сохранить мининальное остовное дерево в предшественниках 〉 я
typedef graph_traits<Graph>::vertex_descriptor Vertex;
std::vector<Vertex> parent(num_vert7ces(g)):
prim^min1mum_spann1ng_tree(g. &parent[0]);
Когда минимальное остовное дерево записано в массиве parent, общий вес вычисляется в цикле по всем вершинам графа сложением весов всех ребер (р己rent[v], v). Если pa rent[v] = % это означает, что либо v является корневой вершиной дерева, л ибо она не была и той же компоненте связности, что и остальные вершины. В любом случае (parent[v], v) не является ребром остовного дерева н должно быть пропущено. Вычисление для графа, изображенного на рис 6.1, дает тот же результат — 145 миль.
《Вычислить вес остовного дерева ) я
property_т叩〈Graph. edge_weight_t>::type_weight = get(edge_weight, g);
int totaTweight = 0:
for Cat v ₽ 0: v < num_vert1ces(g): ++v)
if (parent[v] != v)
total_weight “ get(weight, edge(parent[v]. v. g).first):
std: :cout~« ''полный вес: 〃 « total_weight « std: :endl:
Для наглядности ребра минимгыьного остовного дерева отмечены черными линиями н после записываются в dot-файл. Резулътирующее минимальное остов- ное дерево показано на рис. 6.3. Заметьте, что дерево здесь немного отличается от полученного алгоритмом Краскала. Вместо ребра между Magnetawan и Kearny имеется ребро между Magnetawan и Sprucedale. Это подчеркивает тот факт, что минимальные остовные деревья не уникальны: для конкретного графа их может быть более одного.
〈Пометить древесные ребра черными линиями и вывести в dot-файл 〉三 for (int u = 0: u v num_vertices(g):十+u)
if (parentLu] !^ u)
edge__attr_map[edge(parent[u]. u, g_dot).first]["color,1] = "black”:
std: : of st ream out (и f 1 gs/tel ephone-mst -prim .dot'*):
graph_property < GraphvizGraph. graph_edge_attribute_t >::type &
graph_edge_attr_map - get_property(g_dot7 graph_edge_attribute);
graph_edge_attr_map[ Hcol orr,]=旳「冲":一
write^graphvizCout, g_dot):
Компоненты связностиКомпоненты связности
Компоненты связности
Компоненты связности
Основным вопросом для сети является вопрос о достижимости одних вершин из других. Например, хорошо спроектированный веб-сайт должен иметь достаточно ссылок между страницами, чтобы все страницы были достижимы с главной страницы. Кроме того, хорошим тоном является наличие ссылок на главную страницу или хотя бы на предыдущую страницу в серии последовательных страниц. В ориентированном графе группы вершин, достижимые друг из друга, называются сильными компонентами связности^
Изучение 200 миллионов веб-страниц показывает, что 56 миллионов страниц Интернета составляют одну большую сильную компоненту связности [7]. Это исследование также показало, что если рассматривать данный граф как неориент»- рованный, большую компоненту связности составляют 150 миллионов страниц и 50 миллионов страниц оторваны от этой большой компоненты (они находятся в своих, гораздо меньших, компонентах связности).
Библиотека BGL предоставляет две функции для вычисления всех компонент связности: первая для однократного вычисления (граф не меняется) п вторая для случая, когда граф может расти. Библиотека BGL также реализует алгоритм Тарья на для вычисления сильных компонент связности графа за линейное время.
В следующем разделе мы рассмотрим некоторые определения и затем применим функции из BGL для компонентов связности к Всемирной паутине WWW (World Wide Web).
Определения
Путем называется последовательность вершин, в которой каждая вершина пути соединена со следующей ребром. Если существует путь ^з вершины и в ш, то мы будем говорить, что вершина w достижима нз вершины 〃. Компонента связности —это группа вершин неориентированного ^афа, в которой каждая вершина
Иногда они называются быкомпстептами, сильными компопента^ш. - Примеч. иереи.
достижима из любой другой. Сильная компонента связности — это группа вершин ориентированного графа, которые обоюдно достижимы друг из друга. Отношение достижимости для неориентированных графов является отношением эквивалентности: оно рефлексивно, симметрично и транзитивно. Множество объектов, для которых выполняется отношение эквивалентности, образуют класс эквивш1ент1юсти. Одна компонента связности является, таким образом, классом эквивалентности ио отношению достижимости. Аналогично, сильная компонента связности является классом эквивалентности для отношения обоюдной достижимости. В результате эти два отношения разбивают вершины графа на непере- секающиеся подмножества.
Связные компоненты и связность Интернета
Компоненты связности неориентированного гр?巾а вычисляются с применением поиска в глубш-гу. Идея заключается в том, чтобы прогнать поиск в глубину на графе и отметить все вершины в одном дереве поиска в глубину как принадлежащие одной компоненте связносто. Реализация функции connected_components ( из BGL содержит вызов depth_fi rst_search() со специальным объектом- посети - телем, который присваивает каждой обнаруженной вершине номер текущей компоненты м увеличивает номер на единицу в событийной точке «начальная вер- шииа».
На рис. 7.1 представлена есть интернет-маршрутизаторов, у которой ребра соответствуют прямым соединениям. Шаги для вычисления компонент связности чакой сети следующие: 1) прочитать данные о сета в память; 2) представить их в виде графа BGL; 3) вызвать функцию connected_components(). Каждой вершине графа присваивается целое число, отмечающее компоненту, к которой вершина принадлежит. Схема файла cc-internet.cpp приведена в листинге 7.1.
Листинг 7.1. Файл cc-internet.cpp
cc-internet.cpp )三
1 nc1ude <fstream> ^include <vector> include <string> include <boost/graph/сои neeted^components. hpp> #indude <boost/graph/graphviz.hpp>
Int main{)
{
using namespace boost;
〈Считать граф в память >
Создать вектор для назначения компонент 〉
〈Вызвать функцию connected_components()〉
(Окрасить вершины в зависимости от компонент и зсписать в dot*файл >
}
Данные для графа на рис. 7.1 считываются из([)айла cc-internet.dot, записанного в формате dot-файла Graphviz. Выбран тип GraphvtzGraph, а не GraphvIzDigraph, гак как граф является неориентированным.
Рис. 7.1. Сеть интернет-маршрутизаторов
《Считать граф в память 〉気
GraphvizGraph g:
read_graphv1z(r,flgs/cc-internet .dot°, g):
Для хранения целых чисел для каждой вершины, обозначающих компоненту, используется вектор component размером num_vertices(g)-
《Создать вектор для назначения компонент ) =•
std::vector<1nt> component(num_vertices(g));
Первый аргумент в вызове connected_components() является графовым типом который должен моделировать VertexListGraph и IncidenceGraph. Тип GraphvizGraph подходит, поскольку моделирует эти концепции. Второй аргумент — отображение свойства для преобразования вершин в номера компонент. Отображение свойств создается адаптером 1 terator_property_fnap, который служит оболочкой для итератора, указывающего на начало вектора component. Функция соппес- ted__components() возвращает количество найденных компонент и записывает назначения компонент для каждой вершины в вектор component. Алгоритм connected_components () также имеет именованный параметр для отображения цветов, используемый при поиске в глубину. В нашем случае для параметра установлено значение по умолчанию. Алгоритм создает отображение цветов внутри себя и использует отображение индексов вершин графа для индексирования этого массива.
《Вызвать функцию connected_components() > = int num^comp - connected components(g.
ma ke_ i t e r. a t о r_p rape r ty_rna p (component. beg 1 n (), get(vertex_index, g). component[0])):
Для визуализации результатов вычисления каждой вершине присваиваются цвета в соответствии с номером компоненты. Затем граф записывается в dot- файл.
Результаты окраски вершин представлены на рис, 7,2.
《Окрасить вершины в зависимости от компонент и записать в dot-файл 〉三 property^map < GraphvizGraph. vertex_attribute_t >;:type
vertex_attr_map = get(vertex_attribute. g);
std::string 81or[] = {"white”, "gray", "black”, "lightgray”};
〃 белый4серый. черный, светло-серый
graph traits < GraphvizGraph >::vertex_iterator v「vtfid;
for (tie(vi. vi_end) =* vertices(g): vi != vi_end; +卜川5 { vertex_attr_fnapC*vi] ["color"] = colortcmponent[*«/l ]]: vertex—dttr_mmp[*vi]["style"] = "fiTlad”: 〃 стиль - заполненный if (vert ex_a 11 rjnap [* v 1 ] [ *' col о r,# ] — "black")
vertex_attr_map[*vi][,,fontco1or,*]二"white": // цвет шрифта - белый
}
write_graphviz(”figs/cc-internet-out.dot", g):
Рис* 7.2. Компоненты связности
Сильные компоненты связности и ссылки веб-страниц
Набор связанных ссылками веб-страниц можно абстрактно представить в виде ориентированного графа (рис. 7.3). Наша задача в этом разделе — вычислить сильные компоненты связности для этого графа.
Схема файла scc.cpp для вычисления компонент связности представлена в листинге 7.2. Сначала данные графа будут прочитаны из dot-файла Graphviz в память. Затем создается место для хранения назначений компонент для вершин, которые будут вычислены алгоритмом. После этого вызывается алгоритм strong^componentsi), и его результаты используются для окрашивания вершгш траста в зависимости от номеров сильных компонент связности.
Рис. 7.3* Граф, представляющий ссылки по URL между веб-страницами
Листинг 7.2. Файл scc.cpp
〈scc.cpp 》-
#include <boost/config.hpp>
#include <fstream>
#1nclude <map>
#include <string>
i nclude〈boost/graph/st「0ng_compunent£.hpp>
include <boost/graph/graphviz.hpp>
int
mairiO
{
using namespace boost:
《Считать ориентированный граф в память 〉
Выделить место для назначения компонент >
Вызвать функцию strong__cofnponents()〉
《Окрасить вершины в зависимости от номеров компонент и записать их в dot'файл )
return EXIT_SUCCESS:
}
Данные графа считываются из файла see.dot с использованием типа Graphvlz- Digraph (так как граф ориентированный).
(Считать ориентированный граф в память 〉 к
GraphvizDIgraph g;
read^graphvizC'flgs/scc.dof. g):
В вызове st rong components () адаптер assocl at 1 ve_property_map используется для обеспечения интерфейса отбражения свойств, требуемого функцией. Этот адаптер создает отображение свойств из AssociativeContainer, например из std: :map. Выбор std: :inap для реализации отображений свойств довольно неэффективен в нашем случае, но демонстрирует гибкость интерфейса отображения свойства. Дескриптор вершины для GraphvizDIgraph является целым числом, поэтому он имеет требуемую std: :map операцию «меньше».
〈Выделить место для назначения компонент 〉=
typedef graph_traits < GraptivizDigraph >: :vertex_descriptor vertex_t;
std: :inap < vertex^t, int component:
Результаты вызова strong_components() помещаются в массив component, где каждой вершине сопоставлен номер компоненты. Номера компонент идут от нуля до пигп_сотпр 一 1. Граф, передаваемый функции strong_components(), должен быть моделью концепций VertexListGraph и IncidenceGraph. И действительно, он соответствует этим критериям. Второй аргумент — отображение компонент — должен быть ReadWritePropertyMap. Есть еще несколько именованных параметров, которые могут быть заданы, но все они предназначены для внутренних вспомогательных отображений. По умолчанию алгоритм создает массивы для этих отображений свойств н использует индекс вершины графа как смещение в этих массивах.
〈Вызвать функцию strong^componeлts() ) &
int num comp - strong__components(g, rnake_assoc_property_iriap(component));
Программу завершает окрашивание вершин в соответствии с компонентами, которым они принадлежат. Вывод записывается в dot-файл. Граф с сильными компонентами связности представлен на рис. 7.4.
Окрасить вершины в зависимости от номеров компонент и записать их в dot-файл 〉三
property__map < GraphvizDigraph. vertex_attribute_t >::type vertex_attr_map - get(vertex_attribute. g);
std: : st ri ng color[] - {^white\ 1,gray\ ^black*'. ''lightgray"}:
// белый. серый. черный. светло-серый
graph_trait5 < GraphvIzDigraph >::vertex_iterator vi. vi_end;
for (tie(v1. vi_end) = vertlces(g): vi != vi_end: ++vi)彳
vertex_attr_map[*vl][nco1or"] = colorLcomponentL^vi]];
vertex_attr_map[*vi]["style"] = "filled"; 7/ стиль - заполненный if (vertex_attr_map[*vi][''color1'] “ "black'-)
vertex attrjnap[*vi ] [wfontcolorw] = Mwh1te": // цвет шрифта - белый }
wrlte^graphvizC'figs/scc-out.dot", g}:
Максимальный поток
Задача максимального потока — это задача определения того, как много некоторого количества (например, воды) может пройти через сеть. Алгоритмы для решения задачи максимального потока имеют длинную историю. Первый алгоритм принадлежит Форду и Фалкерсону [12]. Лучший же из известных на сегодня алгоритмов общего назначения — алгоритм проталкивания предпотока (push-relabel algorithm) Гольдберга [9, 16,17], основанный на понятии предпотока (preflow), введенном Карзановым |20]. Библиотека BGL содержит два алгоритма для вычисления максимального потока: алгоритм Эдмондса-Карпа (улучшение оригинального алгоритма Форда-Фалкерсона) и алгоритм проталкивания предпотока.
Определения
Потоковая сеть (flow network) — это ориентированный граф (7 - (V, £) с верши- нои истока s и вершиной стока L Каждое ребро имеет положительную вещественную пропускную способность, и на каждой паре вершин задана потоковая функция / Потоковая функция должна удовлетворять трем ощ)апичениям:
/(% и) £ с(/ 己) У(弘 V)eV xV (ограничение пропускной способности) /(% и) = • /(и, и) VQ, и) g V х V (антисимметричность)
Z /(",單)=° Vw с V - b А (сохранение потока)
Поток — чистый поток, входящий в вершину стока 么 Остаточная пропускная способность ребра есть г (以,г) = с(и, и) -/(以,口),Ребра с г (〃,吞)> 0 называются остаточными ребрами Е,м порождают граф G/ =(匕 ЕД Ребро с г (乂 般)=0 называется насыщенным,
И = Е Ж о
Задача максимального потока сос гоит в определении максимально возможного значения для |/| и соответствующих значений для каждой пары вершин в графе
Важным свойством потоковой сети является тот факт, что максимаьный поток связан с пропускной способностью наиболее? узкого места сети.
Но теореме о максимальном потоке и минимальном разрезе [12] максимальное значение потока от вершины истока до вершины стока в потоковой сети равно минимальной пропускной способности среди всех (5, 7} разрезов. (5, Т)-раз- рез — это разделение вершин графа на два множества 5 и Г, где 5 е 5 н £ е Г Любое ребро с начальной вершиной в 5 и конечной в 7'является прямым ребром разреза, а ребро с начальной вершиной в Т п конечной в 5 — обратным ребром разреза. Пропускная способность разреза — это сумма пропускных способностей прямых ребер (обратные ребра игнорируются). Так что если мы рассмотрим пропускные способности всех разрезов, отделяющих s и t, и выберем разрез с минимальной npQHycKHoii способностью, она будет равна пропускной способности максимального потока в сети.
Реберная связность
При проектировании телефонной сети, сети передачи данных крупного предприятия пли соединений маршрутизаторов для высокоскоростного сегмента сети Интернет очень важным вопросом, который задают себе инженеры, является устойчивость сети к повреждениям. К примеру, если кабель окажется перерезанным, имеются ли другие кабели» по которым может проходить информация? В теории графов это называются реберной связностью графа — минимальное число ребер, при разрезании которых получаются две несвязные компоненты (предпо- лягается, что первоначально граф имел одну компоненту связности). Мы будем использовать а(С) для обозначения реберной связности графа. Множество ребер в разрезе, приводящем к увеличению числа компонент связности, называется сечением (minjmum disconnecting set). Вершины графа разделяются по двум компонентам 5* иУ, поэтому для обозначения минимального разреза мы будем использовать [У, 5"]. Оказывается, что вычисление реберной связности может быть сведено к последовательности решений задач и максимальном потоке. В этом разделе мы рассмотрим алгоритм вычисления реберной связности иеориентироваьь ного графа |27].
11усть а(м, 口) обозначает минимальное число ребер, которое может быть разрезано для разъединения вершин и и v друг от друга. Если эти две вершины являются источником и стоком и пропускная способность каждого ребра равна единице, тс разрез с минимальной пропускной способностью (вычисленной по алгоритму максимального потока) совпадет с минимальным разрезом. Таким образом, решив задачу максимального потока, мы также определим минимальное число ребер, которые должны быть разрезаны для разъединения двух вершин. Теперь для того, чтобы найти реберную связность графа, алгоритм максимального потока нужно запустить для каждой пары вершин. Минимум всех этих попарных мини- Majibifbix разрезов будет минимальным разрезом всего графа.
Выполнять шиоритм максимального потока для каждой пары вершин слишком накладно, поэтому лучше сократить количество пар, которые требуется рас-
смотреть. Этого можно достигнуть с помощью специального свойства сечения [S: 5*]. Пустьр — вершина минимальной степени, а В — ее степень. Если o(G) = 3, то S есть просто р* Если a(G) < 8 - 1, то оказывается, что для любого подмножества 5* (назовем его 5), множество всех вершин, не являющихся соседними для вершин из S должно быть не пусто. Это означает, что минимальный разрез может быть найден, если начать с 5 = р, выбрать вершину k из множества не соседних вершин S, вычислить а( р,疝)и затем добавить k к S. Этот процесс повторяется, пока множество не соседних вершин S не станет пустым.
Мы реализуем алгоритм реберной связности как шаблон функции, который использует BGL-интерфейс VertexListGraph. Функция возвращает реберную с вяз* ность графа, и ребра в несвязном множестве (disconnected set) записываются итератором вывода. Схема функции вычисления реберной связности приведена в листинге 8. L
Листинг 8.1* Алгоритм вычисления реберной связности
Алгоритм вычисления реберной связности > =
template < typename VertexListGraph. typename OutputIterator >
typename graph^traits < VertexListGraph >::degree_s1ze_type
edge connect!vTty(VertexListGraph & g. Outputiterator dlsconnectlng^set)
{--
《Определить типы )
《Определить переменные )
(Создать граф потоковой сети из неорментированно「о графа )
Найти вершину минимальной степени и вычислить соседей S и не соседей S )
〈「ЛдВНЫЙ цикл )
(Вычислить прямее ребра разреза 〉 return с:
}
В первой части реализации (листинг 8*2) создаются некоторые определения типов, чтобы иметь более короткие имена для доступа к типам из свойств графа. Потоковый граф (ориентированный граф) создается на основе неориентированного входного графа д, поэтому используется графовый класс adjacencyj 1 st.
Листинг 8.2.。叩еделенне типов
《Определить типы ) ■
typedef typename graph_tra1ts <
VertexListGraph >::vertex descriptor vertex_descriptor; typedef typename graph^traits <
VertexListGraph >::degree^size__type degree_size_typ®-
typedef color_traits < defau1t_coTоr_type > Color: typedef typename adjacency_l1st_traits < vecS, vecS.
dlrectedS >;:edge_descrTptor edge^descriptor;
typedef adjacency_l 1st < vecS. vecS. directe^S. njproperty, property < edge_capacit5*__t. degree_$1 ze_type. property < edge二residua1 二capacityJE. degree_size_type. property < edge7everse_C edge_descriptor > > > > FlowGraph:
В листинге 8.3 мы используем std:: set для множества 5 н множества соседей 5 (переменная neighbors), поскольку при вставке должна гарантироваться уникальность. Множество 5' (переменная S_5tdr) и множество не соседей S (переменная nonneighbor_S) представлены std: : vector в связи с тем, что мы знаем, что вставляемые элементы будут уникальными.
Листинг 8.3・ Определение переменных
〈Определить перененные )三
ve гtex_de scr1pt о г u, v, p, k;
edge_descriptor el, e2;
bool'lnserted:
Vpename graph_traits < VertexListGraph >::vertex_iterator vi. vi_end;
degree_size_type delta. alpha_star, a1pha_S k:
std: :set < vertex_descriptor > S. neighbor^;
std::vector < vertex_descri ptor > S_$tar, nonneighbor^S;
std::vector < default_color_type > color(num_vertices?g));
std: : vector < edgejiescrlptor > prect(nuni_vertices(g?k
Граф потоковой сети создается на основе входного графа (листинг 8,4). Каждое ребро потокового графа имеет три свойства: пропускную способность, невязку и обратное ребро. Доступ к этим свойствам осуществляется через объекты-отображения свойств: cap, ге5_сар и rev_edge соответственно.
Листинг 8・4・ Создание графа потоковой сети из неориентированного графа
〈Создать граф потоковой сети из неориентированного графа ) ■
FlowGraph flow_g(num_vertices(g)): // потоковый *раф
type name property __map < FlowGraph. edge_capdc1ty_t >::type
cap = get(edge_capacity. f1ow_g);
typename property_map < FlowGraph. edge_resi(iual_c-3pacity3 >::type
res_cap - get(edge_residual_c2pacity. fTow_g);
typename propertyjnap < FlowGraph. edge_reverse_t >::type
rev^edge = get(edge_reverse. fTow g);
typename graph_tr3lts < VertexListGraph >::edge_1tarator el. ei_end;
for (tietelt eT^end) 口 edges(g); ei !« ei end: ++ei) {
u = source(*ei, g). v 户 target(*e1, g);
tlaSL Inserted) « add_edge(u. v, flow_g):
cap[el]冃 1:
tie(e2. inserted) ₽ add_edge(v4 u. f】ow_g):
cap[e2] - 1;
rev_edge[el] = e2:
rev二 edgeiu2j - el:
} -
В главном алгоритме функционал ьн ые блоки выделены в отдельные функции (листинг 8.5), В первом блоке в цикле по всем вершинам графа находится верши* на минимальной степени.
Листинг 8.5. Функция нахождения вершины минимальной степени
〈Функция нахождения вершины минимальной степени ) а
template < typename Graph >
std:rpair < typename graphtraits < Graph >::vertex_descriptor, typename graph^traits < Graph >: :degr£g$ize_type > mln degree_vertex(Graph & g) { ■-
typename graph_traits < Graph >::vertex_descriptor p:
typedef typename graph_traits < Graph >::degrce_size_typc s1ze_typ2;
size_type delta = std:7numer1cjimits < size^type >:7max();
Vpename graph^traits .< Graph >: : vertex J terator i. lend:
for (tied, lend) - vertices(g); i != tend:卄К if (degree(*i. g) < delta) {
delta - degree1*1. g);
р =:
}
return std::make paiNp. delta):
Необходимо обеспечить возл(ож1гость вставки каждого соседа вершины (и мио- жества вершин) во множество. Это осуществляется через adjace门t_vertices(). Мы предполагаем, что итератор вывода сходен с std: : insert_1terator для std:: set. Вспомогательные функции приведены в листинге 8.6.
Листинг 8.6* Вспомогательные функции вывода соседей
《Вспомогательные функции вывода соседей 〉色
tempi ate < typename Graph, typename OutputIterator > void neighborsfconst Graph & g.
t^pename graph_traits < Graph >::vertex_descriptor u.
Output Iterator~result)
{
typename graph_traits < Graph >::adjacency_1terator ai, aend;
for (tied" aend) = adjacent_vertices(u, g): ai != aend: ++ai) *result++ = *a1:
}
tempi ate < typ^name Graph, typename Vertexiterator, typename Outputiterator >
void neighbors(const Graph & g. Vertexiterator flrst.Vertexiterator last * OutputIterator result)
for (: first !■ 1ast; ++fi「st) neighborstg. *f1rst. result〉;
}
На начальном шаге алгоритма (листинг 8.7) осуществляется поиск вершины минимальной степени р, затем S = р, а после этого вычисляются соседи и не соседи 5. Мы используем std: :set_difference() для вычисления V~ St где V— множество вершин графа.
Листинг 8.7. Поиск вершины и вычисление соседей и не соседей S
(Найти вершину минимальной степени и вычислить соседей S и не соседей S 》三 tie(p. delta) я min_degree_verte>c(g);
S_star,push_back(p)7
aTpha_star - delta:
S.insert(p);
neighbor_S,insert(p):
neighborW(g. S. begin。,S.endO, std: inserter (neighbors.
neighbor_S.begin())):
std::set__d1fferencetvertlces(g).first. vertices(g).second, neighbors.beginO, neighbor_S.end(). std: :baclT inserter (nonneighbor_S)):
Итерации алгоритма заканчиваются, когда множество не соседей 5 становится пустым* В каждом шаге алгоритма (листинг 8.8) максимальный поток между р и не соседом 厶 вычисляется с помощью алгоритма Эдмондса-Карпа (см. раздел 13.7Л). Вершины, окрашенные не в белый цвет, во время нахождения максимального потока соответствуют всем вершинам на одной стороне минимального разреза. Таким образом, если размер разреза на данный момент наименьший, окрашенные (не белые) вершины записываются в S] затем А добавляется в 5, соседи и не соседи 5 вычисляются заново.
Листинг 8.8. Главный цикл
〈Главный цикл )=
while (Inonnelghbor_S.empty()j <
k 二 nonneighbor_s7front();
alpha_S_k ^ edmunds_karp_max_f 1 ow
(flow^g. p. k. cap. res__cap. rev edge. &color[0]. &pred[0]);
if (alpha_S_k < alpha_star) {
alpha^star = dlphd_§_k;
S_starxleart):
for (t1e(vi. vi^end) = vert1ces(flow_g): v1 != vLend; —vi)
if (color[*vi] I- Color::white())
S_star.push-back(*vi):
S.insert(k):
neighbor_S.insert(k):
neighborsCg. k. std: : inserter (neighbors, nei ghbor S. begi n ())):
nonnei ghbor_S.clear():
std::£©t_di fference(vertices(g).first+ vertices(g).second, neighbojS.begin(). neighbor_S.end(), std::backi nserter(nonnei ghborS)):
}--
На завершающем этапе (листинг 8.9) осуществляется поиск ребер разреза, которые имеют одну вершину в S, а другую в У. Эти ребра записываются через итератор вывода di sconnecti ng_set (несвязное множество)» а количество ребер в разрезе возвращается оператором return.
Листинг 8・9・ Вычисление прямых ребер разреза
〈Вычислить прямее ребра разреза ) а
std::vector < bool > 1n_S_star(num_vertices(g). false);
typename std::vector < vertex^descriptor >:iterator si:
for (si ^ S_star.begin(); si T= S_star.end(); ++si)
in_S_starr*si] = true:
degree_size^type c ' 0;
for (六=S^star.beginO: si != S_star.end(): ++s1: {
typename graph_tra1ts <VertexListGraph>::out_edge_iterator ei. ei_end;
for (tie(ei. eT_end) = out_edges(*si, g): ei~!= e7_end: ++ei)
if (!in_S_staretarget(*ei, g)]) {
*disconnecting_set++ - *ei:
++c:
}
Файл edge-connectivity.cpp, в котором реализован алгоритм нахождения реберной связности, приведен в листинге 8.10.
Листинг 8.10. Файл edge-connectivityxpp
《edge-connec 11v i ty.epp〉巨
#include <algorithm>
^include <ut11ity>
#i ndude <boost/graph/edmunds_karp_max_f 1ow.hpp>
#include <boost/graph/push_reTabel[max二门ow.hpp>
#include <boost/graph/adjacencyJist.hpp>
#1nclude <boost/graph/graphvi z.hpp>
namespace boost {
《Функция нахождения вершины минимальной степени 〉
《Вспомогательные функции вывода соседей 〉
《Алгоритм вычисления реберной связности )
}
int malM) {
using namespace boost;
GraphvizGraph g:
read^graphvizt^figs/edge-connectivity.dot ”. g):
typedef graph^tralts < GraphvizGraph >::edge_descriptor edge^descriptor;
typedef graph二traits < GraptivizGrapti >: :degree_size_type degree_s1ze_t,ype:
std::vector < edge_descriptor > di sconnecting_set:
degree_size_type e~=
edge_connectivity(g, std::backjnserter(disconnectlng^set));
std: :cout « ° Реберна я связность: ” « с « ”」« std: :endl ;
property_map < GraphvizGraph. vertex_attribute_t >::type
attr_map 匸 get(vertex_attr1bute- g):
std::cout « "Сечение: {";
for (std::vector < edge_descriptor >:iterator i =
di sconnecti ng_set.begTn(); 1 != disconnect1ng^set.end(); ++i>
std::cout « "(* « attr_map[source(*i. g)]「'Tdbel'」«
« attr_map[target(*i, gllf'label''] << ") ”;
std::cout « H}<< std::endl;
return EXITJUCCESS;
}
В результате работы файла edge-connectivityxpp выводится следующее:
Реберная связность: 2.
Сечение: { (О.Е) CD.H) }.
Граф, к которому был применен алгоритм нахождения реберной связности (листинг 8,10), представлен на рис. 8/1.
Неявные графы: обход конем
Задача обхода конем состоит в нахождении такого пути для коня, чтобы он побывал на каждой клетке шахматной доски nx п ровно один раз. Обход конем является примером гамильтонового пути. Гамильтонов путь — это простой замкнутый путь, который проходит через каждую вершину графа только один раз. При обходе конем клетка шахматной доски считается вершиной графа. Ребра графа определяются в соответствии с шахматными правилами для хода коня (например, на две клетки вверх и на одну в сторону). В этом разделе мы используем для нахождения пути коня обобщенный алгоритм поиска с возвратом. Алгоритм с возвратом является алгоритмом грубой силы и достаточно медлителен, поэтому мы также покажем, как улучшить алгоритм, применив эвристику Варнсдорфа [46]. Задача нахождения гамильтонова цикла является NP-полной [15] (при большой размерности она не может быть решена за разумное время). Пример обхода конем традиционной шахматной доски 8x8 показан на рис. 9.1.
Рис. 9.1. Пример обхода конем шахматной доски
Одной из особенностей этого примера является отсутствие явной структуры (вроде класса adjacency_11st) для представления графа. Вместо этого неявная структура графа kni ghts_tour_graph следует из допустимых ходов коня на доске.
Ходы конем как граф
Граф knights一tour_graph является моделью AdjacencyGraph, поэтому нам нужно реализовать функцию adjacent_vertices(), возвращающую пару итераторов смеж* ности. Итератор смежности трактует каждую из клеток, в которую может перейти конь, вершиной, смежной данной.
Набор возможных ходов коня хранится и массиве следующим образом:
typedef std:rpair < mt, mt > Position:
Position knight_jumps[8] = { Position(2. -1), PositionCl. -2).
Position(-1.二2), Pos1tion(-2, -1). Position(-2. 1).
PositionC-l, 2). PositionCl, 2). Position(2( 1)):
Итератор knightedjacency J terator содержит несколько полей данных-членов: текущую позицию на доске m_pos, индекс m_i в массиве ходов kni ght_」umps и указатель на граф m_g. Инкремент итератора смежности (операпия ope「ator++()) увеличивает m_i. Новая позиция может быть некорректной (за пределами доски), поэто' му может понадобиться еще раз увеличить m_i, что и делает функция-метод класса valid_position(). Первая позиция тоже может быть некорректна, поэтому vdl 1d_po5ition() вызывается также и в конструкторе итератора смежности. Указатель на шахматную доску необходим для того, чтобы можно было определить ее размер (так как доска может быть произвольного размера). При разыменовании итератора смежности (operator*()) возвращается смегценне текущей позиции в текущем векторе ходов. Реализация итератора knight_adjacency_1terator приведена в листинге 9.1. Вспомогательная функция boost:: forward_i terator_hel per используется для автоматической реализации операции operator+-+*(i nt) посредством operator++() и операции operatorН() посредством operator==().
Листинг 9.1. Итератор смежности knight_adjacencyjterator
struct knight_ad」acency_iterator:
public boost:;forwardJterator_helper < knight_adjacency_iterator, Position.写td::ptrdfff_t* Position *. Position > {
kn1ght_adjacency_iterator() { }
knigbt_adjacency_iterator(1 nt ii. Position p.
const kni ght$ _tou r_g r a ph & g) : m_pos(p), m_g(&g), m_i(11) { valid_position(5: }
Position operator *0 const { return m_pos + knightjumps[m_1]: }
void operators 0 { ++巾_1; vabci^positionO; }
bool operator 一 (const kn1ght_adjacency_11erator & 犬)const { return m_i == x.mi; }
protected:
void v值1id_p0s1tion();
Position m二pos;
const knights_tour_graph * m_g; i nt m_1:
};
В фун кц и и -методе класса val1d_position() счетчик ходов увеличивается до тех пор, пока не будет найдена позиция на доске или не будет постншут конец массива.
void knight_adjacency_1terator::vail deposit!on() {
Position new_pos = m_pos + knight_jumps[m_i];
while (m_i <~8 && (new pos.first < 0 11 new pos.second < 0
11 inew_pos, f 1 rst >= m_g->m_bodrd_s1ze
11 new^pos. second >e iii_g->m_board__size)) {
new^pos = m_pos + knight Jumps [mJ]:
Функция adjacent_vertices() создает пару итераторов смежности, используя О для начальной позиции итератора ходов и 8 для конечной позиции итератора ходов.
std::pair < knights_tour_graph::adjacency^iterator.
knight s_tour_graph::adjacency_1terator >
adjacent_vertices(knights_tour_graph:;vertex_descriptor v.
const knights_tour_graph & g) {
typcdaf knights_tour_graph::ddjacency_1te「dto「 Iter:
return std::make_pair(Iter(O, v. g), Iter(8; v. g));
}
Класс knights_tour__graph (листинг 9.2) содержит только размер доски (как по- 刀е) и операторы typedef, требуемые для AdjacencyGraph. Функция num_vertices() возвращает число клеток на доске.
Листинг 9.2. Класс knights一tou「_geph
struct knlghts_tour_graph
{
Vpedef Position vertex_descriptor:
typedef stdI:pair < vertex_descriptor, vertex_descriptor > edge^descriptor:
typedef knight_adjacency_iterator adjacency_iterator:
typedef void out_edge_iterator:
typedef void 1n_edgejterator:
typedef void edge_i terator:
typedef void vertex^)terator;
typedef 1nt degree^slze type:
typedef 1nt vertices_s1ze_type:
typedef Int edges_size_type:
typedef directed^tsg directed_category:
typedef d1sallow_parallel_e(ige_tag edge_parallei_category:
typedef adjacency_graph_tag traversal_category:"
k ni ght s_t ou r_g ra ph (i nt n) : rn_board_size(n) { }
int m_board_lT^e;
}:
int num_vertices(const knights_tour_graph & g) { return g.m_board_size * g.in_boaresize:
}" -
Теперь, когда ходы конем отображены в графовом интерфейсе Boost, мы можем рассмотреть отдельные графовые алгоритмы, которые могут быть использованы для решения задачи обхода конем шахматной доски.
Поиск с возвратом на графе
Идея алгоритма поиска с возвратом на графе (backtracking graph search) подобна поиску в глубину в том, что путь нсследуепся до тех порт пока не будет обнаружен туш【к. Поиск с возвратом отличается тем, что после достижения тупика алгоритм возвращается обратно, снимая пометки с тупикового пута, перед тем как продолжить поиск по другому пути, В листаете 9.3 поиск с возвратом реализован с помощью стека (вместо рекурсии), а порядок посещения каждой вершины записывается с помощью отображения свойства. Стек состоит из пар отметка времени — вершина» так что правильная отметка посещения доступна после возвращения из тупика. Поиск завершается, когда все вершины посещены или все возможные пути исчерпаны.
Хотя граф, определенный в предыдущем разделе» был неявным и представлял, в частности, обход шахматной доски конем, он тем не менее моделирует концепцию Graph из BGL.to есть алгоритм поиска с возвратом реализован для концепщш Graph, а не для конкретного гра4)а обхода конем. Получившийся в результате алгоритм можно повторно использовать для любой структуры данных, моделирующей Graph.
Листинг 9.3. Алгоритм поиска с возвратом
template < typename Graph, typename TlmePropertyMap > bool backtrack!ng_search(Graph & g.
typename graph^traits < Graph >: : vertex_^descriptor src. T1 rnePropertyMap time_map)
{-
(Создать стек и инициализировать отметку времени 〉
S.push(std: :make_pair(tinie_stanip. src)):
while (!S.empty()) { // цикл пока стек не пуст
《Получить вершину со стека* записать время и проверить завершение 〉
《Положить все смежные вершины на стек >
〈Если в тупике, откатиться 〉
)// while (!S.empty()) return false:
Для записи вершин, которые нужно проверить, используется std::stack. Вершина заносится в стек вместе с отметкой посещения.
《Создать стек и инициализировать отметку времени )三
typedef typename graph_tгаits < Graph >::vertex_descriptor Vertex:
typedef std::pair < int. Vertex > P; std::stack < ₽ > S: int time_stamp = 0:
Следующий шаг — записать отметку посещения для вершины графа наверху стека и проверить, не занесены ли уже в стек все вершины графа. В последнем случае алгоритм успешно завершается.
〈Получить вершину со стека. записать время и проверить завершение )三
Vertex х;
tie(time_stanipf х) = S.topC): putftime^map. x, time-stamp); // все вершины посещены. успех! if (time_stamp 』 num_vertices(g) - 1) return true:
Теперь просматриваются все смежные вершины, и если смежная вершина еще не была посещена, она кладется в стек. Отсутствие смежных вершин — признак тупика.
《Положить все смежные вершины на стек )三
bool deadend = true: // переменная для индикации тупика
typename graph traits < Graph >::adjacency.iterator i. end:
for (tied. end) - adjacent verticesCx. g): i != end; ++1)
if (getCtlmejnap, *i)『-1) {
S.push(std7:make_pair(time_stamp + 1, *i));
deadend = false;
Если алгоритм достигает тупика, вершины выталкиваются из стека, пока не будет найдена еще не исследованная вершина. В процессе возврата отметки посе тенн只 для каждой вершины сбрасываются, поэтому, возможно, эти же вершины будут достигнуты по более подходящему пути.
〈Если 8 тупике, откатиться )琛
if (deadend) { // если тупик
put(time_map, х. -1);
S,pop();
tlettiin^stamp. х) - 5.top():
while (get(time_mapk x) !■ -1) {
// откатить стек до последней не исследованной вершины
put(time_map. х, -1);
S.pop():~
t1e(time_stamp, х) = S*聯():
}
}
Эвристика Варнсдорфа
Эвристика Варнсдорфа для выбора следующей клетки для хода заключается в упреждающем просмотре каждого из возможных ходов для определения того, сколько дальнейших ходов возможны из этой клетки. Назовем это числом последователей, Клетка с наименьшим числом последователей выбирается для следующего хода. Причина, по которой эта эвристика работает, в том, что, посещая в начале наиболее ограниченные вершиньь мы избегаем потенциальных тупиков. Следующая функция number_of_successors() в листинге 9.4 вычисляет число последователей вершины.
Листинг 9.4. Функция number_oLsuccessors()
template < typename Vertex, typename Graph. typend用r TimePropertyMap > int number_of_successors(Vertex x. Graph & g. TimePropertyMap time_map) {
int s_x = 0;
t^pename graph_traits < Graph >::adjacency_iterator 1. end:
for (tied, end) - adjacent_vert 1 ces'(x. g)7 i !■ end: +X)
if (get(time_map. *1) — ~1)
++s_x: return 5 x:
}
Реализация эвристики Варнсдорфа (листинг 9.5) начинается с алгоритма с возвратом, но вместо заталкивания смежных вершин в стек сначала происходит сортировка вершин по количеству последователей. Сортировка выполняется при помещении смежных вершин в очередь по приоритету (priority queue). После занесения вершин в очередь они извлекаются оттуда и помещаются в стек. Пустая очередь сигнализирует о тупике.
Листинг 9.5. Реализация эвристики Варнсдорфа
tempi ate < typename Graph, typename TiinePropertyMap > bool warnsdorffCGraph & g.
typename graph traits < Graph >::vertex_descriptor src, TiinePropertyMap time^map)
{
《Создать стек и инициализировать отметку времени )
S.pushCstd::make_paiг(time_stamp. src));
while (!S.empty(7) {
< Получить вершину co стека. записать вреия и проверить завершение >
// поместить смежные вершины » локальную очередь по 叩морнтету
std::priority_queue < Р. std::vector < Р >. compare_first > Q:
typename graph_traits < Graph >::adjacencyJterator-!t end:
int num_succ:-
for (tied. end) - adjacent_vertices(x, g): i !■ end: ++i)
if (getCtimejrap. *1) ^ -1) {
num succ = number_of_successors(*i. g. time_map);
Q.pushCstd: :make pair(num_succ. *i>);
}
bool deadend = Q.emptyП:
// переместить вершины из локальной очереди в стек
for (:!Q.empty。: Q,popO) {
tieCnumsucc. x) = Q,top():
S.push(std::make pair(time stamp + L x)):
}■
if (deadend) {
put(ttire_map. x. -1):
S.popC;-
tie(time_stamp, x)二 S.topO:
while (getttimejnap. x) !- -1) {
// откатить стек до последней не исследованной вершины put(time_map. х, -1):
S.popf):
tie(time_staiTip. х) - S.topl);
} // while (IS.emptyO) return false:
}
5 Зак 375
ВзаимодействиеВзаимодействиес другими графовыми библиотеками
Взаимодействие
Взаимодействие
Хотя основной задачей Boost Graph Library является поддержка разработки новых приложений и графовых алгоритмов, существует довольно много кодов, которые могут полнить выгоду от использования BGL. Одним из путей использования алгоритмов BGL с супхествуютими графовыми структурами данных является копирование данных из старого формата в BGL-граф, который затем используется в алгоритмах BGL. Проблема с этим подходом заключается в Агом, что не всегда удобно и эффективно выполнять это копирование. Другим подходом является использование существующих структур непосредственно, используя《обертку» для BGL-интерфейса.
Паттерн Адаптер [14] — это один из механизмов для обеспечения нового интерфейса для существующего класса. Этот подход обычно требует, чтобы адаптируемый объект хранился внутри нового класса^ предоставляющего желаемый интерфейс. При адаптации BGL-графа вложенности объекта не требуется, поскольку графовый интерфейс BGL полностью состоит из независимых (глобальных) функций. Такой интерфейс вместо создания нового графового класса требует только перегрузки независимых функций, из которых он состоит. В разделе 10.3 мы покажем, как это делается, во всех деталях.
Библиотека BGL включает перегруженные функции для типа GRAPH из LEDA [29} тапа Graph* из Stanford GraphBase, а также для std: : vector из STL. LEDA — популярная объектно-ориентированная библиотека для комбинаторных вычислений, включающая графовые алгоритмы и структур!.! данных. Stanford GraphBase, написанная Дональдом Кнутом, является набором графовых данных, генераторов графой и программ, которые выполняют алгоритмы на этих графах.
В следующих разделах мы покажем примеры использования структур данных из LEDA н SGB с алгоритмами BGL. Затем мы рассмотрим реализацию адаптирующих функций из BGL для графов LEDA, приводя пример тогот как нужно писать адаптеры для других графовых библиоз ек.
В разделе 1.4.1 мы демонстрировал и гибкость алгоритмов BGL, применяя topolog1 cal_sort() к графам, представленным вектором списков, к графам в виде boost: :adjacency_list и std: :vector<std: :!1st<1nt>>. Мы продолжим этот пример —планирование выполнения взаимозависимых заданий — в следующих двух
разделах, сначала используя LEDA-тип GRAPH, а затем — тип Graph от Stanford Graph Base (SGB).
Использование топологической сортировки из BGL с графом из LEDA
Заголовочный файл boost/gгаph/Leda_graph. hрр содержит перегруженные функции, которые адаптируют параметризованный тип GRAPH из LEDA к интерфейсу BGL. BGL-интерфейс к LEDA GRAPH описан в разделе 14.3.5. Интерфейс между LEDA и BGL был протестирован с версией LEDA 4,1, одной из свободно распространяемых версий LEDA. В дополнение к Leda_graph.hpp библиотека LEDA но刀來н柱 быть установлена, настроены пути к заголовочным и библиотечным файлам, a LEDA- библиотеки использованы при сборке программы. Более детальный материал на* ходится в документации LEDA.
В листинге ЮЛ приведена схема программы планирования заданий, на этот раз используя GRAPH из LEDA для представления зависимостей между заданиями. Листинг 10.1. Схема про「раммн планирования заданий
《topo-sort-with-leda.срр )三
^include <vector> include <string>
#i nd tide <boost/graph / topol ogi csl^sort, hpp>
#i nd ude <boost/graph/l eda_graph. hpp>
〃 Отменить определение макросов LEDA, конфликтующих с C++ Standard Library
#und2f string
#undef vector
int mainO {
using namespace boost;
〈Создать граф LEDA с вершинами, обозначенными заданияшл )
< Добавить ребра н 「ра0у LEDA >
《Выполнить топологическую сортировку графа LEDA ) return EXIT—SUCCESS;
Класс GRAPH из LEDA позволяет пользователю присоединить объекты-свойства к вершинам и ребрам графа, поэтому здесь мы прицепляем имена (в форме std::stri ng) к вершинам. Мы используем обычную функцию add_vertex() для добавления вершин к 1 eda_g и передаем имена заданий как объект-свойство для присоединения к вершине. Дескрипторы вершин, возвращенные из add_vertex()t хранятся в векторе, так что можно быстро использовать нужную вершину при добавлении ребер. Код для создания графа LEDA приведен в листинге 10,2.
Листинг 10.2. Создание графа LEDA для задачи планирования заданий
(Создать граф LEDA с вершинами. обозначенными заданиями 〉芸
typedef GRAPH < std::string, char >graph_t:
graph_t leda_g:
typedef graph_traits < graph.t >::vertex_descriptor vertex_t;
std::vector ^vertex^t > vert(7);
vert[O] = add_vertexTstd::str1ng(h3a6paTb детей из 出колн"). leda_g);
vertil] = addj/ertex(std: : st ri ng(" купить продукты*'). 1edd_g);
vert [2] - add^vertex (std :: st ri ng(м получить деньги в банкомате*') ( leda_g): vertC3] = add^vertexCstd: :string(,,npneecTH детей на тренировку'1). led』_g): vert[4] = add_vertex(std: istringc^npnготовить ужин"), leda_g):
Листинг 10.2 {продолжений
vert[53 = add_vertex(std::string("забрать детей с тренировки"). ledd_g); vert[6] = add_vertex(std::stringC*съесть ужин"), leda_g):
Следующий шаг — добавление ребер к графу. Вновь мы используем обычную
функцию add_edge().
(Добавить ребра к графу LEOA > =(Добавить ребра к графу LEOA > =ddd_edge(vert[O]4 已 dd 二 edge (vert 口, add^edgefverttlL add_edge(vert[2]. add2edge(vert[3]( add2edge(vert[4]. add_edge(vert[5].ddd_edge(vert[O]4 已 dd 二 edge (vert 口, add^edgefverttlL add_edge(vert[2]. add2edge(vert[3]( add2edge(vert[4]. add_edge(vert[5].vert[3]. leda_g): vert[3J. leda^g): vert[4]. leda^g): vert[lj, leda^g): vert[5], leda_g): vert[6L leda_gk vert [6]. ledaQ):vert[3]. leda_g): vert[3J. leda^g): vert[4]. leda^g): vert[lj, leda^g): vert[5], leda_g): vert[6L leda_gk vert [6]. ledaQ):
(Добавить ребра к графу LEOA > =
(Добавить ребра к графу LEOA > =
ddd_edge(vert[O]4 已 dd 二 edge (vert 口, add^edgefverttlL add_edge(vert[2]. add2edge(vert[3]( add2edge(vert[4]. add_edge(vert[5].
ddd_edge(vert[O]4 已 dd 二 edge (vert 口, add^edgefverttlL add_edge(vert[2]. add2edge(vert[3]( add2edge(vert[4]. add_edge(vert[5].
vert[3]. leda_g): vert[3J. leda^g): vert[4]. leda^g): vert[lj, leda^g): vert[5], leda_g): vert[6L leda_gk vert [6]. ledaQ):
vert[3]. leda_g): vert[3J. leda^g): vert[4]. leda^g): vert[lj, leda^g): vert[5], leda_g): vert[6L leda_gk vert [6]. ledaQ):
1 еперь, когда 1раф построен, можно вызвать topol ogical_sort(). Благодаря eh- терфейсу между LEDA и BGL GRAPH из LEDA может быть использован в неизменном виде с ВСЬ-функцией, Мы просто передаем объект leda_g в этот алгоритм. Функция topologicdl__sort() требует отображения свойства окраски вершин, поэтому мы используем массив вершин LEDA node_array для отображения вершин в цвета. Функция 】eda node一property_map() также определена в boost/graph/leda_graph.hpp и создает адаптер, который удовлетворяет концепции LvaluePropertyMap в контексте node_array. В вектор topo_order записывается обратное топологическое упоря* дочение. Затем вектор переворачивается, и выводится упорядочение. Операции operator[]() класса GRAPH из LEDA используется для доступа к названию задания для каждой вершины- Код топологической сортировки приведен в листинге 10.3.
Листинг 10.3. Выполнение топологической сортировки
《Выполнить топологическою сортировку графа LEDA 〉二 std::vector v vertex_t > topo_order:
node_array < default^color^type > color_array(leda_g):
topological_sort(1eda_g, std::back_1nserter(topo_order).
color_fnap(make_leda_no(ie_property_niap(color_array))):
std: :reverse(topa order^beginO. topo order.endO);
int П a 1:
for (std::vector v vertex^t >:iterator : ^ topo_order.begin(): i != topo_order.end(7; ++1. ++n)
std: :cout «"n « ”: ” « leda_g[*1] « std: :endl:
Использование топологической сортировки из BGL с графом из SGB
Библиотека Stanford Graph Base определяет структуру Graph, которая реализует структуру данных в стиле списка смежности. Перегруженные функции в boost/ graph/stanford_graph,hpp адаптируют структуру Graph к интерфейсу BGL. Помимо включения заголовочного файла stanford_graphJipp необходимо, чтобы была установлена библиотека SGB и применен файл изменения PROTOTYPES (из дистрибутива SGB). Это необходимо потому, что оригиькшьные заголовочные файлы SGB не определяют прототипы функций согласно стандарту ANSI, что требуется для компилятора C++. При компиляции программы с использованием SGB-BGL интерфейса необходимо установить пути к заголовочным файлам и библиотекам для
SGB, нужно также собрать программу с библиотекой SGB. Интерфейс между BGL и SGB задокументирован в разделе 14.3.4. Пример топологической сортировки для графа из SGB приведен в Ш1стииге 10.4.
Листинг 10.4. Топологическая сортировка графа из SGB
〈topo-sort-with-sgb.cpp > 声
♦include <vector>
include《string〉
#inc1ude <iostream>
#include <boost/graph/topological_sort.hpp>
indude <boost/graph/stanford_graph,hpp>
int 呢inC {
using namespace boost;
(Создать SGB-граф )
〈Создать метки для заданий 〉
Добавить ребра к SGB-графу 》
(Выполнить топологическую сортировку на SGB*графе 〉
gb__recycle(sgb_g):
return EXIT_SUCCESS:
Мы создаем SGB-граф вызовом SGB-функции gb_new_graph().
Создать SGB-граф 〉三
const int n_vertices - 7:
Graph *$gb_6= gb_ncw_gr叩h(n_vert1ce£);
Далее мн записываем метки для заданий (цершины) в граф. От дескриптора вершины в SGB легко перейти к целому числу, используя sgb_vertex_id__rnap, определенный в stanford_graph.hpp, поэтому хранение меток в массиве удобно.
〈Создать метки для заданий )-
const char *tasks[]-(
"забрать детей из школы".
"купить продукты".
"получить 冊нь「м в бaнкoмaтё1^ г
"привести детей на тренировку1',
"приготовить ужин",
"забрать детей с тренировки"Ф
"съесть ужин"
};
const Int n_tasks - sizeof(tasks) / sizeoffchar *);
SGB-граф хранит вершины графа в массиве, поэтому можно получить доступ к любой вершине по индексу в массиве. Функция gb_ne_edge() принимает два аргумента Vertex* и вес ребра (нам он не важен).
0):0):0):0):0):0);0);0):0):0):0):0):0);0);< Добавить ребра к SGB-графу ) ■
0):
0):
0):
0):
0):
0);
0);
0):
0):
0):
0):
0):
0);
0);
sgb_g->vertices + 3 sgb2g->vertices + 3 sgb2g->vertlces + 4 sgb2g->vertices + 1 sgb二g・>vertices 十 5 $gb二g~>vertiCE5 + 6 sgbQ-xertices + 6,sgb_g->vertices + 3 sgb2g->vertices + 3 sgb2g->vertlces + 4 sgb2g->vertices + 1 sgb二g・>vertices 十 5 $gb二g~>vertiCE5 + 6 sgbQ-xertices + 6,gb_new_a rc(sgb_g->vert i ces + О gb 二 new 二 arc(sgb二g-xertices + 1 gb_new_arc(sgb_g->vertices + 1 gb二new二arc(sgb二g->vert ices + 2 gb二new二arc《sgb二g ・>vertices + 3 gb 二 new二 arc(sgb二g-xertices + 4 gb二newdrMsgbQ-zerticef + 5.
sgb_g->vertices + 3 sgb2g->vertices + 3 sgb2g->vertlces + 4 sgb2g->vertices + 1 sgb二g・>vertices 十 5 $gb二g~>vertiCE5 + 6 sgbQ-xertices + 6,
sgb_g->vertices + 3 sgb2g->vertices + 3 sgb2g->vertlces + 4 sgb2g->vertices + 1 sgb二g・>vertices 十 5 $gb二g~>vertiCE5 + 6 sgbQ-xertices + 6,
Затем осуществляется топологическая сортировка. Мы передаем в алгоритм сам SGB-граф. На этот раз вместо явного создания отображения цветов мы позволим алгоритму самому создать его. Однако для этого функция topol ogi са 1 _sort () нуждается в отображении вершин в целые числа. Интерфейс между SGB и BGL обеспечивает такое отобра^<ение свойства. Отображение вершин в индексы можно
получить вызовом get (vertex] ndex. sgb_g). Код топологтпеской сортировки при веден в листинге 105
Листинг 10.5. Вычисление топологической сортировки SGB-графа
〈Выполнить топологическую сортировку на SGB-графе > 五
typedef graph_traits < Graph * >::verteK_descriptor vertex_t:
std::vector <~vertex_t > topo_order:
topolog-ical_sort(sgb_gt std: :back_inserter(topo_order) t
vertexJndex_map(get(vertexjndex. $gb_g)));—
int n = I:
for (std::vector < vertex_t >::reverse_iterator i = topo_order,rbegin():
i l^ topo order.rendO; ++1. ++n}~
std::cout « n « “: ° « tasks[get(vertex_1ndex, sgb_g)[*i]] « std::endl:
Реализация адаптеров графов
Написать адаптер для других графовых библиотек и структур данных совсем не сложно. В качестве примера создания новых адаптеров этот раздел предлагает детальное объяснение реализации интерфейса BGL для графа из LEDA.
Первое, с чем нужно определиться, — это какие из концепций будет реализовы* вать BGL граф. Следующие концепции легко реализовать поверх классов из LEDA: VertexListGraph, BidirectionalGraph, VertexMutableGraph и EdgeMutableGraph.
Все типы, ассоциированные с графовым классом из BGL, можно получить с помощью класса graph traits. Этот класс свойствможет быть частично специа,пизи- рован для графового класса GRAPH из LEDA1 (листинг 10.6). Типы node и edge являются эквивалентами дескрипторов вершин и ребер. Класс GRAPH предназначен для ориентированных графов, поэтому мы выбираем тег directed_tag для di rec- ted_category. Так как класс GRAPH автоматически нс запрещает вставку параллельных ребер, в нашем случае устанавливается а 11 ow^paга 11 el_edge_tag для edge_paral - lei .category. Функция number_of_nodes() из LEDA возвращает целое число, поэтому данный тип указан для vertices_slze_type. Тип тега, используемый для traver sal_category, должен отражать моделируемые графом концепции обхода, поэтому мы создаем теговый класс, наследующий от b i d i г ect 1 on а 1 _g г а р h_t a gt adjacency^ graphjag и vertex」; 5t_g「dph_tdg. Типы итераторов описаны далее в этом разделе.
Листинг 10.6. Свойства графоа в LEDA
(Свойства графов для LEOA-графа > я namespace boost {
struct 1eda_graph_tгаversal^category : public vi rtual bi di recti onal_graph__tag. public virtual adjacency_graph_tag7 public virtual vertex_list_graph_tag { };
template < typename V, typename E> struct graph_traits< GRAPH<V.E> > { typedef node vertex_descriptor: typedef edge edge_descr1ptor: typedef di rectedjfag d1rected_category;
Некоторые нестандартные компиляторы, например Visual C++ 6.0, не по/у^ржнвают частичную специа- н нзапн ю. Для доступа к ассоцилрован ны м типам в этом случае класс свойств должен быть полностью специализирован для конкретных типов вершин и ребер В альтернативном вариалтс может быть применен класс-оболочка, содержащий LEDA-граф и требуемые вложенные определения т"“з*.
typedef allowj)aralTel_edge3ag edge^paral 1 el__category:
typedef 1 eda_graph_traversaT_category traversal/ategory:
typedef int vertices__s 1 ze type:
typedef int edges_s1ze_type: typedef int degree_size_type:
(Тип итератора исходящих вершин 〉 // другие typedef для итераторов
} // namespace boost
Сначала мы напишем функцин sourceO и target () /щя концепции IncidenceGraph, которая является частью концепции BidirectionalGraph. Мы используем тип GRAPH из LEDA в качестве параметра для графа и graph__t га its для задания параметра ребра и возвращаемого тапа для вершины. Хотя типы LEDA могут быть применены для вершины и ребра, на практике лучше использовать graph_traits. Тогда, если придется изменить ассоциированный тип вершины или ребра, это можно сделать в одном месте внутри специализации graph^traits, а не во всем коде программы. Так как LEDA предоставляет функции sourceO и target О, мы просто их вызываем.
〈Получение начальной и конечной вершины ребра для графа из LEOA )三
template <c1ass Нуре, class etype>
typename graph_traits< GRAPH<vtype.et.ype> >: :vertex_descriptor
source(typename graph_traits< GRAPH<vtype.etype> >:7edge__descriptor e.
const GRAPH<vtype.etype>& g)
{
return source(e):
}
〃 по аналогии для конечной вершины
Следующая функция из IncidenceGraph — out_edges(). Эта функция возвращает пару итераторов по исходящим ребрам. Поскольку в LEDA применяются итераторы в стиле STL, их нужно реализовать. Написание итераторов, которые совместимы со стандартом C++, может быть сложным процессом. К счастью, в Boost имеется удобная утилита для реализации итераторов — класс 1terator__adaptor. Этот класс позволяет пользователю создавать совместимые со стандартами итераторы, просто предоставляя классы правил (policy class). В листинге 10.7 приведен класс правил для итератора исходящих ребер. В LEDA сам объект-ребро нс- пользуется как итератор. Он имеет функции Succ_Adj_Edge() и Pred_Adj_Edge() для перемещения к следующему или предыдущему ребру.
Листинг 10.7. Правила для итератора исходящих ребер
〈Правила для итератора исходящих ребер 〉三
struct leda-Out_€dgjite「@tor policies
{- --
static void initialize(leda_edge& ) { }
template <typename Iter>
static void Increment(Iter& 1)
{ i .baseO - Succ_Adj_Edge( 1.base(). 0): }
template <typename Iter>
static void decrement(Iter& i)
{ i.baseO = Pred_Adj_Edge(Kbase(). 0): }
template <typename Iter>
static 1eda^edge dereference(const Iter& i)
(return t . baWe。; }продолжение 步
Листинг 10.7 {продолжение)
template <typename Iter>
static bool equal(const Iter& x. const Iter& y)
{ return x.baset) =^ у .baseO; }
}:
Теперь iterator_adaptor используется в качестве типа edgejterator. Первые два параметра шаблона для edgejterator — адаптируемый класс и класс правил. Сле- дуюшие параметры указывают ассоциированные типы итератора, такие как тал значения н тип ссылки.
(Тип итератора исходящих вершин > ■
typedef Iterater_adaptor<leda_edge• 1 eda__out__edge_iterator^pol 1 cies.
leda_ecige. const leda_edge&7 const leda^edge*. ~
std:7forward_iterator^tag. std::ptrdiff二t
out^edgejterator:-
После определения итератора исходящих ребер в классе свойств можно определить функцию out_edges(). В следующем определении (листинг 10.8) возвращаемое значение должно быть парой итераторов исходящих вершин, так что мы используем std: :раяги затем graph_tra i ts для доступа к итераторным типам. В теле функции мы создаем итераторы исходящих ребер, передавая первое ребро для первого итератора и ноль — для второго (в LEDA с помощью нуля обозначается окончание последовательности). Ноль в качестве аргумента к Fi rst__Adj__Edge() говорит LEDA, что мы хотим обрабатывать исходящие ребра, а не входящие.
Листинг 10.8. Функция out_edges() для LEDA
(Функция out_edges() для LEDA )三
template <typenanie V. typename E>
std::pair<typenaine graph_traits< GRAPRwv,E> >::out_edgejterator.
typenaine graph^traits< GRAPH<V.E> >::out二6dge二iterator >
out_edges(typename graph_traits< GRAPH<V.E> >:: vertex descrlptor u.
const GRAPHS,E>& g)
typedef typename graph_traits< GRAPH<V, E> >: :out_edge_1terator Iter:
return std::make_pair("iter(First_Adj_Edge(u,0))7 Iter(O));
}-'
Остальные типы итераторов и интерфейсных функций пишутся аналогично. Полный код для гштерфейса оболочки к LEDA находится в boost/graph/leda_graph*hpp. В листинге 10.9 мы используем проверку концепций BGL, чтобы быть уверенны* мн в правильности релизации интерфейса с BGL. Эти проверки не тестируюг поведение времени исполнения (это тестируется в test/graph.cpp).
Листинг 10.9. Проверка концепции реализованного интерфейса
〈leda-concept-check.срр )至
1 ndude <boost/graph/graph_concepts.hpp>
include ^boost/graph/1eda_graph.hpp>
int iriainO
{
using namespace boost;
typedef GRAPHvint. int> Graph;
funct1on_requires < VertexListGraphConcept<Graph> >();
function二requires < Bl di rec 11 опа1GraphConcept<Graph> >():
function二requires < VertexMutableGraphConcept<Graph> >():
function二requires < EdgeMutableGraphConcept<Graph> >():
return EXIT SUCCESS;
}
Руководство ЧВ по производительное
ллВ этой главе мы обсудим влияние того или иного выбранного графа из семейства графов BGL ad jacency_l 1 st на производительность. Цель этой главы — дать пользователям BGL некоторые базовые сведения о том, какие из типов графов могут быть наиболее эффективны в различных ситуациях. Мы представим серию тестов» отображающих производительность различных базовых операций несколь- кнх разновидностей adjacency_list из BGL. Исследованы скорости выполнения операций над разреженными и плотными графами с использованием двух различных компиляторов (Microsoft Visual C++ и GNU C++).
л
л
Как основной компонент BGL-графа, adjacency_list помогает пользователям контролировать фактические структуры данных, применяемые для внутренних структур графа. Первые два параметра шаблона, EdgeList и VertexL1stT используются для выбора фактических контейнеров для представления последовательностей исходящих ребер и вершин соответственно. Пользователи могут применять vecS, 11 stS или sets для EdgeLI st для выбора контейнеров Std: :vector, std: :11st или std: :set соответственно. Они также могут указать vecS или 11 stS для выбора std: :vector или std: :list соответственно в качестве основы.
Сравнения графовых классов
Мы сравнивали производительность различных вариантов adjacency^llst. Эксперименты покрывают большинство базовых операций над графами: вставка и удаление вершин и ребер, обход графа по вершинам, ребрам и исходящим ребрам каждой вершнны. Тесты были выполнены с разреженными и плотными графами малого (100 вершин), среднего (1 000 вершин) н большого (10 000 вершин) размера. Для разреженного графа число ребер в 10 раз больше числа
вершин. Для плотного графа полное число ребер равно квадрату числа вершин.
Замеры времени проводились на компьютере Dell с двумя процессорами по 733 МГц, с памятью 512 Мбайт. Тесты были продублированы для двух компиляторов: Microsoft Visual C++ 6.0 н GNU C++ 2.95.3 (под cygwin). Для Vi^ sual C++ был установлен режим оптимизации по скорости. Для GNU C+ + были установлены опции -03 и -funroll - loops. Заметим, что реализация adjacen- cy_1ist использует компоненты из STL, которая поставляется вместе с комп и лятором.
В таймере применялась переносима 只 POSIX-функция clock О, которая имеет довольно низкое разрешение. По этой причине эксперименты выполнялись в цикле до тех пор, пока затраченное время не превысило минимальное разрешение по крайней мере в сто раз. Каждый тест был повторен 3 раза, и взято минимальное время из этих трех прогонов. Мы заметали, что стандартное отклонение в измерениях времени составляет приблизительно 10 %.
Далее приведен полный набор графовых типов, использованных в тестах. Также указаны аббревиатуры, которые были применены в результирующих диаг- paMNfax.
• vec
adjacency_1ist<vecS. vecS. directedS. property<vertex_distance_t. lnt>. property<edge_welght_t, InA >
list
adjacencyJist<l 1stS. vecS, directedS, property<vertex_d1stance_t. int>, property<edge_weight_t r int> >
set
adjacencyJ1st<setSt vecS. directedS, property<vertex_distance__t. int>. p roperty<edge_wei ght_t. int> >
listlist
adjacencyj 1 st<l 1 stS. lists, directedS. property<vertex_distance t. int>. property<edge_wei ght_t. int> >
Результаты и обсуждение
Добавление ребер и вершин
В первом эксперименте чередуются вызовы в add_vertex() и add_edge(), пока чисто ребер графа не достигнет |£ | ребер и | V | вершин. Результаты эксперимента показаны на рис. 11.L Победитель в этом эксперименте — один из классов adjacencyjl st с параметром VertexList-11 stS.
Добавление ребер
В этом тесте добавляются |Е| ребер к графу, который уже имеет \V\ вершин. Результаты показаны на рис. 11.2. Очевидным победителем для Visual C++ являет-* ся ad jacency_l 1 st с VertexLI st=vecS, независимо от размера и разреженности графа. При использовании GNU C++ побеждает класс adjacency一11 st с EdgeList-llstS для разреженного графа.
Рис. 11.2. Результаты измерения времени для эксперимента с добавлением ребер
140 Глава 11 • Руководство по производительности
Удаление ребер
В этом тесте добавляются и удаляются |£ | ребер графа с | V | вершинами. Результаты показаны на рис. 1L3. Результат неясен для графов малого размера. Однако ясно, что adjacency_11st с параметром VertexLi st-setS является победителем для больших графов.
1001001000 100001000 10000Разреженный, Visual 8+Разреженный, Visual 8+.42 о.о, 9Q 工工 ееоспи 匚Bsd Он.42 о.о, 9Q 工工 ееоспи 匚Bsd Он3 足 о.63 足 о.6100 1000 10000100 1000 10000Разреженный, GNU C++Разреженный, GNU C++10010001001000Плотный, Visual C++Плотный, Visual C++Плотный, GNU C++Плотный, GNU C++10010001001000
100
100
1000 10000
1000 10000
Разреженный, Visual 8+
Разреженный, Visual 8+
.42 о.о, 9Q 工工 ееоспи 匚Bsd Он
.42 о.о, 9Q 工工 ееоспи 匚Bsd Он
3 足 о.6
3 足 о.6
100 1000 10000
100 1000 10000
Разреженный, GNU C++
Разреженный, GNU C++
1001000
1001000
Плотный, Visual C++
Плотный, Visual C++
Плотный, GNU C++
Плотный, GNU C++
1001000
1001000
Рис. 11,3, Результаты измерения времени для экспериментас удалением ребер
Удаление вершин
В этом тесте добавляются |У| вершин и \Е\ ребер к графу, а затем удаляются все вершины. Результаты показаны на рис. 11.4 Очевидным победителем является listlist, который специально разработан для этой операции. Другие варианты adjacency^ 1st имеют очень плохую производительность, так как эта операция вы- полняетс只 не за постоянное время.
Очистка вершин
В этом тесте сначала добавляются |V| вершин и |£ | ребер к графу, а потом очищаются н удаляются все вершины. Операция cleer_vertex() обходит граф, удаляя все ребра, относящиеся к вершине. Результаты показаны на рис. 11.5. При увеличении размера графа adjacencyjist с параметром VertexLi st^vecS становится победителем.
11.1. Сравнения графовых классов 141
Разреженный,Visual C++
Разреженный,GNU C++
Плотный,Visual C++
asx^xcoczm ю^т ooxxsoscrasaox
юо юоо юооо
Рис. 11 Л. Результаты
Разреженный,Visual C++
1001000
Плотный, GNU C++
измерения времени для
Разреженный,GNU C++
эксперимента с удалением вершин
Плотный,Visual C++
гс^хфхщосз^ H^sds ®OXI2omxs5aox
о,о,6
Плотный, GNU C++
Рис. 11.5. Результаты измерения времени для эксперимента с очисткой вершин
Обход вершин
В этом тесте обходятся все вершины графа, читая значение внутреннего свойства каждой вершины. Результаты показаны на рис. 11.6. Среди первых трех типов графа нет четкого победителя. Обход вершин был быстрым для этих классов, так как они имеют одинаковый параметр VertexL1st=vecS. Для больших графов типа 1 istlist обход был медленнее, так как они используют Ver- texList=listS.
оо1001001000 100001000 10000Рис. 11.6.Рис. 11.6.Разреженный, Visual C++Разреженный, Visual C++0.80,6w0.80,6wк^хфх§сзш К芝0dm SOHHBmoEM匚BNdoHк^хфх§сзш К芝0dm SOHHBmoEM匚BNdoHРезультаты измерения времени для эксперимента с обходом множества вершинРезультаты измерения времени для эксперимента с обходом множества вершинРазреженный, GNU C++Разреженный, GNU C++Плотный, Visual C++Плотный, Visual C++Плотный, GNU C++Плотный, GNU C++
о
о
100
100
1000 10000
1000 10000
Рис. 11.6.
Рис. 11.6.
Разреженный, Visual C++
Разреженный, Visual C++
0.80,6w
0.80,6w
к^хфх§сзш К芝0dm SOHHBmoEM匚BNdoH
к^хфх§сзш К芝0dm SOHHBmoEM匚BNdoH
Результаты измерения времени для эксперимента с обходом множества вершин
Результаты измерения времени для эксперимента с обходом множества вершин
Разреженный, GNU C++
Разреженный, GNU C++
Плотный, Visual C++
Плотный, Visual C++
Плотный, GNU C++
Плотный, GNU C++
Обход ребер
В этом тесте обходятся все ребра графа, читая значение внутреннего свойства каждого ребра. Результаты показаны на рис. 11.7. Явным победителем здесь является adjacency^list с параметром EdgeList=vecS,
Обход исходящих ребер
В этом тесте обходятся все исходящие ребра каждой вершины графа, при счи- тьгвании значения внутреннего свойства каждой вершины и каждого исходящего ребра. Результаты показаны на рис. 11.8. Явным победителем здесь является adjacency list с EdgeList=vecS.
Плотный, GNU C++
vec list set listlist
Плотный, Visual C++
1001000
Разреженный, GNU С++
100 1000 10000
1001000
Разреженный,
WSX®X5C3O 爪工①dm BOHHBBOEHLfBEdoH
Рис. 11*7. Результаты измерения времени для эксперимента с обходом набора ребер
Плотный t
GNU C++
1001000
Разреженный, Visual C++
Разреженньм, GNU C++
Плотный, Visual C++
Рис. 11.8. Результаты измерения времени для эксперимента с обходом исходящих ребер
,8,6,4.20,0,0,0,asHSH匚 о 匚зш«2фаф во 工工 еао£И 匚е 工 doH
11.2. Итоги главы
Разные комбинации альтернатив являются различными вариантами компромисса между скоростью обхода и скоростью добавления н удаления. Следующие положения являются результатами наших экспериментов.
Использование vecS для EdgeList обычно обеспечивает эффективный обход по исходящим ребрам.
Использование vecS для VertexLi st обычно обеспечивает эффективный обход множества вершин.
Эффективное удаление вершин поддерживается sets для EdgeList. В частности, для поеледовал'ел ьности исходящих ребер можно было бы исполь' зовать std: :set.
Для эффективного добавления ребер нужно применять vecS или lists для EdgeList.
Если удаление вершин происходит часто, нужно использовать 11 stS для VertexLi st, так как std:: list в качестве основы выполняет удаление вершины за постоянное время.
Для очистки вершин хорошим выбором для VertexLi st является vecS (функция clear^vertexO можег использоваться для удаления всех инцидентных ребер).
Справочное руководство
Таблица 12,1 (продолжение}
Выражение
Возвращаемый тип или описание
BidirectionalGraph уточняет IncidenceGraph
grap h_t rai ts<G>::in_ed geitera tor
Итерация по входящим рсбрам
in-eciges(v, g)
std::pair<m_edge_iterator, in^edge iterator>
in_degree(vf g)
degree_size_type
degree(e, g)
degree_size__type
AdjacencyGraph уточняет Graph graph traits<G>::adjacency_iterator
Итерация по смежным вершинам.
adj acen t_\rert i ces(v, g)
VertexListGraph уточняет Graph
std: pair <上 djacencyi terator, acljacency_iteratar>
graph_traits<G>::vertex_iterator
Итерация по набору вершин графа
graph_lraits<G>:^ertices_size type
Целый беззнаковый тип для представления числа вершин
num_vertices(g)
vertices size type
vertices(g)
std::pair<vertex_iteratort vertcx_iteratQr>
EdgeListGraph уточняет Graph graph traits<G>::edge_descriptor
Тип объекта для обозначения ребер
graph traits<G>:iedge iterator
Итерация по набору ребер графа
graph_traics<G>::edges_size type
Целый беззнаковый тип для представления числа ребер
numedges(g)
edges_size_tjrpe
edges(g)
std::раi r<edge_iteratoг,edge iterator>
source(e, g)
ver texdesc riptor
target(e, g)
vertex^d escri ptor
Adjacency Matrix уточняет Graph edgeCu.v^g)
std::pair<edge descriptor, bool>
Неориентированные графы
Интерфейс, который BGL предоставляет для доступа и манипуляции неориентированными графами, тот же, что и для ориентированных графов* Интерфейс один, так как есть определенная эквивалентность между неориептированными и ориентированными графами. То есть любой неориентированный граф может быть представлен как ориентированный» ес^ли неориентированное ребро (〃,о) заменить двумя ориентированными (utv)n (,и). Такой о]шентированный граф называется ориентированной версией неориентированноп» графа. На рис. 12.2 показан неориентированный граф и его ориентированная версия. Заметим, ито для каждого ребра неориентированного графа ориентированный граф имеет два ребра. Таким образом, BGL использует функцию out edges () (ил Hi n_edges О) для доступа к инцидентным ребрам в неориентированном графе. Аналопгчно sou г се О и target () применяются для доступа к вершинам. Сначала э го может показаться противоречивым фактом. Но, учитывая зкнивалентнисть между неориент1грованнь1ми и ори-
ентированными графами, BGL позволяет применять многие алгоритмы как к ориентированным, так и к неориентированным графам.
Рис. 12.2. Неориентированный граф и его ориентированный эквивалент
Следующий пример в листинге 12,1 демонстрирует использование out_edges(), sourced и target О с неориентированным фафом. Хотя обычно направление ребра не принимается во внимание для неориентированиых графов, в случае применения функции out_edges() к вершине 辽 начальная вершина для дескриптора ребра всегда 弘 а конечная — смежная с у. В функции 1n_edges(), соответственно, наоборот.
Листинг 12Д, Демонстрация некоторых функций для неориентированного графа
template <typename UndirectedGraph> void undirected_graph_deirol()(
const Int V * 3;
UndirectedGraph undigraph(V):
typename graphjtraits^UndlrectedGraph>::vertex^descriptor zero. one. two:
typename graph2tr3its<UndirectedGraph>::out_edge_iterator out, out_end;
typename graph2traits<Und1rectedGraph>::in_edge_7terator in, in_end;
zero = vertexfO. undigraph):
one = vertexd. undigraph):
two 匸 vertex(2. undigraph):
add_edge(zero. one. undigraph):
add_edge(zero> two, undigraph); add_edge(one. two. undigrapli):
std: :cout « ,*исходящие(0): °:
for (tie(out( out_end)二 out^edges(zero, undigraph): out != out_end: ++out)
std::cout « *out:
std::cout « std: :endl《"входящие(О);":
for (tie(tn, 1n_end) = 1n_edges(zero. undigraph): in != in_end; ++in) std::cout « *in;
std::cout « std::end1:
}
Вывод будет следующим:
исходящие(О): (0.1) (0,2)
входящие(О): (1.0) (2.0)
Хотя интерфейс для неориентировал ных н ориентированных графов одинаков, имеются некоторые отличия в поведении функций, поскольку равенство ребер
graph』га 1 ts<G>:: di rectedcategory
Теги для этой категории: d1 rected_tag и undirected_tag.
graph_traits<G>: :edgejara 11 el_category
Описывает, позволяет ли графовый класс осуществлять вставку параллельных ребер (ребра с одной и той же парой начальной и конечной вершин). Два тега: а 11 ow_pa га П el _edge_ta9 и di sa 11 ow_paraTl e 1 _edge_tag.
,graph_traits<G>: :traversal_category
Описывает виды обхода итераторами, которые поддерживает данный граф. Следующие классы тегов определены:
struct incidence_graph_tag ( };
struct adjacency [graph飞明{ j;
struct bidi rectional_graph_tag . public virtual inci dence_graph_tag { }:
struct vertexj ist_graph_tag {):
struct edgejist_graph_tag { };
struct adjacency^matг1x_tag { }:
IncidenceGraph
Концепция IncidenceGraph обеспечивает интер(^ейс для эффективного доступа к исходящим ребрам ка^сдой вершины графа. Исходящие ребра (out-edges) доступны через итераторы исходящих ребер. Функция out_edges(vt g) по данному дескриптору вершины v и графу g возвращает пару итераторов исходящих вер* шин, Первый итератор указывает на первое исходящее ребро вершины о, а второй итератор — за конец последовательности ребер. Разыменозание итератора исходящих вершин возвращает дескриптор ребра. Инкремент итератора перемещает его к следующему исходящему ребру. Порядок появления исходящих ребер при итерации не фиксирован, хотя конкретная реализация графа может иметь некоторое упорядочение.
Уточнение для
Graph
Ассоциированные типы
Ниже приведены ассоциированные типы для концепции IncidenceGraph.
graph_tra1ts<G>: :edge_d.escnptor
Дескриптор ребра соответствует уникальному ребру в графе. Он должен быть Def己uLtConstructible, Assignable и EqualityComparable.
graph_tга 1ts<G>::out_edge_11erator
Итератор исходящих ребер для вершины 己 обеспечивает доступ к исходящим ребрам вершины v. Тип значения итератора — edge^descrlptor от своего графа. Итератор исходящих вершин должен отвечать требованиям Multi- Passlnputlterator.
graph_traits<G>::degree_slze_type
Это беззнаковый целый тип, аредставляющиЛ число исходящих или инцидентных ребер вершины.
Допустимые выражения
Ниже приведены допустимые выражения для концепции IncidenceGraph.
sourced g)
Тип результата: vertex_descripton
Семантика: возвращает дескриптор вершины и для ребра (以,г)> представленного через а
Предусловие: е — допустимый дескриптор ребра графа д.
target(е. д)
Тип результата: vertex^descrlptor.
Семантика: возвращает дескриптор вершины для ребра d (〃,己),представленного через е.
Предусловие: е — допустимый дескриптор ребра графа д.
out_edges(v. д)
Тип результата: std :: pa 1 r<out_edge_i terator. out_edge_i teratог>.
Семантика: возвращает пару итераторов, обеспечивающих доступ к исходящим ребрам (для ориентирован пых графов) или пицвдентным ребрам (для неориентированных графов) вершины г. Вершина г появляется как начальная во всех исходящих ребрах. Вершины, смежные с г, являются конечными в исходящих ребрах (неважно, ориентированный граф или нет).
Предусловие: v — допустимый дескриптор вершины графа д,
out_degree(v. g)
Тип результата: degree_s1ze_type.
Семантика: возвращает число исходящих ребер (для ориентированных графов) или число инцидентных ребер (для неориенти ров а ни ых графов) вершины а
Предусловие: v — допустимый дескриптор вершины графа д.
Гарантии сложности
Функции $ource(), targetO и out_edges() должны выполняться за постоянное время. Функция out_degree() должна выполняться за линейное время по количеству исходящих ребер вершины.
BidlrectionalGraph
Концепция BidirectionalGraph уточняет IncidenceGraph: добавляется требование для эффективного доступа к вхслящнм ребрам каждой вершины. Эта концепция выделена из IncidenceGraph, потому как предоставление эффективного доступа к входящим ребрам ориентированного графа обычно требует больше памяти t а многие алгоритмы не требуют доступа к таким ребрам. Для неориентированных графов это несущественно, так как дополнительная память не требуется.
Уточнение для
IncidenceGraph
Ассоциированные типы
Ниже приведен ассоциированный тип для концепции BidirectionaLGraph.
grdph_t「dits<G>::in_edge_iterdtor
Итератор входящих ребер для вершины и обеспечивает доступ к входящим ребрам вершины г. Тип значения итератора — edge__descri ptor от своего графа. Итератор исходящих вершин должен отвечать требованиям MultiPassInpuV Iterator.
Допустимые выражения
Ниже приведены допустимые выражения для концепции BidirectionaLGraph.
1n_edges(v. g)
Тип результата: std: :pa 1 r<1 n_edge__1 teratorФ 1n_edgejterator>
Семантика: возвращает пару итераторов, обеспечивающих доступ к входящим ребрам (для ориентированных графов) или инцидентным ребрам (для неори ентированных графов) вершины 己,Вершина г появляется как конечная во всех входящих ребрах. Вершины, для которых вершина 口 一 смежная, являются начальными во входящих ребрах (неважно, ориентированный граф или нет).
Предусловие: v — допустимый дескриптор вершины графа д.
in_degree(v. g)
Тип результата: degree__size_type.
Семантика: возвращает число входящих ребер (для ориентированных графов) и число инцидентных ребер (для неориентирован ных графов) вершины 认
Предусловие: v — допустимый дескриптор вершины графа д*
degree(vt д)
Тип результата: degree_size_typ^-
Семантика: возвращает число входящих и исходящих ребер (для ориентированных графов) и число инцидентных ребер (для неориентированных графов) вершины V*
Предусловие: v — допустимый дескриптор вершины графа д*
Гарантии сложности
Функция i n_edges О должна вьшолняться за постоянное время, функция i n_degree О — за линейное время, по числу входящих ребер.
AdjacencyGraph
Концепция AdjacencyGraph определяет интерфейс- для доступа к смежным вершинам. Смежные вершины могут быть также получены как конечные вершины исходящих ребер. Однако для некоторых алгоритмов исходящие ребра не нужны и более удобно получать доступ к смежным вершинам непосредственно.
Уточнение для
Graph
Ассоциированные типы
Ниже приведен ассоциированный тип для концепции AdjacencyGraph.
graph_t「aits<G>::adjacency一Iterator
Итератор смежности для вершимы г предоставляет доступ к вершинам, смежным с вершиной гл Тип значения итератора смежности — дескриптор вершины своего графа. Итератор должен отвечать требованиям MultiPassInputlterator.
Допустимые выражения
Ниже приведено допустимое выражение для концепции AdjacencyGraph.
adjacent一verticesV, g)
Тип результата: std: :pair<adjacency J terator, adjacency J terator>.
Семантика: возвращает диапазон значений итератора, обеспечивающий доступ к вершинам, смежным с вершиной а Более конкретно: это эквивалентно получению конечных вершин для каждого исходящего ребра вершины 口,
Предусловие: v — допустимый дескриптор вершины графа д.
Гарантии сложности
Функция adjacent_vertices() должна отрабатывать за постоянное время.
VertexListGraph
Концепция VertexListGraph определяет требования для эффективного обхода всех вершин графа.
Уточнение для
Graph
Ассоциированные типы
Ниже приведены ассоциированные типы для концепции VertexListGraph.
дгарЬ_1^а1Х5<6>::vertex_1terator
Итератор вершин (получаемый через vertices(g)) обеспечивает доступ ко всем вершинам графа* Тип итератора должен отвечать требованиям MultiPassInputIterator. Тип значения итератора вершин должен быть дескриптором вершины.
grdph_traits<G>: :vertices_size_type
Это беззнаковый целый тип, котором можно представить число вершин в графе.
Допустимые выражения
Ниже приведены допустимые выражения для концепции VertexListGraph.
vertlces(g)
Тип результата: std:: pal r<vertex_iterator. vertex_1terator>>
Семантика: возвращает диапазон значений итератора, обеспечивая доступ ко всем вершинам графа д.
num_vert1ces(g)
Тип результата: verti ces_size_type.
Семантика: возвращает число вершин в графе д.
Гарантии сложности
Функция verticesO должна отрабатывать за постоянное время. Время выполнения функции num_vertices() должно линейно зависеть от числа вершин.
EdgeListGraph
Концепция EdgeListGraph уточняет концепцию Graph. Дополнительным требованием является обеспечение эффективного доступа ко всем ребрам графа.
Уточнение для
Graph
Ассоциированные типы
Ниже приведены ассоциированные типы для концепции EdgeListGraph.
graph_tr3its<G>::edge_descriptor
Дескриптор ребра соответствует уникальному ребру в графе. Он должен быть DefaultConstructible, Assignable и EqualityComparable.
graph_tralts<G>: :edgejterator
Итератор ребер (полученный через edges (д)) обеспечивает доступ ко всем ребрам графа. Тип edge__1 terator должен соответствовать требовавиям Inputiterator. Тип значения итератора ребер должен быть таким же, как у дескриптора ребер данного графа.
graph_t^1ts<G>: :edges_size_type
Это беззнаковый целый тип, испо;гьзуемый для представления числа ребер в графе.
Допустимые выражения
Ниже приведены допустимые выражения для концепции EdgeListGraph.
edges(g)
Тип результата: std: :pair<edgejterator. edgejterator>.
Семантика: возвращает диапазон значений итератора для доступа ко всем ребрам графа д.
source(e, д)
Тип результата: vertex_descr1 ptor.
Семантика: возвращает дескриптор вершины для начальной вершины и ребра (% 己),заданного через е.
Предусловие: е — допустимый дескриптор ребра графа д.
target(е, д)
Тип результата: vertex_descripton
Семантика: возвращает дескриптор вершины для конечной вершины 口 ребра (w, г)т заданного через е.
Предусловие: е — допустимый дескриптор ребра |рафа д.
num_edges(g)
Тип результата: edges_size_type-
Семаитика: возвращает количество ребер в графе д.
「叩антии сложности
Функции edges О, source() н target О должны отрабатывать за постоянное время. Функция num_edges() должна выполняться за линейное по числу ребер время.
AdjacencyMatrix
Концепция AdjacencyMatrix уточняет концепцию Graph и имеет дополнительное требование обеспечения эффективного доступа к любому ребру в графе по заданным начальной и конечной вершинам.
Уточнение для
Graph
Допустимые выражения
Ниже приведено допустимое выражение для концепции AdjacencyMatrix.
• edge(u, v, g)
Тип результата: std:: pa 1 r<edge_descr 1 ptor. bool>.
Семантика: возвращает пару, состоящую из флага, показывающего, существует ли в графе g ребро между и и v, и дескриптора ребрат если такое найдено.
Предусловие: и, м — допустимые для графа g дескрипторы вершин.
Гарантии сложности
Функция edge() должна возвращать результат за постоянное время.
Концепции для изменения графов
Этот раздел описывает BGL-интерфейс для модификации графа» то есть добавления и удаления вершта и ребер, изменения значений закрепленных за вершинами и ребрами свойств. Как и концепции для обхода фафа, концепции модификации графа раздроблены на множество простых, для того чтобы предоставить разработчикам алгоритмов хороший выбор концепций для описания требований мгорит- мов. В табл. 12,2 приведены допустимые выражения и ассоциироваш1ые типы для каждой концепции» а на рис. 12.3 показаны отношения уточнения между концепциями модификации графов. Некоторые из концепций, изображенных на рис. 12.1, также фигурируют на рис. 12.3, но все их отношения уточнения пропущены.
VertexMutableGraph> VertexMutablePropertyGraph
EdgeListGraph > MutableEdgeListGraph
Рис. 12.3. Концепции для изменения графов и отношения уточнения между ними
Таблица 12.2. Краткая сводка концепций для изменения графа и доступа к свойствам「ра也
Выражение
Возвращаемый тип или описание
VertexMutableGraph уточняет Graph add_vertex(g)
vertexdescriptor
re mo ve_ verte x( v, g)
void
EdgeMutableGraph уточняет Graph clear_vertex(v, g)
void
add_edg£(u, v, g)
std::pair<edge_descriptort bool>
remove_edge(u, v, g)
void
remove _edge(et g)
void
MutablelncidenceGraph уточняет IncidenceGraph и EdgeMutableGraph
remove_edge(eitert g)
void
remove_out_edge_if(u, p, g)
void
MutableBidirectionaLGraph уточняет MutablelncidenceGraph и BidirectionaLGraph
retnove_edge(eiter, g)
void
remove_out_edge_if(uT p, g)
void
MutableEdgeListGraph уточняет EdgeMutableGraph к EdgeListGraph
remov£_Bdge_if(p, g)
void
PropertyGraph уточняет Graph property_map<G, PropertyTag>::type
Тип для изменяемого отображения свойства вершины
property_map<G, Property-
Тип для неизменяемого отображения
T ag>::const_type
свойства вершины
Выражение —Возвращаемы" тип и〃и описание
get(ptag, g)Функция для получения объекта-отображения
свойства вершины get(ptag, g, х)Получить значение свойства для вершины
или ребра х put(ptag, g, х, v)Положить значение свойства для вершины
или ребра х равным v
VertexMutablePropertyGraph уточняет VertexMutableGraph и PropertyGraph add_vertex(vp, g)vertex_descriptor
EdgeMutabLePropertyGraph уточняет EdgeMutableGraph и PropertyGraph add_edge(u, v, ep, g)std::pair<edgc_descriptorF bool>
VertexMutableGraph
Концепция VertexMutableGraph — граф, модифицируемый по множеству вершин (vertex mutable), может быть изменен добавлением или удалением вершин. Управление памятью выполняется при реализации графа. Пользователь графа только вызывает add_vertex() и remove_vertex(), а реализация делает все остальное.
Уточнение для
Graph, DefauLtConstructible
Допустимые выражения
Ниже приведены допустимые выражения для концепции VertexMutableGraph.
add_vertex(g)
Тип результата: vertex_descri ptor.
Семантика: добавляет новую вершину к графу. Возвращает дескриптор вершины для новой вершины.
remove_vertex(u. g)
Тип результата: void.
Семантика: удаляет вершину и из множества вершин графа.
Предусловия: и — допустимый дескриптор вершины графа g н нет ребер, инцидентных вершине и. Функция clear_vertex() может быть использована для удаления всех инцидентных ребер.
Постусловия: num_vertices(g) уменьшается на единицу; вершина и больше не находится во множестве вершин графа, а ее дескриптор больше не может быть использован.
Гарантии сложности
Концепция VertexMutableGraph дает следующие гарантии сложности:
вставка вершины происходит гарантированно за амортизированное постоянное время;
удаление вершины должно происходить не дольше чем за О(|Е | + |V|).
EdgeMutableGraph
Концепция EdgeMutableGraph — граф, модифицируемый по множеству ребер (edge mutable), может быть изменен добавлением или удалением ребер. Управление памятью выполняется при реализации графа. Пользователь графа только вызывает add_edge() и rewve_edge(), а реализация делает все остальное.
Уточнение для
Graph
Допустимые выражения
Ниже приведеггы допустимые выражения для концепции EdgeMutableGraph.
add_edge(u. v. g)
Тип результата: std: :pair<edge_descrlptor. bool >.
Семантика: пытается вставить ребро (w,己)в граф, возвращая вставленное ребро или параллельное ребро и флаг, который показывает, было ли ребро вставлено. Эта операция не должна «портить» дескрипторы вершин или нтераторь; по вершинам графа, но она может сделать недействительными дескрипторы ребер или итераторы по ребрам. Порядок, в котором новое ребро появится при обходе итераторами ребер графа, не определен.
Предусловие: (w, °)являются вершинами фас^а.
Постусловие: (w, и) находится в наборе ребер графа* Возвращенный дескриптор ребра имеет вершину 以 в качестве начальной и г — в качестве конечной. Если граф позволяет иметь параллельные ребра, то возвращаемый флаг будет истиной. Если параллельные ребра в фафе не разрешаются и ребро (弘 口) уже есть в грасре, флаг будет иметь значение «ложь». Если ребра (口,г) еще не было в графе, флаг будет иметь значение «истина».
remove_edge(u. v, g)
Тип результата: void.
Семантика: удалить ребро (以,и) из графа. Если граф позволяет иметь параллельные ребра, функция удаляет все вхождения (и, v) в граф.
Предусловие: (и,己)является ребром из графа д.
Постусловие: (и, и) больше не является ребром графа д*
remove^edgeCe. д)
Тип результата: void.
Семантика: удалить ребро е из графа.
Предусловие: с является ребром графа д.
Постусловие: е больше не является ребром п>афа д.
clear_vertex(u, g)
Тип результата: void.
Семантика: удалить все ребра, инцидентные вершине и графа.
Предусловие: и является допустимым дескриптором вершины для д.
Постусловие: и не участвует ни в одном из ребер графа в качестве начальной или конечной вершины.
Гарантии сложности
Концепция EdgeMutableGraph дает следующие гарантии сложности:
вставка ребра должна происходить за амортизированное постоянное время илн за время, равное O(log(|E |/|У|)), если выполняются проверки для предотвращения добавления параллельных ребер;
удаление ребра выполняется гарантированно за время О(|Е |);
очистка вершины от инцидентных ребер выполняется не более чем за О(|Е | + +м).
MutablelncidenceGraph
Концепция MutabLelncidenceGraph обеспечивает возможность удаления ребер из списка исходящих ребер вершины.
Уточнение для
IncidenceGraph и EdgeMutableGraph
Допустимые выражения
Ниже приведены допустимые выражения для концепции MutabLelncidenceGraph.
remove__edge(eiter# g)
Тип результата: void.
Семантика: удаляет ребро, на которое указывает elter, из графа, где el ter — итератор исходящих вершин гра([;а.
Предусловие: *eiter является ребром графа.
Постусловие: *eiter больше не является ребром графа д.
remove_out_edge_1f(и, р. д)
Тип результата: void.
Семантика: удаляет все исходящие ребра вершины и, для которых предикат р возвращает истинное значение. Это выражение требуется, только если граф также моделирует IncidenceGraph,
Предусловие: и является допустимым дескриптором вершины графа д*
Постусловие: р возвращает ложь для всех исходящих ребер верпшны и и все исходящие ребра, для которых р давал ложь, до сих пор находятся в графе.
Гарантии сложности
Концепция MutabLelncidenceGraph дает следующие гарантии сложности:
функция remove_edge() должна выполняться за постоянное время;
функция remove_out_edge_i f() должна выполняться за линейное по числу исходящих ребер время.
MutableBidirectionalGraph
Концепция MutableBidirectionalGraph определяет интерфейс для удаления ребер из списка входящих ребер вершины.
Уточнение для
BidirectionalGraph и MutabLeIncidenceGraph
Допустимые выражения
Ниже приведено допустимое выражение для концепции MutabLeBidirectionalGraph.
remove_1n_edge_if(v. p. g)
Тип результата: void.
Семантика: удаляет вес входящие ребра верпшны v, для которых р возвращает истину.
Предусловие: v — допустимый дескриптор вершины графа д,
Постуслогше: р возвращает ложь для всех входящих ребер вершины 〃 и все входящие ребра, для которых р давал ложь, до сих пор находятся в графе.
Гарантии сложности
Концепция MutableEdgeListGraph дает следующую гарантию сложности:
функция remove_i n_edge_i f () выполняется за линейное по числу входящих вершин время.
М uta bl eEdge ListG га р h
Концепция MutableEdgeListGraph и^доставляет возможноггь удалять ребра из списка ребер графа.
Уточнение для
EdgeMutabLeGraph
Допустимые выражения
Ниже приведено допустимое выражение для концепции MutableEdgeListGraph.
remove_edge_1f(p. g)
Тип результата: void.
Семантика: удаляет все ребра из графа д, для которых р возвращает истину.
Постусловие: р возвращает ложь для всех ребер в графе и граф до сих лор содержит все ребра, для которых р первоначально возвращал ложь.
Гарантии сложности
Концепция MutableEdgeListGraph дает следующую гарантию сложности:
(функция remove_edge_i f () должна выполняться за линейное время по числу ребер в графе.
PropertyGraph
Концепция PropertyGraph — фаф, который имеет некоторое свойство, связанное с каждой из вершин или ребер графа. Так как данный граф может иметь несколько свойств, связанных с каждой вершиной или ребром, для обозначения свойства мог>гтбыть использованы теги. В описании требований PropertyTag — это тип тега,
a tag — объект типа PropertyTag. Граф предоставляет функцию, которая возвращает объект—отображение свойства.
Уточнение для
Graph
Ассоциированные типы
Ниже приведены ассоциированные типы для концепции PropertyGraph.
property_map<Gt PropertyTag>::type
Тип отображения свойства для свойства, заданного PropertyTag. Этот тип должен быть изменяемым LvaluePropertyMap с таким же типом ключа, как у дескриптора вершины или ребра графа.
property_msp<G. PropertyTag>::const_type
Тип константного отображения свойства для свойства, заданного PropertyTag. Этот тип должен быть неизменяемым LvaluePropertyMap с таким же типом ключа, как у дескриптора вершины или ребра графа.
Допустимые выражения
Ниже приведены допустимые выражения для концепции PropertyGraph.
get(ptag. д)
Тип результата: property_map<Gt PropertyTag>: :type, если д является иэменяе- мым — и property_map<G. PropertyTag>:: const_type — в противном случае.
Семантика: возвращает отображение свойства для свойства» заданного типом PropertyTag. Объект ptag используется только ради своего тнпа.
getCptag. g. х)
Тип результата: p「operty_trdits<PMdp>:: value_type.
Семантика: возвращает значение свойства (заданного типом PropertyTag), связанного с объектом х (вершина или ребро). Объект ptag используется только ради своего типа. Эта ф}гнкция эквивалентна get (get (ptag. g). x).
Гарантии сложности
Функция get() должна выполняться за постоянное время.
1227. VertexMutablePropertyGraph
Концепция VertexMutablePropertyGraph — это VertexMutableGraph и PropertyGraph с дополнительными функциями для указания значений свойств при добавлении вершин к графу.
Уточнение для
VertexMutabLeGraph и PropertyGraph
Ассоциированные типы
Ниже приведен ассоциированный тип для концепции VertexMutabLePropertyGraph.
vertex_property<G>::type
Тип объекта-свойства, закрепленного за вершиной.
Допустимые выражения
Ниже приведено допустимое выражение для концепции VertexMutablePropertyGraph.
add_vertex(vpt g)
Тип результата: vertex_descr1 ptor.
Семантика: добавляет новую вершину к графу и копирует vp в объект-свойство для новой вершины. Возвращается дескриптор вершины для новой вершины.
Гарантии сложности
Концепция VertexMutablePropertyGraph дает следующую гарантию сложности:
add_vertex() гарантарованно выполняется за амортизированное постоянное время.
EdgeMutablePropertyGraph
Концепция EdgeMutablePropertyGraph — это EdgeMutabLeGraph и PropertyGraph снс- полнительными функциями для задания значений свойств при добавлении ребер к графу.
Уточнение для
EdgeMutabLeGraph н PropertyGraph
Ассоциированные типы
Ниже приведен ассоциированный тип для концепции EdgeMutablePropertyGraph.
edge_property<G>::type
Тип объекта-свойства, закрепленного за ребром.
Допустимые выражения
Ниже приведено допустимое выражение для концепции EdgeMutablePropertyGraph,
add_edge(u. v, ер. g)
Тип результата: std : :pai r<edge_desc ri pt or, t ool >.
Семантика: вставляет ребро (% 仃)в граф и копирует объект ер в объект-CBoii- ство для этого ребра.
Предусловие: и. v являются допустимыми дескрипторами вершин графа д.
Гарантии сложности
Концепция EdgeMutablePropertyGraph дает следующую гарантию сложности:
вставка ребра должна происходить либо за амортизированное постоянное время, либо за O(log(|£ |/|V|)), если при вставке проверяется недопустимость добавления параллельных ребер.
Концепции посетителей
Концепции посетителей (visitor concepts) играют такую же роль в BGL, как и функторы (functors) в STL. Функторы предоставляют механизм для расширения алгоритмов. Посетители позволяют пользователям вставлять свои собственные one-
рации в различных точках графового алгоритма. В отличие от алгоритмов STL алгоритмы на графах обычно имеют несколько событийных точек, в которые пользователь может вставить обратный вызов (callback) через функтор. Таким образом, посетители имеют не единственный метод operator()» как у функтора, а несколько методов, соответствующих различным событийным точкам. В этом разделе мы определим концепции посетителей для основных алгоритмов BGL.
Как функциональные объекты в STL, посетители передаются по значению в алгоритма BGL. Это означает, что нужно быть очень осторожным при сохранении состояния в объектах-посетителях.
Обозначения, использованные в этом разделе, приведены ниже:
V — тип, моделирующий концепцию посетителя;
vis — объект типа V;
G — таи, являющийся моделью Graph;
g — объект типа G;
е — объект типа graph__traits<G>: :edge_descriptor;
s, u — объекты типа graph^trai ts<G>: : vertex^descrlptor.
BFSVisitor
Концепция BFSVisitor определяет интерфейс посетителя для поиска в ширину brea<ith_fi rst_search(). Пол^ователи могут определить классе интерфейсом BFSVisitor и передавать объект этого класса алгоритму b「eddth_fi rst_search ()t дополняя действия, производимые во время поиска ио гра巾у.
Уточнение для
CopyConstructible
Допустимые выражения
Ниже приведены допустимые выражекия для концеппии BFSVisitor,
vis.initialize_vertex(u. g)
Тип результата: void.
Семантика: вызывается для каждой вершины графа перед началом поиска в графе.
vi s.discover_vertex(u. g)
Тип результата: void.
Семантика: вызывается, когда алгоритм встречает некоторую вершину w впервые. Все другие вершины, более близкие к исходной вершине, были уже просмотрены, а вершины, расположенные дальше от исходной, — еще нет.
vis,exam1ne_edge(e. g)
Тип результата: void.
Семантика: вызы вается для каждого исходящего ребра каждой, только что посещенной вершины.
vis.tree_edge(e. g)
Тип результата: void.
Семантика: еслн рассматриваемое ребро е входш н дерево поиска, вызывается эта функция. Вызов функции всегда предваряется вызовом функции examl ne_edge().
vls.non_tree_edge(e. g)
Тип результата: void.
Семантика: если рассматриваемое ребро е не входит в дерево поиска, вызывается эта функция. Вызов функции всегда предваряется вызовом функции examine_edge(). Для ориентированных графов такое ребра должно быть либо обратным, либо поперечным, для неориентированных — поперечным.
vis*gray_target(e. g)
Тип результата: void.
Семантика: эта функция вызывается, если рассматриваемое ребро — ребро цикла и если его конечная вершина окрашена в серы!! цвет во время рассмотрения. Вызов функции всегда предваряется вызовом функции cycl e edgei). Серый цвет означает, что ребро находится в данный момент в очереди.
vis.black target(е. g)
Тип результата: void.
Семантика: эта функция вызывается, если рассматриваемое ребро является ребром цикла и если его конечная вершина окрац;ена в черный цвет во время просмотра. Вызов функции всегда предваряется вызовом функции cycl e_edge(). Черный цвет означает, что вершина уже бьтла удалена из очереди.
vis.finish_vertex(u, g)
Тип результата: void.
Семантика: эта функция вызывается для вершины после того, как все ее исходящие ребра были добавлены к дереву поиска и все смежные вершины просмотрены (но перед тем, как были рассмотрены их исходящие ребра).
DFSVisitor
Концепция DFSVisitor определяет интерфейс посетителя для поиска в глуби ну depth^fi rst^searchC). Пользователи могут определить класс с интерфейсам DFSVisitor и передавать объект этого класса алгоритму depth^fi rst_search(), дополняя действия, производимые во время поиска по графу.
Уточнение для
CopyConstructible
Допустимые выражения
Ниже приведены допустимые выражения для концепции DFSVisitor.
vis.Initial1ze_vertex(u. g)
Тип результата: void.
Семантика: вызывается для каждой вершины графа перед началом поиска в графе.
vis.start_vertex(s. g)
Тип результата: void.
Семантика: вызывается для исходной вершины перед началом поиска в графе.
vis.d1scover_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一edges, g)
Тип результата; void.
Семантика: вызывается для прямого или поперечного ребра в графе. Этот метод никогда не вызывается при поиске в неориентирован ном графе.
vis.flnish_vertex(u. g)
Тип результата: void.
Семантика: вызывается для вершины и после того, как finish_vertex() была вызвана для всех вершин дерева поиска с корнем в вершине и. Если вершина и является листом дерева поиска, функция flnl sh_vertex() вызывается для вершины н после просмотра всех исходящих из и ребер.
DijkstraVisitor
Концепция DijkstraVisitor определяет интерфейс посетите刀я для di jkstra_shortest_^ paths () и подобных алгоритмов. Пользователь может создать класс, согласованный с этим интерфейсом, и затем передать объекты класса в di jkstra_shortest_paths() для расширения действий, производимых во время поиска по графу.
Уточнение для
CopyCon struct! Ые
Допустимые выражения
Ниже приведены допустимые выражения для концепции DijkstraVisitor.
vis.discover_vertex(u. g)
Тип результата: void.
Семантика: вызывается, когда алгоритм встречает некоторую вершину w впервые.
v1s.examine_edge(e, g)
Тип результата: void.
Семантика: вызывается для каждого исходящего ребра каждой вершины после ее посещения,
vis.edge_relaxed(e, g)
Тип результата: void.
Семантика: пусть (м, и) — это ребро %d — отображение расстояний, а 侬—отображение весов. Если во время обхода d[u] + av(以,己)< d[v], то ребро требует релаксации (его расстояние сокращается) и вызывается этот метод.
vls,edge_not_relaxed(e. g)
Тип результата: void.
Семантика: если во время обхода ребро не требует релаксации, то вызывается этот метод.
v1s.finish_vertex(uf g)
Тип результата: void.
Семантика: вызывается для вершины после того, как все ее исходящие ребра были добавлены к дереву поиска и все смежные вершины были просмотрены (но перед тем, как были рассмотрены их исходящие ребра),
Bel Iman FordVisitor
Концепция Bellman Ford Visitor определяет интерфейс посетителя для bellman_ford_- shortest_paths(). Пользователь может создать класс с интерфейсом BeLlmanFordVisitor и затем передать объекты класса в bel 1 man_ford__shortest_paths О через параметр visitorO для расширения действий, производимых во время поиска по графу.
Уточнение для
CopyConstructible
Допустимые выражения
Ниже приведены допустимые выражения для концепции BeLlmanFordVisitor.
v1s.init1alize_vertex(s. g)
Тип результата: void.
Семантика: вызывается для каждой вершины графа перед началом поиска в графе.
vis.examine_edge(et g)
Тип результата: void.
Семантика: вызывается для каждого ребра графа num_vertices(g) раз.
vl$.edg6_relaxed(e. g)
Тип результата: void.
Семантика: пусть (〃,彩)—ребро е^ d — отображение расстояний,渺——отображение весов. Если d[u] + 覆?(〃, е) < d[v], то ребро требует релаксации (его расстояние сокращается) и вызывается этот метод.
vl s .edge__not_relaxed(et g)
Тип результата: void.
Семантика: если во время обхода ребро не требует релаксации (см. выше), то вызывается этот метод.
vls.edge_m1nimi zed(е. g)
Тип результата: void.
CeNfантика: после num_vertices(g) итераций по набору ребер графа делается одна последняя итерация для проверки того, каждое ли ребро было минимизировано. Если ребро минимизировано, то вызывается эта функция. Ребро (и, 口) минимизировано, если d[u] + w(u,己)> d[r].
vls.edge_not_mi nimi zed(e. g)
Тип результата: void.
Семантика: если ребро не минимизировано, то вызывается эта функция. Это случается, когда в графе есть отрицательный цикл.
Алгоритмы BGLАлгоритмы BGL
Алгоритмы BGL
Алгоритмы BGL
Обзор
В этой главе представлена детальная информация по применению всех алгоритмов в Boost Graph Library.
Обобщенные алгоритмы BGL разделены на следующие категории:
Основные алгоритмы поиска.
Кра^пайшие пути.
Минимальное остовное дерево.
Компоненты связности.
Максимальны证 поток.
Упорядочение вершин-
Все алгоритмы реализованы как шаблоны функций, где тип графа является параметром шаблона. Это позволяет использовать функцию с любым типом графа, который моделирует требуемые концепции. Описание каждого алгоритма содержит список требуемых графовых концепций, а описание каждого графового класса включает список концепций, которые граф моделирует. По перекрестным ссылкам через концепции можно определить, какие типы графов с какими алгоритмами могут быть использованы.
Кроме того, алгорнтмы иногда параметризукпся отображениями свойств, как например, отображение расстояний для алгоритмов 1фатчайших путей. Это дает пользователю контроль над тем, как свойства хранятся и извлекаются. Алгоритмы также параметризованы по типу посетителя, что позволяет пользователю задать обратные вызовы (call-backs), которые будут выполнены в определенных событийных точках алгоритма.
Информация об ал「оритме
Информация об алгоритме будет представлена в виде следующих подразделов: «Прототипы^, «Описание»,《Где определен», «Параметры», «Именованные параметры», «Предусловиям, «Сложностью» «Примера.
Прототипы
Раздел справочника по каждому алгоритму начинается с прототипа функции. По именам параметров шаблона обычно можно судить о назначении параметра, а иногда также о требуемой параметром концепции. Однако точные требования для каждого параметра даны в разделе описания параметров.
Последний параметр для многих функций — bgl_named_params. Он служит для под* держки именованных параметров, что описано в разделе 2.7 и также рассматривается здесь. Если после params написано = all defaults, то для всех именованных параметров имеются значения по з^молчанию, и эти параметры могут быть опущены*
Описание
В описании функции мы определяем задачу, которую решает эта функция, объясняем терминологию теории графов или идеи, которые необходимы для понимания задачи. Затем мы описываем семантику функций с точки зрения ее воздействия на параметры.
Где определен
В этом разделе указан заголовочный файл, который должен быть включен в программу (с помощью include) для пользования функцией.
Параметры
Здесь приводится список всех обычных параметров функции (именованные рассмотрены в следующем разделе). Обычные параметры обязательны (для них нет значений по умолчанию).
Каждый параметр попадает в одну из следующих категорий:
IN
параметры читаются функцией и используются для получения информации. Функция никак не изменяет эти параметры;
OUT
парс!метры записываются функцией. Результаты работы функции хранятся в OUT-параметрах;
UT1L
параметры требуются для работы алгоритма, однако содержимое объектов, используемых как UTIL-парамстрьъ обычно не интересно для пользователя. Эти параметры обычно как читаются, так и записываются.
Именованные параметры
Как сказано в разделе 2.7, BGL использует специальную технику для повышения удобства работы с функциями с большим набором параметров, когда многие
параметры имеют значения по умолчанию. В этом разделе перечисляются все именованные параметры для функции, с использованием такой же классификации, что » для обычных параметров. Кроме того, для каждого именованного параметра приведено значение по умолчанию.
Предусловия
В этом разделе мы описываем любые предусловия для функции. Обычно это включает требования к состоянию параметров отобрг1Жений свойств.
Сложность
Временная сложность для каждого алгоритма дана в 0-обозначениях. Сложность по памяти всегда не больше O(|V|), если нс указано обратное.
Пример
Для демонстрации использования алгоритма приводится простой пример.
13.2. Базовые алгоритмы
breadth_first_search
template <typename Graph. typename P, typename T, typename R>
void breadth_first_search(Graphs g.
typename graph_tra1ts<Graph>;:vertex_descriptor s.
const bgl_named^params<P. T. R>& params)
Фикция breadth_fi rst_search() выполняет поиск в 1иирину [31 ] ориентированного или неориентированного графа. При поиске в ширину сначала посещаются вершины, которые располохсены ближе к исходной вершине, а потом более дальние вершины. В этом контексте расстояние определяется как число ребер в кратчайшем пути от исходной вершины. Функция breadth_fi rst_search() может использоваться для вычисления кратчайших путей из одной вершины ко всем достижимым вершинам и длин соответствующих кратчайших путей. Определения, связанные с алгоритмом поиска в ширину, и детальны!! пример приведены в разделе 4.1.
Поиск в ширину использует две структура данных для реализации обхода: цветную метку для каждой вершины и очередь. Еще не просмотренные вершины окрашены белым, серым обозначены пройденные вершины, у которых еще есть смежные белые. Черные вершины — уже посещенные вершины» причем вершины, смежные с данными, окрашены в серый или черный цвет. Алгоритм продолжает работу, удаляя вершину и из очереди и исследуя каждое исходящее ребро (以,г). Если смежная верпшиа v ранее не посещалась, она окрашивается в серый цвет и помещается в очередь. После рассмотрения всех исходящих ребер вершина 口 окрашивается в черный цвет и процесс повторяется. Псевдокод для алгоритма поиска в ширину приведен нп*е. В псевдокоде показаны вычисления предшественников р, отметки посещения d и отметки окончания обработки L По умолчанию функция breadth^fi rst_search() не вычисляет эти свойства» однако имеются предопределенные посетители, которые могут быть использованы для этого.
ПОИСК_В_ШИРИНУ(С, s)
для каждой вершины и е V[G]
> инициализировать вершину 〃
color[u] _ БЕЛЫЙ d[u] — оо
7l\u} 6-以
со/呎 s] _ СЕРЫЙ
ф] — 0
В_ОЧЕРЕДЬ(0,5)
о посетить вершину 5
пока (Q w 0)
^<-ИЗ_ОЧЕРЕДИ(е)
> рассмотреть вершину и
для каждой вершины 己 w Adj[u]
а рассмотреть ребро (多 о)
если (color[v] = БЕЛЫЙ)
> (/ 口)— древесное ребро
cohi\v] — СЕРЫЙ d[v] — d[u] + 1 乃[矶 4-以
В_ОЧЕРЕДЬ(а0
> посетить вершину в
иначе
> (% в) — не древесное ребро
если (color[f] ^ СЕРЫЙ)
о (/ г) имеет серый конец
иначе
а (% г) имеет черный конец
color[u] — ЧЕРНЫЙ
> завершение обработки вершины
возвратить (d,w)
①ункцмя breadth_first_search() может быть расширена действиями определенными пользователем, которые вызываются в некоторых событийных точках. Действия должны быть представлены в форме объекта-посетителя, то есть объекта, отвечающего требованиям ВFSVisitor. В указанном выше псевдокоде событийные точки обозначены в комментариях. По умолчанию функция breadth.- fl rst_search() не производит никаких действий, даже не записывает расстояния или предшественников. Однако это может быть легко добавлено определением посетителя.
Где определен
Алгоритм поиска в ширину находится в boost/graph/breadth_first_search.hpp.
Параметры
Ниже приведены параметры функции breadth_fi rst_search().
IN: Graphs g
Ориентированный или неориентированный граф, который должен быть моделью VertexListGraph и IncidenceGraph.
IN: vertex__descriptor s
Исходная вершина, с которой начинается поиск.
Именованные параметры
Ниже приведены именованные параметры функции breadth_f 1 гst_search().
IN: visitor(BFSVisi tor vis)
Объект-посетитель, который активизируется внутри алгоритма в событийных точках, указанных в концепции BFSVisitor.
По умолчанию: defau〕t_bfsj/isitor.
UTIL/OUT: color_map(ColorMap color)
Используется алгоритмом для отслеживания продвижения по графу. Тип Color- Мар должен быть моделью ReadWritePropertyMap, тип ключа — дескриптор вершины графа, тип значения color__map должен быть моделью ColorValue,
По умолчанию: iterato^property^map, созданный из вектора std: : vector с элементами типа defaul t_color^type размером nun_vert1ces(). Он использует i_map для отображения индексов.
IN: vertex_index__map(VertexIndexMap 1_map)
Отображает каждую вершину в целое число из диапазона [О, |V|), Этот параметр необходим только при использовании отображения свойства цвета по умолчанию. Тип VertexIndexMap должен моделировать Readable Property Мар. Тип значения отображения должен быть целочисленным типом. В качестве типа ключа отображения должен задаваться дескриптор вершины графа.
По умолчанию: get(vertex_1ndex. g).
UTIL: buffer(Buffers Q)
Очередь используется для определения порядка, в котором будут рассматриваться вершины. Если используется очередь вида FIFO («первым вошел — первым вышел»), обход графа будет происходить в обычном для поиска в ширину порядке. Могут быть использованы и другие типы очередей. Например, алгоритм Дейкстры может быть реализован с использованием очереди по приоритету. Тип Buffer должен моделировать концепцию Buffer.
По умолчанию: boost:: queue.
Предусловия
Очередь должна быть пуста.
Сложность
Временная сложность порядка О(|Е| + 凹).Пространственная сложность в наихудшем случае равна О(| V|).
Пример
В примере (листинг 13.1) показано применение алгоритма поиска в ширину для графа, изображенного на рис. 13Д. Древесные ребра поиска в ширину обозначены черными линиями. Программа записывает порядок, в котором поиск в ширину посещает вершины |рафа4 Исходный код для этого примера находится в файле example/bfs-example.cpp.
Рис. 13.1. Поиск в ширину на графе
Листинг 13.1. Применение алгоритма поиска в ширину
〈Посетитель записи времени для поиска в ширину 》三
tempi ate《typename TimeMep > class bfs_time_visitor public default_bfs_v1s1tor {
typedef typename property_traits ■< TimeMap >::value_type T: public:
bfs_time_vi sitor(TifneMap tmap. T & t) :m_timernap(tmap). m_time(t) { } template < typename Vertex, typename Graph >
void discover_vertex(Vertex u. const Graph & g) const { put(m_timemap, u, m_tirne++):
TlmeMap m_timemap:
T & m_time:
): -
(bfs-example.cpp }=
#include <boost/graph/adjacency^list.hpp>
#i ndude <boost/graph/breadth_fTrst_search.hpp>
#includa <boost/pend1ng/indirect_cmp.hpp>
#include <boost/pending/integer^range-hpp>
#include <iostream>
using namespace boost:
《Посетитель записи времени для поиска в ширину )
int mainO {
using namespace boost:
// Выбрать графовый тип. который мы будем использовать typedef adjacencyjist < vecS, vecSt undirectedS > graph/, // Подготовить идентификаторы вершин n имена enum { r. s, t. u. v. w. x. y. N }: const char *natne = "rstuvwxy":
〃 Указать ребра графа
typedef std::pair < int. int >E:
E edge__array[] = { E(r. s). E(「,v). E(s. w). E(w. r). E(w. t).
EW. x). E(x. t). E(t. u). E(x. y). E(ll y) };
// Создать графовый объект
const int n edges » sizeof(edge_array) / sizeof(E):
Листинг 13.1 (продолжение)
typedef graph_tга 11s<graph_t>::vertices_size_typt? v_siz2_t:
graph_t gfedge^array. edge^array + n__edges, vsize_t(N)k
// Определения типов
typedef graph_traits < graph_t >::vertex_descr1ptor Vertex;
typedef graph^tralts < graph二t >: :vertices -sizeJ;ype Size:
typedef Size*-liter.
// Вектор для хранения свойства 11время посещении" для каждой вершины std:■vector < Size > dtimeCnum^verticestg)):
Size time = 0:
bfs_time_visitor < Size * >v1s(§dtime[0]l time).
oreadth_fi rst_search(g. vertexes( g). visltor(vls)j:
// Использовать std::sort для сортировки вершин по времени посещения
std::vector<graph_t^jts<graph_t>: *vertlces size type > dlscover_order(N):
1nteger_range < 1nt >range(0. Ю:
std: :copy(range.begin() t range,end() t discover__order.begin()):
std::sort(discover_order.beginC). discover^order.end(L
indirect二emp < liter, std::less < Size > >(&dtime[0]));
std::cout « °порядок посещения: ”;
for (int i = 0; i v N; ++1)
std::cout « nameEdlscover_order[i]] « ” ": std::cout « std::endl;
return EX【T_SUCCESS;
Вывод будет таким:
порядок посещения : srwvtxuy
breadth_first_visit
template <typename IncidenceGraph. typename P. typename T. typename R>
void breadth_first_visit(IncidenceGraphS g,
typename graph_traits<IncicienceGraph>: : vertex_descriptor $ .
const bg 1 _nan)ef_params<P. T. R>& params);
Эта функция аналогична breadth_f 1 rst_search() за исключением того, что цветовые маркеры не нпициализ!ipyются в алгоритме. Пользователь должен сам окрасить вершины в белый цвет перед вызовом алгоритма. Поэтому требуемый тип графа — IncidenceGraph вместо VertexListGraph. Также эта разница позволяет иметь большую гибкость в отображении цветовых свойств. Например, можно использовать отображение, которое реализует функцию исключительно для подмножества вершин, что может быть более эффективно для памяти, поскольку поиск достигает только небольшой части графа.
Параметры
Ниже приведены параметры функции breadth_ri rst_vts1 t ().
IN: IncidenceGraph& g
Ориентированный или неориентированный「pa巾* который должен быть моделью IncidenceGraph.
IN: vertex_descriptor s
Исходная вершина, с которой начинается поиск.
Именованные параметры
Ниже приведены именованные параметры функции breadth_f i rst_vis 11 ().
IN: visitor(BFSViSltor vis)
Объект-посетитель, который активизируется внутри алгоритма в событийных точках, указанных в концепции BFSVisitor.
По умолчанию: bfs_vi si tor<nul l_v1 si tor>.
IN/UTIL/OUT: color_map(ColorMap color)
Используется алгоритмом для отслеживания продвижения ио графу. 11вет каждой вершины должен быть ин 11 циал из и рован до вызова breddth_fi rst_search(). Тип Col огМар должен быть моделью ReadWritePropertyMap, тип ключа — дескриптор вершины графа, тип значения col or тар должен быть моделью ColorValue, По умолчанию: get(vertex_color. g).
UTIL: buffer(Buffer^ Q)
Очередь используется для определения порядка, в котором вершины будут посещены. Если используется очередь вида FIFO («первым вошел — первым вышел»), обход графа будет происходить в обычном для поиска в ширину порядке. Могут быть исполюованы и другие типы очередей. Например, алгоритм Дейкстры может быть реализован с использованием очереди по приоритету. Тип Buffer должен моделировать концепцию Buffer.
По умолчанию: boost : :queue.
depth_first„search
template <typename Graph, typename P, typename T. t^pename R》
void depth_first_search(Graphs g. const bgl_named_parains<P. T. R>& params)
Функцга depth_fi rst_search() выполняет поиск в глубину в ориентированном илн неориентированном графе. Когда возможно, поиск в глубину выбирает вершину, смежную с текущей, для следующего посещения. Если все смежные вершины уже посещены или у вершины нет смежных верш ин, гшгоритм возвращается на последнюю вершину, у которой есть не посещенные соседи. Когда все достижимые вершины просмотрены. алгоритм выбирает вершину из оставшихся не посещенных и продолжает обход. Алгоритм завершается, когда пройдены все вершины графа. Поиск в глубину полезен для классн中нкацш] ребер графа и для наведения упорядочения вершин. В разделе 4.2 описаны различные свойства поиска в глубину и рассмотрен пример.
Подобно поиску в ширину, цветные пометки используются для отслеживания посещенных вершин. Белый цвет обозначает не посещенные вершины, серь【访一 посещенные, но имеющие смежные не посещенные вершины. Черным обозначается посещенная вершина, у которой нет белых соседей.
Функция depth_fir5t_search{) активизирует заданные пользователем действия в определеннь/х событийных точках. Это даст механизм для приспособле! гия обобщенного алгоритма поиска в глубину ко мношм ситуациям, в которых он может быть использован. В следующем ниже псевдокоде событийные точки поиска в глубину
обозначены в метках справа. Действия, определяемые пользователем, должны быть заданы в форме объекта-посетнтеля — объекта, чей тип удовлетворяет требованиям для DFSVisitor. В псевдокоде показаны вычисления предшествен!гиков 冗 отметок посещения d и отметок окончания обработки /. По умолчанию функция depth_f1 rst__search() не вычисляет этих свойств, однако пользователь может определить объекты-посетители для этого.
ПОИСКВ^ГЛУБИНУ(С)
для каждой вершины и € Vо инициализировать вершину и
color[u]^ БЕЛЫЙ л[и] = и time — О
для каждой вершины и е V
если (color 岡 е БЕЛЫЙ)
call ПОИСК_
В_ГЛУБИНУ__ПОСЕЩЕНИЕ(СТ и) с> исходная вершина и
возвратить 3 с/, /)
ПОИСК_В_ГЛУБИНУ_ПОСЕЩЕНИЕ(С, и)
color [г] — СЕРЫЙа посетить вершину и
d[u] — time ч— time + 1
для каждой вершины 口 е Adj[u]
если {color [矶=БЕЛЫЙ)а рассмотреть ребро (〃, г)
я [矶="
color [^] — СЕРЫЙ
call ПОИСК^
В_ГЛУБИНУ_ПОСЕЩЕНИЕ(С, г) о (乩 г) — древесное ребро
иначе если (color [矶二 СЕРЫЙ)
…> (и, v) — обратное ребро
иначе если (color] v\ = ЧЕРНЫЙ)
・,・о (以,匕)—прямое или поперечное ребро
color (以]<-ЧЕРНЫЙо завершение обработки вершины н
f[u] — time ч- time + 1
Где определен
Алгоритм поиска в глубину находится в boost/graph/depth_first_search.hpp.
Параметры
Ниже приведен параметр функции depth_fi rst_search().
IN: G「dph& g
Ориентированный или неориентированный граф, который должен быть моделью VertexListGraph и IncidenceGraph.
Именованные параметры
Ниже приведены именованные параметры функции depth_f 1 rst_search().
IN: vi si tor(DFSVi si tor vis)
Объект-посетитель, который активизируется внутри алгоритма в событийных точках, указанных в концепции DFSVisitor.
По умолчанию: default_dfs_vi si tor.
UTIL/OUT: color_map(Co1orMap color)
Используется алгоритмом для отслеживания продвижения по графу* Тип ColorMap должен быть моделью Read Write Pro ре rtyM ар, тип ключа — дескриптор вершины графа, тип значения color_niap должен быть моделью ColorValue*
По умолчанию: 1te「dtor_property_mdp, созданный из вектора std:: vector с элементами типа de fa u 1 t_col or_ty ре размером num__vert1ces(). Он использует 1__тар для отображения индексов.
IN: vertexjndex_map(VertexIndexMap 1 jnap)
Отображает каждую вершину в целое число из диапазона [О, |V|). Этот параметр необходим, только когда используется отображение свойства цветовой окраски по умолчанию. Тип VertexIndexMap должен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом. В качестве типа ключа отображения должен нсгюльзоватьс只 тип дескриптора вершины.
По умолчанию: get(vertex_1ndex. g).
Сложность
Временная сложность порядка О(|£] + \V\) и пространственная порядка О(|У|).
Пример
В примере (листинг 13.2) показан поиск в глубину для графа, изображенного на рис. 13.2. Ребра леса поиска в глубину обозначены черными линиями. Исходный код этого примера находится в файле example/dfs-exarnplexpp.
Рис. 13.2. Поиск в глубину на графе
Листинг 13.2. Применение алгоритма поиска в глубину
(Посетитель поиска в глубину для записи времен посещения и окончания обработки вершин 〉=
template < typename TimeMdp >
Листинг 13.2 {продолжений
class dfs_time_visitor:public default_dfs_visitor { typedef^typename property^traits < TimeMap >: :value__type T: public:
dfs_time_visitor(TimeMap dmap. TlmeMap fmap. T & t) :m_dtimemap(dniap). m__ftimemap(fmap). m_time(t) {
tempi ate < typename Vertex, typename Graph >
void discover vertexCVertex u. const Graph & g) const { put(m_dtimeiiiap. u, m_time++):
template < typename Vertex, typename Graph >
void finish^vertex(Vertex u. const Graph & g) const { put(m_ftiniemap. u. m_t,me++);
TimeMap m_dtimemap;
TimeMap m^ftimemap:
T & m_time:
}:-
《dfs-example.cpp > 鼻
indude <boost/graph/adjacencyj ist.hpp>
indude <boost/graph/depth_first_search.hpp>
1ncl ude <boost/pending/integer_range.hpp>
1 nclude <boost/pending/i ndlrect_cmp.hpp>
include <iostream>
using namespace boost;
< Посетитель поиска в глубину для записи 叩емен посещения и окончания обработки вершин 〉
int malnO {
// Выбрать графовый тип. который мы будем использовать typedef adjacencyjist < vecS, vecS. directedS > graph」: typedef graph_traTts < graph^t >::vertices^slze^rype sTze_type; // Подготовить идентификаторы вершин и имена
enum
{ u. v, w. x. у, z, N }:
char name[] = { 'u*. 'v', 'w'. 'x' T 'y*, 'z* }:
// Указать ребра графа
typedef std::pal г < int. int >E;
E edge_array[] - { E(u. v). E(u. x). E(x, v). E(y. x).
E(v.~y). E(w. y). E(w. z). E(z. z)
}:
graph_t g(edge_array. edge_array + sizeof(edge_array) / sizeof(E). N):
〃 Опре冊пенмя типов
typedef boost::graph_traits < graph_t >::vertexjdescr1ptor Vertex: typedef size^type* liter;
// Векторы для хранения свойств мвремя посещения" и "время окончания обработки'* для каждой вернины std::vector < size__type > dtime(num_vertices(g)); std::vector < size二type > ft1me(num2vertices(g)): size^type t = 0:
dfs_time_visitor < size__type * >vls(&dtime[O], &ftime[0], t):
depth_first_search(g. visitor(vis));
// Использовать std::sort для сортировки вершин по времени посещения std::vector < size_type > discover_order(N);
integer_range < size_type > r(0. N);
std: :copy(r.begin(), r.end(). d1scover_order.beglnO);
std::sortCdiscover_order.begint). discover_order.end(), 1nd1rect_cmp < liter, std: :less < size_type > X&dtime[0])}; std::cout « "порядок посещения: ”; int 1;
for ci - о: 1 < N: ++i)
std::cout « name[d1scover_orderCi]] « ”":
std::vector < sizjtyp史 > finish_order(N);
std; :copy(r.begind, r.endO , finish_order.begin()):
std::sort(finish_order.begin(). flnish^order.end()t
indirect_cmp < liter, std::less < size.type > >(&ft1meC0])).;
std:;cout « std::endl « "порядок окончания обработки: ”;
for (i » 0: I < N: —i)
std::cout « nameffinish_order[i]]. « ° “;
std::cout « std::endl:
return EXIT_SUCCESS:
}-
Вывод будет таким:
порядок посещения: u v у х w z
порядок окончания обработки: х у v u z w
depth_first_visit
template <typename IncidenceGraph. typename DFSVlsitor, typename ColorMap>
void depth^first_visit(IricidenceGraphS G,
typename^g raph3 ra i t s<Inc1denceGraph>::vertex_descriptor s*
DFSVisitor vis7 ColorMap color):
Функция depth_fi r$t_vi si t (]——рекурсивная часть поиска в глубину. Главная задача этой функции — реализовать depth_fi rst_searchO, но иногда она может быть полезна сама по себе. Для дополнительной мнфорхгации см. описание dcpth_fi rst_- searchO.
Где определен
Функция depth_f 1 rst_vi$11() находится в boost/graph/depth_first_search.hpp.
Параметры
Ниже приведены параметры функции depth^f 1 rst_vi si t ().
IN: Inc1denceGraph& g
Ориентире ванный или неориентированный граф, который должен быть моделью IncidenceGraph.
IN: vertex__descriptor s
Исходная вершина, с которой нужно начинать поиск.
IN: DFSVisitor visitor
Объект-посетитель, который акт и визируется внутри алгоритма в событийных точках, указанных в концепции DFSVisitor.
UTIL: ColorMdp color
Используется алгоритмом для отсичеживання продвижения по графу. Tim ColorMap должен быть моделью ReadWritePropertyMap, тип ключа — дескриптор вершины графа, тип значения colог_птар должен быть моделью ColorValue.
Сложность
Временная сложность порядка О(|Е|) и пространственная — порядка О(|У|)*
topological_sort
template <typename Graph, typename Outputiterator.
typename P. typename T, typename R>
void topologica1_sort(Graph& G. Outputiterator result.
const bgljiame<T_params<P. T* R>& params = all defaults)
Функция topol ogical_sort() реализует алгоритм топологической сортировки, который создает линейное упорядочение вершин, такое, что если ребро (以,и) присутствует в графе, то и стоит в упорядочении раньше, чем г. Граф должен быть ориентированным ациклическим графом.
Обратное топологическое упорядочение записывается итератором вывода result поэтому его нужно обратить для получения нормального топологического порядка. Есть несколько способов это сделать. Можно создать std:: vector размером |V| дая сохранения вывода и затем использовать обратный итератор вектора для итератора result. Или можно использовать итератор вставки в конец backjnsert_1 terator с пустым вектором, а затем применить std :: reverseO. Еще одна альтернатива — итератор вставки в начшю frontjnsertjterator с контейнером вроде std: :11st или std: :deque.
Реализация включает простой вызов поиска в глубину [10], В разделе 1.4.1 есть пример использования топологической сортировки для планирования заданий а в главе 3 топологическая сортировка применяется при написании обрбщенного графового алгоритма.
Где определен
Алгорнтм топологической сортировки находится в boost/graph/topologicaL_sort»hpp
Параметры
Ниже приведены параметры функции topolog1cal_sort().
IN: Graphs g
Ориентированный или неориентированный граф, который должен быть моделью VertexListGraph и IncidenceGraph.
IN: Output Iterator result
Вершины выводятся этим итератором в обратном тополошческом порядке. Тил Output Iterator должен принимать дескрипторы вершин как выход, и тип итератора должен быть моделью Outputiterator.
Именованные параметры
Ниже приведены именованные параметры функции topological_sort().
UT1L/0UT: col о r_ma р(Col о гMa р color)
Используется алгоритмом для отслеживания продвижения по графу. Тип Со】 огМар должен быть моделью ReadWritePropertyMap, тип ключа — дескриптор вершины графа, тип значения со】 ог^ар должен быть моделью ColorValue.
Но умолчанию: 1terator_property_mapt созданный из вектора std: :vector с элементами типа defaul t_color^type, размером nurn_vertices(). Он использует i jnap для отображения индексов.
IN: vertexj ndex_ma р (Ver tex I ndexMa p 1__map)
Отображает каждую вершину в целое число из диапазона [0, | V |). Этот параметр необходим, только когда используется отображение свойств цветовой окраски по умолчанию. Тип Vertex I ndexMa р должен моделировать ReadabLePropertyMap. Тип значения отображения должен быть целочисленным типом. В качестве типа ключа отображения требуется задавать тип дескриптора вершины.
По умолчанию: get(vertex_index. g).
Сложность
Временная сложность порядка O(|V] + |Е|) н прострапственная — О(| V|).
Пример
См. раздел 1Л.1, где дан пример использования toplogical_sort().
13.3. Алгоритмы кратчайших путей
dijkstra_shortest_paths
template <typename Graph. typenaine P. typename T, typename R>
void dijkstra_shorte$t_pdths(con5t Graphs g.
typename graph_traits<Graph>::vertex_descri ptor s.
const bgl_namedj)arams<P r T. R>& params)
Алгоритм Дейкстры [10, И] решает задачу нахождения кратчайших путей из одной вершины на взвешенном, ориентированном или неориентированном графе для неотрицательных весов ребер. Для случая, когда у некоторых ребер имеются отрицательные веса, лучше использовать алгоритм Беллмана-Форда, а когда все веса равны единице, применяется алгоритм поиска в ширину. Описание задачи кратчайшего пути приведено в разделе 5.1.
Есть два варианта получения результата из функции di jkstra_shortest_paths(). Первый — предоставить отображение свойства расстояний через параметр di stan- ce_map(). В этом случае кратчайшее расстояние от исходной вершины до любой другой в графе будет записано в отображения расстояний. Второй вариант — записать дерево кратчайших путей в отображение предшественников: ял只 каждой вершины н £ V, ^[и] будет предшественником и в дереве кратчайших путей (если, конечно, не выполняется 江[w]=弘 так как в этом случае и либо является исходной, либо недостижима из исходной). В дополнение к этим двум вариантам пользователи могут предоставить своего собственного посетителя, который может пронз- водить определенные действия в любой из событийных точек.
Алгоритм Дейкстры находит все кратчайшие пути из одной исходной вершины к любой другой верпшне, итеративно наращивая множество вершин 5, к которым он знает кратчайший путь. На каждом шаге алгоритма добавление следующей вершины к 5 определяется очередью по приоритегу. Очередь содержит вершины из множества V - S, а значением приоритета является их метка расстояния — длина кратчайшего пути, который известен на данный момент для каждой вершины1.
Алгоритм, испол^оваипыЙ здесь, сохраняет память, нс помещая все вершины У - 5 в очередь по приоритету, а только те вершины из V —5, которые были посс1цсны и поэтому имеют расстояние меньше бескоггечиости.
Вершина и в начале очереди добавляется к 5Т а каждое из ее исходящих ребер релаксируется. Если расстояние до вершины и в сумме с весом исходящего ребра меньше, чем метка расстояния для вершины и, то оценка расстояния для вершины г/уменьшается. После этого алгоритм обрабатывает следующую вершину с головы очереди. Алгоритм завершается, когда очередь пуста.
Алгоритм использует цветовые маркеры (белый, серый и черный) для отслеживания принадлежности вершины некоторому множеству. Черные вершины принадлежат 5, белые — множеству V- S. Белые вершины еще не посещались, а серые находятся в очереди по приоритетам. По умолчанию алгоритм заводит массив для хранения цветовых маркеров для каждой вершины графа. Вы можете предоставить свое собственное хранилище и получать доступ к цветам с помощью именованного параметра color_map().
Ниже приведен псевдокод алгоритма Дейкстры для вычисления гсратчайших путей из одной вершины. Здесь w обозначает вес ребра, d — метка расстояния. п — предшественник каждой вершины, используемый для кодирования дерева кратчайших путей. Q — очередь по приоритету, которая поддерживает операцию УМЕНЬШИТЬ КЛЮЧ. Событийные точки для алгоритма обозначены рядом с метками справа.
ДЕЙКСТРА(С,5,ИОдля каждой вершины и е V d[u] _ оо *[“]<—" color[u] — БЕЛЫЙ color[s] _ СЕРЫЙ d[s] — О BCTABHTb(Qs) пока (Qf 0)и 一 ВЗЯТЬ_МИНИМАЛЬНОЕ (Q)S ^~^ 5 и {〃}для каждой вершины и g Adj[u] если (w(u, V)+ d[u] < d[v]) d冋 一 w(u, v) + d[u]汇⑻一〃если (color[v] = БЕЛЫЙ) color[v] 一 СЕРЫЙ ВСТАВИТЬСЯ z) иначе если (color [о] = СЕРЫЙ) УМЕНЬШИТЬ_^КЛЮЧ(О, W,"(",己) иначеcolor [и] ^г-ЧЕРНЫЙ возвратить ®, тг )ДЕЙКСТРА(С,5,ИОдля каждой вершины и е V d[u] _ оо *[“]<—" color[u] — БЕЛЫЙ color[s] _ СЕРЫЙ d[s] — О BCTABHTb(Qs) пока (Qf 0)и 一 ВЗЯТЬ_МИНИМАЛЬНОЕ (Q)S ^~^ 5 и {〃}для каждой вершины и g Adj[u] если (w(u, V)+ d[u] < d[v]) d冋 一 w(u, v) + d[u]汇⑻一〃если (color[v] = БЕЛЫЙ) color[v] 一 СЕРЫЙ ВСТАВИТЬСЯ z) иначе если (color [о] = СЕРЫЙ) УМЕНЬШИТЬ_^КЛЮЧ(О, W,"(",己) иначеcolor [и] ^г-ЧЕРНЫЙ возвратить ®, тг )а инициализировать вершину ио посетить вершину $о рассмотреть вершину 〃t> рассмотреть ребро (〃, 口)ребро (м,己)релаксируетсяпосетить вершину 單+十司)ребро (以,г) не релаксируетсязавершение обработки вершины 〃а инициализировать вершину ио посетить вершину $о рассмотреть вершину 〃t> рассмотреть ребро (〃, 口)ребро (м,己)релаксируетсяпосетить вершину 單+十司)ребро (以,г) не релаксируетсязавершение обработки вершины 〃
ДЕЙКСТРА(С,5,ИО
для каждой вершины и е V d[u] _ оо *[“]<—" color[u] — БЕЛЫЙ color[s] _ СЕРЫЙ d[s] — О BCTABHTb(Qs) пока (Qf 0)
и 一 ВЗЯТЬ_МИНИМАЛЬНОЕ (Q)
S ^~^ 5 и {〃}
для каждой вершины и g Adj[u] если (w(u, V)+ d[u] < d[v]) d冋 一 w(u, v) + d[u]
汇⑻一〃
если (color[v] = БЕЛЫЙ) color[v] 一 СЕРЫЙ ВСТАВИТЬСЯ z) иначе если (color [о] = СЕРЫЙ) УМЕНЬШИТЬ_^КЛЮЧ(О, W,"(",己) иначе
color [и] ^г-ЧЕРНЫЙ возвратить ®, тг )
ДЕЙКСТРА(С,5,ИО
для каждой вершины и е V d[u] _ оо *[“]<—" color[u] — БЕЛЫЙ color[s] _ СЕРЫЙ d[s] — О BCTABHTb(Qs) пока (Qf 0)
и 一 ВЗЯТЬ_МИНИМАЛЬНОЕ (Q)
S ^~^ 5 и {〃}
для каждой вершины и g Adj[u] если (w(u, V)+ d[u] < d[v]) d冋 一 w(u, v) + d[u]
汇⑻一〃
если (color[v] = БЕЛЫЙ) color[v] 一 СЕРЫЙ ВСТАВИТЬСЯ z) иначе если (color [о] = СЕРЫЙ) УМЕНЬШИТЬ_^КЛЮЧ(О, W,"(",己) иначе
color [и] ^г-ЧЕРНЫЙ возвратить ®, тг )
а инициализировать вершину и
о посетить вершину $
о рассмотреть вершину 〃
t> рассмотреть ребро (〃, 口)
ребро (м,己)релаксируется
посетить вершину 單
+十司)
ребро (以,г) не релаксируется
завершение обработки вершины 〃
а инициализировать вершину и
о посетить вершину $
о рассмотреть вершину 〃
t> рассмотреть ребро (〃, 口)
ребро (м,己)релаксируется
посетить вершину 單
+十司)
ребро (以,г) не релаксируется
завершение обработки вершины 〃
Где определен
Алгоритм Дейкстры находится в boost/gгаph/dijkstra_shortest_paths. hрр.
Параметры
Ниже приведены параметры функции di jkstra_shortest_paths().
IN: const G「dph& g
Графовый объект, к которому применяется алгоритм* Тип Graph должен быть моделью VertexListGraph и IncidenceGraph.
IN: vertex^descrlptor $
Исходная вершина. Все расстояния вычисляются от этой вершины, и в ней находится корень дерева кратчайших путей.
Именованные параметры
Именованные параметры функции di jkstra_shortest_paths() приведены ниже.
IN: weight_map(WeightMap w_map)
Вес или «длина» каждого ребра в графе. Тип WeightMap должен быть моделью ReadabLePropertyMap. Дескриптор ребра требуется задавать в качестве типа ключа для отображения весов. Тип значения отображения весов должен быть таким же, как тип значения для отображения расстояний.
По умолчанию: get(edge_weight. g).
IN: vertex_lndex_map(VertexIndexMap i_map)
Отображает каждую вершину в целое число из диапазона [О, |V|). Этот параметр необходим для эффективного обновления структуры данных при релаксации ребра. Тип VertexlndexMaр должен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом. Тип дескриптора вершины графа требуете只 задавать в качестве типа ключа отображения.
По умолчанию: get(vertex_1ndex, g).
OUT: predecessor_map(PredecessorMap pjnap)
Отображение предшественников записывает ребра минимального остовного дерева. По заверп(ении алгоритма ребра (笈[〃],〃)U〃 w V находятся в минимальном остовном дереве. Если я [и]=以,это означает, что либо 〃 является исходной вершиной, либо и недостижима из исходной. Тип PredecessorMap должен быть ReadWritePropertyMap с типами ключа н вершины такнмн же, как тип дескриптора вершины графа.
По умолчанию: dummy_property_niap.
UTIL/OUT: distance_map(DistanceMap d_map)
Вес кратчайшего пути из исходной вершины в каждую вершину графа записан в этом отображении свойств. Вес кратчайшего пути — это сумма весов ребер, из которых состоит путь. Тип DIstanceMap должен моделировать ReadWritePropertyMap. Тип дескриптора вершины графа должен задаваться в качестве типа ключа отображения расстояний. Тип значения — элемент типа Monoid, состоящий из функционального объекта combi ne и объекта zero для нейтрального элемента (см. главу 16). Также тип значения расстояния должен обеспечивать
StrictWeakOrdering (ослабленное строгое упорядочение), что и дает объект-функция compare.
По умолчанию: iterator_propertyjnap, созданный из вектора std:: vector с элементами того же типа, что и тип значения WeightMap, размером num_vertices(). Он использует i_map для отображения индексов.
IN: distance__combine(BinaryFunction combine)
Объект-функция, являющийся операцией концепции Monoid для типа значения расстояния. Этот функциональный объекг складывает длины для получения полной длины пути.
По умолчанию: cl osed_plus<D>, где D — тип значения отображения расстояний Тип closed_plu5 определен в boost/graph/relax.hpp.
IN: distdnce_compdre(BindryPredicate compare)
Объект-функцияr который задает StrictWeakOrdering на значениях расстояний. Используется для определения того, какой из путей короче.
По умолчанию: std : :less<0>, где D — тип значения отображения расстояний.
IN: d1stance_1nf(D inf)
Объект inf должен давать наибольшее значение любого объекта D. То есть compare(d. inf) == true для любого d 1= inf* Тип D — тип значения DistanceMap. По умолчанию: std: :numeriс_11 mlts<D>: :max().
IN: distance__zero(D zero)
Значение должно быть нейтральным элементом для Monoid, состоящего из значений расстояний и объекта-функции combine. Тип D — тип значения для DistanceMap.
По умолчанию: D.
UTIL/OUT : color_map(ColorMap с тар)
Используется во время выполнения алгоритма для маркировки вершин. В начале алгоритма вершины имеют белый цвет и становятся серыми, когда они попадают в очередь. Они перекрашиваются в черный при удалении из очере ди. В конце алгоритма все вершины, достижимые из исходной, имеют чер ный цвет. Остальные вершины будут белыми. Тип Col огМар должен быть моделью ReadWritePropertyMap. Дескриптор вершины требуется задавать в качестве типа ключа для отображения, и тип значения отображения должен моделировать ColorValue,
По умолчанию: 1terator_propertyjnap, созданный из вектора std: :vector с элементами типа defaul t_col ojtypc, размером num_vertices (). Он использует i_map для отображения индексов.
IN: visitorCVis v)
Используйте этот параметр для задания действий^ которые должны выполняться в определенных событийных точках внутри алгоритма. Тип VI s должен быть моделью DijkstraVisitor.
По умолчанию: default_d1jkstra_vis1tor.
Сложность
Временная сложность порядка。((| У | 十 |Е |) log |У|)или просто 0(広 | log | У |), если все вершины достижимы из исходной вершины.
Пример
Исходный код примера из листинга 13.3 находится в файле example/dijkstra- example.cpp. Граф, использо вам 11 ы ii в этом примере, показан на рис. 13.3. Ребра в дереве кратчайших путей обозначены черными линиями.
Листинг 13.3. Пример алгоритма Дейкстры
typedef adjacency_l1st < lists, vec$. directedS, no_property. property < edge_weight_t- int > > graph_t:
typedef graph_traits < graph/ >::vertex_descr1ptor vertex_descriptor;
typedef graph_tra1ts < graph_t >::edge_descriptor edge^descriptor:
typedef std::pa1r<int, int> Edge;
const int num_nodes = 5:
enum nodes { А, В, C, Ot E ):
char name[] - "ABCDE":
Edge edge_array[] = { Edge(A. C). EdgeCB* B). Edge(B. D). EdgeCB. E).
Edge(C,"B). Edge(C. D). Edge(氏 E). EdgWE. A). Edge(Et B)
};
int weights[] = { 1. 2. 1. 2, 7. 3. 1, 1, 1 };
int numarcs = sizeof(edge_array) / sizeof(Edge):
graph_t g(edge_ar'ra.y, edge_array + num_arcst weights, num^nodes):
property_map<graph_t. edge_weight_t>::type weightmap = get(edge_we1ght, g):
std: :vecto^vertex descriptor> p(num_vertices(g));
std::vector<int> dTnum_vertices(g)): vertex_descriptor s = vertextA. g):
dijkstra_shortest_paths(g, s. predecessorjti叩(&p[0]).distance_map(M[Q])):
std::cout « "Расстояния и родители:" « std::endl:
graph traits < graph t >::vertex iterator vi, vend;八
R 产-”-一-Продолжение 夕
Листинг 13.3 (продолжение)
for (tieCvi, vend) * vertices(g): vi != vend: ++vi) {
std: :cout w "distanceC « name[*vi] « ”} = ” « d[*vi] « ”+ ”:
std: :cout « ,#parent(и « nameC*vi] « ") = ” « name[pr*vi]l « std:: endl:
}
std::cout « std::endl;
Программа выводит следующее:
A E A C DA E A C DРасстояния и родители: distanceCА) = 0. parent(A)
A E A C D
A E A C D
parent(B) parent(C) parent(D) parent(E)parent(B) parent(C) parent(D) parent(E)distance(B)二 6
parent(B) parent(C) parent(D) parent(E)
parent(B) parent(C) parent(D) parent(E)
distance(C) - 1 di stanceСЙ) = 4 dlstance(E) = 5
13.32 bellman_ford_shortest__paths
tempi ate <typename EdgedstGraph, typename Size,
.typename P. typename T. typename R>
bool bellFTian_ford_shortest_paths(EdgeL]stGraph& g. Size N,
const bg 1 _named_paranis<P( T. R>& params)
Алгоритм Беллмана-Форда [5,10, 13,26] решает задачу кратчайших путей из одной вершины для графа с положительными и отрицателыiыми весами ребер. Постановка задачи кратчайших путей приведена в разделе 5.1, Если вам нужно решать задачу кратчайших путей для положительных весов ребер, алгоритм Дейкстры предоставляет более эффективную альтернативу. Если веса всех ребер равны единице, поиск в ширину подходит еще больше.
Перед вызовом функции bellman_ford_shortest__paths() пользователь должен назначить исходной вершине расстояние ноль (или нейтральный элемент Monoid, состоящий из значений расстояния н функции combine), а всем другим вершинам -бесконечное расстояние (наибольшее значение расстояния согласно упорядочению, определяемому функциональным объектом compare). Обычно std:: numeric^ i - m1ts<D>: :max() является подходящим выбором для бесконечности» где D — тип значения отображения расстояний. Алгоритм Беллмана - Форда выполняется в цикпе по всем ребрам в графе» с применением операции релаксации к каждому ребру. В следующем псевдокоде г является вер1Ш1Н€)й, смежной с “,з отображает ребра на вес, d — отображение расстояний, которое записывает длину кратчайшего пути до каждой рассмотренной на данный момент вершины.
РЕЛАКСАЦИЯ (必 口 叫〃)
если 3(% 口)+ d[u] v —)
d[v] <— w(ut и) + d[u]
Алгоритм повторяет этот цикл |V| раз, после чего гарантируется, что расстояния до каждой вершины были сокращены до минимально возможных, если в графе нет отрицательных циклов. Если отрицательный ннкл присутствует, в графе будут ребра, которые не минимизированы до конца, то есть будут ребра (〃, г), для которых 並(м,己)+ d[u] v d|r[. Алгоритм проходит циклом по всем вершинам еше раз, чтобы проверитьт что все ребра были минимизированы, возвращая истину, если это так, и ^ложь» в противном случае.
Имеется два основных способа получение результатов из функции bellman_- ford_shortest_paths(). Если пользователь представляет отображение расстояний через параметр distance_map(), то кратчайшее расстояние из исходной вершины до каждой другой вершины в графе записывается в изображение расстояний (конечно, если функция возвращает истинное значение). Пользователь может также записать дерево кратчайших путей, задавая отображение свойства предшественников через параметр predecessor_msp(). В дополнение к этим способам можно использовать своего собственного посетателя, который будет выполнять действия в любых событийных точках алгоритма (см. BelLmanFordVisitor). Если вы заинтересованы только в некоторых событийных точках, создайте своего собственного посетителя из default_beninan_vis1tor для того, чтобы ненужные действия были заменены пустыми в не интересующих вас событийных точках.
Где определен
Алгоритм Беллмана-Форда находится в boost/gгаph/bellman_ford_shortest_paths.hpp.
Параметры
Ниже приведены параметры функции bel 1 man_ford_shortest_paths ().
IN: EdgeL1stGraph& g
Ориентированный или неориентированный графт который должен быть моделью EdgeListGraph.
IN: Size N
Количество вершин в i-рафе. Тип Size должен быть целочисленным типом.
Именованные параметры
Именованные параметры функции bel lman_ford_shortest_paths() приведены ниже.
IN: we1ght_niap(WeightMap w)
Вес (также известный как «длина» или «стоимость») каждого ребра в графе. Тип Wei ghtMap должен быть моделью ReadabLePropertyMap. Типом ключа для этого отображения свойства должен быть дескриптор ребра графа. Тип значения для отображения весов такой же, как и тип значения отображения расстояний. По умолчанию: get(edge_weight. g).
OUT: predecessor_niap(PredecessorM8p p_map)
Отображение п редшествен ни ков записывает ребра минимального остовного дерева. По завершении алгоритма ребра (я [w]T u) Vw е V находятся в минимальном остовном дереве. Если 打["]=% то и является исходной вершиной или вершиной, которая недостижима из исходной. Тип PredecessorMap должен быть моделью концепции ReadWritePropertyMap, чьи типы ключа и значений такие же, как у дескриптора вершины графа.
По умолчанию: dummy__property_map.
UTIL/OUT: distance_map(DIstanceMap djnap)
Вес кратчайшего пути из исходной вершины s в каждую вершину графа запн- сан в этом отображении свойства. Вес кратчайшего пути — это сумма весов
ребер, из которых состоит путь. Тип DistanceMap должен моделировать ReadWritePropertyMap. Тип дескриптора вершины графа требуется задавать в качестве типа ключа отображения расстояний. Тип значения — элемент типа Monoid состоящий из функционального объекта combine и объекта zero для нейтрального элемента. Также тип значения должен быть моделью StrictWeakOrdering (ослабленное строгое упорядочение), что обеспечивает объект^функция compare.
По умолчанию: get(vertex^dlstance. g).
IN: visitOr(Bel 1manfordVIsi tor v)
Объект-посетител ь, который акгивизируется внутри алгоритма в событийных точках, указанных в концепции BeUmanFordVisitor Посетитель по умолчанию — defaul t_bellrnan_vi$1tor, не делает в событийных точках ничего.
Но умолчанию: defaul t_bel 1 man_v1 si tor.
IN: distance_combine(BinaryFunct1 on combine)
Объект-функция, являющийся операцией концепции Monoid для типа эиаче- ния расстояния. Этот функционал ьн ый объект складывает длины для получения полной длины пути.
По умолчанию: cl osed_plus<D>t где D — тип значения отображения расстояний closed plus определен в boost/graph/relax.hpp.
IN: distance_compare(B1naryPreci1cate compare)
Объект-функция, который задает StrictWeakOrdering на значениях расстояний Используется для определения того, какой из путей короче.
По умолчанию: std: :less<D>f где D — тип значения отображения расстояний.
Сложность
Временная сложность порядка O(|V| х 図).
Пример
Исходный код примера из листинга 13.4 находится в файле example/bellman- example.cpp. Граф, использованный в примере, показан на рис. 13.4.
Листинг 13.4. Применение алгоритма Беллмана-Форда
enum {u, v. х,у. z. N}:
char паше[] » {力二'х: 'у: 2’ };
typedef std::pal г < int. int >E:
const int n_edges > 10:
E edge_array[] - { E(u, y), E(u. x), E(u. v). E(v. u).
E(x, y). E(x, v)t E(y, v). E(y. z)t E(z. u), E(z x) }:
int weight[n_edges] = { -4. 8. 5, -2, 9. -3, 7, 2. 6. 7 }:
typcdef adjacencyjist < vecS. vecS. directedS,
no_property. property < edge_we1ght_t. int > > Graph;
Graph g(edge_array. edge_array + n_edges. N):
graph^traits < Graph >::edge_iterator el. ei_end;
property_map<Graph, edge_weight_t>::type weight pnuip 二 get(edgejyeight. g):
Int i к 0:
for (tiefei. ei_end) = edges(g); ei != ei_end: ++ei. ++i) weight_p(nap[*ei] - weight[i]:
stcf::vector<int> distance(N. std::numericalimlts < short >::max()j:
std::vector<std::s1ze_t> parent(N):
for (i = 0: i v N: ++T)
parent[1J * i:
d1stance[z] ^ 0:
bool г = bellman^ford^shortest_path$
(g, int (N). weight_map(weight_pmap).distance_map(&distance[O]). predecessoc_map(&parent[0])):
if (r)
for (i = 0: 1 < N; "i)
std::cout « nameEi] « ": " « std::setw{3} « di stance[1]
« ” ” « name[parent[1]] « std::endl; else
std::cout « "отрицательный цикли « std::endl:
Расстояние и предшественник для каждой вершины такие:
Рис. 13.4, Граф, использованный в примере алгоритма Беллмана-Форда
johnson_all_pairs_shortest_paths
template <typename Graph, typename DistanceMatrix.
typename P. typename T. typename R>
bool johnson_all_pairs_shortest_paths(Graph& g. DistanceMatrlxS D. const bgl_named_params<P, T. R>& params = all defaults)
Функция johnson_al l_pairs_$hortest_paths() (алгоритм Джонсона) находит кратчайшие расстояния между всем и парами вершин графа. Алгоритм возвращает fal se, если в гра巾е присутствует отрицательный цикл, и true в противном случае. Расстояние между каждой парой вершин хранится в матрице расстояний D. Эти один из наиболее требовательных ко времени графовых алгоритмов, так как он имеет временную сложность порядка O(|V| |£ | log |『|)*
Где определен
Алгоритм Джонсона находится в boost/graph/johnson_aLLpairs_shortest_paths,hpp
Параметры
Параметры функции j.ohnson_al 1_ра1 rs_shortest_paths( ) приведены ниже.
IN: const Graphs д
Графовый объект, к которому применяется алгоритм. Тип Graph должен быть моделью VertexList Graph, IncidenceGraph и EdgeListGraph.
OUT: D1stanceMatr1x& D
Кратчайшая длина пути из вершины 〃 н d хранится в。[〃][矶.
Именованные параметры
Именованные параметры функции johnson_al । _palrs_shortest__paths() приведены ниже.
IN: weight__map(We1 ghtMap w_map)
Вес или «длина» каждого ребра в графе. Тип WeightMdp должен быть моделью ReadablePropertyMap. Типом ключа для этого отображения свойства должен бып дескриптор ребра графа. Тип значения для отображения весов такой же, как и тип значения отображения расстояний.
По умолчанию: get(edge_weight. g).
UTIL: weight_map2(WeightMap2 wjnap2)
Вспомогателъное отображение весов. Тип We?1ghtMap2 должен быть модельк ReadablePropertyMap. Типом ключа для этого отображения свойства должен был дескриптор ребра графа. Тип значения для отображения весов такой же, каь и тип значения отображения расстояний.
По умолчанию: get(edge_weight2. g).
IN: ve г tex_1 ndex_ma p (Vertex I ndexMap ijn 叩)
Отображает каждую вершину в целое чнело из диапазона [О, |V|). Этот параметр необходим для эффективного обновления структуры данных при релаксации ребра. Тип VertexlndexMap должен моделировать ReadablePropertyMap. Тил значения отображения должен быть целочисленным типом. Тип дескриптора вершины графа требуется задавать в качестве типа ключа отображения.
По умолчанию: get(vertex_1ndex, g).
UTIL/OUT: distance_map(D1stanceMap d map)
Вес кратчайшего пути из исходной вершины s в каждую вершину графа g зап и ■ сан в этом отображении свойства. Вес кратча11шего пути — это сумма весов
ребер, из которых состоит путь. Тип D istanceMap должен моделировать ReadWritePropertyMap. Тип дескриптора вершины графа должен задаваться в качестве типа ключа отображения расстоянии. Тип значения — элемент типа Monoid, состоящий из операции сложения и объекта zero для нейтрального элемента. Также тип значения должен быть моделью LessThanComparable.
По умолчанию: i terator^property^map, созданный из вектора std:: vector с элементами типа, как тип значения WeightMap, размером nufn_vertices(). Он использует 1_пар для отображения индексов.
IN: dlstance_zero(D zero)
Значение должно быть нейтра^ньным элементом для Monoid, со значением расстояний и операции сложения. Тин D — тип значения для DistanceMap.
По умолчанию: DO.
Сложность
Временная сложность порядка O(\V\ |Е| log |V|).
Пример
Алгоритм Джонсона для кратчайших путей между всеми парами вершин (листинг 13.5) применен к графу со с. 568 «Introduction to Algorithms》[10], который также показан на рнс. 13.5. Результирующая матрица D[u][v] дает кратчайший путь от вершины 以 к s
Рис. 13.5. Граф, использованный в примере алгоритма Джонсона
Листинг 13.5. Алгоритм Джонсона для кратчайших путей между всеми парами вершин
typedef adjacency_list<vecS. vecS. directedS. no^property, property< edge_weight_t. int. property< edge_weight2_t* 1或 > > > Graph;
const int V = 6:-
typedef std::pair < int. Int >Edge;
Edge edge array[] »
{ Edge(O. 1), EdgeCO. 4), EdgetO. 2). Edged. 3), Edged. 4).
Edg削2. 1). Edge⑶ 2). Edge(3. 0). Edge(4. 3)
J *продолжение £
7 «ак 375
Листинг 13.5 (продолжение}
const std::size_t Е = sizeof(edge_array) / slzeof(Edge):
Graph g(edge_array. edge_array + E, V):
property map < Graph, edge weight t >::type w = get(edge weight, g): Int weightst] = { 3. -4. 8: 1. 7「4. 5 2. 6 }: int *wp = weights;
graph_traits < Graph >::edge_iterator e. e_and: for (boost::tie(e. e_end) 。 edges(g); e !-"e_end: ++e) w[*e] - *wp++;
std: vector < int >d(V, std: inumerlcjimits < St >::тнк());
int D[VJ[V]:
johnson_a11_pairs_shortest_paths(g, D, distance_map(&d[O])h
Ниже приведена результ!jpyющая матрица расстояний:
0
1
2
3
4
5
0
0
0
•1
-5
0
-4
1
inf
0
]
-3
2
■4
2
inf
3
0
-4
1
-1
3
inf
7
4
0
5
3
4
Inf
2
-1
•5
0
•2
5
inf
8
5
1
6
0
Алгоритмы минимальных остовных деревьев
kruskaLminimum_spanning_tree
tempi ate <typename Graph, typename Outputiterator.
typename P, Vpename T. typename R>
void kruskal__minimum_spanning_tree(Graph& g.
Outputiterator spanning_tree_edges.
const bgl_named_params<P, T. R>& params = al 1 defaults)
Функция kmskal_minimum_spanning_tгее() находит минимальное остовное дерево в неориентированном графе со взвешенными ребрами. Минимальное остои- ное дерево — это набор ребер, который соединяет все вершины в графе, где общий вес ребер дерева минимизирован. Функция kruskal_minimuni_spanning_tree() выво- “нт ребра остовного дерева итератором $ pa nni ng_t ree_edges т используя алгоритм Краскала [10, 18,23, 44].
Алгоритм Краскала начинается с того, что каждая вершина является сама но себе деревом, без ребер из множества Г, в котором будет строиться минимальное основное дерево. Затем алгоритм рассматривает каждое ребро графа в порядке увеличения веса ребра. Если ребро соединяет две вершины в разных деревьях, алгоритм сливает эти деревья в одно и добавляет ребро к множеству Г Мы используем объединение по рангу и эвристики сжатия пути для обеспечения быстрой реализации операций над непересекающимися множествами (СДЕЛАТЬ- МНОЖЕСТВО, НАЙТИ-МНОЖЕСТВО и ОБЪЕДИ НИТЬ-МНОЖЕСТВА). Ниже приведен псевдокод алторптма Краскала.
КРАСКАЛ—МНН一OCTJJEPEBO(G w)
для каждой вершины и eV
СДЕЛАТЬ_МНОЖЕСТВО(5т и)
0
для каждого ребра (и, v) g £ в порядке неуменьшенвя веса
если НАЙТИ_МНОЖЕСТВО(5, и) * НАЙТИ_МНОЖЕСТВО(5, и)
ОБЪЕДИНИТЬ_МНОЖЕСТВЛ(5, и, г»)
Г_『3(%喇
возвратить Г
Где определен
Алгоритм Краскала находится в boost/graph/kruskal_minirnum_spanning_tree.hpp.
Параметры
Ниже приведены параметры функции kruskа 1 __m1 nimuni^spanning_tree().
IN: const Graphs g
Неориентированный граф. Тип Graph должен быть моделью VertexListGraph и EdgeListGraph.
IN: Output Iterator spannlng_tree_edges
Ребра минимального остовиого дерева выводятся этим Outputiterator.
Именованные параметры
Именованные параметры функции kruskd 1 _mi n1 mum_spann1 ng_tree() приведены ниже.
IN: weight』叩(WeightMap w_map)
Вес или «длина» каждого ребра в графе. Тип WelghtMap должен быть модельго ReadablePropertyMap, и тип его значения — LessThanComparabl е. Типом ключа для этого отображения свойства должен быть дескриптор ребра графа.
По умолчанию: get (edge_wei ght, g).
UTIL: rank_map(RankMap 「_map)
Тип RankMap должен быть моделью ReadWritePropertyMap. Тип дескриптора вершины фафа требуется задавать в качестве типа ключа отображения ранга* Тип значения отображения ранга должен быть целого типа.
По умолчанию: 1terator_property_mapT созданный из вектора std: : vector с элементами целого типа, размером num_vertices(), Он использует i map для отображения индексов.
UTIL: predecessor_map(PredecessorMap p_map)
Тип PredecessorMap должен быть моделью ReadWritePropertyMap. Тип ключа и тип значения отображения лредшественников должны быть типом дескриптора вершин графа.
По умолчанию: 1 terator_property_map, созданный из вектора std: : vector с эле* ментами — дескрипторами вершин, размером num_nertices(g). Он использует i_map для отображения индексов.
IN: vertex_index_map(VertexIndexMap 1_map)
Отображает каждую вершину в целое число из диапазона [О ,|У |). Этот параметр необходим, если значения по умолчанию использованы для отображений ранга и предшественников. Тип VertexIndexMap должен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом. Тип дескриптора вершины графа требуется задавать в качестве типа ключа отображения. По умолчанию: get(vertex_index. g).
Сложность
Временная сложность порядка О(|Е | log |Е |).
Пример
Исходный код примера из листинга 13.6 находится в 4>aihieexampLe/kruskal-example.cpp.
Граф, использованный в этом примере, показан на рис. 13.6.
Листинг 13.6. Пример использования алгоритма Краскала
typedef adjacency]1st < vecS, vecS, undirectedS
njproperty, property < edge_weight_t, int > > Graph;
typedef graph_traits < Graph >7:edge_descriptor Edge;
typedef graph^traits < Graph >::vertex_descriptor Vertex:
typedef std::pair<int. int> E:
const int num_nodes - 5:
E edge^a г ray [] = { E(0, 2). E(l. 3). E(L 4). E(2. D・ EC2. 3).
E(3. 4), E(4. 0). E(4, 1)
}:
int weightst] - { 1, 1. 2. 7. 3. L L 1 };
int num_edges = sizeof(edge_arra^) / sizeof(E):
Graph g(edge_aгray, edge_array + num^edges. weights. num_nodes):
property_map < Graph* edge_weight_t >::type weight ^ get(edge_weight. g): std::vector < Edge > spannTng_tree;
kruskal_mininiurn_spanning__tree(g. std: :backjnserT.er(spanning_tree));
std: :cout « ,rРебра остовно「о дерева:" « std: iendl:
for (std::vector < Edge >:iterator ei = spanning_tree.begin();
ei != spanning_tree.end(); ++ei) {
std::cout «^source(*ei, g) « ” <--> “ « target(*e1. g)
« " с весом " « weight[*ei] « std::endl:
Вывод этот программы следующий:
Ребра остовного дерева:
О <—> 2 с весом 1
3 <--> 4 с весом 1
4 <—> 0 с весом 1
1 <--> 3 е весом 1
prim_minimum_spanning_tree
tempi ate <typename Graph, typename PredecessorMap t typename P. typename T. typename R>
void primjninimuni_spannlng_tree(Graph& G. PredecessorMap pjnap.
const bgl^named^arams<PГT. R>& params = all defaults)
Функция pr 1 m_m1 n i mum_s pa nn 1 ng_t г ее () находит минимальное остовное дерево в неориентированном гра小есо взвешенными ребрами. Минимальное остовное дерево —это набор ребер, который соединяет все вершины в графе, где общий вес ребер дерева минимизирован. Функция prl m__m1 niwm^spanni ng_tree<)выводит ребра остовного дерева в отображении предшественников: для каждой вершины 己 w V, р[и] является родителем р в вычисленном минимальном остовном дереве. Реализация использует алгоритм Прима [10, 18, 38,44].
Алгоритм Прима наращивает минимш1ьное остовное дерево по одной вершине за раз, что очень похоже на построение кратчайших путей алгоритмом Дей- KCTpbiL На каждом шаге алгоритм выбирает ребро для добавления к минимальному остовному дереву. Это ребро — кратчайшее из всех, что соединяют любые вершины в дереве с вершинами вне дерева. Алгоритм использует очередь по приоритету для того, чтобы делать этот выбор эффективно. Если вершина " первая в очереди по приоритету, то ребро (р[〃}以)является следующим наикратчайшим ребром и добавляется к дереву. Псевдокод этого алгоритма приведен ниже.
nPHM_MKH_OCT」IEPEBO(G",侬)
для каждой вершины у е Vd[〃] — со* [以]-"со1от\и] — БЕЛЫЙcolour] — СЕРЫЙ加-0В_0ЧЕРЕДЬ(2 г)пока Q f 0w — ИЗ_ОЧЕРЕДИ((2)для каждой вершины и е Adj\n\ если (研% 矶 < d[硏)для каждой вершины у е Vd[〃] — со* [以]-"со1от\и] — БЕЛЫЙcolour] — СЕРЫЙ加-0В_0ЧЕРЕДЬ(2 г)пока Q f 0w — ИЗ_ОЧЕРЕДИ((2)для каждой вершины и е Adj\n\ если (研% 矶 < d[硏)а инициализировать вершину ио посетить вершину го рассмотреть вершину и> рассмотреть ребро (и, г)а инициализировать вершину ио посетить вершину го рассмотреть вершину и> рассмотреть ребро (и, г)
для каждой вершины у е V
d[〃] — со
* [以]-"
со1от\и] — БЕЛЫЙ
colour] — СЕРЫЙ
加-0
В_0ЧЕРЕДЬ(2 г)
пока Q f 0
w — ИЗ_ОЧЕРЕДИ((2)
для каждой вершины и е Adj\n\ если (研% 矶 < d[硏)
для каждой вершины у е V
d[〃] — со
* [以]-"
со1от\и] — БЕЛЫЙ
colour] — СЕРЫЙ
加-0
В_0ЧЕРЕДЬ(2 г)
пока Q f 0
w — ИЗ_ОЧЕРЕДИ((2)
для каждой вершины и е Adj\n\ если (研% 矶 < d[硏)
а инициализировать вершину и
о посетить вершину г
о рассмотреть вершину и
> рассмотреть ребро (и, г)
а инициализировать вершину и
о посетить вершину г
о рассмотреть вершину и
> рассмотреть ребро (и, г)
Фактически решшзация BGL алгоритма Прима — это вызов алгоритма Дейкстры с особенным и аргу
ментами для параметров distancejcomparc() и d istance соmbine().
d[v] <-並[以,г]а релаксация ребра (м,办)
*[v]— "
если (color[v] == БЕЛЫЙ)
co/oz*[r] — СЕРЫЙ
В_ОЧЕРЕДЬ(2,暮)о посетить вершину v
иначе еслн (со/or[口] ^ СЕРЫЙ)
OBHOBHTb(PQ 口)
иначе
.„о ребро (м, с) не релаксируется
color[u\ 一 ЧЕРНЫЙа завершение обработки вершины м
возвратить (笈)
Где определен
Алгоритм Прима находится в boost/graph/prim^minimum^spanning.tree.hpp.
Параметры
Ниже приведены параметры функции prim_mninium_spann1 ng__tree().
IN: const Graph& g
Графовый объект, к которому применяется алгоритм. Тип Graph должен быть моделью VertexListGraph и IncidenceGraph.
OUT: PredecessorMap p_map
Отображение предшественников записывает ребра минимального остовнз о дерева. Но завершении алгоритма ребра (л■[以} и) Уи е V находятся в мншв мальном остовном дереве. Если 疗[以]=и, то и является исходной вершиной или вершиной, которая недостижима из исходной. Тип PredecessorMap должен быть моделью концепции ReadWritePropertyMap, чьи типы ключа и значений такие же, как у дескриптора вершины графа.
Именованные параметры
Именованные параметры функции prim__m1ninium_spann1ng_tree() приведены ниже.
IN: root_vertex(vertex_descriptor г)
Вертина, которая будет корнем минимального остовного дерева. Выбор корневой вершины произволен, он не влияет на способность алгоритма находить минимальное остовное дерево.
По умолчанию: *vertices(g) .first.
IN: weight_n)ap(WeightMap w_map)
Вес л ли «длииа» каждого ребра в графе. Тип WeightMap должен быть моделью ReadablePropertyMap. Типом ключа для этого отображения свойства должен быть дескриптор ребра графа. Тип значения для отображения весов такой же, как и тип значения отображения расстояний.
По умолчанию: get(edge_weight. g).
IN: vertexjndex^mapCVertexIndexMap i_map)
Отображает каждую вершину в целое число из диапазона [0」V |). Этот параметр необходим для эффективного обновления структуры данных на куче при релаксации ребра. Тип VertexIndexMap должен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом. Тип дескриптора вершины графа должен быть задан в качестве типа ключа отображения. По умолчанию: get(vertex_1 ndex. g).
UTIL: distance_map(DistanceMap d_map)
Вес кратчайшего пути из исходной вершины s в каждую вершину графа g записан в этом отображении свойства. Вес кратчайшего пути — это сумма весов ребер, из которых состоит путь. Тип DI st а псеМа р должен моделировать ReadWriteProperty Мар* Тип дескриптора вершины фафа требуется задавать в качестве типа ключа отображения расстояний. Тип значения — элемент типа Monoid, состоящий из операции сложения и объекта zero для нейтрального элемента (строимого по умолчанию). Также тип значения должен быть моделью LessThanCom parable. По умолчанию: iterator_property_niap» созданный из вектора std: : vector с элементами типа, как тип значения Weight Мар, размером num_vertices(). Он использует i_mdp для отображения индексов.
UTIL/OUT: colorjnapCColorMap с_тар)
Используется во время выполнения алгоритма для маркировки вершин. В начале вершины имеют белый цвет и становятся серыми, когда попадают в очередь. Они перекрашиваются в черный при удалении из очереди. В конце алгоритма вершнны, достижимые из исходной, имеют черный цвет. Все остальные вершины будут белыми. Тип Col огМ叩 должен быть моделью ReadWritePropertyMap, Дескриптор вершины должен быть задан в качестве типа ключа для отображения, и тип значения отображения должен моделировать ColorVaLue,
По умолчанию: Herator_property_niap, созданный из вектора std: : vector с элементами типа defaul t_c。〕or_type, размером num_vert ices(\ Он использует i_map для отображения индексов.
Сложность
Временная сложность порядка 0(|£ | log | V|).
Пример
Исходный код примера из листинга 13.7 находштя в файле example/prim-examplexpp.
Листинг 13<7. Пример использования алгоритма Прима
typedef adjacency_list < vecS. vecS. undirectedS.
property<vertex_distance_t. 1 nt>, property < edge_weight_t, int > > Graph;
typedef std: :paiг'< Ш. int >E:
const 1 nt num_nodes = 5;
E edges[] - {-E(0. 2), E(L1). E(l. 3). E(L 4). E(2. 1). E(2. 3).
E(3. 4). E(4. 0)
打
"t weight乳]={ L 2. L 2. 7. 3. 1. 1 }:
Graph g(edges. edges + sizeof(edges) / sizeof(E). weights, num^nodes):
Листинг 13.7 {продолжениё}
propert^map^raph. edge_weight_t>; ;typc weightmap 二 get(edge_we1ght. g):
std::vector < graph^traits < Graph >::vertex_descriptor >
pCnum^verticesCg)?:
prim_minimun)_spannlng^tree(g. &p[OJ);
for (std::size_t 1 = 0: 1 > p.sizeO: ++1)
If (p[il != i)
std: :cout « "parent/ « i « M] = w « p[i] « std::endl :
else
std::cout « иparent[" « 1 « ”] = no parentи « std::endl;
Вывод этой программы такой:
parent[0] = О
parent[1] = 3
parent[2] = 0
parentГЗ] < 4
parent[4] = Q
Статические компоненты связности
connected_components
tempi ate <typename Graph, typename ComponentMap,
Vpename P. typename T, typename R>
typenmme property_tга11s<ComponentMap>::value_typ£
connected_components(const Graphs g, СотропштЕМар c,
const bgl_named_params<P. T* R>& params * al 1 defaults)
Функция connect ed_components () вы числя er компоненты связности неориентированного графа, используя подход, основанный на поиске в глубину. Компонента связности — это группа вершин неориентированного графа, в которой каждая вершина достижршя из любой другой. Если компоненты связности нужно вычяв лять для растущего графа, то метод, основанный на непересекающихся множествах (функция 1 ncrementа 1 ^components() )> является более быстрым. Для статических графов поиск в глубину быстрее [ 10].
Результат алгоритма записывается в отображение свойства компоненты с, которое содержит номера компонент для каждой вершины. Полное число компонент —возвращаемое значение этой функции.
Где определен
Алгоритм связанных компонент находится в boost/graph/connected_components.hpp.
Параметры
Ниже приведены параметры функции connectec_components().
IN: const Graph& g
Hеориентиронанный граф. Тип графа должен быть моделью VertexListGraph и IncidenceGraph.
OUT: ComponentMap с
Алгоритм вычисляет, сколько компонент связности находятся в графе, и прн- сваивает каждой компоненте целочисленную метку* Затем алгоритм записывает, какой компоненте принадлежит каждая вершина графа, в отображение свойства компонент. Тип ComponentМар должен быть моделью WritabLePropertyMap. Тип значения должен быть vert 1 ces^size_tyре графа* ]」ш ключа — тип дескриптора вершины графа.
Именованные параметры
Ниже приведены именованные параметры функции connected_components().
UTIL: color_map(ColorMap color)
Используется алгоритмом для отслеживания продвижения по графу. Тип Col огМар должен быть моделью ReadWritePropertyMap, тип ключа — дескриптор вершины графа, тип значения с。】 or тар должен быть моделью ColorVaLue.
По умолчанию: iterator_property__map, созданный из веюора std: : vector с элементами типа defaul t__color_type, размером num_vertices(). Он использует 1_тар для отображения индексов.
IN: vertexjndex_map(VertexIndexMap ijndp)
Отображает каждую вершину в целое число из диапазона | 0, |V|). Этот параметр необходим, только когда используется отображение свойства цветовой окраски по умолчанию. Тип Vertex I ndexMa р должен моделировать ReadablePropertyMap. Тип значения отображения должен быть целочисленным типом. Тип дескриптора вершины графа должен быть задан в качестве типа ключа отображения. По умолчанию: get(vertex__1 ndex, g).
Сложность
Временная сложность для сильных компонент связности порядка O(|V| + |Е |). Временная сложность для компонент связности того же порядка.
Пример
Вычисление компонент связности неориентированно го фафа приведено в лис* тинге 13.8.
Листинг 13.8. Вычисление компонент связности неориентированного графа
《connected-components.срр )=
^include <boost/config.hpp> include <iostream> ^include <vector>
i ncl ude <boo st / g ra ph / con пес ted^cornpo ne nt s. hpp>
indude <boost/graph/adjacencyJist.hpp>
int mainO {
using namespace boost:
typedef adjacencyjlst < vecS. vucS. undirectedS > Graph; typedef graph一tracts < Graph >::vertex_descriptor Vertex:
const Int N = 6:
Graph G(N);
add edged). 1. G):
Листинг 13.8 {продолжений
add_edge(l. 4. G);
add二edge(4, 0. G);
add二 edge(2, 5. G):
std::vector<int> c(num_vertices(G)):
int num = connected_components
(G. make_1terator2property_map(c.begin(), get(vertex_index. G). c[0])):
std::cout « std::endl;
std::vector < int >::Iterator 1:
std::cout « "Общее число компонент: “ << num « std::end!:
for (i - c.begin。; i != c.endO: ++i)
std::cout « "Вершина " « i - c.beginO
« " в компоненте u « *1 « std::endl;
std::cout « std::endl:
return EXIT_SUCCESS;
}
Вывод будет следующим:
Общее число Вершина 0 в Вершина 1 в Вершина 2 в Вершина 3 в Вершина 4 в Вершина 5 вОбщее число Вершина 0 в Вершина 1 в Вершина 2 в Вершина 3 в Вершина 4 в Вершина 5 вкомпонент: 3 компоненте О компоненте О компоненте 1 компоненте 2 компоненте О коипоненте 1компонент: 3 компоненте О компоненте О компоненте 1 компоненте 2 компоненте О коипоненте 1
Общее число Вершина 0 в Вершина 1 в Вершина 2 в Вершина 3 в Вершина 4 в Вершина 5 в
Общее число Вершина 0 в Вершина 1 в Вершина 2 в Вершина 3 в Вершина 4 в Вершина 5 в
компонент: 3 компоненте О компоненте О компоненте 1 компоненте 2 компоненте О коипоненте 1
компонент: 3 компоненте О компоненте О компоненте 1 компоненте 2 компоненте О коипоненте 1
strong_components
template <class Graph, class ConiponentMap. class P, class T, class R>
typename property」raitsvComponentM叩〉::value_type
str(jng_componentsTGraph& gr ComponentMap comp:
const bgl_named_params<P. T. R>& params - all defaults)
Функция strong_components () вычисляет сильные компоненты связности ориентированного графа с использованием алгоритма Тарьяна, который основан на поиске в глубину [43].
Результат алгоритма записывается в отображение свойства компонент comp, которое содержит номер компоненты для каждой вершины. Идентификационные номера изменяются от нуля до числа компонент в графе минус один. Полное число компонент — возвращаемое значение этой функинн.
Где определен
Алгоритм Тарьяна находится в boost/graph/strong_components.hpp.
Определения
Сильная компонента связности ориентированного графа G = ( V, £) — максимальный набор вершин U^V, такой, что для каждой пары вершин 以 н 己 в U мы имеем как - путь из w в и, так и путь из 己 в 〃, то есть «иг достижимы друг из друга.
Ниже дано неформальное описание алгоритма Тарьяна для вычисления сильных компонент связности. В основном это вариация поиска в глубину с дополнительными действиями в событийных точках《посещение вершины» и «окончание обработки вершины». Можно думать о действиях в точке «посещение вершины»
как о выполняемых «на пути вниз》по дереву поиска в глубину (от корня к листьям), а о действиях в точке «окончание обработки вершины» — как о выполняемых «на пути вверх».
Три вещи должно произойти по пути вниз. Для каждой посещенной вершины и мы записываем отметку посещения 抑〃]помещаем и во вспомогательный стек и присваиваем root[u} = и. Поле то£ в итоге будет отображать каждую вершину на самую верхнюю вершину в той же сильной компоненте связности. У станавливая то订〃]=% мы начинаем с ситуации, когда каждая вершина — компонента сама по себе*
Опишем теперь то, что происходит по пути вверх. Предположим, что мы только что закончили посещение всех вершин, смежных с некоторой вершиной 以.Теперь можно рассмотреть каждую из смежных вершин снова, проверяя корень каждой нз них. Корень, имеющий наименьшее значение метки посещения, назовем корнем а Затем мы сравниваем а с вершиной 〃 н рассматриваем следующие слунан:
. Если d[a] < d[〃]『 то мы знаем, что а — предок м в дереве поиска в глубину, и значит, мы имеем цикл и и должна быть в одной сильной компоненте связности с а. Тогда мы присваиваем root[u\ = аи продолжаем путь назад, вверх по дереву поиска в глубину.
. Если 仪=% то мы знаем, что и должна быть самой верхней вершиной поддерева, определяющего сильные компоненты связности. Все вершины в этом поддереве расположены ниже в стеке по отношению к вершине ut так что мы выталкиваем вершины из стека, пока не достигаем и、и отмечаем каждую как принадлежащую той же самой компоненте.
. Если 4й 1 > d【〃L то смежные вершины находятся в разных сильных компонентах связности. Мы продолжаем путь назад вверх по дереву поиска в глубину.
Параметры
Ниже приведены параметры функции strong一components ().
IN: const Graphs g
Ориентированный граф. Тип графа должен быть моделью VertexListGraph и IncidenceGraph.
OUT: ComponentMap comp
Алгоритм вычисляет количество компонент связности в графе и присваивает каждой компоненте целочисленную метку. Затем алгоритм записывает, какой компоненте связности принадлежит каждая вершина графа, записывая номер компоненты в отображение свойства компонент. Тип ComponentMa р должен быть моделью WritablePropertyMap. Тип значения требуется задавать целым числом, лучше таким же, как vertices_size_type графа. Тип ключа должен быть типом дескриптора вершины.
Именованные параметры
Ниже приведены именованные параметры функции strong_components(),
UTIL: root_map(RootMap rmap)
Используется алгоритмом для записи кандидатов в корневые вершины для каждой вершины. В конце работы алгоритма у каждой сильно11 компоненты связности всего одна корневая вершина и get (r_map, n) возвращает корневую
вершину для той компоненты, в которую входит v. RootMap должен быть ReadWritePropertyMap, где тип ключа и тип значения должны совпадать с типом дескриптора вершины в графе.
По умолчанию: 1terator_property_mapt созданный из вектора std:: vector описателей вершин, размером num_vertices(g). Использует 1_тар для отображения индексов.
UTIL: discover—tirn&TimeM叩 t_map)
Используется алгоритмом для отслеживания упорядочения вершин поиска в глубину. TimeMap должен быть моделью ReadWritePropertyMap и его тип значения должен быть целым типом. Тип ключа должен быть дескриптором вершины графа.
По умолчанию: 1terator_property_map, созданный из вектора std: : vector целых чисел, размером num_vert1ces(g). Использует i_map для отображения индексов
UTIL: color_map(ColогМар с_тар)
Используется алгоритмом для отслеживания продвижения по графу. Тип Col огМар должен быть моделью ReadWritePropertyMap, тип ключа — дескриптор вершинь графа, тип значения color map должен быть моделью ColorValue.
По умолчанию: 1 terator_property__mapf созданный из вектора std:: vector с элементами типа default_col or_typeT размером nun_vertices(). Он использует i_map для отображения индексов.
IN: vertex_index_map(VertexIndexMap ijnap)
Отображает каждую вершину в целое число из диапазона [О, N)『me N — число верштш графа. Этот параметр необходим, только когда используется значение по умолчанию для одного из других именованных параметров. Тип Vertex Index Мар должен моделировать Readable Pro pertyM ар. Тип значения отображения 口。刀- жен быть целочисленным типом. Тип дескриптора вершины графа требуется задавать в качестве типа ключа отображения.
По умолчанию: get(vertex_index, g).
Сложность
Временная сложность для алгоритма нахоадения сильных компонент связности порядка O(\V\ + |Е|).
Смотри также
Для дополнительной информации смотрите connected_components () и i ncremental_coai' ponentsO.
Пример
Вычисление сильных компонент связности для ориентированного графа прнве- дено в листинге 13.9.
Листинг 13*9. Вычисление сильных компонент связности для ориентированного графа
〈st гong-components.ерр〉育
1nclude <boost/config.hpp>
#include <vector>
i nclude <1ostream>
#i ncl ude <boost/graph/st rang_components + hpp>
#include <boost/graph/adjacency_list.hpp>
int maint) {
using namespace boost;
typedef adjacency_list < vecS, vecS. directedS > Graph:
const int N = 6; ~
Graph G(N);
ddd_edge(O, LG); add__edge(l, LG): add_edge(l, 3. G);
actd_edge(L 4. G): ddd[edge(3. 4, G); add:edge(3. 0. G):
ddd_edge(4, 3* G): add二edge⑸ 2, G):
std::vector<int> c(N):
Int num = strong_components
(G. make_1terator_property_map(G.begin(), gettvertexjndex, G). c[0]));
std::cout « "Общее число компонент; " « num « std::end1: std::vector < int >:iterator i;
for (i = c.begiM); i != c;end(): ++i)
std: :cout « "Вершина " « 1 - c.beginO
« “ в компоненте ° « *i « std::endl:
return EXIT SUCCESS:
} i
Общее число компонент:Общее число компонент:3. О о1 о о23. О о1 о о2Программа выводит следующее:
Общее число компонент:
Общее число компонент:
3. О о
1 о о
2
3. О о
1 о о
2
Вершина Вершина Вершина Вершина Вершина ВершинаВершина Вершина Вершина Вершина Вершина ВершинаО в компоненте 1 в компоненте 2 в компоненте 3 в компоненте 4 в компоненте 5 в компоненте
Вершина Вершина Вершина Вершина Вершина Вершина
Вершина Вершина Вершина Вершина Вершина Вершина
Растущие компоненты связности
Этот раздел описывает семейство функций н классов, которые вычисляют компоненты связности неориентированного графа. Алгоритм, который используется здесь, основан на структуре данных для представления непересекаияцихся множеств (disjoint-sets) [10, 44], которая является лучшим средством для ситуаций, когда граф растет (к нему добавляются ребра) и информация о компонентах связности нуждается в постоянном обновлении. Класс непересекающихся множеств (НМ) описан в разделе 16.6.
Следующие операции являются основными функциями для вычисления и поддержания компонент связности. Использованные здесь объекты — граф д, НМ-объект ds н вершины и и v.
Initiali zej ncreoient a ^components (g.ds)
Основная инициализация структуры непересекаюпшхся множеств. Каждая вершина графа g в своем собственном множестве.
i пегemental_components(g.ds)
Компоненты связности вычисляются на основе ребер в графе g и информации, включенной в НМ-объект ds.
ds.find_set(v)
Выдает информацию о компоненте для вершины v из НМ-объекта.
ds.un1on_set(u.v)
Обновляет НМ-объект, когда ребро (w,己)добавляется к графу.
Сложность
Временная сложность для всего процесса порядка О(|У| + |Е|а(|Е|, |V|)), где |Е| — полное число ребер в графе (в конце процесса) н |叫一число вершин, а — функция, обратная функции Аккермана. Последняя имеет взрывной рекурсивно-экспоненциальный рост. Значит, обратная ей функция растет крайне медленно. На практике а(гл,门)W 4, что означает временную сложность немного больше, чем О(|У | + |£ ).
Пример
В примере из листинга 13.10 мы поддерживаем компоненты связности графа при добавлении ребер, используя структуру данных непересекающихся множеств. Полный исходный код этого примера находится в файле example/incremental-components-egxpp.
Листинг 13.10. Пример увеличивающихся компонент связности
〃 Создать граф
typedef adjacency_list < vecS, vecS. undirectedS > Graph:
typedef graph_traits < Graph >::vertex_descriptor Vertex;
const int N - 6:
Graph G(N):
add_edge(O. LG): add_edge(l. 4. G);
// создать НМ-объект. для которого нужны свойства ранга и родителя вершины
std::vector < Vertex > rank(num^vert^ces(G)):
std::vector < Vertex > parent(num_vert1ces(G)):
typedef graph_traits<Graph>: :vertTces_size__type* Rank:
typedef Vertex* Parent:
dlsjolnt^sets < Rank. Parent > ds(&rank[O]. &parent[O]);
// определить компоненты связности, сохраняя результат в НМ-объекте initialize_incremental—Components(G, ds):
1ncrementaT_components?Gt ds):
// Добавить еще пару вершин и обновить непересекающиеся множества graph_traits < Graph >::edge_descriptor е; bool flag:
tie(e. flag) = add_edge(4. 0. G);
ds.un1on__set(4. 0):
tle(e( fTag) = add_edge(21 5. G); dsunion_set(2. 5)7
graph_traits < Graph >: ;vertexjterator iter, end;
for (tleCiter. end) = verticesCG); iter != end; ++iter)
std::cout « мпредставитель[" « *1ter « ”]二 “ « ds.find_set(*iter) « std::endl;; std::cout << std::endl;
typedef component^ndex < unsigned int Components;
Components components(pa rent.begln(). parent.end());
for (Components::size^type 1 = 0; i < components.sizeO: ++i) {
stdt:cout « мкомпонента ” « i « ” содержит: ”:
for (Components::value_type::iterator j - components[i].begin();
j 1= componentstil.end(): ++j)
std::cout « *j « " °: std::cout « std::endl;
Вывод будет следующим:
представительно]工 1
представитель[1] - 1
представитель[2] = 5
представнтель[3] - 3
представитель[4] = 1
прелставмтель[5:二 5
компонента 0 содержит: 410
компонента 1 содержит: 3
компонента 2 содержит: 5 2
Где определен
Все функции этого раздела определены в boost/graph/incremental_components.hpp.
initialize^ncrementaLcomponents
template <t^pename VertexListGraph. typename DisjointSets>
void ini11 a11ze_incrementalcomponents(VertexLlstGraphS G, DisjointSets& ds)
Функция 1 n1tialize_incrementa 1 components() инициализирует структуру ме- пересекающихся множеств для алгоритма увеличивающихся компонент связности, делая каждую вершину неориентированного графа членом своей собственной компоненты.
Сложность
Временная сложность порядка O(|V[).
incrementaLcomponents
template <typename EdgeListGraph, typename D1sjo1ntSets>
void 1ncremental_components(EdgeListGraph& g, DisjointSets& ds)
Функция i ncremental components С) вычисляет компоненты связности неориентированного графа, включая результаты в НМ-структуру данных.
Сложность
Временная сложность порядка 0(|£ |).
same_component
template <typena(ne Vertex, typename D1sjointSets>
bool same_component(Vertex u. Vertex v. DisjointSets& ds)
Функция same_component() определяет, находятся ли и и г в той же самой компоненте.
Сложность
Временная сложность — О(а(|Е |, |V |)).
componentjndex
component] ndex<Index>
Класс componentj ndex обеспечивает представление данных для компонент графа, подобное контейнеру STL. Каждая компонента является контейиероподобным объектом, и объект component_1 ndex обеспечивает доступ к объектам компоненть: через operator[]. Объект componentjndex инициализируется свойством родителей в непересекающихся множествах, вычисленных функцией i ncremental components〔).
Параметры шаблона
Index — целый беззнаковый тип, используемый для подсчета компонент.
Где определен
Класс component^ ndex находится в boost/graph/incrementaLcomponents.hpp.
Ассоциированные типы
Ниже приведены ассоциированные типы класса componentj ndex.
componentjndex: :value_type
Тип для объекта-компоненты. Тип компоненты имеет следующие члены*
component^ndex::size_type
Тип, используемый для представления числа компонент.
Функции — методы класса
Ниже приведены функции — методы класса componentj ndex*
template <typename ComponentsContainer>
componentjndex::componentjndex(const ComponentsContainer& c)
Создает component^! ndex, используя информацию из контейнера компонент & который был результатом выполнения 1 ncrenrentalcomponents.
template <typename ParentIterator>
component_1ndex::componentJ ndex(Pa rent 1terator first. Pa rent Iterator last)
Создает индекс компонент из «родителей^* вычисленных функцией incremental ^components ().
value_type componentjndex; :operator[ ](size_type 1) const
Возвращает 1 -ю компоненту графа.
size_type componentJ ndex::si ze() const
Возвращает число компонент графа.
Ассоциированные типы для компоненты
Тип va1ue_type для componentjndex — компонента, которая имеет следующие ассоциированные типы.
value_type::value_typ^
Тип значения для объекта-компоненты — идентификатор вершины.
va1ue^type::1terator
valujtype:: constJterator
Этот итератор может быть применен для обхода всех вершин компоненты. Данный итератор разыменовывается в идентификатор вершины.
Функции — методы класса компоненты
Тип val ue_type для component^! ndex является представлением компоненты и имеет следующие функции — методы класса.
Iterator beginC) const
Возвращает итератор, указывающий на первую вершину компоненты.
iterator endО const
Возвращает итератор, указывающий на последнюю вершину компоненты.
13.7. Алгоритмы максимального потока
edmunds_karp_max_flow
template <typename Graph, typename P, typename T, typename R>
typename detail■:edge_capacity_value<Graph. P, T. R>::typ2
edmunds_karp_rnax_fl ow?Graph& g:
typename graph7traits<Graph>::vertex_descriptQr src.
typename graph3raits<Graph>: :verte^escriPtor sink.
const bg 1 _named^pararns<P. T, R>& params = all defaults)
Функция edmunds^karp_max_f 1 ow() въпиоляет максимальный поток в сети (см. главу 8). Максимш1Ы1ый поток является возвращаемым значением этой функции, функция также вычисляет значения потока 人〃,v) V(w, 口) е Е, которые возвращаются в форме остаточной мощности r(u, d) = с(/ г) -f(u, г»
Где определен
Алгоритм находится в boost/graph/edmunds_karp_max„_flow.hpp.
Параметры
Ниже приведены параметры функции edmunds_kdrp_mdx_f】ow( ).
IN: Graph& g
Ориентированный граф. Тип графа должен быть моделью VertexListGraph и Inci- denceG「aph, Для каждого ребра (〃, г) обратное ребро (г, и) также должно быть в графе.
IN: vertex_descrlptor sre
Исходная вершина для графа потоковой сети.
IN: vertex^descrlptor sink
Сток для графа потоковой сети.
Именованные параметры
11иже приведены именованные параметры фхшкции edmunds_k^rp_max_f 1 ow().
IN: capadty_map(CapacityEdgeMap cap)
Отображение свойства мощности ребра. Тип должен быть моделью константной LvaluePropertyMap, Тип ключа отображения должен быть типом дескриптора ребра графа.
Но умолчанию: get(edge^capacity. g).
OUT: resldual_capacity_map(ResidualCapscityEdgeMap res)
Отображение свойства остаточной мощности ребра. Тип должен быть моделью изменяемой LvaluePropertyMap. Тип ключа отображения должен быть типом дескриптора ребра графа.
По умолчанию: get(edge_residual_capacity, g).
IN: reverse_edge_map(ReverseEdgeMap rev)
Отображение свойства ребра, которое отображает каждое ребро графа (и, г) в об ратное ребро (己,и). Это отображение должно быть моделью консгантной Lvalue- PropertyMap. Тип ключа отображения должен быть типом дескриптора ребра графа.
По умолчанию: get(edge_reverse, g).
UTIL: predecessor_map(PredecessorMap p__map)
Это отображение предшественников отличается от обычного отображения предшественников тем, что тип значения является типом дескриптора ребра, а не вершины. Тип ключа для этого отображения — дескриптор вершины.
По умолчанию: iterator__property_map, созданный из вектора std :: vector описателей ребер, размером num_vertices(g). В качестве индекса отображения используется i_map.
UTIL: color_map(ColorMap c_map)
Это отображение используется во внутренних вычислениях. Тип Col огМар должен быть моделью ReadWritePropertyMap. Дескриптор вершины требуется задавать в качестве типа ключа отображения, и тип значения отображения должен быть моделью CoLorValue.
По умолчанию: iterator^propert^map, создалный из вектора std:: vector элементов типа default/。〕or type, размером nuni_vertices(g). В качестве индекса отображения используется i_map.
IN: vertexJndex_map(VertexIndexMap index^rnap)
Это отображение необходимо, если применялось отображение цветов по умолчанию или отображение предшественников по умолчанию. Каждая вершина графа отображается в целое число из диапазона [0, | V|). Отображение должно быть моделью константной LvaluePropertyMap. Тип ключа отображения должен совпадать с типом дескриптора вершины графа.
По умолчанию: get(vertex_index. g).
Пример
Программа в листинге 13.11 читает пример задачи максимального потока (граф с мощностями ребер) из файла в формате DIMACS [1].
Листинг 13.11. Решение задачи максимального потока
edmunds-karp-eg.cpp 〉 а
^include <boost/config.hpp>
nd ude <iostream>
1 nclude <str1ng>
include <boost/graph/ed*unds_kdrp_max_flow.hpp>
#include <boost/graph/adjacencyjist,hpp>
1nclude <boost/graph/read_dimacs.hpp>
include <boost/graph/graph_uti1ity.hpp>
int mainO {
using namespace boost:
typedef adjacency !ist_tга1ts < vecS. vecS. directedS > Traits;
typedef adjacency l1 st < lists. vecS. directedS.
property < vertex_name_t. std::string >,
property < edge_capacity_t. long.
property < edge_residual2capacity_t. long.
property < edge_reverse_t. Traits::edge_descriptor > > > > Graph:
Graph g:
property_(nap < Graph. edge_capacity_t >: :type
capacity - get(edge_capacity. g);~
propertypp < Graph,"'edge_reverse_t >::type rev - get(edge_reverse, g):
property^map < Graph, edge^res1duaT_capacity_t >::type
residual capacity = get<edge_residual_capaciV* g);
Traits::vertex_descriptor s. t;
read_diniacs__max_flow(g, capacity, rev. s. t):
1 ong flow = edniunds_karp_max_f 1 ow(g, s. t):
std::cout « "с Полный поток:" « std:I end!;
std::cout « "s ” 《 flow « std::endl « std::endl;
std: :cout « ”с значения потока:'1 « std:;endl:
graph_traits < Graph >:vertexJ terator u_iter* u_gnd;
graph二traits < Graph >::out_edge_iterator-ei. e_end:
for (tie(u_iter, u_and) - vertices(g): u]ter 1- u_end: ++ujter)
for (tieTei. eend) - out_edges(*u_iter, g); ei T= e_end: ++Ei)
if (capacityf*ei] > 0) ~
std: :cout « "f ” « *ujter у " ” « target(*ei, g) « ” " « <capacity[*ei] - res1dual_capacity[*ei]) « std::endl:
return EXIT_SUCCESS: }
Программа выводит следующее:
с Полный поток:
S 13
с значения потока;
f 0 6 3
f 0 1 6
410540050250343072011122233fffffffff410540050250343072011122233fffffffff
410540050
250343072
011122233fffffffff
410540050
250343072
011122233fffffffff
156477463445566715647746344556670 4 0 0 5 3 о о0 4 0 0 5 3 о о
15647746
34455667
15647746
34455667
0 4 0 0 5 3 о о
0 4 0 0 5 3 о о
f 7 5 О
13,72 push_relabel_nnax_flow
template <typename Graph. typename P. typename T. typendme R>
typename detail ::edgej:apacity_value<Graph. P. T. R>::type push^relabel_max_flow?Graph& g.
typename graph^traits<Graph>::vertex^descriptor src*
typename graph^traits<Graph>::vertex^descriptor s,nk.
const bgl _named^_pa rams<P, T. R>& pa ranis)
Функция push_rel abel_max_fl ow() вычисляет максимальный поток сети (см. главу 8). Максимальный поток является возвращаемым значением этой функции. Функция также вычисляет значения потока/(〃,乙)V(以,v) е Е, которые возвращаются в форме остаточной мощности г (% 口) = с(/ и) - А/ и). Сеть с ребрами, отмеченными значениями потока и мощности, изображена на рис, 13.7.
Имеется несколько особых требований к входному графу и параметрам отображений свойств для этого алгоритма. Во-первых, ориентированный граф G = (У, Я), представляющий сеть, должен быть расширен так, чтобы для любого ребра мз Е в него вошло также и обратное ребро. То есть входной граф должен быть Gj#(匕{月UE ?})• Аргумент rev для ReverseEdgeMap должен отображать каждое ребро в исходном графе на обратное ребро, то есть (〃,а) т (己,u) V(w,己)е £. Аргумент cap от Сарасi tyEdgeMaр должен отображать каждое ребро в Ена положительное число, а каждое ребро в £ 7 в 0. Друга ми словами, отображение мощности должно удовлетворять этим ограничениям: с(〃,и) > 0 и с(и, “) = 0 для каждого (以,口) £ Е.
Где определен
Алгоритм находится и boost/graph/push_relabel_rnax_flow.hpp.
Параметры
Ниже приведены параметры функции push_relatel_max fl ow().
IN: Graphs g
Ориентированный граф. Графовый тип должен быть моделью VertexListGraph и IncidenceGraph. Для каждого ребра (и.好)обратное ребро (а, м) также должно быть в графе.
IN: vertex^descriptor src
Исходная вершила для графа потоковой сети.
IN: vertex_descriptor sink
Сток для графа потоковой сети.
Именованные параметры
Ниже приведены именованные параметры функции push_relabel_rnsx_f 1 ow().
IN: capacity_map(Cap3cityEdgeMap cap)
Отображение свойства реберной мощности. Тип должен быть моделью константной LvaluePropertyMap. Тип ключа отображения должен быть дескриптором ребра rpag
По умолчанию: get(edge_capacity. g).
OUT: residual_capacity_rnap(Res1dualCapac1tyEdgeMap res)
Отображение свойства остаточной реберной мощности. Тип должен быть моделью неконстантной U/aluePropertyMap. Тип ключа отображения должен быть дескриптором ребра графа.
По умолчанию: get(edge_residual_capacity. g).
IN: revers.e_edge_map(ReverseEdgeMap rev)
Отображение свойства ребра, которое отображает каждое ребро (% е) в гра收 на обратное ребро (и и). Отображение должно быть моделью константной Lvalue- Property Мар. Тип ключа отображения должен быть дескриптором ребра графа. По умолчанию: get(edge_reverse. g).
IN; vertex_1ndex^ma p(Vertex IndexMap index_map)
Это отображает каждую вершину на целое в диапазоне | Oi N ), meN — количество вершин в графе. Отображение должно быть моделью константной LvaluePropertyMap. Тип клюпа отображения должен быть дескриптором ребра графа. По умолчанию: get (vertex J ndex. g).
Пример
Пример в листинге 13.12 читает пример задачи максимального потока (граф с мощностями ребер) из файла в формате DIM ACS [1].
Листинг 13.12. Решение задачи максимального потока методом проталкивания предпотока
(push-relabel-eg.cpp 》三
i nclude <boost/conflg.hpp>
1nclude <iostream>
^include <string>
1nclude <boost/graph/push_relabel_max_flow.hpp>
include <boost/graph/adjacency_11st.hpp>
1nclude <boost/graph/read_dimacs.hpp>
Листинг 13.12 (продолжение)
using namespace boost;
typedef adjacencyjist traits < vecS, vecS. directedS > Traits:
typedef adjacencyjist^ vecS, vecS, directedS.
property < vertex_name_t. std:istring >.
property < edge_capacity_t( long.
property < edge^residua1^capacity^t. long.
property v edge^reverse^t. Traits":edge_descriptor > > > > Graph;
Graph g:
property』叩 v Graph. edge_capacity_t >: :type capacity = get(edge_capacity, g)「
property__inap < Graph,^edge_residua 1 __capaci ty__t >: : type
residuaIncapacity - get(edge_residual_capacity, g):
propertyjnap < Graph. edge_reverse_t >:?type rev = get(edge_reverse. g):
Traits::vertex_descriptor s. t:
read_dimacs_max^flow(g, capacity, rev. s. t):
long flow = push_re1abel_max_flow(g, s. t);
std:xout « "c The total flow:" « std::endl:
std::cout « "s ” « flow « std::endl « std::endl:
std::cout « "c flow values:1* « std::endl:
graph_traits < Graph >::vertexjterator u_iter. u_end:
graph^traits < Graph >::out_edge_iterator"ei. e_end:
for (tie(ujter. u_end) = vertices(g): и J ter != u_end: ++u_1ter) for (tielei. e^end) = out_edges(*ujter. g): ei T= e_end:~++ei) if (capacity[*ei] > 0) ~
std::cout « "f ” « *u_iter « ° ” « target(*ei. g) « "" « (cdpacity[*ei] - residual capacity[*ei]) « std::endl;
return EXIT_SUCCE5S:
Программа выводит следующее:
с Полный поток:
s 13
с значения потока:
f 0 6 3
f 0 1 о
f О 2 10
f 1 5 1
f 1 О О
f 1 3 о
f 2 4 4
f 2 3 6
f 2 0 0
f 3 7 5
f 3 2 0
f 3 1 1
f 4 5 4
f 4 6 0
f 5 4 0
f 5 7 5
f 6 7 3
f 6 4 0
f 7 6 0
f 7 5 0
Классы BGL
Классы графов
adjacencyjist
adjacencyj ist<EdgeList, VertexList. Directed.
VertexProperties. EdgeProperties. GraphProperties>
Класс adjacencyjl st реализует интерфейс BGL-графа, используя несколько различных вариантов традиционного представления графа в виде списка смежности. Представление графа в виде списка смежности содержит последовательность исходящих ребер для каждой вершины. Для разреженных графов это экономит место по сравнению с матрицей смежности, поскольку памяти требуется только порядка О(| VI + |Е I), а не О(| V |2). Кроме того, доступ к исходящим ребрам для каждой вершины может быть сделан эффективным. Представление ориентированного графа в виде списка смежности показано на рис. 14.1.
Рис. 14.1. Представление ориентированного графа в виде списка смежностиРис. 14.1. Представление ориентированного графа в виде списка смежности
Рис. 14.1. Представление ориентированного графа в виде списка смежности
Рис. 14.1. Представление ориентированного графа в виде списка смежности
Параметры шаблона класса adjacencyjist предоставляют много возможностей, это позволяет вам выбрать наиболее подходящий вариант класса. Параметр
шаблона VertexLIst класса adjacencyjlst задает вид контейнера для представления г 1 оследовательности вершин (прямоугольник на рис. 14.1)* Параметр шаблона EdgeLlst задает вид контейнера для представления последовательности исходящих ребер для каждой вершины (овалы на рис. 14.1). Выбор EdgeLI st w VertexList определяет затраты памяти н эффективность различных операций на графе. Возможные варианты и компромиссы рассмотрены далее в этом разделе.
Параметр шаблона Di rected указывает, является ли граф ориентированным, неориентированным или ориентированным с доступом к входящим и исходящим ребрам (который мы называем двунаправленным). Двунаправленный граф требует в 2 раза больше места (на каждое ребро), чем ориентированный, так как каждое ребро появляется как в списке исходящих, так и в списке входящих ребер. Представление в виде списка смежности неориентированного графа показано на рис. 14.2. Двуналравленное представление ориентированпого графа показано на рис, 14.3.
Рис. 14.2. Представление неориентированного графа в виде списка смежностиРис. 14.2. Представление неориентированного графа в виде списка смежности
Рис. 14.2. Представление неориентированного графа в виде списка смежности
Рис. 14.2. Представление неориентированного графа в виде списка смежности
Рис* 14.3. Двунаправленное представление списка смежности ориентированного графа
Пример
В следующем примере (листинг 14.1) граф используется для представления генеалогического дерева.
Листинг 14.1. Работа с фамильным деревом
《family-t гее-eg.ерр 〉三
^include <iostream>
#1nclude <vector>
#1nclude <string>
#include <boost/graph/adjacencyJisr.hpp>
#1 nclude <t>oost /1up 1 e/tupl e. hpp>
enum family { Jeanie, Debbie, Rick, John, Amanda. Margaret, Benjamin. N };
int mainO {
using namespace boost;
const char *name[] = { "Jeanie1'. "Debbie". "Rick''.
0John", "Amanda". "Margsret0. "Benjamin''
};
adjacencyjist о g(N):
add_edge(Jeanie. Debbie, g);
add_edge(Jeanie. Rick, g):
add二edgefJeanie. John, g);
add二edge(Debbie. Amanda, g):
add_edge(R1ck. Margaret, g);
add二 edge(John. Benjamin, g):
g「dph_tra1ts < adjacencyj 1st <> >: rvertexjterator i. end:
graph^traits < adjacencyJ1 st <> >::adjacency_iterator ai.己一end;
property_map < adjacencyjist <>t vertex_index_t >: :type
index map - get(ve「tex_index, g):
for (tied, end)匸 vertices(g): 1』end; ++i) {
std::cout « name[get(1ndex_map. *1)]:
tietai, a end) = ddjdcent_ve「tices(*i. g);
if (ai 一 a_end)
std::cout « м не имеет детейп:
else
std::cout « м является родителем для ”:
for (; ai !e a_end: ++ai) {
std: :cout « name[get(index_niap. *di)]:
if (boost::next(ai) != a_end)
std::cout « °,
std:;cout « std::endl:
}
return EX UNSUCCESS;
} 一
Эта программа выводит следующее:
Jeanie является родителем для Debbie, Rick. John
Oebbie является родителем для Amanda
Rick является родителем для Margaret
John является родителем для Benjamin
Amanda не имеет детей
Margaret не имеет детей
Benjamin не имеет детей
Параметры шаблона
Ниже приведены параметры шаблона класса adjacency^ 1st.
EdgeList
Контейнер для представления списка ребер каждой вершины.
По умолчанию: vecS.
VertexLi st
Контейнер для предсгавления множества вершин графа.
По умолчанию: vecS.
Directed
Определяет, является ли граф ориентированным, неориентированным или ориентированным с двунаправленным доступом к ребрам (доступ к исходящим н входящим ребрам). Соответствующие значения: di rectedS, undi - rectedS и bi di recti ona IS.
По умолчанию: di rectedS.
Vertexproperties
Задает внуч реннее хранилище для свойств вершин.
По умолчанию: no_property.
Edgeproperties
Задает внутреннее хранилище для свойств ребер.
По умолчанию: no_property.
Graphproperties
Задает хранилище для свойств графа. По умолчанию: nojroperty.
Модель для
DefaultConstructible, CopyConstructible^ Assignable, VertexListGraph, EdgeListGraph, IncidenceGraph, AdjacencyGraph, VertexMutabLeGraph и EdgeMutableGraph*
Также adjacencyj i st моделирует концепцию BidirectionaLGraph, когда Di rected=b1 - di rectionaIS или Di rected^undi rectedS, и моделирует VertexMutablePropertyGraph и Ed- geMutablePropertyGraph, когда добавляются соответствующие внутренние свойства.
Где определен
Класс adjacenc>_list находится в файле boost/graph/adjacency_LisLhpp.
Ассоциированные типы
Ниже приведены ассоциированные типы класса adjacencyjist.
graph_tra1ts<adjacency_list>: :vertex__descriptor
Тип дескрипторов вершин, ассоциированный с adjacency_l 1 st.
(Требуется для Graph.)
graph_traits<adjacencyjist>: :edge_descriptor
Тип дескрипторов ребер, ассоциированный с adjacencyjist.
(Требуется для Graph.)
graph_traits<adjacency_list>::vertex_iterator
Тип итераторов, возвращаемый vertlcesO.
(Требуется для VertexListGraph.)
graph_traits<adjacencyjist>: :edge_iterator
Тип итераторов, возвращаемый edges().
(Требуется для EdgeListGraph.)
graph_traits<adjacency_l'ist>: :out_edge_iterator
Тип итераторов, возвращаемый out_edges().
(Требуется для IncidenceGraph.)
graph_traits<adjacency_l1st>::in_edge_iterator
Этот тип доступен для неориентированных или двунаправленных списков смежности, но не для ориентированных. Итератор 1n_edge_1terator — тип итератора, возвращаемый функцией 1n_edge5().
(Требуется для BidirectionaLGraph.)
g「aph_t「dits<adjacency_li$t>: :adjacencyjterator
Тип итераторов, возвращаемый adjacent_vert 1 ces ().
(Требуется для AdjacencyGraph.)
gra ph_t ra i t $<a dj acencyj1st>::directed一category
Предоставляет информацию о том, является ли граф ориентированным (di гес- tedjag) или неориентированным (undi「ectRd_tag).
(Требуется для Graph.)
graph_traits<adjacency_l ist>::edge_parallel_category
Дает информацию о томт позволяетлл гра巾 вставку параллельных ребер (ребер, у которых одна и та же наиальнан н конечная вершины). Два возможных тега: а 11 ow_parallel_edgeHd1sallow_parallel_edge_tag. Вариантыc sets Hhash^setSне позволяют задавать параллельные ребра, тогда как другие варианты позволяют. (Требуется для Graph.)
g「dph_t「dit5V3djdcency_1 i$t>::traversal.category
Категория обхода (traversal category) отражает подерживаемые графовым классом виды итераторов. Для списка смежности это включает итераторы вершин, ребер, исходящих ребер и итераторы смежности. Итератор входящих ребер доступен для неориентированных и двунаправленных, но не для ориентированных списков смежности.
graph_traits<adjacencyj1st>::vertlces_size_type
Тип используется для представления числа вершин в графе.
(Требуется для VertexListGraph.)
graph_traits<adjacencyj 1st>: :edges_size_type
Тип используется для представления числа ребер в графе.
(Требуется для EdgeListGraph.)
graph_tralts<adjacencyj ist>::degrcjsizjtype
Тип используется для представления числа исходящих ребер в графе. (Требуется для IncidenceGraph.)
property_map<adjacencyj ist, PropertyTag>::type property_fnap<adjacencyj 1 st. PropertyTag〉: : const_type
Тип отображения для свойств вершины или ребра графа. Свойство задается аргументом PropertyTag шаблона и должно совпадать с одним из свойств из VertexProperties или Edgeproperties для графа.
(Требуется для PropertyGraph,)
Функции — методы
Ниже приведены функции — методы класса adjc.cency_list.
adjacencyJ1 st(const GraphPropertiesS p = GraphPropertiesO)
Конструктор по умолчанию. Создает пустой объект-граф с нулевым числом вершин и ребер.
(Требуется для DefaultConstructible.)
adjacencyJ1 st(vertices size_type 口+ const GraphProperties& p - GraphPropertiesO)
Создает объект-граф с п вершинами к без ребер*
template <typename Edgelterator>
adjacencyJist(EdgeIterator first, EdgeIterator last. vertices_size_type n. edges^size^type m 工 0.
const Era ph Prope rt1es& p 二 GraphPropertiesO)
Создает「pa《)с n вершин и m ребер. Ребра заданы в списке ребер из диапазона [first, /&¥£). Если 门 или т равно нулю, число вершин или ребер вычисляется по списку ребер. Тип значения для Edgeiterator должен быть std : :ра1гт где в паре используется целый тип. Целые числа соответствуют вершинам, и все они должны относиться к диапазону [0, и).
tempi ate <typename Edgeiterator.
typename EdgePropertiesIterator>
adj acencyj 1st (Edgeiterator fl rst.
EdgeIterator last. EdgePropertiesIterator ep_iter.
vertIces^slze_type n, edges_slze_type m : 0* const GraphProperties& p = GraphProperties())
Создает графовый объект c w вершин и т ребер. Ребра заданы в списке ребер из диапазона \first, last). Если п или m равно нулю, число вершин или ребер вычисляется 口о списку ребер. 'Гнн значении для Edge Iterator должен быть std: :рз1 г, где в паре используется цельп! тип. Целые числа соответствуют вертинам, все они должны относиться к диапазону [0, л). Тип value_type итерато* pa ep_1ter должен совпадать с параметром шаблона Edgeproperties.
Функции — не методы
Ниже приведены функции — не методы класса ajjacencyjist.
std: :panr<vertex__iterator, vertex_iterator> vert ices(const adjacency_list& g)
Возвращает пару итераторов, обеспечивающую доступ к множеству вершин графа g.
(Требуется для VertexListGraph.)
std::pair<edge Jterator. edge_iterator>
edges (const adjacencyjist& gS
Возвращает пару итераторов, обеспечивающую доступ к набору ребер графа 目 (Требуется для EdgcListGraph.)
std: :pai r<adjacencyJterator. adjacenc,y_iterator> adjacent^vertices(vertex_descnptor v.
const adjacency listS g)
Возвращает пару итераторовт обеспечивающую доступ к вершинам, смежным с вершиной 口 графам
(Требуется для AdjacencyGraph.)
std: :pair<out_edgeJterator. out_edgejterator> out_edges(vertex^descriptor v, const ddjdcency_list& g)
Возвращает пару итераторов, обеспен и ваюшу ю доступ к исходящим ребрам вершины V графа g. Если граф является неориентированным, этот итератор обеспечивает доступ ко всем ребрам, инцидентным вершине г.
(Требуется для IncidenceGraph.)
std::palr<in_edgeJterator. in_edgejterator>
1 n_edges(vertex_de$criptor v, const adjacencyJistS g)
Возвращает пару итераторов, обеспечивающую доступ к входящим ребрам вершины V 1рафа g. Эта операция недоступна, если для параметра шаблона D1 rected указано di rectedS, и доступна, если используются undi rectedS и bidi rectional 3* (Требуется для Bi di recti о naLG га ph.)
vertex_descriptor sourcetedge^descr 1 ptor e. const adjacencyjist& g)
Возвращает начальную вершину ребра s*
(Требуется для IncidenceGraph.)
vertex_descriptor target(edge_descriptor e, const adjacency_list& g)
Возвращает конечную вершину ребра 仇
(Требуется для IncidenceGraph.)
degree_size_type out_degree(vertex_descriptor u.
const adjacencyJistS g)
Возвращает число ребер, исходящих из вершины 〃.
(Требуется для IncidenceGraph.)
degree_size_type in_degree(vertex_descr1ptor u, const adjacencyJisU g)
Возвращает число ребер, входящих в вершину и. Операция доступна, только если параметр шаблона Di rected был задан как bi di recti oral S.
(Требуется для BidirectionaLGraph.)
vertices_size_type num^vertices(const adjdcency_l1st& g)
Возвращает число вершин в графе g.
(Требуется для VertexListGraph.)
edges_size_type num_edges(const adjacencyjist& g)
Возвращает число ребер в графе g.
(Требуется для EdgeListGraph.)
vertex^descriptor vertex(vertices^s1ze_tyP^ n, const adjacencyjlst& g)
Возвращает,-ю вершину в списке вершин графа.
std::pair<edge_descriptor. bool>
edge(vertex_descriptor u, vertex_descriptor y. const adjacencyjist& g)
Возвращает ребро, соединяющее вершину и с вершиной & в графе g. (Требуется для Adjacency Matrix.)
std::pa i r<out_edgeJ terator. out_edge_1terator> edge_range(vertex_descr1ptor u. vertex_descrlptor v.
const adjacencyjlst& g)
Возвращает пару итераторов исходящих ребер, дающих доступ ко всем параллельным ребрам из и в у. Эта функция работает, только когда EdgeList для adjacencyJhst является контейнером, сортирующим исходящие ребра по ко- печным вершинам, а также когда параллельные ребра разрешены, mul ti sets в качестве EdgeList является таким контейнером.
std: ipai^edge^descriptor. bool>
acld_edge(vertex_descriptor u. vertex^descriptor v. adjacencyJistS g)
Добавляет ребро (以,и) к графу и возвращает дескриптор ребрадля нового ребра. Для тех фафов, которые не разрешают иметь параллельные ребра, в случае если ребро уже присутствует в графе, дубликат добавлен не будет, а флаг bool будет иметь значение «ложь». Также, если и и г являются дескрипторами одной и той же вершины, а граф является неориентированным» создающее петлю ребро добавлено не будет и флаг bool будет иметь значение «ложь». Когда флаг ложен, дескриптор ребра не является правильным и его нельзя использовать. Место нового ребра в списке исходящих ребер в общем случае не определено, хотя задать порядок в списке исходящих ребер можно при выборе EdgeLI 5二, Если VertexLi st^vecS и если один из дескрипторов вершин и шш v (целые числа) имеет значение, большее, чем текущее число вершин фафа, граф увеличивается таким образом, что число вершин становится равным std: :max(u.v) + 1. Если EdgeList^vecS, добавление ребра делает недействительным любой итератор исходящих ребер (out_edge_iterator) для вершины и. То же самое происходит, если EdgeLlst — определенный пользователем контейнер, итераторы которого «портятся» при вызове push(container, х). Если граф является двунаправленным, то итераторы входящих ребер для г (1 n_edge_i terator) тоже《портятся*. Если граф является неориентированным, то любой итератор out_edgej terator для 口также «портится». Если фаф является ориентированным, add_edge() «портит» любой итератор ребер (out_edge).
(Требуется для EdgeMutableGraph.)
std::pair<edge_descr1ptor. bool〉 add_edge(vertex_descriptor u, vertex_descrlptor v, const EdgeProperties^ p. adjacencyjlst& g)
Добавляет ребро (以,v) к графу и присоединяет р как значение внутреннего свойства ребра. См. также описание предыдущей функции — не метода класса. (Требуется для EdgeMutablePropertyGraph.)
void remove_edge(vertex_descriptor u. vertex^descriptor v. adjacencyjist& g)
Удаляет ребро (以,己)из графа. Эта операция вызывает сбой во всех еще не обработанных дескрипторах ребер и итераторах, которые указывают на ребро (必 р). Кроме того, если в качестве EdgeList выбран vecS, тогда эта операция вызывает сбой во всех итераторах, указывающих на элемент списка ребер верш и* ны и. То же самое происходит для вершины г в случае неориентированного или двунаправленного графа< Аналогично, для ориентированных графой эта операция вызывает сбой любого итератора ребер (edge_iterator).
(Требуется для EdgeMutableGraph.)
void remove_edge(edge_des c ri ptor e, adjacency_list& g)
Удаляет ребро e из графа. Отличается от функции remove_edge(u. v, g) в случае мультиграфа. Эта функция удаляет единственное ребро графа, тогда как функция edge(u. v. g) удаляет все ребра (〃,г). Данная операция делает недействительными любые еще не обработанные дескрипторы ребер и итераторы для ребра е. Кроме того, эта операция «портит» все итераторы» которые указывают на список ребер для target(е. д). Аналогично, для ориентированных графов эта операция вызывает сбой любого итератора ребер.
(Требуется для EdgeMutableGraph.)
void remove_edge(out_edgeJterator iter. adjacencyjist& g)
Имеет тот же эффект, что и remove_edge(*1 ter, g). Разница состоит в том, что эта функция выполняется за постоянное время в случае ориентированных графов, тогда как remove_edge(e* g) имеет временную сложность порядка 0(|£ |/|V|)t (Требуется для MutablelncidenceGraph.)
template <typename Pred1cate>
void remove_out_edge_if (vertex_descnptor u. Predicate predicate, adjacencyjist^< g)
Удаляет все исходящие ребра вершины и из графа, которые удовлетворяют предикату, то есть если предикат возвращает истину при применении к дескриптору ребра, ребро удаляется. Эффект для дескриптора и итератора такой же, что н при вызове remove_edge() для каждого из подлежащих удалению ребер, (Требуется для MutablelncidenceGraph.)
template <typenarne Predicate>
void remove_1n_edge_1f (vertex_de5criptor v.
Predicate predicate, adjacencyJ1st& g)
Удаляет все входящие ребра вершины и из графа, которые удовлетворяют предикату! то есть, если предикат возвращает истину при применении к дескриптору ребра, ребро удаляется. Эффект для дескриптора и итератора такой же, что и при вызове remove_edge() для каждого из подлежащих удалению ребер.
(Требуется для MutabLeBidirectionalGraph.)
template <typename Predicate>
void renove edgeJf (Predicate predicate.
adjacency list& g)
Удаляет все ребра из графа, которые удовлетворяют предикату, то есть если предикат возвращает истину при применении к дескриптору ребра, ребро удаляется. Эффект для дескриптора н итератора гакой же, что и при вызове remove_edge() для каждого из подлежащих удалению ребер.
(Требуете# дл# MutableEdgeListGraph.)
vertex^descrlptor add_vertex(adjacency_list& g)
Добавляет вершину к графу и возвращает дескриптор вершины для вновь созданной вершины,
(Требуется для VertexMutabLeGraph.)
vertex__discriptor add_vertex(const VertexPropertiesS p, adjacency!ist& g)
Добавляет вершину к графу н возвращает дескриптор вершины для вновь созданной вершины,
(Требуется для VertexMutablePropertyGraph.)
void clear_vertex(vertex_descrlptor u. adjacency_list& g)
Удаляет все ребрат входящие и исходящие нз верптны и. Вершина остается во множестве вершин графа. Эффект для дескриптора и итератора такой xt\ что н при вызове remove_edge() для всех ребер, у которых и — начальная или конечная вершина.
(Требуется для EdgeMutableGraph.)
void clеаr_out_edges(vertex_descriptor u, adjacency_11st& g)
Удаляет все ребра, исходящие из вершины *. Вершина остается во множестве вершин графа. Эффект для дескриптора и итератора такой жет что и при вызове 笛~ move_jedge() для всех ребер, у которых и — начальная вершина. Эта операция не применяется к неориентированным графам (вмс^сто нее исполъз>4гге clear_vertex()).
void clear_1n_edges(vertex_descrlptor u, adjacency_li$t8i g)
Удаляет все ребра, входящие в вершину и. Вершина остается во множестве вершин фафа. Эффект для дескриптора л итератора такой же, что и при вызове remove_edge() для всех ребер, у которых и — конечная вершина. Эта операция применима только к двунаправленным графам,
void remove_vertex(vertex_descriptor u. adjacency_Hst& g)
Удаляет верш ину и из множества вершин грнфа. Предполагается, что на момент удаления у этой вершины нет входящих или исходящих ребер. Чтобы гарантировать такое состояние, можно заранее применить cl еаr_vertex(). Если параметр шаблона VertexLIst списка adjacency_li st — vecS, то все дескрипторы вершин, дескрипторы ребер и итераторы для графа становятся недействительными. Встроенные свойства vertex_1 ndex_t для каждой вершины после этой операции будут перенумерованы таким образом, что индексы вершин по-прежнему образуют непрерывный диапазон [0, |У|). Если вы используете внешнее хранилище свойств, основанное на встроенных индексах вершин, то внешнее хранилище должно быть соответствующим образом перестроено. Другой воз-
можностью является отказ от использования встроенного индекса вершин в пользу использования свойства для добавления собственного индекса вершин. Если вам необходимо часто использовать функцию remove_vertex(), И stS является гораздо лучшим выбором для шаблонного параметра VertexList.
(Требуется для VertexMutableGraph.)
template <typename PropertyTag>
property_map<adjacency_l1 st. PropertyTag>::type
get (P ropertyTag. adjacency」i st & g)
Возвращает изменяемый объект-отображение свойств для свойства вершины, заданной PropertyTag. PropertyTag должен совпадать с одним из свойств, заданных в шаблонном параметре VertexProperties графа.
(Требуется для PropertyGraph.)
template <typename PropertyTag〉
property_map<adjacencyj1 st. PropertyTag〉::const_type
get(PropertyTag. const"adjacencyjist& g)
Возвращает константный объект-отображение свойств для свойства вершины, заданной PropertyTag. PropertyTag должен совпадать с одним из свойств, заданных в шаблонном параметре Vertexproperties графа.
(Требуется для PropertyGraph.)
template <typename PropertyTag, typename X>
typename property_traits<
typename property_map<adjacency_l1st.
PropertyTag>::const_type>::value_type
get(PropertyTag, const adjacencyjfst& g, X x)
Возвращает значение свойства для х, где х — дескриптор вершины или ребра. (Требуется для PropertyGraph.)
template <typename PropertyTag. typename X.
typename Value>
void put(PropertyTag, const adjacency!lst& g, X x,
const Values value)
Присваивает значение value свойству x, где x — дескриптор вершины или ребра. Значение должно быть преобразуемым к typename property_t га 1 ts<proper- ty_map<adjacency_l1 st, PropertyTag>::type>::value_type.
(Требуется для PropertyGraph.)
tempi ate <typename Graphproperties.
typename GraphPropert1es>
typename property_value<GraphProperties.
GraphPropert1es>::type&
get_property(adjacencyj1st& g, GraphProperties);.
Возвращает свойство, заданное no Graphproperties и присоединенное к графовому объекту д. Класс свойств property^value определен в boosVpending/property.hpp.
tempi ate <typename GraphPropcrtie$.
typename GraphPropert1es>
const typename property_value<GraphProperties.
GraphPropert1es>::type&
get_property(const adjacency__Hst& g. GraphProperties):
В 3a#. 373
Возвращает свойство, заданное по GraphPropertles, присоединенное к графовому объекту д. Класс свойств property_val ue определен в boost/pending/property.hpp.
Выбираем EdgeList и VertexList
В этом разделе уделено внимание тому, какую версшо класса adjacencyjist ис- иолыювать в той или иной сшуацин* Список смежности имеет очень много возможностей для конфигурирования* Интересующие нас параметры EdgeList и VertexList задают структура данных, используемые для представления графа. Выбор EdgeList 冃 VertexList влияет на временную сложность мио nix графовых операций и пространственную сложность графового объекта,
BGL использует контейнеры из STL, такне как вектор std::vector, список std: :11st и множество std: :set для представления множества вершин и структуры смежности (входящие и исходящие ребра) графа, В качестве контейнера для EdgeList и VertexList могут быть выбраны разные типы:
vecS задает std: : vector.
KstS задает std :: 11 st.
si 1 stS задает std:: si 1 st1.
sets задает std: :set.
hash_setS задает std:: hash_set2.
Выбираем тип VertexList
Параметр VertexList определяет вид контейнера, который будет использован длл представления множества вершив или двумерной структуры графа. Контей* нер должен быть моделью Sequence или RandomAccessContainer. В общем случае lists является хорошим выбором, если вам нужно быстро добавить и удалить вершины. Но тогда появляются дополнительные накладные расходы по сравнению с vecS.
Пространственная сложность: std: :11st требует хранить больше информации для каждой вершины, чем std:: vector, так как дополнительно храпит два указателя.
Временная сложность: выбор VertexList влияет на временную сложность следующих операций.
add_vertex()
Эта операция выполняется за амортизированное постоянное время как для vecS, так и для 1 IstS (реализована с push-back О). Однако, когда тип VertexLI st^vecS является типом vecS, время выполнения этой операции иногда больше из-за того, что вектор приходится размещать в памяти заново, а весь граф копировать • remove_vertex()
Эта операция выполняется за постоянное время для 】i stS и за времх О(| V | + |Е |) для vecS. Большая временная сложность vecS объясняется темг что дескрипторы вершин (которые в этом случае являются индексами, соответствующими
Если 1>еализация STL, которую вы применяете, исполмуст std::slist
Если рса^тизация STL, которую ны ггримснясгс, имеет std::hash_set+ Нанрнмер, SGI STL является такой реализацией.
месту в списке вершин) должны быть скорректированы в исходящих ребрах всего графа.
vertexO
Эта операция выполняется за постоянное время для vecS и О(| М |) для 1 i stS.
Выбираем тип EdgeList
Параметр EdgeList определяет, какой вид контейнера используется для хранения исходящих ребер (и, возможно, входящих тоже) для кажпо折 вершины в графе. Контейнеры, используемые для списков ребер, должны удовлетворять требованиям либо Sequence, либо AssociativeContainer.
Одним из первых вопросов, которые необходимо рассмотреть при выборе EdgeLi st, является то, хотите ли вы гарантировать отсутствие параллельных ребер в графе. Если нужна гарантия того, что граф не станет мультиграфом, можно использовать sets или hash_sct5. Если вы хотите иметь мультиграф или знаете, что параллельные ребра вставляться не будут, тогда можно выбрать один из типов* поеледовательностей: vecS, lists или sllstS. Помимо этого, необходимо принять во внимание разницу во временной и пространственной ложности для различных графовых операций. Мы используем |V| для обозначения общего числа вершин графа и |Е| для числа ребер. Операции, которые здесь не рассмотрены, выполняются за постоянное время.
Пространственная сложность: выбор EdgeList влияет на объем памяти, выделяемой на одно ребро в графовом объекте. В порядке возрастания требуемого места следуют: vecS, slistSJIstS, hash_setS и sets.
Временная сложность: в следующем описаншг временной сложности различных операций мы используем |Е | /1V | внутрн обозначений с «большим О》для выражения длины списка исходящих ребер. Строго говоря, это не совсем точно, так как |Е|/|У| дает только среднее число ребер на вершину в графе. В худшем случае число исходящих ребер для вершины равно \V\ (если граф — не мультиграф). В разреженных графах |Е| обычно намного меньше, чем |У |, и может рассматриваться как константа.
add_@dgu()
Когда EdgeList является UniqueAssociativeContainer (ассоциативный контейнер с уникальными элементами) как std :: set, отсутствие параллельных ребер после добавления ребра гарантировано. Дополнительное время поиска имеет временную сложность O(log(|£ I / |VI)), Типы EdgeLi st, которые моделируют Sequence (последовательность)» не осуществляют такую проверку и поэтому add_edge() выполняется за амортизированное постоянное время. Это означает, что если вам безразлично, имеет ли граф параллельные ребра, или вы уверены, что параллельные ребра не будут добавляться к графу, то лучше использовать основанный на последовательностях EdgeList. Функция add edgeO для последовательного EdgeList реализована как push_fronto или pU5h_back(). Однако для std: :11 st и std::si 1st эта операция обычно выполняется быстрее, чем для std:: vector, который иногда перемещается в памяти и копирует свои элементы.
remove_edge()
Для последовательных типов EdgeList эта операция ревизуется с использованием std: :remove_if(). Это означает, что среднее время равно |£\/^\. Для
основанных на множествах типов EdgeLi st используется функция — метод клас - са erase。,которая имеет временную сложность log(|E | /1V |).
edgeO
Временная сложность этой операции равна О(|Е | / |V|), когда тип EdgeLi st явл; - ется Sequence, и O(log(|£ |/ |V|)), когда тип EdgeList является Associ ativeContai пе г. • clеаrevertex()
Для ориентированных графов с последовательным типом EdgeList временная сложность порядка О(|Е| + |V|), тогда как для EdgeList на основе ассоциативного контейнера операция выполняется быстрее, всего за О(| V |log(|£ |/| У |)). Для неориентированных графов данная операции имеет временную сложность порядка О((|Е| /IV 1)2) и 0(図log(|EI / |V|) / |V|).
remove_vertex()
Временная сложность порядка 0(\E\ + \V\) вне зависимости от типа EdgeList
out_edge_i terator::operator++()
Эта операция выполняется за постоянное время для всех типов EdgeList. Однако имеется значительная разница (постоянный множитель) по времени между различными типами. И это важно, поскольку операция является «рабочей лошадкой» многих алгоритмов на графах. Скорость этой операции в порядке ее уменьшения: vecS, slistS, lists, sets, hash^setS.
1n_edgeJterator::operator++()
См, выше.
vertexjterator::operator++()
Эта операция выполняется за постоянное время и достаточно быстро (выполняется со скоростью инкремента указателя). Выбор OneD не влияет на скорость этой операции.
edge_iterator::operator++()
Данная операция выполняется за постоянное время и показывает похожее у по- рядочение по скоростям, что и out_edge_iterator в отношении выбора EdgeList. Обход всех ребер имеет временную сложность порядка 0(|Е| + |V|).
adjacency_1 terator: :operator/)
Данная операция выполняется за постоянное время и показывает похожее упорядочение по скоростям, что и out edge—iterdtor в отношении выбора EdgeList.
Стабильность и сбои итераторов и дескрипторов
При изменении структуры графа (путем добавления или удаления ребер) необходимо действовать осторожно. В зависимости от типа adjacency_l 1 st и от операции некоторые объекты-итераторы и объекты-дескрипторы, указывающие на граф, могут стать некорректными. Например, результаты выполнения кода в листинге 14.2 неопределенны и могут оказаться разрушительными.
Листинг 14.2. Пример сбоя итераторов
// VertexLIst^vecS
typedef adjacency_list<listS. vecS> Graph:
Graph G(N);
// Наполнить граф..,
// Попытка удалить все вершины. Неверно!
gгаph_tгаits<Graph> ;: vertex_iterator vi, vi__end:
for (tiefvl, v1_end) = vertices(G); vi Iя vi_end; *vi) remove_vertex7*vi. G);
// Другая попытка удалить все вершины. Все равно неверно!
graph_traits<Graph>::vertexjterator vi. vi_end4 next:
tie(vi. vi_end) =* vertices(G);
for (next a vi: vi 卜 vi_end: vi = next) {
++next;
remove_vertex(*vi, G);
} -
Причина этой проблемы в том, что мы вызываем remove^ertexO, который при использовании adjacency_l1st с VertexHst^vecS вызывает сбой всех итераторов и дескрипторов графа (в нашем случае — vi н vi_end), таким образом, ошибка по- 只ал只етс貝 в последующих итерациях цикла.
При использовании другого вида adjacency^ 1 st, где VertexH st=l i stS, итерато* ры не «портятся> при удалении вершин, если, конечно, итератор не указывал на удаляемую вершину. Это демонстрирует код из листинга 14.3.
Листинг 14.3. Пример сбоя итераторов (2)
// VertexList-BstS
typedef adjacency_l1st<11stS. 11stS> Graph:
Graph G(N):
// Наполнить граф...
// Попытка удалить все вершины. Неверно!
graph^traits<Graph>:ivertex^lterator vi. vi_end;
for (tiefvl, vi.end) = vertlces(G): vi != vG_end; ivi)
ren)ove_vertex7*vi ( G):
// Удалить все вершины. Правильно.
graph_traits<Graph>::vertexciterator vi. vi_end, next;
tie(vTt vi_end) = vertices(G):
for (next = vi: vi != v1_end; vi = next)(
-н-next:
remove_vertex(*vi, G):
}
Наиболее безопасным и эффективным способом массового удаления ребер из adjacencyjist является применение функции remove_edgR一if().
Вопрос корректности касается также дескрипторов вершин и ребер. Например, предположим, что вы используете вектор дескрипторов вершин для отслеживания родителей (или предшественников) вершин в дереве кратчайших путей (см. файл example/dijkstra-example.cpp). Вы создаете вектор родителей вызовом di jkstra_^hortest_paths(), а затем удаляете из графа вершину. После этого вы пытаетесь использовать вектор родителей, но поскольку все дескрипторы вершин стали некорректны, результат также неверен. Это можно проследить в листинге 14.4.
Листинг 14.4. Пример сбоя дескрипторов вершин
std::vector<Vertex> parent(num^vertices(G)):
std::vector<Vertex> distance(num_vertices(G)); dijkstra_shortest_paths(G. s. distance^mapC&distance[O]).
p redecessorjnapT^pa rent[0])1:
// Плохая идея! Дескрипторы вершин становятся некорректными
// в векторе родителей remove_vertex(s, G):
// Получаем неверные результаты
for(tie(vi, vend) = vertices(G): vi != vend: ++vi:
std::cout « p[*vi] « " является родителем для ” « *vi « std::endl:
При поиске причины сбоев итераторов н дескрипторов следуег обратить вин- манне, что затрагиваются дескрипторы и итераторы, не участвующие в операции неи(7средственно. Например, выполнение remove_edge(u. v, g) всегда делает не- действительньши дескриптор ребра (/ г) или итератор, указывающий на (м, г), независимо от вида adjacencyjlst. То есть в этом разделе нас волнуют эффекты, которые вызывает reirove_edge(u. v. g) для дескрипторов и итераторов, указывающих на отличные от (/ 己)ребра.
В общем случае, если вы хотите, чтобы дескрипторы вершин и ребер были стабильными (никогда не «портились»), следует использовать UstS или sets для шаблонных параметров VertexList и EdgeList класса adjacencyjlst. Если для вас важнее затраты памяти и скорость обхода графа, используйте vecS для шаблонных параметров VertexList и/или EdgeList.
Ориентированные и неориентированные списки смежности
Класс ad jacency_l 1 st может быть использован для представления как ориентированных, так и неориентированных графов, в зависимости от аргумента, присвоенного шаблонному параметру D1 rected. У крайнем di rectedS или bi di recti опа IS выбирается ориентироватшый граф, тогда как undi rectedS выб![рает неориентированный. См. раздел 12.1Л, где дано описание разливши между ориентированным и неориентированным графами в BGL. Выбор bi di recti опа 1S указывает» что граф предоставит функ・ цию in_edges() в дополнение к функции out_edges(). Это требует двойных расходов памяти па одно ребро (и служит причиной того, почему 1 n_edges () необязательна).
Внутренние свойства
Свойства могут быть закреплены за вершинами и ребрами графа, заданного спис ком смежности, через интерфейс свойств (property interface). Шаблонные параметры Vertexproperties и Edgeproperties класса 3djacency_l 1 st подразумевают заполнение классом свойств, который определен следующим образом.
tempi ate <typename PropertyTag, typename T.
typename NextProperty = no_property>
struct property:
PropertyTag — это тип, который просто идентифицирует или дает уникальное имя свойству. Имеются несколько предопределенных тегов (см. раздел 15.2 3), и очень легко добавить новые. Для удобства BGL также предоставляет предопределенные объекты теговых типов (в данном случае значения перечисления enup) для использования в качестве аргументов к функциям, которые принимают объекты-теги свойств (к ним относится, например, (функция get О нз adjacency」i$t).
Параметр Т свойства property обозначает тип значений свойств. Параметр Next - Property позволяет указать следующее свойство, так что произвольное число свойств может быть связано с одним и гем же графом.
Следующий код показывает, как свойства вершин и ребер могут быть использованы при создании графа. Мы связали свойства «расстояние» со значениями типа float и свойство «имя» со значениями типа std: :string с вершинами графа, С ребрами графа связано свойство «вес» со значениями типа float.
typcdef propsrty<di stance_t. fl oat,
property<nanie_t, std: :string> > VertexProperties;
typedef property<wetght_t. float> Edgeproperties;
typedef adjacencyJ1 st<rnapS. vecS, undirectedS.
VertexPropertles, EdgePropertles> Graph:
Graph g(num_vertices):// построить графовый объект
Значения свойств могут быть прочитаны и записаны с использован нем отображений свойств. Описание того, как извлечь отображения свойств из графа, см* в разделе 3.6. Глава 15 целиком посвящена использованию отображений свойств.
Свойство индекса вершины
Если Vert exList графа есть vecS, то п)аф имеет встроенное свойство «индекс», которое может быть получено через свойство vertexj ndex_t. Индексы находятся в диапазоне [О, |У|), без пропусков. Когда вершина удаляется, индексы перестраиваются так, что опять находятся в соответствующем диапазоне и без пропусков. Необходима некоторая осторожность при пользовании этими индексами для доступа к свойствам, хранящимся вне графового объекта, поскольку пользователь должен обновить внешнее хранилище в соответствии с новыми индексами.
Свойства ребер, созданные пользователем
Создание пользовательских типов свойств — достаточно простое дело. Нужно тол ько определить теговый класс для нового свойства. В следующем коде определяются теговый класс для свойств «мощностью и «поток», которые мы закрепляв ем за ребрами графа.
enum edge_capacity_t { edge.capacity };
enum edg[flow_t「edge_fl 加};
namespace boost {
BOOST—1NSTALL一PROPERTY(edge. flow):
BOOST_INSTALL_PROPERTY(edge. capacity):
} - -
Теперь вы можете использовать тег нового свойства в определении свойств так же, как и один из встроенных тегов.
typedef property<capadty_t. 1nt> Cap:
typedef property<f1ow_t. Tnt. Cap> EdgeProperties:
typedef adjacency_l1st<vecS. vecS. n。亠property, EdgePropertles> Graph;
Как обычно, отображения свойств для этих свойств могут быть получены нз графа через функцию get О.
property jnap<6raph. edge_capacity_t>::type
capacity - get(edge__capacity, GT;
propert^_map<Graph, edge_f】ow_t>::type
flow = get(edge flow, G):
В файле edge_property.cpp приведен полный исходный код для рассмотренного примера.
Свойства вершин, созданные пользователем
Закреплять за вершинами некоторые свойства так же легко, как закреплять их за ребрами. Здесь мы хотим закрепить за вершинами графа имена людей.
enuni vertex_fi rst_name_t { vertex_fi rst^name };
namespace boost {
BOOST^INSTALL_PROPERTY(vertex. flrst_name):
Теперь мы можем использовать новый тег в классе свойств property при сбор* ке графового типа. Следующий код (листинг 14.5) показывает создание графового типа и затем создание объекта-графа. Мы заполняем ребра и так же назначаем имена вершинам. Ребра представляют информацию о том, «кто кому должен».
Листинг 14.5. Создание графового типа и объекта
typedef adjacency_li st<vecS. vecS( di rectedS,
property<vertex2first_name3. std::string> > MyGraphType:
typedef pair<int Jnt> Pair:Pair edge_array[ll]匸{ Ра1г(0Л).Pair(2.0).typedef pair<int Jnt> Pair:Pair edge_array[ll]匸{ Ра1г(0Л).Pair(2.0).Pair(3.4).Pair(3.4).PairfO.S). Pair(0.3). Pa1r(0.4).PdiN3.0). PaiN2M). Pdi「(3.D, Pair(4(0). PaiN4,l) };PairfO.S). Pair(0.3). Pa1r(0.4).PdiN3.0). PaiN2M). Pdi「(3.D, Pair(4(0). PaiN4,l) };
typedef pair<int Jnt> Pair:
Pair edge_array[ll]匸{ Ра1г(0Л).
Pair(2.0).
typedef pair<int Jnt> Pair:
Pair edge_array[ll]匸{ Ра1г(0Л).
Pair(2.0).
Pair(3.4).
Pair(3.4).
PairfO.S). Pair(0.3). Pa1r(0.4).
PdiN3.0). PaiN2M). Pdi「(3.D, Pair(4(0). PaiN4,l) };
PairfO.S). Pair(0.3). Pa1r(0.4).
PdiN3.0). PaiN2M). Pdi「(3.D, Pair(4(0). PaiN4,l) };
MyGraphType G(5);
for (Int i=0: 1<11; -H-i)
add_edge(edge_array[i].fi rst. edge_array[1].second. G);
property_map<MyGraphType. vertex_fi rst_name_t>:: ty^e name =~get(vertex_f1rst^name. G):
"Jeremy");"Rich”);“Andrew”);•Jeff"):// можно и так"Jeremy");"Rich”);“Andrew”);•Jeff"):// можно и такboost: :put(nan)e. 0, boost::put(name. 1, boast::put(name, 2, boost::put(name. 3. name[4] = "Kinis";
"Jeremy");
"Rich”);
“Andrew”);
•Jeff"):
// можно и так
"Jeremy");
"Rich”);
“Andrew”);
•Jeff"):
// можно и так
who_owes_who(edges(G).first, edges(G).second. G):
Функция who_owes_who(), написанная для э^^oгo примера, была реализована в обобщенном стиле. Ввод задан классом -ш абл оном, так что мы незнаем реального типа графа. Для нахождения типа отображения свойства для свойства f 1 rst_nane нам необходимо использовать класс свойств verttex_property_maр. Тип const^tYP- использован из-за того, что параметр-граф также является константой. Как только мы получили тип отображения свойства, мы можем сделать заключение о типе значений свойства, используя класс property_tra1ts. В нашем примере известно, что тип значения свойства — std:: string, но написанная в таком обобщенном стиле функция who_owes_who() может работать и с другими типами значений свойства, Код функции приведен в листинге 146
Листинг 14.6. Функция who_owes_who()
template <class EdgeIter. class Graph>
void who_owes_who(EdgeIter first. Edgelter last, const Graph& G)
〃 Доступ к типу средства доступа к свойству для этого графа typedef typename property_rnap<G га ph. vertex_f i rst_name_t>
::const type NamePA:
NamePA name - get(vertex_f1rst^name, G).
typedef typenairie boost : :property_traits<NamePA>: :value_type Na肥Type: NameType src_name, targ^name;
while (first != last) {
src_name - boost::get(name. source(*f1rst.G)): ta rename = boost: : get (name. target(*fi rst#G));
cout w src_name « ” должен ”
« t a rename « ” деньги" « endl:
++fi rst;
}
Эта программа выводит следующее:
Jeremy Jeremy Jeremy Jeremy Andrew AndrewJeremy Jeremy Jeremy Jeremy Andrew Andrewдолжен Rich деньги должен Andrew деньги должен Jeff деньги должен Kinis деньги должен Jeremy деньги должен Kinis деньги
Jeremy Jeremy Jeremy Jeremy Andrew Andrew
Jeremy Jeremy Jeremy Jeremy Andrew Andrew
Jeff должен Jeremy деньги Jeff должен Ri ch деньги Jeff должен Kinis деньги
Kinis должен Jeremy деньги Kinis должен Rich деньги
Полный исходный код этого примера можно найти в файле interior_proper- ty_map.cpp.
Настройка хранилища для списка смежности
Класс adjacency」1 st реализован с использованием двух видов контейнеров. Один из типов контейнеров содержит все веригины графа, а другой — список исходящих ребер (и, возможно, входящих) для каждой вершины. BGL предоставляет классы- селекторы для того, чтобы пользователь мог выбрать среди нескольких контей^ неров из STL. Также есть возможность для использования своего собственного контейнерного типа. При настройке VertexList вам необходимо определить генератор контейнера. При настройке EdgeLi 5t нужно определить генератор контейнера и свойства параллельных ребер. Файл container_gen.cpp является примером того, как использовать настройку хранилища.
Генератор контейнера
Класс adjacency_li st использует класс свойств, называемый contalner^gen, для отображения селекторов EdgeList и VertexLI st на реальные контейнерные типы, применяемые для хранения графа. Версия класса свойств по умолчанию приведена в листинге 14.7 вместе с примером того, как класс специализируе'гся для селеюю- pa lists.
Листинг 14.7. Версия класса свойств по умолчанию
namespace boost {
template <t у ре патле Selector, typenaine ValueType>
продолжение 衣
Листинг 14.7 {продолжений
truct centalпег^деп { };
template <typename Va】ueType>
truct container_gen<l1stS. ValueType> {
typedef std::Tlst<ValueType> type;
};
}
Для использования другого контейнера на ваш выбор определите класс-селектор и затем специализируйте containerjen для вашего селектора (листинг 14.8), Листинг 14.8. Версия класса свойств со специализацией для селектора
struct custom_conta1nerS { }: // ваш селектор
namespace boost {
" специализация для вашего селектора
template <typename ValueType〉
struct container_gen<custom containers. Va1ueT^pe> {
typedef custom_container<ValueType> type;
}:-
Могут возникнуть ситуации, когда вы хотите использовать контейнер» который имеет больше шаблонных параметров, чем прости ValueType. Напрнмер, вы можете захотеть представить тип распределителя памяти (allocator type). Один из способов сделать это — четко прописать в дополнительных параметрах в специализации contdiner_gen. Однако если вы хотите большей гибкости, то можно добавить ша6・ лонный параметр к классу-селектору. В следующем коде в листинге 14.9 показано, как создать селектор, который позволяет задать распределитель памяти в std: :11st.
Листинг 14.9. Версия класса свойств со специализацией для селектора и типа размещения
template <typename Allocator〉struct 11st_with_a11ocatorS {}; namespace boost {
template <typename Alloc, typename ValueType>
struct centalner_gen<list_with allocatorS<Alloc>. Va1ueType>
{''"
Vpedef typename Al 1 oc::tempiate_rebind<ValueT.ype>::other Allocator;
typedef std::11st<ValueType. Allocator^ type:
}:
}
〃 теперь вы можете определить граф, используя std::11st
〃 и специальное размещение
typedef adjacencyj1st< 1ist_with_a11ocatоrS< std::al locatorsnt> >(
vecS. directed$> MyGraph: ~
Свойства параллельных ребер
В дополнение к специализации класса container_gen можно также специализировать класс para 11 el_edge_traits для задания того, позволяет ли контейнерный тип иметь параллельные ребра (является Sequence) или не позволяет (является Associative- Container).
template <typename StorageSelector>
struct parallel_edge_tra11s { };
template <> struct parallel_edge_traits<vecS> {
typedef allow_pa rail el edgetag type;
};
template Q struct paraUe1_edge_traits<setS> { typedef disa11ow_para11 el_edge_tag type:
Контейнер списка ребер: функции push。и erase()
Необходимо указать adjacency_li st как ребра могут быть эффективно добавлены и удалены из контейнера списка ребер. Эго выполняется перегрузкой функций push() и eraseO для собственного контейнерного типа. Функция push() должна возвращать итератор» указывающий па только что вставленное ребро, и логический флаг, говорящий о том, было ли ребро вставлено. Если было задано allow_pa- railel_edge_tag дл兄 paral 1 el_edge_t「d1 ts, то push() должна всегда вставлять ребро и возвращать истину. Если было задано di sail ow_pa г а 11 el _edge_t a g. функция push() должна возвратить ложь и не вставлять ребро, если такое ребро уже есть в контейнере, и итератор должен указывать на уже существующее ребро.
Следующие функции — push() и eraseO (листинг 14.10) по умолчанию уже написаны для контейнеров из STL. Семейство перегруженных функций push__dis- pdtch() и erase_dispatch() обеспечивает различные способы вставки и удаления, которые могут быть выполнены для стандартных контейнеров*
Листинг 14.10. Функции push() и erase()
template <typename Container, typename T>
std::pair<typename Container::iterator, bool〉 push(Cbhtainer& c. const T& v) {
return push^dlspatch(c. v, centalner_category(c)):
}"
template <typename Container, typename T> void erase(Containers c, const T& x)
{
erase_dispatch(ct x. cental ner_category(c)): }
adjacency_matrix
adjacencyjnatrix<D1 rected. VertexProperty. Edgeproperty. GrdphProperty>
Класс ddjacency」nat ri x реализует интерфейс ВСЬ-1рафа, используя несколько раз* личных вариантов традиционной графовой структуры матрицы смежности. Для графа с |У| вершин используется матрица j V | х | V |, где каждый элемент 也炉 является логическим флагом, свидетельствующим о том, имеется ли ребро из вершины i в вершинуj. Представление графа в виде матрицы смежности покзано на рис. 14.4.
Преимуществом такого матричного формата над списком смежности является то, что ребра вставляются и убираются за постоянное время. Есть и несколько недостатков. Во-первых, объем используемой памяти имеет порядок 0( W) в^е сто О(|У| 十 |£ I) (где |Е| — число вершин 叩афа). Во-вторых, операции по всем нс* ходящим ребрам каждой вершины (как поиск в ширину) имеют временную сложность О(| V [2) в отличие от О(|Р| + |Е |) для списка смежности. Матрицу смежности лучше использовать с плотными фафами (где |Е| 与 |,产),а список смежности — с разреженными (где |£ | намного меньше |Р |")*
Рис. 14.4. Представление графа в виде матрицы смежностиРис. 14.4. Представление графа в виде матрицы смежностиОООО011О 1ОООООО1 О ООООО011О 1ОООООО1 О ОА В С D Е FА В С D Е FООО
Рис. 14.4. Представление графа в виде матрицы смежности
Рис. 14.4. Представление графа в виде матрицы смежности
ООО
О01
1О 1
ООО
ООО
1 О О
ООО
О01
1О 1
ООО
ООО
1 О О
А В С D Е F
А В С D Е F
О О 1
ООО
О 1 О 10 о ООО
Класс adjacency_matri х расширяет традиционную структуру данных, позволяя прикреплять объекты к вершинам и ребрам посредством параметров шаблонов свойств. Информацию по использованию внутренних свойств см. в разделе 3,6.
Рис. 14.5. Представление неориентированного графаРис. 14.5. Представление неориентированного графаВ случае неорнентированного графа класс adj acency_mat ri х использует не всю матрицу IУI х IVI,а только ее нижний треугольник (диагональ и ниже), поскольку матрица для неориентированного графа симметрична. Это сокращает расходы на хранение до (|V| х \ V |)/2. Представление неориентированного графа в виде мат- ри цы смежности показано на рис. 14.5.
Рис. 14.5. Представление неориентированного графа
Рис. 14.5. Представление неориентированного графа
Пример
В листинге 14.11 приведен пример построения графа, изображенного на рнс. 14.4, а в листинге 14.12 — изображенного на рис. 14.5.
Листинг 14.11. Создание графа (на рис. 14.4)
enum { А. В, & D* Е. F, N }:
const char* name = "ABCDEF";
вершин:вершин:typedef adjacency_matrix<directedS> Graph:
вершин:
вершин:
Graph g(N): add_edge(B.
C.
g)
add^edgeCC,
A.
g)
add^edgeCD.
E.
g)
ddd_edga(F.
A.
g)
add_£dge(B. F. add^edgeCC. C. add_edge{E, D,
std::cout « "набор
print_vertices(g. мне):
std::cout « std::endl:
std::cout « "набор ребер:":
print_edges(g( name):
std::cout « std::endl:
std::cout « иисходящие ребра: " « std::endl;
print_graph(g. name):
std::cout « std::endl:
Эта программа выводит следующее:
набор вершин: ABCDEF
набор ребер: (В.С) (ВЛ) (С.А) (С.0 (D.E) (E.D) (F.A)
исходящие ребра:
F с с А Е D АF с с А Е D АABCDEFABCDEF
F с с А Е D А
F с с А Е D А
ABCDEF
ABCDEF
Листинг 14.12. Создание графа (на рис. 14.5)
enum { А, В, J D, Е, F. N }: const char* name - "ABCDEF";
typedef adjacency^matri x<undi rectedS> UGraph; UGraph ug{N): add^edgeCB. C. ug): add二edge(B, F. ug); add_edge(C. A. ug); add^edgeCD, E, ug): add_edge(F, A. ug);
std::cout « "набор вершин;"; print_vertices(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;
Эта программа выводит следующее:
набор вершин: ABCDEF
набор ребер: (С.А) (С.В) (E.D) (F.A) (F.B)
инцидентные ребра:
А <--> С F
В < -> С F
С <--> А В
D <--> Е
Е <--> D
F <*-> А В
Где определен
Класс adjocency_matrix находится в boost/gгаph/adjacency_matrix.hрр.
Параметры шаблона
Ниже приведены параметры шаблона класса adjacency_(natr1x.
01rected
Селектор для выбора графа: ориентированны й или неориентированный. Соответствующие опции: di rectedS и und1 rectedS.
По умолчанию: di rectedS.
VertexProperty
Задает внутреннее хранилище свойств вершин.
По умолчанию: no_p「operty.
EdgeProperty
Задает внутреннее хранилище свойств ребер.
По умолчанию: noj^roperty.
Graphproperty
Задает внутреннее хранилище свойств графа.
По умолчанию: no_property.
Модель ДЛЯ
VertexListGraph, EdgeListGraph, IncidenceGraph, AdjacencyGraph, AdjacencyMatrix, Vertex- MutablePropertyGraph и EdgeMutabLePropertyGraph.
Требования к типам
Значение свойства должно быть DefaultConstructibLe и CopyConstructible.
Ассоциированные типы
Ниже приведены ассоциированные типы класса adjacency_niatrix.
graph_tralts<adjacency_matri х>::vertex_descriptor
Тип дескрипторов вершин, ассоциированных с матрицей смежности.
(Требуется для Graph.)
graph_trai ts<adj acency_matrix>::edge_descriptor
Тип дескрипторов ребер, ассоциированных с матрицей смежности.
(Требуется для Graph.)
graph__tга 1 ts<adjacenc^_matr 1 х>: :vertexjterator
Тип итераторов, возвращаемых функцией vertlcesO.
(Требуется для VertexListGraph.)
graph_traits<adjacency_matr1x>: :edgejterator Тип итераторов, возвращаемых функцией edges О.
(Требуется для EdgeListGraph.)
graph_traits<adjacency_niatrix>: :out_edge_iterator
Тип итераторов» возвращаемых функцией cut_edges().
(Требуется для IncidenceGraph.)
grdph_traits<adjacency.matrix〉: adjacency—iterator
Тип итераторов, возвращаемых функцией adjacent_vertices().
(Требуется для AdjacencyGraph.)
graph_traits<adjacency_matrlx>::directed_category
Предоставляет информацию о том, является граф ориентированным (direc- ted_tag) или неориентированным (undirected一tag).
(Требуется для Graph.)
graph_traits<adjacency_matrix>::edge_paга11 el ^category
Матрица смежности не позволяет вставлять параллельные ребра, так что тип всегда di sal 1 ow_pa rail el _edge_tag・
(Требуется для Graph.)
gra ph_t r a11s<a dj acency_mat rix>;:verti ces_si ze_type
Тип для работы с числом вершин в графе.
(Требуется для VertexListGraph.)
graph_tra1ts<adjacency_niatrlx>: :edges_s1ze_type
Тип для работы с числом ребер в графе.
(Требуется для EdgeListGraph.)
graph t га 1 ts<adjacency^mat ri x> : : degree^sl ze_type
Тип для работы с числом исходящих ребер в графе.
(Требуется для IncidenceGraph.)
property_map<adjacency_matriх. PropertyTag>::type propert,y_niap<adjacency_matr1 x. PropertyTag>: :const_type
Тип отображения для свойств вершины и ребра графа. Свойство задается аргументом шаблона PropertyTag и должно совпадать с одним из свойств, указанных в Vertexproperty или EdgeProperty графа.
(Требуется для PropertyGraph.)
Функции — методы
Ниже приведены функции — методы класса adjacencyjnatrlx.
adjacency_matriх(vertices_sizjtype n.
const GraphPropertyS p = GraphPropertyO)
Создает графовый объект с п вершин и без ребер.
template <typename Edgelterator>
adjacency_matrix(Edge Iterator first. EdgeIterator last. vertices_size_type n.
const GraphPropertyA p = GraphPropertyC))
Создает графовый объект с п вершин и ребрами, заданными списком ребер в виде диапазона [first, last). Тип значения итератора Edgeiterator должен быть std: :ратг, где тип внутри пары является целым типом. Целые числа соответствуют вершинам и должны находиться в диапазоне [0, п).
tempi ate <typename Edgeiterator, typename
EdgePropertyIterator>
adjacency_matrix(Edgeiterator first. Edgeiterator last. Edgepropertyiterator epjter. vertices_size_type n. const GraphProperty& p = GraphProperty?))
Создает графовый объект с л вершин и ребрами, заданными списком ребер в виде диапазона [first, last), и свойствами, заданными epjter в списке свойств ребер. Тип значения итератора Edgeiterator должен быть std: :pair, где тип внутри пары является целым типом. Целые числа соответствуют вершинам и должны находиться в диапазоне [0,也).Тип значения epjter должен быть EdgeProperty.
Функции — не методы
Ниже приведены функции — не методы класса edjacency_niatrix.
std: : pai r< vert ex_it era tor. vertexjterator>
vertlces(consOdjacency_matrix& g)
Возвращает пару итераторов, обеспечивающих доступ к множеству вершин графа д.
(Требуется для VertexListGraph.)
std::palr<edge_iterator. edge_iterator>
edges (const adjacency__matri x& g)
Возвращает пару итераторов, обеспечивающих доступ к набору ребер графа д, (Требуется для EdgeListGraph,)
std: :pair<adjacencyjterator. adjacencyjterator> adjacent_vertices(vertex_descriptor v.
const adjacency_matrix& g)
Возвращает пару итераторов, обеспечивающих доступ к множеству вершин, смежных с данной вершиной 口 графа д.
(Требуется для AdjacencyGraph,)
std::pa 1r<out_edgeJterator. out^edgeJterator> out_edges-(vertex_descriptor v. const adjacency_matnx& g)
Возвращает пару итераторов, обеспечивающих доступ к исходящим ребрам вершины 己 графа д. Если граф неориентированный, эти итераторы обеспечивают доступ ко всем ребрам, инцидентным вершине и (Требуется для IncidenceGraph.)
vertex_descriptor source(edge_descr1ptor e, const adjacency__matrix& g)
Возвращает начальную вершину ребра &
(Требуется для IncidenceGraph.)
vertex_descriptor target(edge_descrlptor e.
const adjscency^rnatrixS g)
Возвращает конечную вершину ребра е.
(Требуется для IncidenceGraph.)
degree_slze_type out^degгее(vertex__descriptor u.
const adjacency_iratrix& g)
Возвращает число ребер, исходящих из вершины и.
(Требуется для IncidenceGraph.)
vertices_size_type num_vertices(const 己djdcency_matrix& g)
Возвращает число вершин в графе д.
(Требуется для VertexListGraph.)
edges_size_type num_edges(const adjacency_rnatrix& g)
Возвращает число ребер в графе g.
(Требуется для EdgeListGraph,)
vertex descriptor_vertex(vert1ces_size_type n. const adjacency__matrix& g)
Возвращает 几-ю вершину в списке вершин графа.
std::ра1r<edge_descriptor. bool>
edge(vertex_descriptor u, vertex_descr1ptor v.
const adjacency__matrix& g)
Возвращает ребро, соединяющее вершину и с вершиной v в графе д.
(Требуется для AdjacencyMatrix.)
std::palr<edge_descriptor( boo】> add_edge(vertex_descriptor u, vertex_descriptor v. adjacency_matrlx& g)
Добавляет ребро (м,с) к графу и возвращает дескриптор ребра для нового ребра. Если ребро уже присутствует в графе, то повторно оно добавлено не будет и логический флаг будет ложью* Эта операция не «портит» итераторы и дескрипторы графа.
(Требуется для EdgeMutableGraph,)
std : : pai r<edge__descriptor. bool> add_edge(vertex_descriptor u. vertex_descriptor v.
const EdgeProperty& p. adjacency__matrlx& g)
Добавляет ребро (/ 己)к графу и присоединяет р как значение свойства ребра для внутреннего хранения (см, также предыдущую функцию-метод класса add_edgeC)). • void remove_edge(vertex_descriptor u. vertex_descnptor v.
adjacency_matrix& g)
Удаляет ребро (以,о) из графа.
(Требуется для EdgeMutableGraph,)
void remove_edge(edge^descriptor e. adjacency_matnx& g)
Удаляет ребро e из графа.
(Требуется для EdgeMutableGraph.)
void clear_vertex(vertex_descriptor u. ad:jacency_matrix& g)
Удаляет все ребра, исходящие и входящие, для вершины 以.Вершина остается во множестве вершин графа. Воздействие на корректность дескрипторов и итераторов такое же, как если бы для всех этих ребер отдельно вызывалас;» removjedgk).
(Требуется для EdgeMutableGraph.)
template <typename Property〉
property_map<adjacency__matrix. Property>::type
get(Property. adjacenc,_mdtrix& g)
Возвращает объект-отображение свойств, указанный с помощью Property (свойство). Свойство должно совпадать с одним мз свойств^ указанных в шаблонном аргументе Vertexproperty графа* (Требуется для PropertyGraph.)
template <typename Property〉
property_map<adjacency_mstrlx, Property〉::const_type
get(Property, const adjacency_matrix& g)
Возвращает объект-отображение свойств, указанный с помощью Property (свойство). Свойство должно совпадать с одним из свойств, указанных в шаблон ном аргументе Vertexproperty графа.
(Требуется для PropertyGraph.)
template <typenarne Property. typename X>
typename prope「ty_tra1ts< typenarne
property_map<adjacency_matrix. Property>::const_type
>::value_type
get(Property, const adjacency_matrix& g* X x)
Возвращает значение свойства для х, где х — дескриптор вершины или ребра. (Требуется для PropertyGraph.)
tempi ate <typename Property, typename X. typenaw Value〉 void put(Property, const adjacency_matrix& g, X x.
const Values value)
Устанавливает значение свойства для х в value, где х — дескриптор вершины или ребра. Значение value должно быть преобразуемым к тапу значения отображения свойства, указанного тегом Property.
(Требуется для PropertyGraph.)
template <typena(ne GraphProperties, typename GraphProperty>
typename property_value<GraphProperties.
GraphProperty>::type&
get_property(adjacencyjnatrix& g, GraphPropert.y):
Возвращает свойство, указанное Graph Property, которое было закреплено за графовым объектом д* Класс свойств property_value определен в файле boost/ pending/property, hpp.
template <typename Graphproperties, typend肥 GraphProperty>
const typename property_value<GraphPropert1es, GraphProperty>::type&
get_property(const adjacency_matrlx& g. GraphProperty):
Возвращает свойство, указанное Graph Property, которое закреплено за графовым объектом д. Класс свойств property_value определен в файле boost/pendi ng/ property, hpp.
14.2. Вспомогательные классы
graph_traits
graph jtraits<Graph>
Класс graph—tralts обеспечивает механизм для доступа к ассоциированным типам графового типа согласно определениям различных графовых концепций BGL (см, раздел 12Л). Когда вы хотите использовать один из ассоциированных типов графа, создайте экземпляр шаблона graph_traits с графовым типом и задайте соответствующий! дескриптор типа (typedef). Например, для получения типа дескриптора вершины для некоторого графа можно выполнить следующее:
template <typename Graph> void my_graph_algortthm(Graph& g) {
// Получить экземпляр graphtraits с графовым типом Graph.
typedef boost::graph3raits<Graph> Traits:
// Описание типа для доступа к ассоциированному типу.
typedef typename Traits::vertex_descriptor Vertex:
}
Неспециализированная версия (по умо/панию) шаблона класса graph_traits подразумевает, что графовый тип предоставляет составные дескрипторы типов для всех ассоциированных типов. Эта версия приведена в листинге 14Л 3.
Листинг 14.13« Неспециализированная версия класса graph一traits
namespace boost {
template <typename G> struct gsph_traits { 〃 итераторы;
vertex_descriptor: edge_descriptor; acijacenc>_iterator: out_edgejterator: m_edge_Tterator; vertex_iterato.r; edge_iterator:di rected_category: edgjpara】 lei _cmtegory:traversa1 category:vertices_$i zetype; edges_size_type: degree_si zetype:vertex_descriptor: edge_descriptor; acijacenc>_iterator: out_edgejterator: m_edge_Tterator; vertex_iterato.r; edge_iterator:di rected_category: edgjpara】 lei _cmtegory:traversa1 category:vertices_$i zetype; edges_size_type: degree_si zetype:typedef typename G: : vertex_tlescriptor typedef typename G::edge_descriptor typedef typename G::adjacencyJterator typedef typename G::out^edgeJterator typedef typena眦 G: :in_edgejterator typedef typename G::vertexjterator typedef typename G::edge_iterator // категории
vertex_descriptor: edge_descriptor; acijacenc>_iterator: out_edgejterator: m_edge_Tterator; vertex_iterato.r; edge_iterator:
di rected_category: edgjpara】 lei _cmtegory:
traversa1 category:
vertices_$i zetype; edges_size_type: degree_si zetype:
vertex_descriptor: edge_descriptor; acijacenc>_iterator: out_edgejterator: m_edge_Tterator; vertex_iterato.r; edge_iterator:
di rected_category: edgjpara】 lei _cmtegory:
traversa1 category:
vertices_$i zetype; edges_size_type: degree_si zetype:
typedef typename G::di rected_categDfy typedef typename G::edge_parallel_category typedef typename G::traversal^category // типы размеров
typedef typename G::vert1ces_$ize_type typedef typename G::edges_size_type typedef typename G::degree_s1ze_typ®
};
} // namespace boost
С другой стороны, graph_traits может быть специализирован по графовому типу. Например, следующий код специализирует graph_trants для структуры Grapf библиотеки Stanford Graph Base. Полностью оболочка для SGB-графов описана в заголовочном файле boost/graph/stanford_graph.hpp.
namespace boost { tempi ate о struct grapli_traits<Graph*> {
};
}
Если тип фафа является шаблоном класса, то класс graph_t^its х!ожет быть частично спешга.'Шзирован, Зто означает, что еще остаются некоторые «свободные» параметры. Ниже приведена частичная социализация graph_tra1ts для параметризованного типа GRAPH из LEDA. Полност1>ю интерфейс оболочки для этого типа находится в файле boost/graph/leda_graph.hpp,
namespace boost {
template <t>pename vtype. typename etype>
struct graph_traits< GRAPH<vtype,etype> > {
卜
}
Нм одна конкретная графовая концепция н也 требует, чтобы были определены все ассоциированныетипы. При реализации графового класса, который должен удовлетворять одной или более графовым концепциям, для ассоциированных типов, не затребованньпс этими концепциями^ мо*но использовать vo’d в качестве типа (когда используются составные дескрипторы типов внутри графового класса) или оставить typedef вне специализации graph^traits для этого графового класса.
Теги категорий
Категория di rect6d_category должна быть дескриптором одного из следующих двух типов.
namespace boost {
struct directed_tag { }:
struct undirected^tag { };
Категория edge__paral 1 &】.category должна быть дескриптором одного из следующих двух типов.
namespace boost {
struct а11ow_paга11 el_edge_ta9 {};
struct disalTow_paralTel_edge_tag {};
}---
Категория t raversa 1 ^category должна быть дескриптором одного из следующих типов или типа, который наследует от одного и^ этих классов.
namespace boost {
struct incidence_graph__tag { };
struct adjdcancy二graph二tag {,:
struct bidirect1onal_graph_tag :
public virtual incidence_graph_tag { }:
struct vertex_list_graph_tag :
public virtual 1ncidence_graph_tag,
public virtual adj acen c>_g rap h_t ag { };
struct edge_1ist_graph_tag { }:
struct vertex_and_edgeJ ist^graph^tag :
public virtual edge_Tist jraph_tag.
public virtual vertexjist_graph_tag { }:
struct adjacency^matrix^tag ( }:
}
Параметры шаблона
Ниже приведен параметр шаблона класса graph^trai ts.
GraphГрафовый Tim, модель для категории Graph.
Где определен
Класс graph_traits находится в файле boost/graph/graph_traits.hpp.
Методы
Ниже приведены методы класса graph^traits.
graph_traits::vertex^descriptor
Тип дескрипторов вершин^ ассоциированных с Graph.
graph_traits::edge_descriptor
Тип дескрипторов ребер, ассоциированных с Graph.
graph—tralts: : vertexjterator
Тип итераторов, возвращаемых функцией verticesO.
graph_traits: :edgejterator
Тип итераторов, возвращаемых функцией edges().
graph_traits::out_edge_iterator
Тип итераторов, возвращаемых функцией out__edges().
graph_traits::adjacency_iterator
Тип итераторов, возвращаемых функцией adjacent_vertices О.
graph_tra1ts::d1rected_category
Сообщает, является граф ориентированным или неориентированным.
graph—tra its:: edge^pa га 11 el_category
Сообщает, позволяет ли граф иметь параллельные ребра.
g га ph_t га Its: :traversal_category
Сообщает, какие виды обхода обеспечиваются графом*
graph^traits:: vert1ces_si ze^type
Беззнаковый целый тип, используемый для работы с количеством вершил в графе.
graph_traits: :edges_slze__typ^
Беззнаковый целый тип, используемый для работы с количеством ребер в п)афе.
graph_traits::degree_size_type
Беззнаковый целый тип» используемый для работы с кол ичеством исходящих ребер в графе.
adjacency_list„traits
adjacencyJ ist_trai ts<EdgeL 1st, VertexLI st, 01 rected>
Класс adjacency J i st_traits предоставляет альтернативный метод для доступа к некоторым ассоциированным типам класса adjacency_list. Главной причиной создайся этого класса является то, что иногда требуются свойства графа, значениями которых являются дескрипторы вершин или ребер. Если вы попытаетесь использовать для этого graph__traits, возникнет проблема с взаимно-рекурсивны - ми типами. Для решения этой проблемы предлагается класс adj a cency_l 1 st_t га its, который предоставляет пользователю доступ к тинам дескрипторов вершин н pt- бер, не требуя задания типов свойств графа.
tempi ate <typename EdgeList. typename VertexList. typename Di rected>
struct adjacency^ ist_traits {
typedef … vertex^descrlptor:
typedef … edge_descriptor; typedef -.. di rected category; typedef edge^paral1el_category:
Где определен
Класс adjacency J i st_trai ts находится в файле boost/graph/adjacency_list.hpp.
Параметры шаблона
Ниже приведены параметры шаблона класса adjacency_l 1st_tra1ts.
EdgeList
Тип селектора для реализации контейнера ребер.
По умолчанию: vecS.
VertexLIst
Тип селектора для реализации контейнера вершин.
По умолчанию: vecS.
Directed
Селектор для выбора ориентированного или неориентированного графа.
По умолчанию: di rectedS.
Модель ДЛЯ
DefaultConstructible и Assignable.
Методы
Ниже приведены методы класса adjacency_l 1 st__traits*
adjacency_l1st_tra1ts::vertex_descri ptor
Тип объектов, используемых для идентификации вершин графа.
adjacencyJ1st_t rai ts::edge_descrlptor
Тип объектов, используемых для идентификации ребер графа.
adj acencyJist^t rai ts : :dl rected^cdtegory
Сообщает, является ли граф неориентированным (undi rected_tag) или ориентированным (directad_tdg)・
adjacencyj i st_tfa^s: :edge_paral 1 el_category
Сообщает, позволяет ли граф добавлять параллельные ребра (allowjaral- lel_edge_tag) или автоматически удаляет их (di sа 11 ow_paга 11 el_edge_tag).
Смотри также
Дополнительная информация находится в описании класса adjacency_11st<
adjacency_matrix_traits
adjacencyjnatrlx_t гa i t s<D i ге cte d>
Класс adjacency_matr1 x_tra1t5 предоставляет шштернативный метод для доступа к некоторым ассоциированным типам класса adjacency_matrix. Главной причиной создания этого класса является то, что иногда требуются свойства графа, значениями которых являются дескрипторы вершив или ребер* Если вы попытаетесь использовать grdph traits для этого, возникнет проблема с взаимно-рекурсивными типами. Для решения этой проблемы предлагается класс adjacenc.y_niatr1x_t^3l ts, который предоставляет пользователю доступ к типам дескрипторов вершин и ребер, не требуя задания типов свойств графа.
template <Vpename Directed〉
struct adjacency__matrix_traits {
typedef vertex^descriptor:
typedef ... edge_descriptor;
typedef … directed^category; typedefedge_paral 1 el_category :
}:_
Где определен
adjacency_matr1x_tra1ts находится в файле boost/graph/adjacency_matrix.hpp.
Параметры шаблона
Ниже приведен параметр шаблона класса adj a cency__ma t г i x_t га i t s.
D1 rectedУказывает, является граф ориентированным
или неориентированным.
По умолчанию: di rectedS,
Модель ДЛЯ
DefauLtConstructibLe и Assignable.
Методы
Ниже приведены методы класса adjacency_matr 1 x_tга 11s.
adjacency_fnatrl x_j;rdits : :vertex_descrl ptor
Тип объектов, используемых для идентификации вершин графа.
adjacency_matri x^tral ts : :edge__descriptor
Тип объектов, используемых для идентификации ребер графа.
adj acency__mat гi x_t га i ts : :d1rected_category
Сообщает, является rpat}) неориентированным (und1rected_t&9)или ориентированным (directed__tag)*
ad jacency_matri x_tra1 ts : :edge_pa га 11 el ^category
Матрица смежности не позволяет добавлять параллельные ребра, нозто、1у здесь используется dlsallow_parallel_adgjtag.
Смотри также
Дополнительная информация находится в описании класса adjacency^matrix.
property_map
property』叩<Gr叩h. PropertyTag>
Класс property_map — класс свойств для доступа к типу отображения свойства, хранящемуся внутри графа. Специализация этого класса свойств требуется дл$; типов, которые моделируют концепцию PropertyGraph.
Пример
В следующем примере (листинг 14.14) создается граф с внутренним свойством для хранения имен вершин, а затем осуществляется доступ к типу свойств и мег вершин с помощью класса свойств property_map. Объект - отображение свойства получается из графа, с использованием функции get О.
Листинг 14.14. Пример внутреннего хранения имен вершин
< property-map4raits-eg.срр > 三
1 nclude <boost/config.hpp>
1 nclude <string>
indude <boost/graph/adj3cency_l ist.hpp>
int main() {
using namespace boost:
typedef adjacency_li$t < lists, lists, directedS.
property < vertex_name_t, std::string > >graph_t;
gr叩h_t g;
graph traits < graph_t >: :vertex_descriptor it = add_vertex(g);
property_map < graph). vertex_name_t >::type
name_map = get(vertex_naine, g);
name_map[u] = "Joe";
std: :cout « nanie^mapLu] « std: :end1:
return EXIT SUCCESS:
}
Профамма выводит следующее:
Joe
Где определен
Класс propertyjrap находится в файле boost/graph/properties.hpp.
Параметры шаблона
Ниже приведены паргшетры шаблона класса property_map.
GraphТип фафа, которьН* должен быть моделью
PropertyGraph.
PropertyTagТеговый класс для задания свойства.
Модель для
Нет.
Общедоступные базовые классы
Нет,
Ассоциированные типы
Ниже приведены ассоциированные типы класса property_map.
property_map<Graph т PropertyTag>::type
Тип изменяемого отображения свойства для доступа к внутреннему свойству, указанному PropertyTag.
property_(nap<Graph. PropertyTag〉; :con$t_type
Тип константного отображения свойства для доступа к внутреннему свойству, указанному PropertyTag.
Функции — методы класса
Нет.
Функции — не методы класса
Нет.
14.2.5. property
property〈PropertyTag. Т. NextProperty>
Класс property может использоваться с классами adjacency_l 1st и adjacency_ma - tr1x для указания видов свойств, закрепляемых за вершинами и ребрами графа и за самим графовым объектом.
Параметры шаблона
Ниже приведены параметры шаблона класса property.
PropertyTagТип для обозначения (придания уникально
го имени) свойству. Имеются несколько предопределенных тегов, и несложно определить новые. Для удобства BGL также предоставляет предопределенные объекты теговых типов (в данном случае — значений перечисления enum) для их использования в качестве аргументов функций, которые ожидают объекты теговых типов (например, как функции отображения свойства get() к adjacency_llst).
ТЭтот тип указывает тип значений свойств.
NextPropertyNextPropertyЭтот параметр позволяет типам свойств быть вложенными, так что с графом может быть связано любое количество свойств.
NextProperty
NextProperty
По умо/панию: nojroperty.
Где определен
Класс property находи лся в(|)айле boost/pending/property.hpp.
Теги свойств
Следующие теги свойств (листинг 14.15) определены в boost/graph/properties*hpp
Листинг 14.15. Определения тегов свойств
namespace boost {
〃 иня〃 вес"индекс〃 мощность〃 иня〃 вес"индекс〃 мощность// Теги свойств ребер:
〃 иня
〃 вес
"индекс
〃 мощность
〃 иня
〃 вес
"индекс
〃 мощность
enum enum enum enum епиптenum enum enum enum епиптedge_name_t { edge_name }: edge weight t { edge_weight }: edge_i ndexjt { edgeJndex }: edge_capacity_t { edge^capaclty }:
enum enum enum enum епипт
enum enum enum enum епипт
edge^residua1 _capacity__t { edge_residual_capacity }:
〃 остаточная мощность
обратноеимярасстояние до вершиныиндексцветстепеньобратноеимярасстояние до вершиныиндексцветстепеньenum edge_reverse_t { edge_reverse };〃
обратное
имя
расстояние до вершины
индекс
цвет
степень
обратное
имя
расстояние до вершины
индекс
цвет
степень
// Теги свойств вершин:
enum vertex_name_t { vertex^name };//
enum vertex_distance_t f vertex_distance }: 〃 enum vertex^!ndex_t { vertex^!ndex };//
enum vertex_color_t { vertex__color } ://
enum vertex_degree__t { vertex__degree }://
enum vertex_out_degree_t { vertex_out_degree }:
// степень по исходящим ребрам
enum vertex_i n_degree_t { vertex^in degree }: /7 степень по входящим ребрам
enum vertexjdlscoverj:inie_t { vertex_discover__tinie }:
// время посещения
enum vertex^flnlsh_time_t { vert^x_f1ni sh^tlme }:
// Тег свойства графа:enum graph_name_t { дгарй_пагпе };// имя// Тег свойства графа:enum graph_name_t { дгарй_пагпе };// имя// вреия окончания обработки
// Тег свойства графа:
enum graph_name_t { дгарй_пагпе };// имя
// Тег свойства графа:
enum graph_name_t { дгарй_пагпе };// имя
BOOST_INSTALL_PROPERTY(vertex, index);
BOOST~1NSTALLZPROPERTY(edge. i ndex): // ...
14.3. Графовые адаптеры
edge_list
edge_l 1 st<EdgeIteretor, ValueType. DiffType>
Класс edge_l i st — это адаптер, который превращает пару итераторов ребер в класс, моделирующий EdgeListGraph. Тип значения (vdlue_type) итератора ребер должен быть std:: pai г (или, по крайней мере, нметь методы fl rst (первый) и second
(второй)). Типы fir5t_type и second__type должны быть одинаковыми и использоваться в качестве дескриптора вершины графа. Типы шаблонных параметров ValueType и Di ffType нужны только в случае, если ваш компилятор не поддерживает частичную специализацию. В противном случае они имеют правильные значения по умолчанию.
Пример
См, раздел 5.3, где дан пример использования edge_l 1st.
Параметры шаблона
Ниже приведены параметра шаблона класса edge_11st.
Edge IteratorМодель для Inputiterator, чей value_type дол
жен быть парой дескрипторов вершин.
ValueTypeТип значения для Edgeiterator.
По умолчанию: std: :iterator_traits<Edgelte- rator>::value_type.
DI ffTypeТип разности для Edgeiterator.
По умолчанию: std ; : i terator_trsi ts<EdgeIte- rator>::difference_type.
Модель для
Класс edge_l 1 st поддерживается концепцией EdgeListGraph.
Где определен
Класс edge_l i st находится в файле boost/graph/edge_list.hpp.
Ассоциированные типы
Ниже приведены ассоциированные типы класса edgejlst.
graph^tralts<edge_l1st>::vertex_descri ptor
Тип дескрипторов вершин, ассоциированный с edgejlst- Это тот же самый тип, что и fjrst_typ2 для пары std:;pdir, являющейся типом значения для Edgelterator.
gга ph_t га its<edgejist>: :edge_descriptor
Тип дескрипторов ребер, ассоциированный с edge_list.
graph_tга 1ts<edge_l1st>::edge_i terator
Тип итераторов, возвращаемый функцией edges(). Категория iterato「_с己tegory для итератора ребер та же, что и у Edgeiterator.
Функции — методы
Ниже приведена функция — метода класса edge_l i st.
edgejist(EdgeIterator first. Edgeiterator last)
Создает графовый объект из п вершин с ребрами, заданными в списке ребер из диапазона [first, last).
Функции — не методы
Ниже приведены функции — не методы класса edge」1 st.
std::pai r<edge_iterator, edge_iterator>
edges(const edge_11st& g)
Возвращает пару итераторов, обеспечивающих доступ к набору ребер графа &*
vertex_descriptor source(edge_descriptor е, const edgej1st& g)
Возвращает начальную вершину ребра е.
vertex_descriptor target(edge^descriptor e. const edge_11st& g)
Возвращает конечную вершину ребра е.
reverse_graph
reverse一graphvBidi rectiondlGraph〉
Класс reversejraph меняет местами входяпще и исходящие ребра BidirectionaLGraph (двунаправленного) гр呻а, эффективно транспонируя граф. Построение обращенного графа выполняется за постоянное время, таким образом обеспечивая высоко^ □ффеюнвньп! способ для получения транспон и ро ванного представления фафа.
Пример
Пример в листинге 14,16 взят из файла examрLes/reverse-graph-eg*cpp.
Листинг 14.16. Обращение направлений ребер графа
typedef adjacencyj1 st < vecS, vecS. bi di rectionaIS > Graph;
Graph G(5}: add_edge(0, 2, G); add^edged. 4, G" add:edg鼠2. 4, G); add7edge(4. 0. G);Graph G(5}: add_edge(0, 2, G); add^edged. 4, G" add:edg鼠2. 4, G); add7edge(4. 0. G);add^edged. LG); ddd[edge(2, LG): ddd:edgu(3. LG); add二edge{4. LG);add^edged. LG); ddd[edge(2, LG): ddd:edgu(3. LG); add二edge{4. LG);add_edge(l. 3. G): add口dge(2, 3, G): add2edge(3, 4. G):add_edge(l. 3. G): add口dge(2, 3, G): add2edge(3, 4. G):
Graph G(5}: add_edge(0, 2, G); add^edged. 4, G" add:edg鼠2. 4, G); add7edge(4. 0. G);
Graph G(5}: add_edge(0, 2, G); add^edged. 4, G" add:edg鼠2. 4, G); add7edge(4. 0. G);
add^edged. LG); ddd[edge(2, LG): ddd:edgu(3. LG); add二edge{4. LG);
add^edged. LG); ddd[edge(2, LG): ddd:edgu(3. LG); add二edge{4. LG);
add_edge(l. 3. G): add口dge(2, 3, G): add2edge(3, 4. G):
add_edge(l. 3. G): add口dge(2, 3, G): add2edge(3, 4. G):
std: :cout « "исходный граф:11 « std: :end 1 ; p「int_grdph(G. gettvertexjndex. G));
std:.cout w std::endl « иобращенной граф:° « std;:endl: pr'int_graph(make_reverse_graph(G), get(vertex_index. G)):
Эта программа выводит следующее:
Исходный граф: 0 ■•> 2
1 -> 1 3 4
2 -> ] 3 4
3 —> 1 4
4 -> 0 1
обращенный граф:
О --> 4
1 --> 1 2 3 4
2 --> 0
3 > 12
4 —> 1 2 3
Параметры шаблона
Ниже приведен параметр шаблона класса reverse__graph.
Bi di「G「dphТип фафа, для которого строится адаптер.
Модель ДЛЯ
Класс reversejraph поддерживается концепциями BidirectionaLGraph и (необяза' тельно) VertexListGraph и Property Graph.
Где определен
Класс reverse_graph находится в файле boost/graph/reverse_graph.hpp.
Ассоциированные типы
Ниже приведены ассоциированные типы класса reverse_graph.
graph_traits<reverse_graph>::vertex_descriptor
Тип дескрипторов вершин, ассоциированных с обращенным графом.
(Требуется для Graph.)
graph_tra1ts<reverse_graph>: :edge__descriptor
Тип дескрипторов ребер, ассоциированных с обращенным графом.
(Требуется для Graph.)
graph_t^alts<reverse__graph>: :vertex_Herator
Тип итераторов, возвратцаемых функцией verticesO.
(Требуется для VertexListGraph.)
graph_tr3'>ts<reverse_graph>: :edge4terator
Тип итераторов, возвращаемых функцией edges (),
(Требуется для EdgeListGraph.)
graph__traits<rever$e_graph>: : out^edgejterator
Тип итераторов, возвращаемых функцией out_edge5().
(Требуется для IncidenceGraph.)
graph_tra1ts<reverse_graph>::adjacency_iterator
Тип итераторов, возвращаемых функцией adj3cent_vertices().
(Требуется для BidirectionalGraph.)
graph_traits<reverse_graph>::d1rected_category
Предоставляет информацию о том, является граф ориентированным или неориентированным.
(Требуется для Graph*)
graph_t rd i ts<reverse_graph>::edge_parallel^category
Сообщает, позволяет ли граф осуществлять вставку параллельных ребер (ре・ бер с одш гаковым и начальным к п оди каковыми конечными верш и нами )< Теги: al 1 ow__paга 11 el_edge_t^9 и d 1 sa 11 ow_para 11 el_edge__tag. Варианты графов с параметрами sets и hash_setS всегда используют di sail ow_paral lel_edge_tag, тогда как другие могут позволить включение параллельных ребер.
(Требуется для Graph.)
graph_t^1ts<reverse_graph>: :traversal_category
Категория обхода отражает, какие возможные виды итераторов поддерживаются графовым классом. Для rever5c_graph это будет тот же тип, что и для traversal category исходного графа.
(Требуется для Graph.)
graph_tга 1 ts<reverse_graph>: : vertices_si ze__typ^
Тип для работы с числом вершин в графе.
(Требуется для VertexListGraph.)
graph_traits<reverse__graph>: :edge_s1ze_type
Тип для работы с числом ребер в графе.
(Требуется для EdgeListGraph*)
graph_trai ts<reverse_graph>: :degree^slze_typ&
Тип для работы с числом ребер, инцидентных вершине в графе.
(Требуется для IncidenceGraph.)
property_map<reverse_graph. Property〉::type property_map<reversejraph * Property>: :const_type
Тип отображения свойств для свойств вершин и ребер графа. Конкретное свойство указывается шаблонным аргументом Property и должно совпадать с одним из свойств в VertexProperty или Edge Property графа.
(Требуется для PropertyGraph.)
Функции — методы
Ниже приведена функция — метод класса reverse graph.
reversejraph(B1d1rectionalGraph& g)
Конструктор. Создает обращенное (транспонированное) представление графа д.
Функции — не методы класса
Ниже приведены функции — не методы класса reverse_graph.
template <clas$ B1d1rect1onalGraph> reverse_graph<Bid1rectionalGraph> make_reverse__graph(BidirectionalGraph& g)
Вспомогательная функция для создания обращенного графа.
std::palr<vertex_1terator. vertex^!terator> vertices(const reverse_graph& g) ~
Возвращает пару итераторов, обеспечивающих доступ к множеству вершин графа д.
(Требуется для VertexListGraph.)
std::palr<out_edgeJterator. out_edge_iteratоr> out_edges(vertex^descriptor v. const reverse_graph& g)
Возвращает пару итераторов, обеспечивающих доступ к набору исходящих ребер вершгшы v графа д. Эти исходяшие ребра соответствуют входящим ребрам исходного графа.
(Требуется для IncidenceGraph.)
std::ра1r<in_edge_11е га tо г. 1n_edge_iterator>
1n_edges(vertex_descriptor v, const reverse_graph& g)
Возвращает пару итераторов, обеспечивающих доступ к набору входящих ребер вершины V графа д. Эти входящие ребра соответствуют исходящим ребрам исходного графа.
(Требуется для BidirectionalGraph.)
std::pa1r<adjacencyJterator. adjacency_iterator> adjacent_vertices(vertex^desemptor v,- const reversejraphSt gT
Возвращает пару итераторов, обеспечиваюпцтх доступ к смежным вершинам вершины V графа д.
(Требуется для AdjacencyGraph.).
vertex_descriptor source(edge^descr1ptor e.
const reverse_graph& g)
Возвращает начальную вершину ребра е.
(Требуется для IncidenceGraph.)
vertex_descriptor target(edge_descr1ptor e. const reverse_graph& g)
Возвращает конечную вершину ребра е.
(Требуется для IncidenceGraph.)
degree_size_type out_degree(vertex_descrlptor u. const reverse_graph& g)
Возвращает число ребер, исходящих из вершины и*
(Требуется для IncidenceGraph.)
degree_size_type i n_degгее(vertex_descriptor u. const reverse_graph& g)
Возвращает число ребер, входящих в вершину и. Операция доступна, только если был указан селектор bidirectionalS.
(Требуется для BidirectionalGraph).
vertIces_size^type num_vertices(const reverse_graph& g)
Возвращает число вершин в графе g.
(Требуется для VertexListGraph.)
vertex__descriptor vertex(vertices_size_type n, const reverse_graph& g)
Возвращает п-ю вершину в списке вершин графа.
std: :palr<edge_descr1 ptor, bool>
edge(vertex_descriptor u. vertex_descriptor v.
const reverse^raph& g)
Возвращает ребро, соединяющее вершину и с вершиной v.
(Требуется для AdjacencyMatrix.)
template <class Property〉
property_map<reverse_graph. Property〉::type
get (Property. reversejraphS g)
template <class Property>
property_niap<reverse_graph. Tag>: :constatype get(Property, const reverse_graphJ g)
Возвращает объект-отобраясение свойств, указанный с помощью Property. Свойство должно совпадать с одним нз свойств, указанных в шаблонном аргументе VertexProperty графа.
(Требуется для PropertyGraph.)
template <class Property, class X>
typename property_tra1ts<property_map<reverse_graph.
Property〉::const_type>::value_type
get (Property, const reverse__graph& g. X x)
Возвращает значение свойства для х, где х — дескриптор вершины или ребра.
template <class Property. Glass X. class Value> void put(Property, const reverse_graph& g. X x.
const Values value)
Устанавливает значение свойства для х в val не, где х — дескриптор вершины или ребра. Значение value должно быть преобразуемым к типу typename proper- ty_traits<propertyjnap<reversejraph. Property: :type>: :value_type
template <class GraphPropertles, class GraphProperty>
typename property_value<GraphPropert1es,
GraphProperty>:: type®
get__property(reverse_graph& g. GraphProper-y):
Возвращает свойство, указанное Graph Property, которое относится к графовому объекту. Класс свойств property^val ue определен в заголовочном файле boost/ pending/property.hpp.
terrplate <class Graphproperties, class GraphProperty>
const typename property_va1ue<GraphProperties f GraphProperty>::type&
get_property(const reverse_graph& g. GraphProperty):
Возвращает свойство, указанное Graphproperty, которое относится к графово* му объекту. Класс свойств property^val ие опредипен в заголовочном файле boost/ pending/property.hpp.
filtered_graph
filtered_graph<Graph. EdgePredic己te. VertexPredicate>
Класс f1ltered_graph является адаптером, который создает фильтрованное представление графа. Фун кция - предикат для ребер и вершин определяет, какие вершины и ребра исходного rpaipa показывать в фильтрованном графе. Любая вершина, для которой предикатная функция возвращает ложь» и любое ребро, для которого предикатная ф}^ткштя возвращает ложь, будут показываться удаленными в результирующем представлении графа. Класс filtered^graph не создает копии исходного графа, ио использует ссылки на него. Время жизни исходного грас|)а должно быть дольше любого использования фильтрованного
графа. Объект fl Her ed_g га ph не изменяет структуру исходного графа, хотя свойства исходного графа можно изменить с помощью отображения свойств фильтрованного графа.
Пример
Следующий функциональный объект, определяемый в листинге 14.17, является примером предиката, который отфильтровывает ребра с неположительным весом.
Листинг 14.17. Предикат, определяющий, имеет ли ребро положительный вес
tempi ate <typename EdgeWeightMap>
struct posltive_edge_weight { // положительный вес ребра?
pos.i t^ ve_edge2we 19htО { }
pos1tive_edge_weight(EdgeWeightMap weight) : m_weight(weight) { }
tempi ate <typename Edge>
bool operator()(const Edge& e) const ( return 0 < boost::get(m_welght. e):
)-
EdgeWei ghtMd p m_wei ght:
Пример в листинге 14,18 использует фильтрованный фаф с описанным выше предикатом pos 111 ve_edge_wei ght для создания фильтрованного представления небольшого графа. Ребра (Лт С), (С, Е) и (Е, С) имеют нулевой вес и потому не появляются в (фильтрован нo^f граете.
Листинг 14.18. Граф, фильтрованный с помощью предиката
typedef adjacencyjist<vecS. vecS. directedS, no_property* property<edge_weight_t( 1nt> > Graph:
typedef properV__map<Graph. edge_weTght_t>: ;type EdgeWe 1 ghtMap:
enum { А. В. C. 0. E. N };
const char* name - "AB雌";
Graph g(N);
add_edge(A, B.2.g);add_edge(A.C.0.g);
add_edge(C. D.Lg).add_edge(C.E.0.g);
add^edgeCD. B.3.g):add二 edge(E.C.0,g):
positlve_edge_weight<EdgeWeightMap> filter(get(edge_weight. g});
f11 tered_graph<Graph. pos11ive_edge_weight<EdgeWeightMap> >
fgtg. filter):
std.:cout « ^отфильтрованный набор ребер:":
pr1nt_edges(fg, name};
std::cout « "отфильтрованные исходящие ребра:" « std::endl;
print_graph(fg, name):
Эта программа выводит следующее:
отфильтрованный набор ребер: (А,В) (C,D) (D\B)
отфильтрованные исходящие ребра:
А —> В
В -->
С -> D
D —> В
Е
「〃е определен
Класс f11tered_graph находится в файле boost/graph/filtered_graph<hpp.
Параметры шаблона
Ниже приведены параметры шаблона класса filtered_graph.
Graph EdgePredicateGraph EdgePredicateVertexPredicateVertexPredicateГрафовый тип для адаптации.
Graph EdgePredicate
Graph EdgePredicate
VertexPredicate
VertexPredicate
Функционалъный объект, выбирающий, какие ребра исходного графа будут присутствовать в отфильтрованном графе. Должен быть моделью Predicate. Тип аргумента — тип дескриптора ребра графа. Также предикат должен быть DefaultConstructibLe
Функцион:1льный объект, выбирающий, ка кие вершины исходного графа будут присутствовать в отфил ьтрованном графе. Должен быть моделью Predicate. Тип аргумента — тил дескриптора вершины графа. Также предикат должен быть Defau It Со п st г u cti b Le.
По умолчанию: keep_all (сохранить все)
Модель для ■
Концепции, которые моделирует fl 1 tered_graph<Graph. ЕР. VP>T зависят от типа Graph. Если Graph моделирует VertexListGraph, EdgeListGraph, IncidenceGraph, Bidirec- tionaLGraph, AdjacencyGraph или PropertyGraph, то же самое делает и filtered_ grdph<G「aph,EP.VP>.
Ассоциированные типы
Ниже приведены ассоциированные типы класса filtered_graph.
graph__traits<filtered_graph>: : vertex_descrlptor
Тип дескриптора вершины, ассоциированный с fl 1 tered_graph.
(Требуется для Graph.)
graph_tf3lts<flltered_graph>::edge_descriptor
Тип дескриптора ребра, ассоциированный с f11tered_graph.
(Требуется для Graph.)
graph_t га 11s<f 111ered__graph> : : vertex_iterctor
Тип итераторов, возвращаемых функцией vertlcesO. Тип vertex J terator — тот же самый, что и для исходного графа.
(Требуется для VertexListGraph.)
graph_t^a1ts<fi1tered_graph>::edge_1terator
Тип итераторов, возвращаемых функцией edges。. Итератор моделирует концепцию Multi Passin putlterator.
(Требуется для EdgeListGraph.)
graph_trai ts<filtered_graph>::out_edge_iterator
Тип итераторов, возвращаемых функцией out_edges(). Итератор моделирует концепцию Multi Passinputiterator.
(Требуется для IncidenceGraph.)
graph_traits<f11tered_graph>::i n_edgej terator
Тип итераторов, возвращаемых функцией in_edges(). Итератор моделирует концепцию М u ItiPassin putlteratoг.
(Требуется для BidirectionalGraph.)
graph_traits<filtered_graph>: adjacency J terator
Тип итераторов, возвращаемых функцией adjacent_vert1ces(). Этот итератор моделирует ту же концепцию, что и итератор исходящих вершин.
(Требуется для AdjacencyGraph.)
g га ph_t га11s<f111ered_g raph>::directed_category
Сообщает, является граф неориентированным (und1rected_t^g) или ориентированным (directed^tag).
(Требуется для Graph.)
graph_tra1ts<f11 tered_graph>: : edge^parall el_category
Сообщает, позволяет ли граф осуществлять вставку параллельных ребер (ребер с одинаковыми начальными и одинаковыми конечными вершинами). Тот же самый, что и edge^paга 11 el ^category исходного графа.
(Требуется для Graph.)
graph_tra1ts<f11tered_graph>::vertices_s1zjtype
Используется для работы с количеством вершин в графе.
(Требуется для VertexListGraph.)
graph_tга its<fi 1 tered_graph> : : edges_size_type
Используется для работы с количеством ребер в графе.
(Требуется для EdgeListGraph,)
graph^tга 1 ts<fi 1 tered_graph> : idegree_s1ze_type
Используется для работы с количеством исходящих ребер вершины.
(Требуется для IncidenceGraph.)
property_map<fi1tered_graph. PropertyTag>::type property_mdpvfiltered_grdph. Property Tag〉: : constjype
Тип отображения свойств вершины или ребра в графе. Типы отображения в адаптированном графе те же, что и в исходном.
(Требуется для PropertyGraph.)
Функции — методы
Ниже приведены функции — методы класса fllteredjraph.
fl 1tered_graph(Graph& g. EdgePredicate ep)
Конструктор для представления графа g с отфильтрованным и ребрами на основании предиката ер*
fl 1tered_graph(Graph& g. EdgePredicate ер.
VertexPredicate vp)
Конструктор для отфильтрованного представления графа g на основании предиката для ребер - ерт и для вершин — vp.
Функции — не методы
Функциональность, поддерживаемая классом filtered_grapht зависит от лежащего в основе исходного графа. Например, если тип Graph не реализует in_edges(, именно это будет делать отфильтрованный граф. Ниже перечислены возможные функции, которые f11tered_graph может поддерживать, если задан тип Graph, являющийся моделью VertexListGraph, EdgeListGraph, IncidenceGraph, BidirectionalGraph. AdjacencyGraph, PropertyGraph и BidirectionalGraph.
std: :pair<vertexjteratofe vertex_iterator> vertices(const f11tered_graph& g)
Возвращает пару итераторов, обеспечивающих доступ к множеству вернш:1 графа д.
(Требуется для VertexListGraph.)
std: :pa1r<edgejterator. edgejterator> edges(const filtered_graph& g)
Возвращает пару итераторов, обеспечивающих доступ к набору ребер графа 】, (Требуется для EdgeListGraph.)
std::pair<ddjdcency—iterator, adjacency^terator> adjacent_vert1ces(vertex_descrlptor v.
const filtered_graph& g)
Возвращает пару итераторов, обеспечивающих доступ к смежным вершинам вершины V в графе д.
(Требуется для Adjacency Graph.)
std::palr<out_edge_iteratort out_edge_1terator> out_edges(vertex_descriptor v. const f11t5redjraph& g)
Возвращает пару итераторов, обеспечивающих доступ к исходящим ребрам вершины V в графе д. Если граф неориентированный, эти итераторы предоставляют доступ ко всем ребрам, инцидентным вершине v.
(Требуется для IncidenceGraph.)
vertex_descrlptor source(edge_descriptor e.
const filtered_graph& g)
Возвращает начальную вершину для ребра е.
(Требуется для IncidenceGraph.)
vertex^descrlptor t a rget(edge_d es с г1pto г e. const filteredjraph& g)
Возвращает конечную вершину для ребра е.
(Требуется для IncidenceGraph.)
degree_size_type out_degree(vertex_descrlptor u* const filtered_graph& g)
Возвращает число ребер, исходящих из вершины и.
(Требуется для IncidenceGraph.)
vertices_size_tyре num_vertices(const fi]ter6d_graph& g)
Возвращает число вершин в нижележащем графе д.
(Требуется для VertexListGraph.)
edges一sizjtype num_edges(const fiUered_graph& g)
Возвращает число ребер в графе g.
(Требуется для EdgeListGraph.)*
template <typename Property〉
property_rnap<fi 1 tered_graph. Property〉: : type
get(Property. f11tered_graph& g)
template <typename Property〉
property_map<f11 teredjr己ph. Property>::const^type
get (Property, const filtered__graph& g)
Возвращает объект-отображение свойств, заданный Property. Свойство Property должно совпадать с одним из свойств в шаблонном аргументе VertexPropert^ графа.
(Требуется для PropertyGraph.)
template <typename Property, typenarne X>
typename propert)/_traits<
typename property_map<fi 1 teredjraph.
Property〉::const_tyPe
>::value_type
get (Property, const f1lteredjraph& g. X x)
Возвращает значение свойства для х, где х — дескриптор вершины шш ребра.
(Требуется для PropertyGraph.)
template <typename Property, typename X, typename Value> void put(Property, const f11tered_graph& g. X x.
const Valued value)
Устанавливает значение свойства для х в value. Здесь х ——дескриптор вершины или ребра. Значение должно быть преобразуемо в тип значения указанного свойства.
(Требуется для PropertyGraph.)
Указатель на SGB Graph
Graph*
Заголовочный файл boost/graph/stanford_graph.hpp из BGL адаптирует Stanford GraphBase-указатель (SGB) [22] на Graph в граф, совместимый с BGL. Заметим, что класс графового адаптера не используется, a Graph* из SGB сам становится моделью нескольких графовых концепций (см. раздел «Модель для» ниже) с помощью определения нескольких перегруженных ф)гнкций.
Обязательно применяйте файл изменений PROTOTYPES к нашей установке SGB, с тем чтобы заголовочные файлы SGB соответствовали ANSI С (и, значит, могли ком пилироваться компилятором C++).
Мы благодарны Андреасу Шереру за помощь в реализации и документирова' кии адаптера Graph* из библиотеки SGB.
Пример
Примеры см. в файлах example/miLes„spanxpp, example/roget_components.cpp и example/girthxpp.
Параметры шаблона
Нет.
Модель для
VertexListGraph, IncidenceGraph, AdjacencyGraph и PropertyGraph. Набор тегов свойств, который можно использовать для SGB-графа, дан ниже в разделе «Свойства вершин и ребер».
Где определен
Указатель на SGB Graph находится в файле boost/graph/stanford_graph.hpp.
Ассоциированные типы
Ниже приведены ассоциированные типы указателя на SGB Graph.
gra.ph_tra 1 ts<Graph*>: : vertex_descr 1 ptor
Тип дескриптора вершин, ассоциированный с SGB Graph* Мы используем тип Vertex* в качестве дескриптора вершины (где Vertex — typedef в заголовочном файле gb_graph.h.) (Требуется для Graph.)
graph_tra1ts<Graph*>::edge_descri ptor
Тип дескриптора ребер, ассоциированный с SGB Graph* Используется тип boost:: sgb_edge_typs* В дополнение к поддержке всех требуемых в BGL операций дескриптора вершины класс boost : :sgb_mdge имеет следующий конструктор: sgb^edge::sgb_edge(Arc* arc. Vertex* source).
(Требуется для IncidenceGraph.)
graph_traits<Graph*>::vertexJterator
Тип итераторов, возвращаемых функцией vertices。* Этот итератор должен моделировать Random Accessiterator.
(Требуется для VertexListGraph.)
graph_tra1ts<Graph*>: : out_edge_i te га to г
Тип итераторов, возвращаемых функцией out_edges(). Если Edged st^vecS» этот итератор моделирует Multi Passinputiterator.
(Требуется для IncidenceGraph.)
gra ph_t га 1ts<Graph*>::adjacencyJ terator
Тип итераторов, возвращаемых функцией adjacent_vert1ces(). Этот итератор моделирует ту же концепцию, что и out_edgejterator.
(Требуется для AdjacencyGraph.)
graph^tгаits<Graph*>::directed_category
Предоставляет информацию о том, является граф ориентированным или неориентированным. Так как SGB Graph* — ориентированный, этот тип — di rected_ta,;J* (Требуется для Graph.)
graph」га i ts<Graph*>:: edge_pa r al 1 el .category
Описывает возможности графа по вставке параллельных ребер (ребер с однна- ковммм нач^ьными и конечными вершинами). Graph* из SGB не препятствует добавлению параллельных ребер, поэтому этот тип имеет al 1 owjDarall el_edge_tag. (Требуется для Graph.)
graph_tга 1 ts<Graph*> : : traverse ^category
Graph* из SGB обеспечивает обход множества вершин, исходящих ребер и смежных вершин. Таким образом, тег категории обхода определен следующим образом:
struct sgb_tгаversal_tag :
public'virtual vertexJ i st_graph_tag, public virtual 1ncidence_graph_tag. public virtual adjacency二graph二tag { };
(Требуется для Graph.)
g ra ph_t га 11 s<Gra ph*> :: vertl ces^sl zetype
Тип используется для работы с количеством вершин в графе.
(Требуется для VertexListGraph.)
g га ph_t га 11 s <G гаph*> : : edges_s i ze_type
Тип используется для работы с количеством ребер в графе.
(Требуется для EdgeListGraph.)
graph_traits<Graph*>: :degree_size_type
Тип используется для работы с количеством исходящих ребер вершины.
(Требуется для IncidenceGraph.)
property_map<Graph*. PropertyTag〉::type property_map<Graph*. PropertyTag>::const_type
Тип отображения для свойств вершин и ребер графа. Свойство задается шаблонным аргументом PropertyTag и должно быть одним из тегов, описанных ниже в разделе «Свойства вершин и ребер».
(Требуется для PropertyGraph.)
Функции — методы
Указатель на SGB Graph функций — методов класс не имеет.
Функции — не методы
Ниже приведены функции — не методы указателя на SGB Graph.
std: :palr<vertex_iterator. vertexjterator>
vert1ces(const~Graph* g)
Возвращает лару итераторов, обеспечивающих доступ к набору вершин графа д. (Требуется для VertexListGraph.)
std: :pa 1r<edge__1 terator. edge_1terator> edges(const Graph* g)
Возвращает пару итераторов, обеспечивающих доступ к набору ребер д.
(Требуется для EdgeListGraph.)
std: :pair<adjacencyjterator. adjacencyjterator> adjacent_vertices(vertex__descriptor v. const Graph* g)
Возвращает пару итераторов, обеспечивающих доступ к вершинам, смежным с вершиной V в графе д.
(Требуется для AdjacencyGraph.)
std: :pair<out_edge_1terator. out__edgejterator> out edges(vertex_descriptor v. const Graph* g)
Возвращает пару итераторов, обеспечивающих доступ к исходящим ребрам вершины и в графе д. Если гра@)неориентированный, эти значения дают пестун ко всем ребрам, инцидентным данной вершине v.
(Требуется для IncidenceGraph.)
vertex_descriptor source(edge_dsscriptor e.
const Graph* g)
Возвращает начальную вершину ребра е.
(Требуется для IncidenceGraph.)
vertex__descrlptor target(edge_descriptor e, const Graph* g)
Возвращает конечную вершину ребра е.
(Требуется для IncidenceGraph.)
degree^si ze type out_degree(vertex_cfescri Dtor u, const Graph* g)
Возвращает число ребер, исходящих из вершины и.
(Требуется для IncidenceGraph.)
vertlces__size_typ^ num_vertices(const GraDh* g)
Возвращает число вершин в rpa(J>e g.
(Требуется для VertexListGraph.)
edges_size__tyре num_edges(const Graph* g)
Возвращает число ребер в графе g.
(Требуется для EdgeListGraph.)
vertex^descriptor vertex(vertices_size^pe n. const Graph* g)
Возвращает 小ю вершину в списке вершин графа.
template <typename PropertyTag>
property_ma p<Graph*. PropertyTag>::type get(PropertyTag. Graph* g)
tempi ate <typename PropertyTag>
propert^_ma p<Gra ph*. PropertyTag>::const_type
get(PropertyTag. const Graph* g)
Возвращает объект-отображение свойствт заданный PropertyTag.
(Требуется для PropertyGraph.)
template <typename PropertyTag, typename X> typename property_traits<
typename property_map<Graph*. PropertyTag>::const_type
>::value_type
get(PropertyTag. const Graph* g. X x)
Возвращает значение свойства для х, где х — дескриптор вершины млн ребра.
(Требуется для PropertyGraph.)
tempi ate〈typename PropertyTag, typename X. t^pename Value> void putCPropertyTag, const Graph* g. X xr const Values value)
Устанавливает значение свойства для х в value. Здесь х — дескриптор вершины или ребра. Значение должно быть преобразуемо в тип значения свойства, соответствующий PropertyTag, (Требуется для PropertyGraph.)
Свойства вершин и ребер
Структуры Vertex и Аге из SGB предоставляют вспомогательные поля для храпе* гшя дополнительной информации. Мы предлагаем BGL-оболочки, которые обеспечивают доступ к этим полям через отображения свойств. Кроме того, предоставлены отображения индекса вершины и Д7ип1Ы ребра. Объект-отображение свойств может быть получен из SGB Graph* применением функции get(), описанной в предыдущем разделе, а тип отображения свойства — через класс свойств property^map.
У казанные ниже тега свойств могут быть использованы для задания вспомогательного поля, для которого требуется отображение свойства (листинг 14.19).
Листинг 14.19. Теги свойств для SGB Graph*
// Теги свойств вершин:
template <typename Т> u_pгорегty;
empldte <typename Т> v_5roperty:
template <typename T> w_property:
template <typename T> x^property; tempi ate <typename T> у二property: template <typename T> z^property:
// Теги свойств ребер:
ernpl ate <typename T> a_property ;
template <typename T> 匸property;
Шаблонный параметр T для этих тегов ограничен типами в объединении util, декларированном в заголовочном файле gb_graph.h библиотеки SGB. Перечислим эти типы: Vertex*, Arc*, Graph*, chaг* и long. Отображения свойств для вспомогательных полей являются моделям и LvaluePropertyMap.
Отображение свойств для индексов вершин может быть получено с помощью тега vertex_i ndex_t и это отображение свойств моделирует ReadabLePropertyMap. Отображение свойств для длин ребер указывается тегом edge_lcngth_t, н это отображение свойств ——модель Ь/aLuePropertyMap, чей тип значения — long.
GRAPH<4E> из библиотеки LEDA
GRAPH<V.E>
Шаблон класса GRAPH из LEDA может быть напрямую использован как BGL- граф благодаря перегружен11ым функциям, определенным в заголовочном файле boost/gra р h/Leda_graph. hpp.
Реализация BGL-интерфейсадля класса GRAPH из LEDA обсуждалась в разделе 10.3 при написании адаптеров для графовых классов из сторонних графовых библиотек.
Пример
В листинге 14.20 приведен пример работы с LEDA графом, как если бы это бы.г BGL-граф.
Листинг 14.20. Работа с графом LEDA
#iпс 1 ude <boost/graph/1 eda_graph. hpp>
#include <iostream>
#undcf string // макрос из LEDA
int mainO
{
using namespace boost:
typedef GRAPH < std::string, int >gr叩h_t;
graph t g:
g. new_node(''Phil octetes");
g.new二node("Heracles”):
g.new二node(HAlcmena"):
g.new^nodeCEurystheus"):
g.new_node(,,AniphitryonM):
typedef property』叩 < g「dph_t. vertex_all_t >::type NodeMap;
NodeMap node_name_inap * get(vertex_all7 g):
graph_traits"< graph_t >::vertexjterator vi, vifid:
for (tietvi t vi_end)二 vertices(g): vi H vi_end; ++vi)
std: :cout « node_name_map[*vi] « std;:endl;
return EXITSUCCESS:
}-
Эта программа выводит следующее:
Phil octetes
Heracles
Alcmena
Eurystheus
Amphitryon
Параметры шаблона
Ниже приведены параметры шаблона класса GRAPH.
VТип объекта, прикрепленного к каждой вер
шине в графе LEDA.
ЕТип объею а, прикрепленного к каждому ре
бру в графе LEDA.
Модель ДЛЯ
VertexListGraph, BidirectionalGraph и AdjacencyGraph. Также VertexMutabLePropertyGraph и EdgeMutablePropertyGraph для тегов свойств vertexes! 1 _t н edge_а11_t, которые обеспечивают доступ к объектам V и Е в графе LEDA. Тип GRAPH также является PropertyGraph для vertex^i ndex_t и edge_1ndex_t, которьп! предоставляет доступ к идентификационным (ID) номерам, которые LEDA присваивает каждому узлу (вершине).
Где определен
Класс GRAPH находится в файле boost/graph/Ledd_graph.hpp.
Ассоциированные типы
Ниже приведены ассоциированные типы класса GRAPH.
gra ph_t га11 s <GRAPH>::vertex_descriptor
Тип дескрипторов вершин, ассоциированный с GRAPH. Используется тип node из LEDA.
(Требуется для Graph.)
gга ph_t га 1 ts<GRAPH> :: edgjdesc门 ptor
Тип дескрипторов ребер, ассоциированный с GRAPH. Используется тип edge из LEDA.
(Требуется для Graph.)
gra ph_t га 11 s<GRAPH> : :vertex_i terator
Тип итераторов, возвращаемых функцией vertlces().
(Требуется для VertexListGraph.)
g「dph_traitsvGRAPH>: :out_edge_ite「dtor
Тип итераторов, возвращаемых функцией out__edges().
(Требуется для IncidenceGraph.)
graph—trai ts<GRAPH>: :in_edge_1 terator
Тип итераторов, возвращаемых функцией 1n_edges().
(Требуется для BidirectionaLGraph.)
grapli_tra1ts<GRAPH> : : adj acencyJ terator
Тип итераторов, возвращаемых функцией adjacent_vert1 ces ().
(Требуется для AdjacencyGraph.)
graph_tra1ts<GRAPH>::directed_category
Тип GRAPH из LEDA — для ориентированных графов, поэтому здесь используется directed_tag.
(Требуется для Graph*)
gra ph_t г а 11 s <GRAPH> : : edge_pa ra 11 el _ca tego ry
Тип GRAPH из LEDA позволяет добавление параллельных ребер, поэтому здесь используется allow__paraile1_edge_tdg.
(Требуется для Graph.)
g「aph_traits<GRAPHA: :traversal_category
Описываемый тип графа представляет итераторы вершин, исходящих н входящих ребер, итераторы смежности. Тип тега категории обхода следующий: struct leda_graph_tгаversa1_category :
public virtual bi di recti onal_graph_tag.
public virtual ddjdCRauy_grdph_tmg, public virtual vertexJist_graph_tag ( }:
(Требуется для Graph.)
graph__tга 1 ts<GRAPH> : : verti ces_si ze_type
Тип используется для представления числа вершин графа, а эго in匸 (Требуется для VertexListGraph.)
gra ph_tга11 s<GRAPH>::edges_size_type
Тип используется для представления числа ребер графа, а это тоже int.
(Требуется для EdgeListGraph.)
g га ph_t га11 s<GRAPH>::degree_size_type
Тип используется для представления числа исходящих ребер графа — это Int, (Требуется для IncidenceGraph.)
рroperty_ma p<GRAPH. PropertyTag>::type property__map<GRAPH. PropertyTag>: :const_type
Тип отображения для свойств вершин и ребер в графе. Конкретное свойство задается шаблонным аргументом PropertyTag и должно быть одним из следующих: vertex_1ndex_ti edgejndex^, vertex_all_t или edge__all_t. Теги с eall» но пользуются для доступа к объектам V п Е графа LEDA. Теги vertex_1 ndex_t и edge_i ndex_t обеспечивают доступ к идентификационным номерам, которые LEDA присваивает каждому узлу и ребру.
(Требуется для PropertyGraph.)
Функции — методы
У класса GRAPH нет дополнительных функций — методов (так как это потребовала бы модификации исходного кода LEDA)
Функции — не методы
Ниже приведены функции — не методы класса GRAPH*
std::pair<vertex_iterator. vertex_iterator>
vertices(const~GRAPH& g)
Возвращает пару итераторов, обеспечивающих доступ к множеству верш ни графа д.
(Требуется для VertexListGraph.)
std: :pair<edge_iterator. edgejteratoo edges(const adjacency_matrix& g)
Возвращает пару итераторов, обеспечивающих доступ к набору ребер фафа g (Требуется для EdgeListGraph.)
std::pair<adjacency_iterator. adjacencyJterator> adjacent_vertices(vertex_descriptor v.- const adjacency_rnatrix& g)
Возвращает пару итераторов, обеспечиваюших доступ к множеству вершин, смежных с данной вершиной v графа д.
(Требуется для AdjacencyGraph.)
std::palr<out__edge_iterator. out_edgejterator> out^edges(vertex^descriptor v. const GRAPH& g)
Возвращает пару итераторов, обеспеч ивающпх доступ к исходящим ребрам вершины V в графе д. Если граф неориентированный, эти значения дают доступ ко всем ребрам, инцидентным вершине 匕
(Требуется для IncidenceGraph.)
std::pa1r<in_edge_iterator. in_edge_iterator>
1 n^edges(vertex_descriptor v. consCGRAPH& g)
Возвращает пару итераторов, обеспечивающих доступ к входящим ребрам вершины V в графе д. Операция недоступна, если в качестве шаблонного параметра Di rected было указано di rectedS, и доступна при undi rectedS и bi di recti ona IS. (1'ребуется для BidirectionaLGraph.)
vertex_descriptor source(edge_descriptor e. const GRAPH& g)
Возвращает начальную вершину ребра е.
(Требуется для IncidenceGraph.)
vertex_descriptor target(edge_descriptor e. const GRAPH& g)
Возвращает конечную вершину ребра е.
(Требуется для IncidenceGraph.)
degree^size_tyре out_degree(vertex_descriptor u. const GRAPH& g)
Возвращает число ребер, исходящих из вершины и.
(Требуется для IncidenceGraph.)
degree_size_type 1n_degree(vertex^descriptor и. const GRAPHS g)
Возвращает число ребер, входящих в вершину и. Эта операция доступна, только если bid1 rectionaIS было указано в качестве шаблонного параметра D1 rected. (Требуется для BidirectionaLGraph.)
vertices_size_type num^verticesCconst GRAPH& g)
Возвращает число вершин в графе д.
(Требуется для VertexListGraph.)
edges_size_type num_edges(const GRAPH& g)
Возвращает число ребер в графе д.
(Требуется для EdgeListGraph.)
std::pa 1r<edge_descriptor. boo1> ddd_edge(vertex_descriptor u. vertex descriptor v.
GRAPHS g)
Добавляет ребро (w, и) к графу и возвращает дескриптор ребра ^1ля вновь добавленного ребра. Для этого графового типа логический флаг всегда будет ложным. (Требуется для EdgeMutableGraph.)
std::pa i r<edge_descri ptor. bool>
add_edge(vertexudescriptor u. vertex_descrlptor v.
const E& ep, GRAPHS g)
Добавляет ребро (〃, 口) к графу и закрепляет за ним ер в качестве значения для хранения внутреннего свойства.
(Требуется для EdgeMutablePropertyGraph.)
void remove_edge(vertex descrlptor u. vertex_descriptor v,
GRAPH& g)-
Удаляет ребро (к,軍)из графа.
(Требуется для EdgeMutabLeGraph.)
void remove_edge(edge_descriptor е. GRAPHS g)
Удаляет ребро (и, г) из графа. Функция отличается от remove_edge (u. v, g) в слу - чае мультиграфа: данная функция удаляет одно ребро, тогда как функция ге- move_edge(u. v* g) удаляет все ребра (w,己).
(Требуется для EdgeMutableGraph.)
vertex__descriptor add_vertex(GRAPH& g)
Добавляет вершину к графу и возвращает дескриптор вершины для новой вершины*
(Требуется для VertexMutableGraph.)
vertex_descriptor add vertexCconst VertexPropertiesS p, GRAPHS g)
Добавляет вершину (и ее свойство) к графу и возвращает дескриптор вершины для новой вершины.
(Требуется для VertexM u table Property Graph.)
void clear_vertex(vertex_descriptor и, GRAPH& g)
Удаляет все ребра, исходящие и входящие, для данной вершины. Вершина остается во множестве вершин графа.
(Требуется для EdgeMutabLeGraph.)
void remove_vertex(vertex_descriptor u. GRAPH& g)
Удаляет вершину и из множества вершин графа.
(Требуется для VertexMutableGraph.)
template <typename PropertyTag> property_map<GRAPH. PropertyTag>::type get(PropertyTag, GRAPH& g)
Возвращает изменяемый объект-отображение свойств для свойства вершины, указанной с помощью PropertyTag.
(Требуется для PropertyGraph.)
tempi ate <typename PropertyTag>
property_map<GRAPH. PropertyTag>::const_type get(PropertyTag. const GRAPH& g)
Возвращает константный объект-отображение свойств для свойства вершины, указанной с помощью PropertyTag.
(Требуется для PropertyGraph.)
template <typename PropertyTag* typename X>
typename property_traits<
typename property_niap<GRAPH. PropertyTag>: :const_type
>::value_type
get(PropertyTdg. const GRAPH& g, X x)
Возвращает значение свойства для х, где х — дескриптор вершины шш ребра. (Требуется для PropertyGraph.)
template <typename PropertyTag, typename X. typename Value>
void putCPropertyTag. const GRAPH& g, X x, const Valued value)
Устанавливает значение свойства для х в value. Здесь х — дескриптор вершины или ребра*
(Требуется для PropertyGraph.)
14.3.6. std::vector<EdgeList〉
std:: vector<EdgeL1 st>
Перегрузка функций в boost/graph/vector_as_graph.hpp делает возможным трактовать тип вроде std: :vector<std: :list<int» как граф.
Пример
В этом примере (листинг 14.21) мы создаем граф, используя контейнерные классы из стандартной библиотеки и применяя функцию pr1 nt_graph() из BGL (которая написана в терминах графового интерфейса BGL) для вывода графа.
Листинг 14.21. Вектор в качестве графа
(vector-as-graph.ерр }直
#include <vector>
1 nclude <11st>
i nclude <boos t/g ra ph/vec t о r_a s_g ra ph.hpp>
include <boost/graph/graph-utiTlty<hpp>
int main() {
enum { r. s. t, u. v. % x. y, N }:
char name[] = "rstuvwxy”;
typedef std::vector < std::11 st < int > > Graph:
Graph g(N);
g[r].push_back(v): g[s].push__back(r): g[s].push_back(r): g[s].push-back(w); g[t].push-back(x): g[uLpush-back(t): g[w] .push_bdck(t): g[wLpush_back(x) ; g[x] .push_back(y); gLy3-push-back(u);
boost::print_graph(g. name);
return 0:
}
Эта программа выводит следующее:
Г --> V
s --> Г Г W
u t
W --> t X
x…У у —> u
Где определен
Тип vector_as_graph находится в файле boost/graph/vector_as_graph.hpp.
Параметры шаблона
Ниже приведен параметр шаблона типа vector_as_graph.
EdgeLi stContainer, в котором val uejype позволяем' пре
образование к si ze_type для std :: vector (чтобы значения можно было использовать в качестве дескриптора вершины).
Модель ДЛЯ
VertexListGraph, IncidenceGraph и AdjacencyGraph.
Ассоциированные типы
Ниже приведены ассоциированные типы vector_as__graph.
graph_traits<std::vector>::vertex_descriptDr
Тип дескрипторов вершин, ассоциированных с фафом.
(Требуется для Graph.)
gra ph_t г a i 15 <st d :: vecto r> : : edge_des crlptor
Тип дескрипторов ребер, ассоциированных с графом*
(Требуется для Graph.)
graph_traits<std: :vector>: :vertexjterator
Тип итераторов, возвращаемых функцией vertlcesO, (Требуется для VertexListGraph.)
graph_tr^'its<std: :vector>: :out_edg.ejterator
Тип итераторов, возвращаемых функцией out_edges ().
(Требуется для IncidenceGraph,)
graph_trdits《std::vector>::adjacency_1terator
Тил итераторов, возвращаемых функцией adjdcent_verti ces().
(Требуется для AdjacencyGraph,)
graph_traits<std::vector>::di rected_category
Графовый тип для ориентированных графов, поэтому здесь используется di ■ rected_tag.
(Требуется для Graph.)
graph_traits<stc!: :vector>: ;edge^paга 11 el_category
Этот графовый тип позволяет иметь параллельные ребра, поэтому тип катего* рии — al 1 owjarall el_edg jtag.
(Требуется для Graph.)
graph_traits<std: :vector>: : vertices_s1ze__type
Тип, используемый для представления количества вершин в графе.
(Требуется для VertexListGraph.)
graph__traits<std: :vector>: :degree_size_type
Тип, используемый для представления количества исходящих ребер для вершины графа.
(Требуется для IncidenceGraph.)
Функции — методы
Нет дополнительных функций - методов класса.
Функции — не методы
Ниже приведены функции — не методы класса vector_ds_graph.
std::palr<vertex_1terator. vertexJterator> vertices(const stid::vectors g)
Возвращает пару итераторов, обеспечившопшх доступ к множеству вершнн графа д. (Требуется для VertexListGraph.)
std::palr<adjacencyJterator.
adjacency^iterator>-adjacent_vertices(vertex__descriptor v.
const std::vector& g)
Возвращает пару итераторов, обеспечивающих доступ к вершинам, смежным вершине v в графе д.
(Требуется для AdjacencyGraph.)
std::palr<out_edgejterator, out_edge_1terator> out^edges(vertex^descr1ptor v. const std::vectors g)
Возвращает пару итераторов, обеспечивающих доступ к исходящим ребрам вершины V в графе д. Если граф неориентированный» эти значения дают доступ ко всем ребрам, инцидентным вершине v.
(Требуется для IncidenceGraph.)
vertex^descrlptor source(edge^descriptor e. const std::vector& g)
Возвращает начальную вершину для ребра е.
(Требуется для IncidenceGraph.)
vertex^descriptor target(edge__descriptor e. const std::vector& g)
Возвращает конечную вершину для ребра е.
(Требуется для IncidenceGraph.)
degree_size_type out_deg гее (ve rt ex__des cr 1 pt о r u.
const std7:vector&~g)
Возвращает число ребер, исходящих из вершины и*
(Требуется для IncidenceGraph.)
vertices__size_type num_vertices(const std: :vector& g)
Возвращает число вершин в графе g.
(Требуется для VertexListGraph.)
10 Зак 375
Библиотека отображений свойств
Большинство алгоритмов на графах требуют доступа к различным свойствам, относящимся к вершинам и ребрам графа. Например, такие данные, как длина или мощность ребра, могут быть необходимы для алгоритмов, равно как и вспомогательные флаги, такие как цвет, для индикации посещения вершины. Существует много способов хранить эти данные в памяти: от полей данных объектов-верши н н объектов^ребер до массивов, индексированных некоторым индексом, или свойств, вычисляемых при необходимости. Для отделения обобщенных алгоритмов от деталей представления свойств вводится абстракция, называемая отображением свойств^.
Несколько категорий свойств доступа предоставляют различные возможности: readable (только чтение). Свойство может быть только прочитано. Данные возвращаются по значению. Многие отображения свойств для входных данных задачи (таких, как веса ребер) могут определяться как отображения свойств только для чтения.
writable (только запись). Свойство может быть только записано* Массив родителей для записи путей в дереве поиска в ширину является примером отображения свойства, которое может быть определено как только для записи.
read/write (чтение и запись). Свойство может быть как записано, так и прочитано. Свойство расстояния для алгоритма кратчайших путей Дейкстры требует как чтения, так и записи.
lvalue (1-значение). Свойство фактически расположено в памяти и есть возмо*- ность получить на него ссылку. Отображения свойств этой категории также поддерживают как чтение, так и запись.
Для каждой категории отображений свойств определена теговая структура.
В предыдущих ctstijHX, описывающих BGL, концепция средстЕ{а доступа была названа Decorator («украшатель*). В м агистерскойдиссертаци п Дитмара К юля [24| (редства доступа к свойствам названы средствами доступа к данным (data accessors).
namespace boost {
struct readable_property_fiiap_tag { }:
struct wr i t a b1e_p rope гty_ma p_t a g ( }:
struct read_write_property_map_tag :
public readable property_map_tag,
public wr11аЫe_property_map_tag { };
struct 1value_property_map_ta9 ■
public read__write_property_FDap tag { }:
}
Подобно классу Iterator^traits из STL, есть класс property_traits (листинг 15.1)r который может быть использогтн для выведения ассоциированных типов отображения свойств: типа ключа и типа значения, а также категории отображения свойств. Имеется специализация prcperty_trdi ts, чтобы указатели могли быть использованы как объекты отображений свойств.
Листинг 15.1. Класс property_traits
namespace boost {
template <typename PropertyMap> struct property_traits {
typedef typename Propert^Map::key_type key_t>pe;
typedef typename PropertyMap::value_type value_type:
typedef typename PropertyMap::reference reference: typedef typename PropertyMap:category category;
〃 специализация для использования указателей как отображений свойств tempi ate <typename Т> struct property_trai ts<T*> { typedef T value_type; typedef T& reference;
typedef std::ptrdiff_t key_type;
typedef 1value_property_map_tag category:
};
template <typename T>
struct property^trai ts<const T*> {
typedef T value_type;
typedef const T& reference:
typedef std::ptrdiff_t key_type;
typedef 1 val ue_property_n)ap_tag category ;
}:
Концепции отображений свойств
Интерфейс отображений свойств состоит из набора концепций, которые определяют общий механизм для отображения объектов-ключей на соответствующие объекты- значения, скрывая, таким образом, детали реализации отображения от алгоритмов, использующих отображения свойств. Для обеспечения большей гибкости, требования к отображениям свойств по типу объектов-ключей и значений специально не определены. Поскольку операции отображений свойств — глобальные функции, можно перегрузить функции отображения так, что будут использоваться почти произвольные типы отображений свойств и типов клюие丸 Интерфейс отображений свойств состоит из трех функций: get(), put О и operator[]. В листинге 15.2
показано, как эти три функции могут быть использованы для доступа к адресам ассоциированным с разными людьми.
Листинг 15.2. Пример использования интерфейса отображения свойства
template <typename AddressMap>
void fooCAddressMap address)
{
typedef typename boost: :propertyraits<AddressMcip>::
va 1 ue_type value_type:
typedef typename boost: ;property_traits<AddressMdp>: :key_type key_type; vdlue_type old_address, new_address:
key.type fred - "Fred":
old二address = get(address, fred):
new二address » ”384 Fitzpatrick Street^
put (address, fred, newaddress):
key_type joe 二"Joe";
value_type& joes_address - addressCjoe];
joes_address = "325 Cushing Avenue0:
}
Для каждого объекта-отображения свойств имеется набор допустимых клю・ чей, для которых отображение на объекты-значения определено. Вызов функции отображения свойств для недопустимого ключа приводит к неопределенному поведению. Концепции отображений свойств не определяют, как это множество до пустимых ключей создается или модифицируется. Функция, которая использует отображение свойств, должна указывать ожидаемый набор допустимых ключей в своих предусловиях.
Обозначения
В следующих разделах использованы такие обозначения:
РМар — тип отображения свойств;
ртар — объект отображения свойств типа РМар;
key — объект типа property_trait5<PMdp>:: keyjype;
val — объект типа property_traits<PMap>: :value_type.
ReadablePropertyMap
Концепция ReadablePropertyMap предоставляет доступ по чтению к объекту-значению, ассоциированному с данным ключом, через вызов функции get (). Функция get О возвраидает копию объекта-значения.
Уточнение для
CopyConstructible
Ассоциированные типы
Ниже приведены ассоциированные типы концепции ReadablePropertyMap.
property_t га 11 s<PMaр> :: valujtype
Тип свойства.
property_t га i ts<PMap>::reference
Тип, преобразуемый к типу значения.
property^ га 11 s<PMaр> : : key_type
Тип объекта-ключа, используемого для поиска значения свойства. Отображение свойств может быть параметризовано по типу ключа, в некотором случае этот тип может быть void.
property__tгаi ts<PMap>:: category
Категория свойства: тип, преобразуемый к readabl e_property_map_t3g.
Допустимые выражения
Концепция ReadablePropertyMap имеет одно допустимое выражение.
get(ртар, key)
Возвращает тип: reference.
Семантика: поиск свойства объекта, ассоциированного с ключом key.
WritablePropertyMap
Концепция WritablePropertyMap имеет возможность присваивать объект-значение, ассоциированный с данным объектом-ключом, посредством функции put().
Уточнение для
CopyConstructible
Ассоциированные типы
Ниже приведены ассоциированные типы концепшш WritablePropertyMap.
property」гаits<PA>: :val ue^type
Тип свойства.
p roperty_t ra11s<PA>::key_type
Тип объекта-Ю1юча, используемого для поиска значения свойства. Отображение свойств может быть параметризована по типу ключа, в некотором случае этот тип может быть void.
property_tr3its<PA>: : category
Категория свойства: тип, преобразуемый к writable_property_map_tag.
Допустимые выражения
Концепция WritablePropertyMap имеет одно допустимое выражение.
put(ртар. key. val)
Возвращает тип: void.
Семантика: присваивает val свойству, ассоциированному с ключом key.
ReadWritePropertyMap
Концепция ReadWritePropertyMap уточняет концепции ReadabLePropertyMap н WritablePropertyMap, ReadWritePropertyMap также добавляет требование, чтобы
property_traits<PA>::category имела тип, преобразуемый к read_write_pro- perty_map.
LvaluePropertyMap
Концепция LvaluePropertyMap обеспечивает доступ к ссылке на объект-свойство (вместо копии объекта, как в get()). LvaluePropertyMap может быть изменяемой и неизменяемой. Изменяемая Lva】 uePropertyMap возвращает ссылку, тогда как неизменяемая —ссылку на константу.
Уточнение для
ReadabLePropertyMap для неизменяемой, ReadWritePropertyMap для изменяемой.
Ассоциированные типы
Ниже приведены ассоциированные типы концепции LvaLuePropertyMap.
ргорегХуЗга^5<РМаР>:: reference
Ссылочный тип, который должен быть ссылкой или константной ссылкой на valu jtype отображения свойств.
property_t га11$<РМа р>::category
Категория свойства: тип, преобразуемый к 1 val ue_property_map_tag.
Допустимые выражения
Концепция LvaluePropertyMap имеет одно допустимое выражение.
pmaptkey]
Возвращает тип: reference.
Семантика: получает ссылку на свойство, идентифтщированное ключом.
Классы отображений свойств
property_traits
prope rty_t га 11 s<PropertyMap>
Класс property_traits предоставляет механизм для доступа к ассоциированным типам отображения свойств. Неспециализированная (использует значения по умолчанию) версия класса property_traits предполагает, что отображение свойства предоставляет определения для всех ассоциированных типов.
namespace boost {
template <typename PA>
struct property^raits {
typedef typename PA::key_type key_type;
typedef typename PA::value_tyPe value_type:
typedef typename PA;:reference reference:
typedef typuname PA:category category:
};
} 〃 namespace boost
Задание типа category должно быть определением для одного мз следующих типов или типа, который наследует из одного из следующих типов:
namespace boost (
struct readable_property_map_tag { }:
struct writabie^property^p^tag { }:
struct read_write__property_map_tag : readable_property_map_tag.
w r i t a b1e_p го pe rty_ma p_t ag { }:
struct 1 vaTue_property_iriap_tag : read_wr 1 te_property^niap_tag { };
} // namespace boost
Часто удобно использовать указатель как объект-отображение свойств, тогда key_type является целочисленным смещением. Такая специализация для property」 「a its и перегрузка функций отображений приведены в листинге 153
Листинг 15.3. Специализация для property_traits и перегрузка функций
namespace boost {
tempi ate <typename T>
struct p rope г ty_t ra i t s <T*> {
typedef std::ptrdiff_t key_type:
typedef T value_type:
typudef value_type& reference:
typedef 1value_property_map_tag category:
};
tempi ate <typename T>
void put(T* pa. std::ptrdiff_t k, const T& val) { paCk] = val: }
tempi ate <typename T>
const T& get(const T* pa. std::ptrdiff_t k) { return pa[kJ: }
template <typename T>
T& dt(T* pM std::ptrdiff_t k) { return pa[k]: }
} // namespace boost
Параметры шаблона
Класс pnDpe「ty_trdit5 имеет один параметр шаблона.
PropertyMap
Тип отображения свойств.
Где определен
Класс propertyjraits находится в файле boost/pro ре rty_map.hpp.
Методы
Ниже приведены методы класса property_traits.
property_traits::key_typR
Тип объекта-ключа» используемого для поиска свойства.
property_traits: :value_type
Тип свойства.
property_tra^ts::reference
Ссылка на тип значения,
property_traits::category
Тег категории отображения свойств.
iterator_property_map
1terator_property_map<Iterator, IndexMap, Т, R>
Адаптер 1 terator_property_map создает оболочку для тапа, моделирующего Ran* ботАссеБьИега^гдля создания LvaluePropertyMap, Этот адаптер часто полезен для создания отображения свойств из массива, где ключом являются целые числа- смещения от начала массива, а массив содержит объекты-значе!jия. Когда тип ключа — целый, можно просто использовать 1 dent 1 ty_property__niaр для параметра шаблона IndexMap. В противном случае придется предоставить отображение свойств, которое преобразует ключ в целое. Например, граф может иметь внутреннее свойство для vertex_index_t, которое может быть получено применением класса property_map.
Пример
Пример в листинге 15.4 демонстрирует создание отображения свойств из массива.
Листинг 15.4. Пример создания отображения свойств
1 ndude <iostream>
indude <boost/property^map.hpp>
int mainO {
using namespace boost;
double x[] = { 0.2, 4,5. 3.2 }:
iterator_property_map < double ★, identity_property_map.
double, doubles > pmap(x):
std::cout « Mx[l] - ” « getCpmap, 1) « std::endl:
puttpmap. 0. 1.7):
std: :cout vv 3[0] - “ « [нпарСО] « std: :endl:
return 0:
}
Вывод будет следующий:
X[l] . 4.5
НО] = 1.7
Где определен
Адаптер »七2「日1。「_口广。9©「1丫_而0 находится в файле boost/graph/property_map.hpp.
Параметры шаблона
Ниже приведены параметры шаблона адаптера 1terator_property_map.
IteratorАдаптируемый тал итератора. Должен моделировать
RandomAccessIterator
IndexMapОтображение свойств, которое преобразует тип
ключа в целое смещение. Должно быть моделью ReadablePropertyMap
ТТип значения итератора.
По умолчанию^урепагпе
std::1terator_trai ts<Iterator>::value_type
RТип ссылки итератора*
По умолчанию:
typename std;:iterator^traits<Iterator>;:reference
Модель для
LvaliiePropertyMap
Ассоциированные типы
Все типы, требуемые для LvaliiePropertyMap.
Функции — методы
Н иже приведены функции — методы адаптера 1terator_property_map.
iterator_property_map(Iterator iter = IteratorO. IndexMap Index^map = IndexMapO)
Конструктор.
template <typename Key>
reference operatorf ](Key k) const:
Возвращает *(1 ter 卜 get(index_map, k)).
Функции — не методы
Ниже приведены функции — не методы адаптера iterator_propertv_niap.
template <typename Iterator, typename IndexMap>
jterator_property_map<Iterator. IndexMap.
typename std::iteratoretralts<Iterator>::value_type,
typename std::iterator_traits<Iterator>::reference>
make_1terator_property_map(Iterator iter.
IndexMap lndex_map)
Создать отображение свойств итератора.
Теги свойств
В листинге 15.5 приведены теги для некоторых свойств BGL.
Листинг 15.5. Теги свойств
namespace boost {
\\ индекс вершины\\ индекс ребра\\ имя ребра\\ вес ребра\\ имя вершины\\ имя графа\\ расстояние вершины\\ цвет вершины\\ степень вершиныпродолжение £\\ индекс вершины\\ индекс ребра\\ имя ребра\\ вес ребра\\ имя вершины\\ имя графа\\ расстояние вершины\\ цвет вершины\\ степень вершиныпродолжение £enum vertex_index_t { vertex Jndex - 1J:
\\ индекс вершины
\\ индекс ребра
\\ имя ребра
\\ вес ребра
\\ имя вершины
\\ имя графа
\\ расстояние вершины
\\ цвет вершины
\\ степень вершины
продолжение £
\\ индекс вершины
\\ индекс ребра
\\ имя ребра
\\ вес ребра
\\ имя вершины
\\ имя графа
\\ расстояние вершины
\\ цвет вершины
\\ степень вершины
продолжение £
enum edge_i ndex_t { edge_1ndex - 2};
enum edge2namO ( edge_name 口 3 }:
enum edge二 weigRt_t { edge_weight = 4 }; enum vertex^name^t { vertex_name = 5 enum graph^ame^t { graph_name ■ 6 }: enum vertex_distance_t { vertex^distance =7}; enum vertex_color_t { vertex color = 8 }: enum vertex二degree/ { vertex_degree = 9 }: enum vertex^ln_degree_t ( vertex_1n_degree = 10 \\ ^степень вершины по входящим ребрам
Листинг 15.5 {продолжений
vertex_out_degree - 11 };по исходящим ребрам{ vertex_d1scove r^t1me = 12 }: аершиныvertex fi n 1 sh_time = 13 }:обработки вершиныvertex_out_degree - 11 };по исходящим ребрам{ vertex_d1scove r^t1me = 12 }: аершиныvertex fi n 1 sh_time = 13 }:обработки вершиныenum vertex__out_degree_t { \\ "степень вершины елиш vertex^di scover_tirne_t \\ время посещения enum vertex亠广ini sh-timjt { W ^время окончания
vertex_out_degree - 11 };
по исходящим ребрам
{ vertex_d1scove r^t1me = 12 }: аершины
vertex fi n 1 sh_time = 13 }:
обработки вершины
vertex_out_degree - 11 };
по исходящим ребрам
{ vertex_d1scove r^t1me = 12 }: аершины
vertex fi n 1 sh_time = 13 }:
обработки вершины
namespace boost {
BOOST INSTALL PROPERTY(vertex, index);BOOST INSTALL PROPERTY(vertex, index);\\ установить свойство
BOOST INSTALL PROPERTY(vertex, index);
BOOST INSTALL PROPERTY(vertex, index);
BOOSriNSTALrpROPERTYCedge. index): BOOST」NSTALL二PROPERTY(edge, name);
}
Создание пользовательских отображений свойств
Главным назначением интерфейса отображений свойств является повышение гибкости обобщенных алгоритмов. Это позволяет хранить свойства многими возможными способами, в то же время предоставляя алгоритмам общий интерфейс. Следующий раздел содержит пример использованпя отображений свойств для адаптации к сторонней графовой библиотеке Stanford GraphBase (SGB) (см. раздел 14.3.4). После этого мы рассмотрим реализацию отображения свойств с использованием std::map,
Отображения свойств для Stanford Gra ph Base
Адаптер BGL для Stanford GraphBase включает в себя отображение свойств для доступа к различным полям структур Vertex и Arc из SGB. В этом разделе мы описываем одну из сторон реализации адаптера SGB в качестве примера реализации отображений свойств.
SGB испо刀ьзуег следующую структуру Vertex для хранения информации о вершинах в графе. Указатель arcs — связный список исходящих ребер вершины. Поле паше и второстепенные поля от и до z — свойства вершины (util — это объединение C++ (union), позволяющее хранить различные сущности в вершине). Этот раздел описывает, как создать отображение свойств для доступа к полю name.
typedef struct vertex^struct { struct arc_struct* arcs: char* name; util u. v. w. x. y* z:
} Vertex;
Основная идея реализацш! этого отображения свойств — определение функций operator[](), get() и put О в терминах доступа к методам структуры данных. Эту работу проще сделать с помощью класса put_get_he1 per, в котором реализованы put () н get О посредством operator]]. Следовательно, остается реализова ть только операцию operator[]. Кроме того, ассоциированные типы, требуемые отображением свойств, также должны быть определены.
В листинге 15.6 приведена реализация sgb_vertex_name_map. Мы используем класс put_get_hel per (он определен в boost/property_map.hpp) для упрощения создания этого отображения свойств. Мы реализуем operator[]()t aput_get_helper реализует put() и get(). Первый аргумент-тип в шаблоне класса put_get_hel per — тип возвращаемого значения для operator】], который в нашем случае есть char* Второй аргумент — тип самого отображения свойств. Тип reference должен действительно быть ссылкой, только если отображение свойств должно быть Lva 1 uePropertyMap. В нашем случае мы создаем ReadablePropertyMap. Адаптер SGB использует Vertex* для дескриптора вершины vertex_descriptor графа, так что это key type отображения свойств.
Листинг 15.6. Реализация sgb_vertex_nanne_map
class sgb_vertex_name_map
:public put_get_heTper< char*, sgb_vertex_name_map > { public:
typedef boost::readable_property_map_tag category:
typedef char* value__type;
typmdef char* reference;
typedef Vertex* kmy.type;
reference operator[ ](Vertex* v) const { return v->name: }
};
Отображение свойств из std::map
В предыдущем примере объект-отображение свойств не нуждался в сохранении какого-либо состояния, так как объегсг-значение мог быть получен непосредственно по ключу. Так бывает не всегда. Обычно ключ используется для поиска объекта-значения в некоторой вспомогательной структуре данных. Очевидным кандидатом на такую структуру является std::map. Отображение свойств, которое использует std: :map в качестве реализации, нуждается в храпении указателя на этот ассоциативный контейнер. Мы сделали тип контейнера параметром [иаблона, так что отображение свойств может быть использовано с другими контейнерами, таки* ми как hash_map. Концепция, описывающая такой вид контейнера, называется UniquePai rAssociativeContaiпег (листинг 15.7).
Листинг 15.7. Реализация associative„property_map
template <typename UniquePairAssociativeContainer>
class assoc1ative_property_map
:public put_get_helper<
typename UniquePairAssociativeContainer::value_type::second_type&.
associative_property_map<UniquePairAssoc1ativeContainer> >
{—
typedef UniquePalrAssociat1veContaiпег C:
public:
typedef typename C::key_type key_type;
typedef typename C:: val ue_type :: secondlype value_type:
typedef value_type& reference:
typedef 1 value_property_?nap_tag category;
associative_property_map() 丁 m_c(0) { }
務sociatNe二property二map(C& c)~: m_c(&c) { } reference operator[ ](const key_type& k) const { return (*m c)[k];
private:
C* m_c:
Вспомогательные концепции, классы
Buffer
Концепция Buffer (буфер) — это нечто, во что можно поместить некоторые элементы, а затем удалить. Концепция Buffer имеет очень мало требований. Она не требует какого-то определенного порядка хранения элементов или порядка, в котором они удаляются. Однако обычно имеется некоторое правило упорядочения.
Обозначения
В дашюй главе будут использованы следующие обозначения:
В — тнп, моделирующий Buffer;
Т — тип значения элементов В;
t — объект типа Т.
Требования
Чтобы тип моделировал Buffer, он должен иметь следующие методы класса.
В::va1ue_type
Тип объекта, хранимого в буфере. Тип значения должен быть Assignable.
В: :s1ze_type
Беззнаковый целый тип для представления числа объектов в буфере.
b.push(t)
Вставляет t в буфер. При этом b.sIzeO увеличивается на едишщу.
Ь.рор。
Удаляет объект из Buffer. Это тот самый объект, который возвращает b.top(). При этом b.sIzeO уменьшается на единицу.
Предусловие: b. empty О — ложь.
b*top()
Возвращает ссылку (или константную ссылку) на некоторый объект в буфере. Предусловие: b.empty() — ложь.
b.sizeO
Возвращает число объектов в буфере.
Инвариант: b. size О >= 0.
b.empty О
Тип возвращаемого значения — bool. Результат эквива^чентен b.slze() == 0.
Гарантии сложности
Для концепции Buffer иыеются следующие гарантии сложности.
push(), pop О и sizeO должны выполняться не более чем за линейное время от размера буфера.
top О и empty О должны выполняться за амортизированное постоянное время.
Модели
std::stack,boost::mutablenqueue, boost::priority^queue и boost::queue.
ColorValue
Концепция ColorValue описывает требования к типу, используемому для значений цветов. Многие алгоритмы BGL применяют отображение свойства ^пвет» для отслеживания продвижения алгоритма по графу. Тип значений цвета должен быть EqualityComparable. Класс color_traits специализирован для Т так, чтобы были определены следующие функции. Здесь Т — тип, который моделирует ColorValue.
color__traits<T>: :white()
Возвращает тип: Т.
Семантика: возвращает объект, который представляет белый цвет.
color_traits<T>::grayО
Возвращает тип: Т.
Семантика: возвращает объект, который представляет серый цвет.
coIor_traits<T>::black()
Возвращает тип: Т.
Семантика: возвращает объект, который представляет черный цвет.
MultiPassinputiterator
Концепция MultiPassInputlterator многопроходного итератора является уточнен л ^ ем Inputiterator. Она добавляет требования, чтобы итератор мог быть использован для неоднократного перебора всех значений, и если Itl ~ it2 и ill можно разыменовать, то *卄壮1 == *++it2.
Итератор Multi Passinputiterator подобен Forwarditerator. Разница состоит в том, что Forwarditerator требует, чтобы тип reference был val ue_type&, тогда как MultiPass- Inputlterator похож на Inputiterator в том, что тип reference должен быть преобразуем к типу val ue__type.
Monoid
Концепция Monoid описывает простой вид алгебраической системы. Она состоит из множества элементов 5, бинарной операции и нейтрального элемента (identity element). В C++ моноид представляет собой объект-функцию, реализующий бинарную операцию, множество объектов, представляющих элементы 5, н объект — нейтральный элемент.
Уточнение для
Тип элемента множества 5 должен быть моделью Assignable и CopyConstructible. Тип функционального объекта должен моделировать Binary Function.
Правильные выражения
Тип X — это тип элемента. Объекты а, b н с — объекты типа X, представляющие элементы множества S. Объект i — объект типа X, обладающий свойствами нейтрального элемента (приведены ниже). Объект ор — это функциональный объект, который реализует операцию на моноиде.
ор(а. Ь)
Возвращает тип: X.
Семантика: см. ниже.
а з Ь
Возвращает Tim: bool.
Семантика: возвращает истану* если а и b представляют один и тот же элемент 5.
d != D
Возвращает тип: bool.
Семантика: возвращает истину, если а и b представляют разные элементы 5*
Инварианты
Замкнутость.
Результат ор(а. Ь) также является элементом S.
Ассо [ 1и ат и вность.
ор(ар(а. Ь). с) а ора ор(Ь. с))
Определение нейтрального элемента.
ор(а. 1) == а
mutable_queue
mutable^queu^IndexedType. Container. Compare. 1D>
Адаптер mutablenqueue представляет собой специальный вид очереди по приоритетам (реализована с использованием кучи), которая имеет операцию обнов-
ления (update). Это позволяет изменять порядок элементов в очереди. После того как критерий упорядочения для объекта элемента х меняется,необходимо вызывать Q.upddte(x). Чтобы эффективно находить х в очереди, необходимо написать функтор (объект-функцшо) для отображения х в уникальный идентификатор (ID)T который mutabl enqueue затем использует для отображения размещения элемента в куче. Генерируемые идентификаторы должны быть от 0 до N, где N — значение, переданное конструктору mut a bl enqueue.
Параметры шаблона
Ниже приведены параметры шаблона адаптера mutablenqueue.
IndexedType
Если ID не поддерживается, то должна быть определена функция mdex(t) (где t — объект типа IndexedType), которая возвращает некоторый целый тип.
Container
Модель для Ra nd о гп AccessCo ntai пег. Тип значения контейнера должен быть таким же, как тип IndexedType.
По умолчанию: std: :vector<IndexedType>.
Compare
Модель для BinaryPredicate (бинарный предикат), которая получает объекты типа IndexedType в качестве аргументов.
По умолчанию: std: : less<typename Container:: value_type>.
ID
Модель для ReadablePropertyMap, которая принимает IndexedType в качестве типа ключа, а возвращает некоторый целый тип в качестве типа значения.
По умолчанию: Identity^property^p.
Методы
Ниже приведены методы адаптера mutabl enqueue.
va1ue_tyре
Тот же тип, что и IndexedType.
size_type
Тип, используемый для представления размера очереди.
mutable_queue(s1ze_type 门・ const Compares с.
const』ID& id - IDO)
Конструктор. Резервируется место для п элементов.
template <class Inputlterator>
mutable_queue(lnputlterator flrst, Inputiterator last.
const Compares c. const ID& id = IDO)
Конструктор, Контейнер std:: vector по умолчанию заполняется нз диапазона [first, last).
bool emptyO const
Возвращает истину, если очередь пуста.
void popО
Удаляет объект из очереди.
value_type& top()
Возвращает ссылку на первый элемент очереди.
va 1 ujty ре& front О
Другое имя для top().
void push(const value_type& x)
Вставляет копию объекта x в очередь.
void update(const value_type& x)
«Значение» элемента изменилось, и должно быть заново произведено упорядочение кучи. Этот метод подразумевает, что имеется старый элемент у в куче, для которого IndexQy) == Index(x), и что х — новое значение элемента.
Непересекающиеся множества
disjoint_sets
disjolnt_sets<RankMap. РаrentMap, FindCompress>
Класс disjol nt_sets предоставляет операции на непересекающихся множествах (НМ), иногда называемых структурой данных для объединения и поиска (unionfind data structure). Структура данных НМ поддерживает набор 5 = 5Р 5?,…,^ непересекающихся множеств. Каждое множество идентифицируется своим представителем, взятым из множества. Множества представлены корневыми деревьями, которые закодированы в отображении свойства ParentMap, Для ускорения операций используются две эвристики: объединение по рангу и сжатие путей.
Параметры шаблона
Ниже приведены параметры шаблона класса disjoint_sets.
RankMap
Должен быть моделью ReadWritePropertyMap с целым типом значения “типом ключа, совпадающим с типом элемента множества
ParentMap
Должен быть моделью ReadWritePropertyMap. Типы ключа и значения должны совпадать с типом элемента множества
FlndCompress
Должен быть одним из функциональных объектов, которые мы обсудим позднее в этом разделе.
По умолчанию: fl nd_with_ful 1 jath_compression
Пример
Типичный образец использования непересекающихся множеств можно обнаружить в алгоритме kruskal_minimum_spann1ng_tree(). В примере в листинге 16.1 мы
вызываем llnkO вместо unwn_set(), так как и и v получаются из find_set(), а значит, уже являются представителями своих множеств.
Листинг 16.1. Использование непересекающихся множеств
〃…
disjolnt_sets<RankMap. ParentMap. FindCompress> dsets(rank, p);
for (ui = vertlces(G).first; ul != vertices(G).second: ++ui) dsets.make_set(*ui);
while ( !Q.empty() ) {
e = Q.frontO:
Q.popO ;
u = dsets.f1nd^set(source(e)):
v - dsets.find_set£target(e)):
if ( u > v И
*out++ - e:
dsets.11nk(u. v); 〃 связывание
}
}
Методы
Ниже приведены возможные методы класса disjo1nt_sets.
disjolnt^sets(RankMap г, ParentMap p)
Конструктор.
disjoint^sets(const disjoint_sets& x)
Конструктор копирования.
template 〈typename Element〉
void make_set(Element x)
Создает множество из одного элемента, содержащего элемент х*
template <typename Element〉
void link(Element x. Element y)
Объединить два множества, представленные элементами х иу.
template <typename Element〉
void union_set(Element x. Element y)
Объединить два множества, содержащие элементы хну. Это эквивалентно
11nk(f1nd_set(x). f1nd_set(y))*
template <typename Element〉
Element find__set(Elenient x)
Возвращает представителя множества, содержащего элемент х.
template <typename ElementIterator>
std::size_t count_sets(ElementIterator first. Element Iterator last)
Возвращает число непересекающихся множеств.
template <typename ElementIterator>
void compress_sets(ElementIterator first.
Elementiterator last)
Выравнивает дерево родителей так, что родитель каждого элемента является его представителем.
Сложность
Временная сложность порядка Oiina^m,门)),где а — функция, обратная функции Аккермана, т — число операций на непересекающихся множествах (makc_5et(), find_set() и llnkO) и л — число элементов, Фупкцртя, обратная функции Аккермана, растет очень медленно, намного медленнее логарифма.
find_with_path_halving
fi nd_wi th_path_hal vi ng
Этот фунетор находит вершину-представшеля для компоненты, в которой содержится элемент х, и в то же время сжимает дерево, используя деление пути пополам.
template <typename ParentMap. typename Element^ Element operator()(ParentMap p. Element x)
find_with_full_path_compression
fi nd_with_ful l_path_compression
Этот функтор находит вершину-представителя для компоненты, в которой содержится элемент хт и в то же время сжимает дерево, используя полное сжатие пути.
tempi ate <typename ParentMap. typename Element〉
Eleinent operatort)(ParentMap p. Element x)
tie
template <typenaine TL typendme T2>
tuple<TL T2> t1e(Tl& 己.T2& b):
Эта функция из Boost Tuple Library (библиотека кортежей), написанная Яакко Ярви, делает более удобной работу с функциями, которые возвращают пары (или, в общем случае, кортежи). Функция tie() позволяет присваивать двазначе- ния пары двум различным переменным.
Где определена
Функция tleO находится в файле boost/tuple/tuple.hpp.
Пример
В примере функция tleO используется вместе с функцией verticesO, которая возвращает пару типа std: :pair<vertex_1terator, vertex_1terator>. Пара итераторов присваивается переменным i и end.
graph_tra1ts<graph t>::vertex_itera.tor i. end:
for(tie(1T end) = vertices(g); i !- end; ++1)
〃…
В листинге 16.2 приведен другой пример, который нспо/ьзует tie() для работы с std::set.
Листинг 16.2. Пример использования функции tie().
#include <set>
#include <algorithm>
#lnc】ude <lostream>
include <boost/1uple/tuple.hpp>
int main() {
typedef std::set<int> SetT:
SetT::iterator i. end;
bool Inserted;
int vals[53 = { 5. 2. 4. 9. 1 }:
SetT sfvals. vals + 5);
Int new_vals[2] = { 3, 9 }:
for (int k - 0; k < 2; ++k) {
// Используем tie() co значением типа pai「《iterator. bool> boost: :tie(1 inserted) 口 s Jnsert(new_vals[k]);
if C!inserted)
std: :cout « *1 « ” уме находилось во множестве.**« std::endl;
else std::cout « *1 « ” успешно вставлено.1' « std::endl;
return EXIT^SUCCESS:
}
Программа выводит следующее:
3 успешно вставлено.
9 уже находилось во множестве.
16.8. graph_property_iter_range
graph_property_i ter_range<Graph, PropertyTag>
Этот класс генерирует пару итераторов begin/endT которые предоставляют доступ к свойству вершины для всех вершин в графе или к свойству ребра для всех ребер в графе.
Пример
В примере (листинг 16.3) перебираются все вершины в графе, с присвоением строк н свойство «имя». Затем снова перебираются все вершины и выводятся имена в стандартный вывод.
Листинг 16.3. Файл graph-property-iter-eg.cpp
《graph-property-iter-eg.cpp ) s
#include <string>
#indude <boost/graph/adjacencyjist.hpp>
#i ncl ude <boost/graph/property_iter__range. hpp>
int
mainO
продолжение 4
Листинг 16Л {продолжений
{
using namespace boost:
typedef adjacencyjist < lists. vecS. directedS, property < vertex name t. std::string > >graph t; graph_t g(3):
const char *vertex_names[] - { nKubr'ick\ "Clark". "Hal" }: int I ■ 0:
graph_property_iter range < graph/. vertex_name_t >:ilterator v. vend: for (tie(v, v_end) - get_propertyjter_range(g, vertex_name):
v != v_end: hv, ++f)
*v = vertex_names£1]:
tie(v. v_end)工 get_property_1 ter_range(g. vertex__name): std::copy(v. v_end."std::ostreamjterator < std::string > (std::cout."”)):
std::cout « std::endl: return 0:
}
Программа выводит следующее:
Kubrick Clark Hal
Где определена
Класс graph_property_i ter_range находится в файле boost/graph/property_iter_range.hpp.
Параметры шаблона
Ниже приведены параметры шаблона класса grdph_property_iter_「dnge.
GraphГрафовый тип должен быть моделью PropertyGraph,
PropertyTagТег указывает, к какому свойству ребра или верши-*
ны происходит доступ.
Ассоциированные типы
Ниже приведены ассоциированные типы класса graph^propertyj ter^range.
graph_property_iter_range::Iterator
Изменяемый итератор, тип значения которого — свойство, указанное тегом свойства.
g「dph_property_ite jrange: :const-iterator
Константный итератор, тип значения которого — свойство, указанное тегом свойства.
graph_propertyjter_range::type Тип std: :pair<1terator, iterator>.
graph_property_i ter_range: :const_type
Тип std::pair<const_iterator. const_iterator>
Функции — методы
He имеет.
Функции — не методы
Ниже приведены функции — не методы класса graph_propertyjter_range.
tempiate<typename Graph, typename 7ag>
typename graph_propertyJter__range<GraphT Tag>: :type get_property_iter_rangeTGraph& graph, const Tag& tag)
Возвращает пару изменяемых итераторов, которые дают дооуп к свойству, указанному тегом. Итераторы пробегают по всем вершинам или по всем ребрам графа.
tempiate<typename Graph, typename Tag>
typename graph_property_iter_range<Graph. Tag>::const_type get_property_iter_range?const G「dph& graph,
const Tag& tag)
Возвращает пару константных итераторов, которые дают доступ к свойству, указанному тегом* Итераторы пробегают по всем вершинам или по всем ребрам графа.
Библиография
Dimacs implementation file format. http://dimacs.rutgers.edu/Challenges/.
A. ?Vexandrescu. Better template error messages. C/C++ Usersjournal, March 1999.
M. H. Austern. Generic Programming and the STL. Professional computing series. Addison-Wesley, 1999,
G, Baumgartner and V, F. Russo. Signatures: A language extension for improving type abstraction and subtype polymorphism in C++. Software—Practice and Experience, 25(8):863-889, August 1995.
R. Bellman. On a routing problem. Quarterly of Applied Mathematics, 16(1): 87-90, 1958.
Boost. Boost C++ Libraries, http://www.boost.org/.
A. Broder, R. Kumar, F. Maghoul, P. Raghavan, S. Rajagopalan, R. Stata, A. Tomkins, and J. Wiener. Graph structure in the web. In 9th World Wide Web Conference, 2000.
К. B. Bruce, L. Cardelli, G. Castagna, the Hopkins Objects Group, G. T. Leavens, and B. Pierce. On binary methods. Theory and Practice of Object Systems, 1:221-242, 1995.
В. V. Cherkassky and A. V. Goldberg. On implementing push-relabel method for the maximum flow problem. Technical report, Stanford University, 199 1.
T. Cormen, C. Leiserson, and R, Rivest Introduction to Algorithms. McGraw- Hill, 1990.
E. Dijkstra. A note on two problems in connexion with graphs. Numerische Mathematik, 1:269-271, 1959.
L. R. Ford and D. R. Fulkerson. Maximal flow through a network. Canadian Journal of Mathematics, pages 399-404, 1956.
L R. Ford and D. R. Fulkerson. Flows in Networks. Princeton University Press, 1962.
Е. Gamma, R. Helm» R. Johnson, and J. Viissides. Design Patterns: Elements of Reusable Object-Oriented Software. Professional Computing. Addison- Welsey, 1995.'
M. Garey and D Johnson. Computers and Intractibility: A Guide to the Theory of NPCompleteness. W.H. Freeman, New York, 1979.
A. V. Goldberg. A new max-flow algorithm. Technical Report MIT/LCS/TM- 291, MIT, 1985.
A. V, Goldberg and R. E. 7'aijan. A new approach to the maximum flow problem. Journal of the ACM, 1988.
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.
C. Hedrick Routing information protocol. Internet Requests For Comments (RFC) 1058June 1988.
A. V. Karzanov. Determining the maximal flow in a network by the method of preflows. Sov* Math. Dokl., 1974.
2LS. E. Keene. Object-Oriented Programming in Common LISP: A Programmer's Guide to CLOS. Addison-Wesley, 1989.
D. E. Knuth. Stanford GraphBase: A Platform for Combinatorial Computing. ACM Press, 1994.
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.
D. KiihL Design patterns for the implementation of graph algorithms. Master's thesis, Technische Universitat Berlin ^ July 1996.
J. Lajoie and S. B. Lippman. C++ Primer. Addison Wesley, 3rd edition, 1998.
E. L. Lawler. Combinatorial Opimization: Networks and Matroids. Holt, Rinehart, and Winston, 1976.
D. Matula. Determining edge connectivity in o(mn). In Sumposium on Foundations of Computer Science, pages 249-251, 1987.
J. McQuillan. The new routing algorithm for the arpanet. IEEE Transactions on Communications, May 1980.
K. Mehlhorn and S. Naher. The LEDA Platform of Combinatorial and Geometric Computing. Cambridge University Press, 1999.
B. Meyer. Object-oriented Software Construction. Prentice Hall International Series in Computer Science. Prentice Hall, 1988.
E. Moore. The shortest path through a maze. In International Symposium on the Theory of Switching, pages 285-292, Harvard University Press, 1959.
R. Morgan. Building an Optimizing Compiler. Butteworth-Heinemann, 1998.
33* J. Moy. Rfc 1583: Ospf version 2. Network Working Group Request for Comment, March 1994.
D, R. Musser, G. J. Derge, and Л. Saini. STL Tutorial and Reference Guide. Addison*Wesley, 2nd edition, 200L
D. R. Musser 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 SIG Ada.
N. C、Myers/fraits: a new and useful template technique. C++ Report, June 1995
R. Perlman, Fault-tolerant broadcast of routing information. Computer Networks December 1983.
R. Prim, Shortest connection networks and some generalizations. Bell Systen: Technical Journal, 36:1389-1401, 1957.
J Stek and A* Lumsdaine. Concept checking: Binding parametric polymorphisn. in C++. In First Workshop on C++ Template Programming, Erfurt, Germany, October 10 2000.
A. A. Stepanov and M. Lee. The Standard Template Library. Technical Report X3J16/94-0095, WG21/N0482, ISO Programming Language C++ Project, May 1994.
j. B, Stroustrup* Design and Evolution of C++. Addison-Wesley, 1994.
B. Stroustrup. The C++ Programming Language. Addison Wesley, special edition, 2000.
R. Tarjan. Depth-first search and linear graph algorithms. SIAM Journal on Computing, 1(2):l46-l60t 1972.
R. E. Tarjan. Data Structures and Network Algorithms. Society for Industrial and Applied Mathematics, 1983.
B. L. van der Waerden. Algebra. Frederick Ungar Publishing, 1970*
H. C. Warnsdorff. Des roesselsprungs einfachste und allgemeinste loesimg, Schmalkalden, 1823,
Дополнение к библиографии
Теория графов
Ахо А., Хопкрофт Дж., Ульман Дж. Построение и анализ вычислительных алгоритмов. — М.: Мир, 1979.
Беллман Р., Дрейфус С. Прикладное задачи динамического программирования. —М.: Наукат 1965.
Евстигнеев В. А., Касьянов В. Н. Теория графов: Алгоритмы обработки деревьев. —Новосибирск: Наука, 1994.
Касьянов 3. Н., Евстигнеев В. А. Графы в программировании: обработка, визуализация и применение. — СПб.: БХВ-Петербург, 2003.
Кристофидес Н. Теория графов. Алгоритмический подход. — М.: Ммр, 1978.
Липский В. Комбинаторика для программистов. — М.: Мирт 1988.
Оре О. Теория графов. — М.: Наука, 1968.
Форд Л. Р., Фалкерсон Д. Р Потоки в сетях. — М.: Мир, 1966.
Харари Ф. Теория графов. — М.: Мир, 1973.
C++ и STL
Александреску Л, Современное проектирование на C++: Обобщенное программирование и прикладные шаблоны проектировш111я. — Киев: Издательский дом《Вильямс», 2002,
Буч「Объектно-ориентированный анализ и проектирование с примерами приложений на C++. — М.: Издательский дом《ВИНОМ», 2001,
Вандевурд Д., Джосаттис Н. Шаблоны C++: Справочник разработчика.— Киев: Издательский дом «Вильямс», 2003,
Галша Я, Хелм Р’Джохсон Р., ВлиссидесДж, Приемы объектно-ориентированного проектирования: Паттерны проектирования. — СПб*: Питер, 2001
Джосьютис Н. C++ стандартная библиотекя- Для профессионалов. — СПб. Интер, 2003.
Мейерс С. Эффективное использование STL: Библиотека программиста.—— СПб: Питер, 2002
Остерн М.「Обобщенное программированием и STL: использование и наращивание стандартной библиотеки C++. — СПб.: Невский Диалект, 2004.
Плаугер П., Ли М., Массер Д., Степанов A. STL — стандартная библиотека шаблонов С+. — СПб/ ВХВ-Петербург, 2004.
Саттер Г. Решение сложных задач на C++. Серия C++ In-Depth. — Киев: Издательский дом «Вильямс», 2003.
Страуструп Б. Язык программирования C++, — М.: Издательский дом «БИНОМ», 2001.
1L Страуструп 5. Дизайн и эволюция языка C++. — М.: ДМК, 2000.
Алфавитный указатель
А
adapters 32
algorithm visitor 67
algorithm visitors 31
allocator type 234
В
back edge 69, 83
backtracking graph search 127
bidirectional graph 32
Boost 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 1 】9
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, MTL 17 minimum disconnecting set 118, 125
N
named parameters 58
О
overloaded functions 33
P
policy class 135
preflow 117
priority queue 129
property interface 230
property map 27
push-relabel algorithm 117
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 reversegraph 33
для Stanford GraphBase 282 аддитивная абелева группа 40 алгебра
многосортная И алгоритм
Белл мана-Форда 92, 188
Дейкстры 96, 183
Джонсона 192
Краскала 102, 194 обобщенный 13 поиска в глубину 62, 82, 177 поиска в ширину 77, 89, 172 Прима 102, 197
алгоритм (продолжение)
связных компонент 200
Тарьяна 202
топологическо!i сортировки 182
увеличивающихся компонент
связности 207
Форда- Фалкерсона 117
Эдмондса^Карпа 117, 209
ассоциированные типы 47
Б
базовый блок 84
библиотека
LEDA 130
алгоритмов на графах 59
лексического разбора 79
проверки концепций 54
бикомпонент 109
В
вершина
достижимая 109
предок 77, 89
родитель 77t 89
смежная 25
внешнее хранилище свойств 64
время окончания обработки вершины 62
время посещения 62
Г
гарантии сложности 48
граф 25
knights_tour_graph 125
вершина 24
двунаправленный 32, 216
дуга 25
неориентированный 25
ориентированный 25
ребро 25
узел 24
графовые адаптеры 32
д
дерево
поиска в глубину 62
поиска в ширину 77, 89
диспетчеризация
тегов 52
днепетчеризация вызовов
времени выполнения 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 adj acency list_t raits 246 adjacency_matrix 32, 33, 235 adjacency_matrix_traits 247 array^traits 50
back_ed ge_record er 85 bacon_number_recorder 81 eo!or_traits 285 ColorPoint 43
ColorPoint2 44 camponent_index 208 disjoint_sets 288 edge !ist 250 filtered_graph 256 graph_property_i ter_ra nge 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
Adjacency Graph 154
Adjacency Matrix 157
BellmanFordVisitor 168
BFSVisitor 165
Bi directional Graph 153
Buffer 284
ColorValue 285
DFSVisitor 166
Dijkstra Visitor 167
EdgeList Graph 156
EdgeM utableGraph 160
EdgeMutablePropertyGraph 164
Graph 151
IncidenceGraph 152
Lval u eProperty Map 278
Monoid 286
MultiPassinputiterator 285
MutableBi d irec t ional Grap h 161
M u ta bieEdge Lis t Gra ph 162
MutablelncidenceGraph 161
PropertyGraph 162
ReadablePropertyMap 276
ReadWritePropertyMap 277
U niquePairAssociativeContainer 283
VertexListGraph 155
VertexMutableGraph 159
VertexMutablePropertyGraph 163
Wri tabi eP roperty Map 277
отображения свойства 27
кратчайшее расстояние 77, 89
крашайший путь 77, 89
вес 89
Л
лес
поиска в глубину 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
стандартная библиотека шаблонов 16, 2U
стиль грамотного программирования 20 структура
Аге 282
Vertex 282
тег 52
тип данных
абстрактный 39
тин распределителя памяти 234
топологическое упорядочение 34
У
уточнение концепции 47
Ф
файл
adjacency_list.hpp 218, 246
adjacency_matrix.hpp 238, 247
beHman-exaniple.cpp 190
bellman-ford-internet.cpp 92
bellinan__(brd_shortest paths.hpp 189 bfs-example.cpp 174
bread th_fi rst_search.hpp 173
cc-intemet.cpp 110
connected_components.hpp 200
container gen.cpp 233
depth_first_search.hpp 178, 181 dfs-example.cpp 179
dijkstra-example.cpp 187, 229
d ij kst ra_shortest_paths.hpp 185
edge-connectivity .cpp 122
edge_list.hpp 251
edge property.cpp 232
edmunds_karp max flow.hpp 209
family-tre&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 96t 104
incremental-components-eg.cpp 206 incremental components.hpp 207. 208 interior_property_map.cpp 233
johnson_afl_pairs_shortest_j>athsJipp 192 kevin_bacon.txt 79
kruskal-ex amp le.cpp 196
knaskalminimu mspanni ng_t ree.hpp 195 kruskal-telephone.cpp 104
Ieda graph.hpp 33, 131, 136, 244,
“ 266, 267
miles spanxpp 262
prim-example.cpp 199
prim_minimum_spanning_tree.hpp 198 pri m-telephone.cpp 106
property.hpp 66, 225, 226, 242.
243, 248, 250, 256
property_iter_raiige.hpp 292
файл (продолжение)
property map.hpp 73, 279, 280, 283 PROTOTYPES 132, 261 relaxJipp 186, 190 reverse-graph -eg.cpp 252 reverse_graph.hpp 253 roget_components.cpi)262 see .cpp* 112
stanford_graplLhpp33, 132, 244, 261, 262
st rong_components.hpp 202 topological_sort.hpp 182 topo-sort 1.cpp 35 topo-sort2.cpp 37 topo-sort-with -sgb.cpp 133 tuple.hpp 290
vector as graph.hpp 33, 271, 272 формат DI MACS 210 функтор 30, 164 функция
accumulate 46
add_edge 29
add_vertex 29 adjacent_vertices 29, 64 back edge 67
backtracking—* arch 127 bellnian_ford_shortest_path 58, 92 breadth_first search 31, 79, 172 compute_loop_extent 86
connected_components 110, 200 depth_first_search 34, 38, 82,177 depth__first__visit 82, 88, 181 dijkstra shortestjaths 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 edges 29, 148 incremental components 207 initialize_incremental_components 207 is selMoop 26
jo h nson_al l_pai rs_shortest_pat hs 192
функция (продолжение) kruskal_minimum_spanning_tree
104. 194
makei teratoi^property _map 93
num_ vert ices 56
number_of_successor$ 128
operator 275
out edges 29,148
prim_minimuni_spanning_tree 197
print_trans_delay 27, 28
print_vertex_name 27, 28
put 275
push relabel max flow 212
read_graphviz 96
samecomponent 207
std 30, 80
sum 40
source 148
strong_components 202
tie 28,290
topa_sort 65, 69
функция (продолжение) topo_sort_dfs 63 topological_sort 34, 182 tree_edge 67, 81
target 148 vertexjndex_map 36 visitor 31, 82 wamsdorff 129 who_owes_who 232 встраиваемая 42 обобщенная 14 перегруженная 33
ц
цикл 84
голова 84
ч
частичная специализация 51 число Бэкона 79
число Эрдеша 78
Джереми Сик, Лай-Кван Ли, Эндрю Ламсдэйн
Boost Graph Library
i Обобщенное программирование на C++ t Концепции и алгоритмы BGL
•4 Алгоритмы в интернет-маршрутизации
丁 Задачи планирования сети
■
Addi 丸”С^ППТЕР
С^пптер
Jeremy G. Siek, Lie-Quan Lee, Andrew Lumsdaine
The Boost Graph Library
User Guide and Reference Manual
A
Addison-Wesley
Boston • San Francisco • New York • TorontoMontreal • London • Munich • Paris • Madrid • CapetownSydney • Tokyo • Singapore • Mexico City
БИБАИПТЕКА ПРОГРАММИСТА
Джереми Сик, Лай-Кван Ли, Эндрю Ламсдэйн
C++
Boost Graph Library
[^ППТЕР^
Москва - Санкт-Петербург - Нижний Новгород • ВоронежРостов-на-Дону - Екатеринбург - Самара • НовосибирскКиев • Харьков • Минск
2006
Джереми Сик, Лай-Кван 〃и, Эндрю Ламсдэйн
C++ Boost Graph Library. Библиотека программиста
Перевел с английского Р. Сузи
Главный редакторЕ、Строганова
Заведующий редакциейА. Кривцов
Руководитель проектаА, Адаменко
Научный редакторВ. Лаптев
ИллюстрацииВ. Демидова, Л. Родионова
Литературный редактор£. Яковлева
ХудожникЛ. Адуеве кая
КорректораЯ Викторова, Я Смирнова
ВерсткаД. Родионова
ББК 32,973-018.1
УДК 004.43
Дж. Снк, Л> Ли, Э. Ламсдэйн
С35 C++ Boost Graph Library. Библиотека программиста / Пер. с английского Сузи Р.— СПб.: Питер, 2006. — 304 и: ил.
ISBN 5-469-00352-3
Издание, являющееся переводом одной нз книг серин «C++ in Depth», посвящено описанию Boost Graph Library (BGL) — библиотеки для построения структур данных и алгоритмов вычислений на графах, предназначенных для решения самых разнообразных задач: от оптимизации интернет- маршрутизацми и пла>гирования телефонных сетей до задач молекулярной биологии. Содержэт развернутое описание BGL. демонстрирует примеры приложений к реальным задачам. Первая часть является полным руководством пользователя, начинается с введения понятий теории графов, терминологии и описания обобщенных алгоритмов на графах, знакомит пользователя со всеми основными возможностями библиотеки BGL. Вторая часть — полное справочное руководство, содержит документацию ко всем концепш1ям BGL, ее алгоритмам и классам.
© Pearson Education I пи, 2002
© Перевод на русский языкт ЗАО Издательский дом «Питер», 2006
© Издание на русском языке, оформление, ЗАО Издательский дом «Питер», 2006
Права на издание получены по соглашению с Addison-Wesley Longman,
Все права защищены Никакая часть данной книги не может быть воспроизведена 8 »сакой бы то ни было форме без письменного разрешения владельцев авторских пран,
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полнот/ приводимых сведений и не несет ответственность за возможные ошибки, связанные с использованием книги
ISBN 5-469-00352-3
ISBN 0-201-72914-8 (англ.)
ООО «Пнтер Принт», 194044. Санкт-Петербург, Б. Сампсониевскнй пр., д. 29а.
Лицензия ИД № 05 784 от 07.09.0L
Налоговая льгота — обшероссийскнй классификатор продукции ОК 005-93, том 2; 95 3005 — литература учебная.
Подписано к печаги 2 8.09.05. Формат 70x100/16. Уел. п. m 24,51. Тираж 2000. 3аказ3 75
Отпечатано с готовых диапозитивов в ОАО «Техническая книга»
190005, Санкт-Петербург, Измайловский пр” 29
Краткое содержание
Предисловие И
Введение 15
Часть I. Руководство пользователя 23
Глава 1. Введение 24
Глава 2. Обобщенное программирование в C++ 39
Глава 3. Изучаем BGL... 59
Глава 4. Основные алгоритмы на графах 77
Глава 5. Задачи нахождения кратчайших путей*…一 89
Глава 6. Задача минимального остовного дерева102
Глава 7. Компоненты связности 109
Глава 8. Максимальный поток 117
Глава 9. Неявные графы: обход конем 124
Глава 10. Взаимодействие с другими графовыми библиотеками .,…,.130
Глава 11. Руководство по производительности 137
Часть II. Справочное руководство 145
Глава 12. Концепции BGL 146
Глава 13. Алгоритмы BGL 170
Глава 14. Классы BGL 215
Глава 15. Библиотека отображений свойств 274
Глава 16. Вспомогательные концепции, классы и функции 284
Библиография 294
Дополнение к библиографии 297
Алфавитный указатель 299
Содержание
11151115Предисловие
11
15
11
15
Введение..,.
Обобщенное программирование Немного из истории BGL Что такое Boost?
Получение и установка программного обеспечения BGL ......#
Как пользоваться книгой
Грамотное программирование
Благодарности
Лицензия *
От издательства
Часть I. Руководство пользователя