При необходимости
дополнительной информации о вершинах
и рёбрах, используются взвешенные
графы.
Если каждому ребру
графа приписано некоторое положительное
число, то такое число называется весом,
а сам граф называется взвешенным
графом.
Четвёрка <V,
E,
f,
g>
называется взвешенным
графом,
когда для вершины v
элемент f(v)
– вес вершины,
а для ребра e
элемент g(е)
– вес ребра.
Информацию
о весах рёбер во взвешенном графе
представляют с помощью матрицы весовW=wij
,
Пример
7.8.
Схема
автомобильных дорог с указанием их
протяжённости будет
описана матрицей весов:
7.7. Упорядочивание вершин и дуг орграфа.
Расчеты в задачах,
связанных с графами, заметно упрощаются,
если их элементы упорядочены. Под
упорядочиванием вершин
связного орграфа без циклов понимают
такое разбиение его вершин на группы,
при котором:
1) вершины первой
группы не имеют предшествующих вершин,
а вершины последней группы последующих;
2) вершины любой
другой группы не имеют предшествующих
в следующей группе;
3) вершины одной
и той же группы дугами не соединяются.
Аналогичным
образом вводится понятие упорядочения
дуг. В
результате упорядочения элементов
получают орграф, изоморфный исходному.
Упорядочение
элементов выполняется графическим или
матричным способом.
Графический способ
упорядочивание вершин, дуг орграфа
носит название алгоритма
Фалкерсона.
Алгоритм Фалкерсона для упорядочения вершин:
1. Находят вершины
графа, в которые не входит ни одна дуга.
Они образуют первую группу. Нумеруют
вершины группы в натуральном порядке
1, 2, … . При этом присвоение номеров
вершинам внутри группы может быть
сделано не единственным образом, что
не имеет значения.
2. Вычеркиваем все
пронумерованные вершины и дуги, из них
выходящие. В получившемся графе найдется,
по крайней мере, одна вершина, в которую
не входит ни одна дуга. Этим вершинам,
входящим во вторую группу, присваивается
очередной номер и т.д. Повторяем до тех
пор, пока все вершины не будут упорядочены
(пронумерованы).
Алгоритм Фалкерсона для упорядочения дуг:
1. Найти дуги, не
имеющие непосредственно предшествующих
(они образуют I
группу).
2. Вычеркнуть
найденные дуги; после этого появится,
по крайней мере, одна новая дуга, не
имеющая непосредственно предшествующей
(в графе без дуг I
группы). Такие дуги составляют II
группу. Повторять этот шаг, пока все
дуги не будут разбиты на группы. В
заключение упорядочения дугам присваивают
новые обозначения с индексами 1, 2, … .
Пример 7.11.
Графическим способом упорядочить
вершины и дуги заданного орграфа.
Решение.
Используя алгоритм Фалкерсона,
упорядочим вершины и дуги заданного
орграфа.
Вершина b
называется достижимой
из вершины а, если существует (a,b)-путь.
Пусть A – матрица
смежностей ориентированного графа G.
Элемент на
пересечении i-й строки и j-го столбца
матрицы An
равен
количеству путей длины n из i-й вершины
в j-ю вершину.
Получив матрицу
Bn=A+A2+…+An,
можно определить количество
путей из vi
в vj
длины, меньшей или равной n.
Пусть G=(V,E) –
ориентированный граф, содержащий n
вершин. Матрица P размерности n x n,
элементы которой задаются выражением
называется
матрицей
достижимости
(путевой матрицей) графа G.
Соседние файлы в папке Лекции_2
- #
- #
- #
- #
- #
- #
- #
- #
- #
- #
- #
Занятие 3-4: Графы — основные определения¶
Цель: Повторить на практике основные определения теории графов и освоить использование библиотеки визуализации NetworkX¶
Повторить¶
- Определение понятия граф, вершины, ребра, матрица смежности, ориентрованный и не ориентированный граф.
- Гамильтонов цикл.
- Задача коммивояжёра.
- Поиск длины кратчайшего пути при помощи алгоритма Дейкстры.
Основные определения¶
Графом называется конечное множество вершин и множество ребер. Каждому ребру сопоставлены две вершины – концы ребра.
В ориентированном графе одна вершина считается начальной, а другая – конечной.
Если некоторое ребро u соединяет две вершины A и B графа, то говорят, что ребро u инцидентно вершинам A и B, а вершины в свою очередь инцидентны ребру u.
Вершины, соединенные ребром, называются смежными.
Ребра называются кратными, если они соединяют одну и ту же пару вершин (а в случае ориентированного графа – если у них совпадают начала и концы).
Ребро называется петлей, если у него совпадают начало и конец.
Степенью вершины в неориентированном графе называется число инцидентных данной вершине ребер (при этом петля считается два раза, то есть степень – это количество «концов» ребер, входящих в вершину).
Путем на графе называется последовательность ребер, в которой конец одного ребра является началом следующего ребра. Начало первого ребра называется началом пути, конец последнего ребра – концом пути.
Если начало и конец пути совпадают, то такой путь называется циклом.
Путь, который проходит через каждую вершину не более одного раза называется простым путем. Аналогично определяется простой цикл.
Граф называется связным, если между любыми двумя его вершинами есть путь.
Если граф несвязный, то его можно разбить на несколько частей (подграфов), каждая из которых будет связной. Такие части называются компонентами связности.
Деревом называется связный граф не содержащий простых циклов.
Способы представления графов в памяти¶
Матрица смежности¶
При представлении графа матрицей смежности информация о ребрах графа хранится в квадратной матрице (двумерном списке), где элемент A[i][j]
равен 1
, если ребра i
и j
соединены ребром и равен 0
в противном случае.
Если граф неориентированный, то матрица смежности всегда симметрична относительно главной диагонали.
Задание 1 Напишите код, заполняющий матрицу смежности для графа, представленного на рисунке ниже
Список смежности¶
При представлении графа списками смежности для каждой вершины i
хранится список W[i]
смежных с ней вершин.
Например, для графа выше:
W[0] = [1, 2] W[1] = [0, 2] W[2] = [0, 1, 2]
Таким образом, весь граф можно представить одним списком, состоящим из вложенных списков смежности вершин.
W = [[1, 2], [0, 2], [0, 1, 2]]
В таком способе удобно перебирать ребра, выходящие из вершины i
(это просто список W[i]
), но сложно проверять наличие ребра между вершинами i
и j
– для этого необходимо проверить, содержится ли число j
в списке W[i]
.
Но в языке Python можно эту часть сделать более эффективной, если заменить списки на множества – тогда проверка существования ребра между двумя вершинами также будет выполняться за О(1)
.
Задание 2 — В ячейке ниже реализуйте код, который строит список смежности на основе множеств из матрицы смежности, графа из задания 1. Проверьте в коде существование ребер между вершинами графа, изображенного выше. Для каждой пары вершин, между которыми существует ребро, выведите True и False в противном случае.
Взвешенные графы¶
Очень часто рассматриваются графы, в которых каждому ребру приписана некоторая числовая характеристика — вес. Соответствующие графы называются взвешенными.
При представлении графа матрицей смежности вес ребра можно хранить в матрице, то есть A[i][j]
в данном случае будет равно весу ребра из i
в j
. При этом при отсутствии ребра можно хранить специальное значение, например, None
.
При представлении графа списками смежности можно поступить двумя способами. Можно в списках смежности хранить пару (кортеж) из двух элементов – номер конечной вершины и вес ребра. Но в этом случае неудобно проверять наличие ребра между двумя вершинами.
Другой способ – хранить списки смежности как ранее, а веса ребер хранить в отдельном ассоциативном массиве (dict
в Python), в котором ключом будет пара из двух номеров вершин (номер начальной и конечной вершины), а значением будет вес ребра между этими вершинами.
Гамильтонов цикл¶
Гамильтоновым циклом в графе называют цикл, проходящий через все вершины.
Гамильтонов путь — незамкнутый путь, проходящий через все вершины.
Ниже приведен переборный алгоритм поиска гамильтонова цикла в графе.
Если перенумеровать вершины в графе, то номера вершин в порядке следования их в гамильтоновом цикле образуют некоторую перестановку чисел от 1 до n
. Можно перебрать все возможные перестановки и для каждой из них проверить, что данная перестановка соответствует циклу на графе, то есть каждые два соседних элемента в перестановке, а также первый и последний элемент перестановки соединены ребром.
Алгоритм поиска перестановок во многом напоминает алгоритм обхода в глубину, но главное его отличие заключается в том, что если из какой-то вершины не удается продолжить путь дальше (то есть были рассмотрены все ребра и все возможные продолжения привели в тупик), то алгоритм возвращается в предыдущую вершину, при этом покинутая вершина «перекрашивается», то есть с нее снимается отметка о том, что эта вершина была посещена ранее. При этом алгоритм может вернуться в эту вершину еще раз, уже по другому пути (и даже обязан это сделать, если в графе существует гамильтонов путь, так как гамильтонов путь проходит через все вершины).
Пусть n
— число вершин в графе, вершины пронумерованы числами от 0
до n-1
. Граф задан матрицей смежности A
. В глобальной переменной Path
будет храниться список вершин, входящих в путь. Функция hamilton()
принимает в качестве параметра номер вершины, добавляемой к пути и возвращает значение True
, если удалось построить гамильтонов путь и False
, если не удалось. Причем если путь построить удалось, то построенный путь будет храниться в списке Path
:
Visited = [False] * n Path = [] def hamilton(curr): Path.append(curr) if len(Path) == n: if A[Path[0]][Path[-1]] == 1: return True else: Path.pop() return False Visited[curr] = True for next in range(n): if A[curr][next] == 1 and not Visited[next]: if hamilton(next): return True Visited[curr] = False Path.pop() return False
Функция hamilton()
прежде всего добавляет вершину curr
в конец списка Path
. При этом если длина списка стала равна n
, то есть все вершины включены в путь Path
, проверяется, что первая и последняя вершина в пути соединены ребром (это не требуется при помощи гамильтонова пути), если это так — то алгоритм возвращает True
(цикл найден), в противном случае из списка Path
удаляется последний элемент и алгоритм возвращает False
(цикл не найден).
Если же длина списка меньше n
, то вершина curr
отмечается, как посещенная и осуществляется перебор дальнейших продолжений. Последовательно перебираются все оставшиеся вершины next
и если вершина next
соединена ребром с curr
и вершина next
не была посещена, то алгоритм рекурсивно запускается из вершины next
, пытаясь сделать продолжение пути в вершину next
. При этом если рекурсивный вызов из вершины next
вернет True
, то есть удалось построить цикл, то алгоритм сразу же возвращает True
, при этом из списка Path
ничего не удаляется, поэтому Path
будет хранить полный гамильтонов цикл. Если же ни одно из продолжений не получилось, то осуществляется «откат» вершины curr
— она помечается, как непосещенная, удаляется из конца списка Path
и управление передается назад, на последнюю вершину в списке Path
.
Видно, что сложность алгоритма может быть не менее, чем n!
, поэтому для больших графов такой алгоритм непригоден.
Задание 3 — Для графа из задания 3 выведите на экран гамильтонов цикл.
Задача коммивояжера¶
Близкой задачей к задаче нахождения гамильтонова цикла является задача коммивояжера. Коммивояжеру необходимо посетить n
городов и вернуться домой. Коммивояжер не хочет посещать города более одного раза и при этом хочет проделать наиболее короткий путь. То есть в неориентированном взвешенном графе необходимо найти путь наименьшей стоимости.
Задача коммивояжера решается аналогично задаче о гамильтоновом пути, но при этом нужно перебрать все возможные пути. При замыкании пути нужно вычислить его вес (лучше это делать не в конце замыкания, а одновременно с добавлением следующей вершины увеличивать вес построенного фрагмента пути на вес рассмотренного ребра) и сравнить вес найденного пути с весом наилучшего известного пути.
Алгоритм Дейкстры¶
Алгоритм Дейкстры назван в честь голландского ученого Эдсгера Дейкстры (Edsger Dijkstra). Алгоритм был предложен в 1959 году для нахождения кратчайших путей от одной вершины до всех остальных в ориентированном взвешенном графе, при условии, что все ребра в графе имеют неотрицательные веса.
Рассмотрим две модели хранения взвешенного графа в памяти. В первой модели (матрица смежности) будем считать, что вес ребра из вершины i
в вершину j
равен w[i][j]
, то есть в матрице w
хранятся веса ребра для любых двух вершин. Если из вершины i
в вершину j
нет ребра, то w[i][j]==INF
для некоторого специального значения константы INF
. Значение INF
следует выбирать исходя из задачи.
Алгоритм Дейкстры относится к так называемым «жадным» алгоритмам. Пусть расстояние от начальной вершины start
до вершины i
хранится в массиве dist[i]
. Начальные значения dist[start]=0
, dist[i]=INF
для всех остальных вершин i
. То есть в самом начале алгоритму известен путь из вершины start
до вершины start
длины 0
, а до остальных вершин кратчайшие пути неизвестны. Между тем алгоритм будет постепенно улучшать значения в массиве dist
, в результате получит кратчайшие расстояния до всех вершин.
Основная идея для улучшения называется «релаксацией ребра». Пусть из вершины i
в вершину j
есть ребро веса w[i][j]
, при этом выполнено неравенство dist[i] + w[i][j] < dist[j]
. То есть можно построить маршрут из начальной вершины до вершины i
и добавить к нему ребро из i
в j
, и суммарная стоимость такого маршрута будет меньше, чем известная ранее стоимость маршрута из начальной вершины в вершину j
. Тогда можно улучшить значение dist[j]
, присвоив dist[j] = dist[i] + w[i][j]
.
В алгоритме Дейкстры вершины красятся в два цвета, будем говорить, что вершина «неокрашенная» или «окрашенная». Изначально все вершины неокрашенные. Если алгоритм Дейкстры покрасил вершину i
, то это означает, что найденное значение dist[i]
является наилучшим возможным и в последствии не будет улучшаться, то есть значение dist[i]
является кратчайшим расстоянием от начальной вершины до вершины i
. Если же вершина не покрашена, то величина dist[i]
для такой вершины i
равна кратчайшему пути из вершины start
до вершины i
, который проходит только по покрашенным вершинам (за исключением самой вершины i
).
На каждом шаге алгоритма Дейкстры красится одна новая вершина. В качестве такой вершины выбирается неокрашенная вершина i
с наименьшим значением D[i]
. Затем рассматриваются все ребра, исходящие из вершины i
, и производится релаксация этих ребер, то есть улучшаются расстояния до вершин, смежных с i
.
Алгоритм заканчивается, когда на очередном шаге не останется неокрашенных вершин или если расстояние до всех неокрашенных вершин будет равно INF
(то есть эти вершины являются недостижимыми).
Запишем алгоритм Дейкстры. Пусть — число вершин в графе, вершины пронумерованы от 0 до n-1
. Номер начальной вершины — start
и веса ребер хранятся в матрице w
:
INF = 10 ** 10 dist = [INF] * n dist[start] = 0 used = [False] * n min_dist = 0 min_vertex = start while min_dist < INF: i = min_vertex used[i] = True for j in range(n): if dist[i] + w[i][j] < dist[j]: dist[j] = dist[i] + w[i][j] min_dist = INF for j in range(n): if not used[j] and dist[j] < min_dist: min_dist = dist[j] min_vertex = j
Массив used
будет хранить информацию о том, была ли покрашена вершина. Сначала инициализируются массивы dist
и used
. Затем запускается внешний цикл алгоритма, который выбирает неокрашенную вершину с минимальным расстоянием, номер этой вершины хранится в переменной min_vertex
, а расстояние до этой вершины — в переменной min_dist
. Если же min_dist
оказывается равно INF
, то значит все неокрашенные вершины являются недостижимыми и алгоритм заканчивает свою работу. Иначе найденная вершина окрашивается и после этого релаксируются все ребра, исходящие из этой вершины.
Для восстановления ответа, то есть для нахождения пути из начальной вершины до всех остальных, необходимо построить дерево кратчайших путей. Это дерево будет состоять из тех ребер, которые были успешно срелаксированы в результате исполнения алгоритма. То есть если происходит релаксация ребра из i
в j
, то теперь кратчайший маршрут из вершины start
до вершины j
должен проходить через вершину i
и затем содержать ребро i-j
. Тем самым вершина i
становится предшественником вершины j
на кратчайшем пути из начальной вершины до вершины j
.
Рассмотрим реализацию алгоритм Дейкстры с восстановлением ответа на графе, хранимым в виде списка смежности на языке Python. Набор вершин, смежных с вершиной i
будет храниться в множестве w[i]
. Также необходимо хранить веса ребер, будем считать, что для хранения весов ребер используется словарь weight
, где ключом является кортеж из двух вершин. То есть вес ребра из i
в j
хранится в элементе weight[i, j]
словаря весов:
dist = [INF] * n dist[start] = 0 prev = [None] * n used = [False] * n min_dist = 0 min_vertex = start while min_dist < INF: i = min_vertex used[i] = True for j in w[i]: if dist[i] + weight[i, j] < dist[j]: dist[j] = dist[i] + weight[i, j] prev[j] = i min_dist = INF for i in range(n): if not used[i] and dist[i] < min_dist: min_dist = dist[i] min_vertex = i
Для нахождения кратчайшего пути из вершины start
до вершины j
будем переходить от каждой вершины к ее предшественнику:
path = [] while j is not None: path.append(j) j = prev[j] path = path[::-1]
Алгоритм Дейкстры применим только в том случае, когда веса всех ребер неотрицательные. Это гарантирует то, что после окраски расстояние до вершины не может быть улучшено. Если в графе могут быть ребра отрицательного веса, то следует использовать другие алгоритмы.
Задание 4 — Для ненаправленного взвешенного графа, заданного парами вершин и их весов найдите кратчайший путь из вершины 0 в вершину 3 с помощью алгоритма Дейкстры:
- (0, 1, вес = 10)
- (0, 2, вес = 40)
- (1, 2, вес = 15)
- (0, 3, вес = 20)
- (3, 1, вес = 5)
Библиотека NetworkX для визуального представления графов¶
При решении задач контеста удобным инструментом для визуализации графов из примеров является библитотека NetworkX.
Установка библиотеки:
Подключение библиотеки:
NetworkX предназначена для изучения структуры, динамики и функционирования сложных сетей. Она позволяет создавать и хранить графы в стандартных и нестандартных форматах, генерировать много типов случайных и классических графов, анализировать их структуру, строить сетевые модели и создавать новые алгоритмы.
Документация на библиотеку находится по адресу.
Классы графов¶
NetworkX содержит четыре класса графов:
- Graph — граф без кратных рёбер (петли допустимы)
- DiGraph — ориентированный граф без кратных рёбер (петли допустимы)
- MultiGraph — граф с кратными рёбрами (в том числе с кратными
петлями) - MultiDiGraph — ориентированный граф с кратными рёбрами (в том
числе с кратными петлями)
Внутреннее представление графов реализовано в виде списков смежности (словарь словарей словарей). Однако во избежании появления несогласованности, все операции с графами должны производится с использованием API функций библиотеки.
Вершины и рёбра¶
Вершиной может быть любой неизменяемый тип с вычислимой функцией hash().
Например, подойдут соедующие типы Python:
- str
- int
- float
- кортеж из строк и чисел
- frozenset (неизменяемое множество)
Рёбра представляют собой связь двух вершин и чаще вершины имеют привязанные к ним данные — свойства рёбер. Для указания веса ребра, используйте свойство weight.
Создание графа¶
Графы могут быть созданы тремя основными способами:
- явное добавление узлов и рёбер
G = nx.Graph() # создаём экземпляр графа G.add_edge(1, 2) # ребро добавляется сразу со своими вершинами G.add_edge(2, 3) # стандартный вес ребра weight=1 G.add_edge(3, 4, weight = 0.9) # можно задать weight сразу при создании ребра G.add_node(5) # изолированный узел можно добавить отдельно G.add_node(6, x = 1.5, y = -5.0, data = ['any']) # и сразу задать ему любые свойства
- генераторами графов — алгоритмами порождения стандартных сетевых
топологий
G = nx.complete_graph(10) # полносвязный граф с 10 вершинами G = nx.path_graph(10) # 10 узлов, расположенных "в линеечку" G = nx.cycle_graph(10) # 10 узлов, связанных кольцом G = nx.star_graph(5) # звезда с 1 узлом в середине и 5 узлами-лучами G = nx.balanced_tree(2, 3) # сбалансированное двоичное дерево высоты 3 G = nx.empty_graph(10) # граф с 10 вершинами без рёбер
- импорт данных графа из некоторого формата (обычно из файла)
d = {0: {1: {'weight': 10}, 2: {'weight': 20}}, 1: {0: {'weight': 10}, 3: {'weight': 30}}, 2: {0: {'weight': 20}}, 3: {1: {'weight': 30}}} G = nx.Graph(d) dd = nx.to_dict_of_dicts(G) # d == dd
Визуализация графа¶
Визуализация графов — нетривиальная задача! Существует много полноценных библиотек, предназначенных именно для этого: Cytoscape, Gephi, Graphviz или PGF/TikZ для LaTeX. Для их использования можно экспортировать граф из NetworkX в формат GraphML.
Однако, есть и самый простой способ визуализации, встроенный в саму библиотеку NetworkX, при подключении библиотеки
matplotlib.pyplot.
nx.draw(G) # отобразить граф при помощи Matplotlib nx.draw_circular(G) # Использовать расположение circular layout nx.draw_random(G) # Использовать расположение random layout nx.draw_spectral(G) # Использовать расположение spectral layout nx.draw_spring(G) # Использовать расположение spring layout nx.draw_shell(G) # Использовать расположение shell layout nx.draw_graphviz(G) # Использовать graphviz для расположения вершин
Перед выполнением примера ниже не забудьте установить библиотеку matplotlib:
pip install -U matplotlib
Пример визуализации графа №1¶
Выполните приведенный ниже пример в ячейке code
import matplotlib.pyplot as plt import networkx as nx G=nx.path_graph(8) nx.draw(G) plt.savefig("simple_path.png") # сохранить как png файл plt.show() # вывести на экран
Пример визуализации графа №2¶
Пример добавления этикеток на вершины и подкрашивания рёбер:
""" Отрисовка графа через matplotlib, с разными цветами. """ __author__ = """Aric Hagberg (hagberg@lanl.gov)""" import matplotlib.pyplot as plt import networkx as nx G=nx.cubical_graph() pos=nx.spring_layout(G) # позиции всех вершин # вершины nx.draw_networkx_nodes(G, pos, nodelist=[0,1,2,3], # список вершин node_color='r', # красный цвет node_size=500, # размер alpha=0.8) # прозрачность nx.draw_networkx_nodes(G, pos, nodelist=[4,5,6,7], node_color='b', node_size=500, alpha=0.8) # рёбра nx.draw_networkx_edges(G, pos, width=1.0, alpha=0.5) # все рёбра nx.draw_networkx_edges(G, pos, edgelist=[(0,1),(1,2),(2,3),(3,0)], width=8, alpha=0.5, edge_color='r') # красные рёбра nx.draw_networkx_edges(G, pos, edgelist=[(4,5),(5,6),(6,7),(7,4)], width=8, alpha=0.5, edge_color='b') # синие рёбра # добавим математические названия вершин labels={} labels[0]=r'$a$' labels[1]=r'$b$' labels[2]=r'$c$' labels[3]=r'$d$' labels[4]=r'$alpha$' labels[5]=r'$beta$' labels[6]=r'$gamma$' labels[7]=r'$delta$' nx.draw_networkx_labels(G, pos, labels, font_size=16) plt.axis('off') plt.savefig("labels_and_colors.png") # сохранить как png картинку plt.show() # вывести на экран
Выполните пример в ячейке ниже.
Пример визуализации графа №3¶
Ещё один пример добавления этикеток на вершины и подкрашивания рёбер:
""" Пример использования Graph как взешенного. """ __author__ = """Aric Hagberg (hagberg@lanl.gov)""" import matplotlib.pyplot as plt import networkx as nx G = nx.Graph() # добавляем рёбра и вершины G.add_edge('a', 'b', weight=0.6) G.add_edge('a', 'c', weight=0.2) G.add_edge('c', 'd', weight=0.1) G.add_edge('c', 'e', weight=0.7) G.add_edge('c', 'f', weight=0.9) G.add_edge('a', 'd', weight=0.3) elarge = [(u,v) for (u,v,d) in G.edges(data=True) if d['weight'] >0.5] # "тяжёлые" esmall = [(u,v) for (u,v,d) in G.edges(data=True) if d['weight'] <=0.5] # "лёгкие" pos = nx.spring_layout(G) # позиции всех вершин # вершины nx.draw_networkx_nodes(G, pos, node_size=700) # рёбра nx.draw_networkx_edges(G, pos, edgelist=elarge, width=6) # "тяжёлые" nx.draw_networkx_edges(G, pos, edgelist=esmall, width=6, alpha=0.5, edge_color='b', style='dashed') # "лёгкие" # метки nx.draw_networkx_labels(G,pos,font_size=20,font_family='sans-serif') plt.axis('off') plt.savefig("weighted_graph.png") # сохранить как png картинку plt.show() # вывести на экран
страницы: 1 2 3 4
Содержание
- Содержание
- Ориентированные графы
- Взвешенные графы
- Способы представления графов
- Матрица смежности
- Примечания
Ориентированные графы
Орграф — это граф, все рёбра которого имеют направление. Такие направленные рёбра называются дугами. На рисунках дуги изображаются стрелочками (см. рис. 11.6).
Рис. 11.6. Орграф
В отличие от рёбер, дуги соединяют две неравноправные вершины: одна из них называется началом дуги (дуга из неё исходит), вторая — концом дуги (дуга в неё входит). Можно сказать, что любое ребро — это пара дуг, направленных навстречу друг другу.
Если в графе присутствуют и рёбра, и дуги, то его называют смешанным.
Все основные понятия, определённые для неориентированных графов (инцидентность, смежность, достижимость, длина пути и т. п.), остаются в силе и для орграфов — нужно лишь заменить слово «ребро» словом «дуга». А немногие исключения связаны с различиями между рёбрами и дугами.
Степень вершины в орграфе — это не одно число, а пара чисел: первое характеризует количество исходящих из вершины дуг, а второе — количество входящих дуг.
Путь в орграфе — это последовательность вершин (без повторений), в которой любые две соседние вершины смежны, причём каждая вершина является одновременно концом одной дуги и началом следующей дуги. Например, в орграфе на рис. 11.6 нет пути, ведущего из вершины 2 в вершину 5. «Двигаться» по орграфу можно только в направлениях, заданных стрелками.
Таблица 11.2. Примеры ориентированных графов
Орграф | Вершины | Дуги |
---|---|---|
Чайнворд | Слова | Совпадение последней и первой букв (возможность связать два слова в цепочку) |
Стройка | Работы | Необходимое предшествование (например, стены нужно построить раньше, чем крышу, т. п.) |
Обучение | Курсы | Необходимое предшествование (например, курс по языку Pascal полезно изучить прежде, чем курс по Ada, и т. п.) |
Одевание ребенка | Предметы гардероба | Необходимое предшествование (например, носки должны быть надеты раньше, чем ботинки, и т. п.) |
Европейский город | Перекрёстки | Узкие улицы с односторонним движением |
Организация | Сотрудники | Иерархия (начальник — подчинённый) |
Взвешенные графы
Взвешенный (другое название: размеченный) граф (или орграф) — это граф (орграф), некоторым элементам которого (вершинам, рёбрам или дугам) сопоставлены числа. Наиболее часто встречаются графы с помеченными рёбрами. Числа–пометки носят различные названия: вес, длина, стоимость.
Замечание: Обычный (не взвешенный) граф можно интерпретировать как взвешенный, все рёбра которого имеют одинаковый вес 1.
Длина пути во взвешенном (связном) графе — это сумма длин (весов) тех рёбер, из которых состоит путь. Расстояние между вершинами — это, как и прежде, длина кратчайшего пути. Например, расстояние от вершины a до вершины d во взвешенном графе, изображённом на рис. 11.7, равно 6.
Рис. 11.7. Взвешенный граф
N–периферия вершины v — это множество вершин, расстояние до каждой из которых (от вершины v) не меньше, чем N.
Таблица 11.3. Примеры взвешенных графов
Граф | Вершины | Вес вершины | Рёбра (дуги) | Вес ребра (дуги) |
---|---|---|---|---|
Таможни | Государства | Площадь территории | Наличие наземной границы | Стоимость получения визы |
Переезды | Города | Стоимость ночёвки в гостинице | Дороги | Длина дороги |
Супер–чайнворд | Слова | — | Совпадение конца и начала слов (возможность «сцепить» слова) | Длина пересекающихся частей |
Карта | Государства | Цвет на карте | Наличие общей границы | — |
Сеть | Компьютеры | — | Сетевой кабель | Стоимость кабеля |
Способы представления графов
Существует довольно большое число разнообразных способов представления графов. Однако мы изложим здесь только самые полезные с точки зрения программирования.
Матрица смежности
Матрица смежности Sm — это квадратная матрица размером NxN (N — количество вершин в графе), заполненная единицами и нулями по следующему правилу:
Если в графе имеется ребро e, соединяющее вершины u и v, то Sm[u, v] = 1, в противном случае Sm[u, v] = 0.
Заметим, что данное определение подходит как ориентированным, так и неориентированным графам: матрица смежности для неориентированного графа будет симметричной относительно своей главной диагонали, а для орграфа — несимметричной.
Задать взвешенный граф при помощи матрицы смежности тоже возможно. Необходимо лишь внести небольшое изменение в определение:
Если в графе имеется ребро e, соединяющее вершины u и v, то Sm[u,v] = ves(e), в противном случае Sm[u,v] = 0.
Это хорошо согласуется с замечанием, сделанным в предыдущем пункте: невзвешенный граф можно интерпретировать как взвешенный, все рёбра которого имеют одинаковый вес 1.
Небольшое затруднение возникнет в том случае, если в графе разрешаются рёбра с весом 0. Тогда придётся хранить два массива: один с нулями и единицами, которые служат показателем наличия рёбер, а второй — с весами этих рёбер.
В качестве примера приведём матрицы смежности для трёх графов, изображённых на рис. 11.5, рис. 11.6 и рис. 11.7 (см. рис. 11.8).
Таблица 11.8. Примеры матриц смежности
a | b | c | d | f | 1 | 2 | 3 | 4 | 5 | a | b | c | d | |||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
a | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | a | 0 | 1 | 10 | 0 |
b | 1 | 0 | 1 | 1 | 1 | 2 | 0 | 0 | 0 | 0 | 0 | b | 1 | 0 | 2 | 10 |
c | 1 | 1 | 0 | 1 | 1 | 3 | 1 | 1 | 0 | 0 | 1 | c | 10 | 2 | 0 | 3 |
d | 0 | 1 | 1 | 0 | 1 | 4 | 0 | 0 | 1 | 0 | 0 | d | 0 | 10 | 3 | 0 |
f | 0 | 1 | 1 | 1 | 0 | 5 | 0 | 0 | 0 | 0 | 0 |
Удобство матрицы смежности состоит в наглядности и прозрачности алгоритмов, основанных на её использовании. А неудобство — в несколько завышенном требовании к памяти: если граф далёк от полного, то в массиве, хранящем матрицу смежности, оказывается много «пустых мест» (нулей). Кроме того, для «общения» с пользователем этот способ представления графов не слишком удобен: его лучше применять только для внутреннего представления данных.
страницы: 1 2 3 4
Примечания
Иногда дугам графа сопоставляются (приписываются) числа — дуге ставится в соответствие некоторое число называемое весом, или длиной, или стоимостью (ценой) дуги. Тогда граф называется графом со взвешенными дугами. Иногда веса (числа ) приписываются вершинам графа, и тогда получается граф со взвешенными вершинами. Если в графе веса приписаны и дугам, и вершинам, то он называется просто взвешенным.
При рассмотрении пути представленного последовательностью дуг за его вес (или длину, или стоимость) принимается число равное сумме весов всех дуг, входящих
Таким образом, когда слова «длина», «стоимость», «цена» и «вес» применяются к дугам, то они эквивалентны по содержанию, и в каждом конкретном случае выбирается такое слово, которое ближе подходит по смыслу и совпадает с принятым в литературе.
Рис. 1.3.
Длиной (или мощностью) пути называется число дуг, входящих в него.
Разберем несколько причин, почему граф
, который строит алгоритм Крускала, действительно остовное дерево графа:
-
У
нет циклов, поскольку алгоритм Крускала не может их создать
-
— связной граф, иначе мы могли бы добавить ребро между компонентами
без цикла
-
— охватывающий граф, который включает каждую вершину. Иначе мы могли бы добавить ребро из
в вершину, которая не входит в
, не вызывая цикла
-
Мы знаем, что у деревьев на
вершинах всегда
ребро, поэтому нам нужно добавить ровно
ребро
Теперь рассмотрим, почему у дерева Крускала
— минимальный вес. Представим минимальное расходящееся дерево
, которое не равно
. Когда мы проходим через процесс Крускала и добавляем ребра, мы добавляем в
ребро
, которое не является частью
.
Предположим, мы попытаемся добавить
в
. Это создаст цикл по основным свойствам деревьев. Вдоль этого цикла должно быть еще какое-то ребро
, которого нет в
.
— это дерево, и оно не может содержать все ребра этого цикла.
В этом примере показана гипотетическая часть
вместе с
и
:
Теперь удалим
из
и заменим его на
— это дерево
. Если просто менять одно ребро цикла на другое,
все равно прямое дерево
.
Процесс Крускала добавил ребро
, а не
. Это означает, что вес
меньше или равен весу
. Это делает
минимальным прямым деревом
, которое согласуется с
на одно ребро больше, чем
.
Мы можем повторять этот процесс, пока не получим минимальное остовное дерево, которое согласуется с
по каждому ребру. Это означает, что
должно быть минимальным остовным деревом.