Графы Основные понятия теории графов

advertisement
Графы
Основные понятия теории графов
Граф – это совокупность двух конечных множеств: множества точек и множества линий, попарно Вершина
соединяющих некоторые из этих точек.
Множество точек называется вершинами (узлами) графа. Множество линий, соединяющих вершины
графа, называются ребрами (дугами) графа.
Ориентированный граф (орграф) – граф, у коРебро
торого все ребра ориентированы, т.е. ребрам которого
присвоено направление.
Дуга
Неориентированный граф (неорграф) – граф, у
которого
все ребра не
ориентированы,
т.е. ребрам которого не задано направление.
Смешанный граф – граф, содержащий как ориентированные, так и неориентированные ребра.
Петлей называется ребро, соединяющее вершину саму с собой.
Две вершины называются смежными, если существует соединяющее их ребро.
Простой граф – это граф, в котором нет ни петель,
ни кратных ребер.
Путь (маршрут) – последовательность вершин, в которой следующая вершина (после первой) является смежной
с предыдущей – все вершины и ребра различны: (10, 8, 3, 5).
Длина пути — количество дуг (ребер), составляющих путь.
Путь (маршрут) называется открытым, если его
начальная и конечная вершины различны, в противном случае он называется замкнутым.
Цикл – это замкнутая цепь, у которой различны все
ее вершины, за исключением концевых (4, 6, 7, 8, 3, 4).
Связный граф – если для любой пары вершин существует соединяющий их путь.
Вес вершины – число (действительное, целое или рациональное), поставленное в соответствие данной вершине (интерпретируется как стоимость, пропускная способность и т.
д.).
Вес (длина) ребра – число или несколько чисел, которые интерпретируются по отношению к ребру как длина, пропускная способность и т. д.
Взвешенный граф – граф, каждому ребру которого поставлено в соответствие некое значение (вес ребра).
Полным называется граф, в котором проведены все возможные ребра. Для графа,
имеющего n вершин, таких ребер будет n(n-1)/2.
Граф называется связным, если все его вершины взаимно достижимы..
Если граф не является связным, то его можно разбить на связные подграфы, называемые компонентами.
Компонента связности – это максимальный связный подграф. В общем случае граф
может состоять из произвольного количества компонент связности. Любая изолированная
вершина является отдельной компонентой связности. Например, в приведённом ниже графе
содержится 4 компоненты связности: abhk, gd, c, f.
1
Способы представления графов в памяти
Графический способ представления графов непригоден для вычислительной машины.
Выбор структуры данных для хранения графа в памяти компьютера имеет принципиальное значение при разработке эффективных алгоритмов. Рассмотрим варианты представления
графов с помощью матриц смежности и инциндентности.
Матрица смежности
Матрица смежности – это матрица n×n, где n - число вершин. Строка с номером i
содержит 1 в строке с номером j, если существует дуга из вершины i в вершину j (для невзвешенного графа) или вес ребра (для взвешенного графа).
Данный способ рекомендуется использовать, когда надо проверять смежность или
находить вес ребра по двум заданным вершинам.
Задание 1. Составить матрицу смежности для взвешенного графа:
Решение:
a
b
c
d
a
0
1
10
0
b
1
0
2
10
с
10
2
0
3
d
0
10
3
0
Невзвешенный граф можно интерпретировать как взвешенный, все ребра которого
имеют одинаковый вес, например, 1.
1
2
3
4
5
6
Задание 2. Неориентированный граф задан матрицей смежности. Нарисовать граф.
Ответ:
1
2
3
4
5
6
0
1
1
0
0
0
1
2
0
0
0
1
1
0
0
1
0
0
0
0
4
0
0
0
0
1
0
6
5
3
0
0
1
0
0
0
0
0
0
0
0
0
Матрица смежности для неориентированного графа будет симметричной относительно своей главной диагонали, а для ориентированного графа - несимметричной.
Удобство матрицы смежности состоит в наглядности и прозрачности алгоритмов, основанных на ее использовании. А неудобство - в несколько завышенном требовании к памяти: если граф далек от полного, то в массиве, хранящем матрицу смежности, оказывается
много "пустых мест" (нулей).
2
Матрица инцидентности
Матрица инцидентности1 имеет размер m*n, где n – количество вершин, m – количество дуг графа. Элемент матрицы, соответствующий k-дуге и i-ой вершине, равен
 +1, если дуга выходит из вершины;
 –1, если дуга входит в вершину
 и нули во всех остальных строках.
Пример. Для неориентированного графа на рисунке матрица инцидентности:
Задание 1. Составить матрицу инцидентности для следующего
графа:
Ответ:
1
2
3
4
5
a
1
-1
0
0
0
b
0
1
-1
0
0
c
0
0
1
-1
0
d
-1
0
0
1
0
e
0
-1
0
1
0
Задание 2. Ориентированный граф задан матрицей инцидентности. Вершины обозначены номерами 1, 2, 3, 4, 5, 6, а ребра латинскими буквами a, b, c, d, e, f, g, h, i.
a b c d e f g h i
1 1 0 1 0 0 0 0 0 0 


2 1 1 0 1 0 1 0 0 0 
3 0 1 0 0 1 0 1 1 0 


4  0 0 0 0 0  1  1 0  1
5 0 0 1 1 1 0 0 0 0 


6  0 0 0 0 0 0 0  1 1 
Нарисовать граф.
Ответ:
3
a
1
2
b
d
c
6
f
3
e
h
4
5
i
Обход графов
Существует
много
алгоритмов
на
графах,
в
основе
которых
лежит систематический перебор вершин графа, такой, что каждая вершина просматривается
(посещается) в точности один раз. Поэтому важной задачей является нахождение хороших
методов поиска в графе.
Под обходом графов (поиском на графах) понимается процесс систематического
просмотра всех ребер или вершин графа с целью отыскания ребер или вершин, удовлетворяющих некоторому условию.
При решении многих задач, использующих графы, необходимы эффективные методы
регулярного обхода вершин и ребер графов. К стандартным и наиболее распространенным
методам относятся:
поиск в глубину (Depth First Search, DFS);
поиск в ширину (Breadth First Search, BFS).
Эти методы чаще всего рассматриваются на ориентированных графах, но они применимы и для неориентированных, ребра которых считаются двунаправленными. Алгоритмы обхода в глубину и в ширину лежат в основе решения различных задач обработки графов,
например, построения остовного леса, проверки связности, ацикличности, вычисления расстояний между вершинами и других.
Поиск в глубину
При поиске в глубину посещается первая вершина, затем необходимо идти вдоль ребер
графа, до попадания в тупик. Вершина графа является тупиком, если все смежные с ней
вершины уже посещены. После попадания в тупик нужно возвращаться назад вдоль пройденного пути, пока не будет обнаружена вершина, у которой есть еще не посещенная вершина, а затем необходимо двигаться в этом новом направлении. Процесс оказывается
завершенным при возвращении в начальную вершину, причем все смежные с ней вершины
уже должны быть посещены.
Таким образом, основная идея поиска в глубину – когда возможные пути по ребрам,
выходящим из вершин, разветвляются, нужно сначала полностью исследовать одну ветку и
только потом переходить к другим веткам (если они останутся нерассмотренными).
Алгоритм поиска в глубину
Шаг 1. Всем вершинам графа присваивается значение не использованная. Выбирается
первая вершина и помечается как использованная.
Шаг 2. Для последней помеченной как использованная вершина выбирается смежная вершина, являющаяся первой помеченной как не использованная, и ей присваивается
4
значение «использованная». Если таких вершин нет, то берется предыдущая помеченная вершина.
Шаг 3. Повторить шаг 2 до тех пор, пока все вершины не будут помечены как использованные ( рис. 5).
Рис. 5 Демонстрация алгоритма поиска в глубину
Пример
Для графа указать порядок включения вершин
в множество достижимых вершин, если поиск начинается с вершины v1, при обходе в первую очередь
предпочтение отдается вершине с меньшим номером.
Ответ: v1, v2, v3, v4,v5, v6, v7,v8, v9.
#include <iostream>
#include <fstream>
using namespace std;
//Описание рекурсивной функции для поиска в глубину
void DFS(int n, int **graph, bool *used, int Node)
{
used[Node] = true;
cout << Node + 1 << ' ';
for (int i = 0 ; i < n ; i++)
if (graph[Node][i]!=0 && !used[i])
DFS(n,graph,used,i);
}
void main()
{
int n,i,j;
ifstream f;
f.open("a.txt",ios::in);
cin>>n; //количество ребер
bool used[10];
int **a;
a=new int *[n];//задаем массив из n элементов, каждый из которых является адресом
строки
for (i=0;i<n;i++)
a[i]=new int[n]; //выделяем память под каждую строку массива
for (i=0;i<n;i++)
5
for (j=0;j<n;j++)
f>>a[i][j];
for (i=0;i<n;i++) used[i]=false;
DFS(n,a,used,0);
f.close();
}
Пример.
0101100
1010010
0100001
1000100
1101000
0100001
0010010
Ответ – 1,2,3,7,6,4,5.
Также часто используется нерекурсивный алгоритм поиска в глубину. В этом случае рекурсия заменяется на стек. В стек заносятся просмотренные вершины, у которых еще
есть смежные непросмотренные вершины. Как только у вершины не остается смежных
непросмотренных вершин, она удаляется из стека и берется следующая, помещенная туда.
Поместить в стек первую вершину;
Вывести ее на экран;
Отметить как помеченную;
while (стек не пуст)
{
while (не найдена очередная непросмотренная смежная вершина и не достигнут конец строки матрицы)
Переход к следующей вершине;
if (вершина найдена)
{
Взять вершину в качестве текущей;
Поместить ее в стек;
Пометить вершину как просмотренную;
Вывести вершину;
}
else
{
Удалить вершину из стека;
if (стек не пуст) извлечь очередную вершину их стека;
}
}
#include <iostream>
#include <fstream>
#include <stack>
using namespace std;
//Описание нерекурсивной функции для поиска в глубину
void DFS(int n, int **graph, bool *used, int Node)
{
stack <int> h;
h.push(Node);
used[Node] = true;
cout << Node + 1 << ' ';
int i=0;
while (!h.empty())
{
while ((graph[Node][i]==0 || used[i]) && i<n)
i++;
6
if (i<n)
{
Node=i;
h.push(Node);
used[Node] = true;
cout << Node + 1 << ' ';
i=0;
}
else
{
h.pop();
i=0;
if (!h.empty()) Node=h.top();
}
}
}
void main()
{
int n,i,j;
ifstream f;
f.open("a.txt",ios::in);
cin>>n; //количество ребер
bool used[10];
int **a;
a=new int *[n];//задаем массив из n элементов, каждый из которых является адресом строки
for (i=0;i<n;i++)
a[i]=new int[n]; //выделяем память под каждую строку массива
for (i=0;i<n;i++)
for (j=0;j<n;j++)
f>>a[i][j];
for (i=0;i<n;i++) used[i]=false;
DFS(n,a,used,0);
f.close();
}
Временная сложность зависит от представления графа. Если применена матрица
смежности, то временная сложность равна O(n2).
При выполнении обхода графа по этим правилам стремимся проникнуть "вглубь"
графа так далеко, как это возможно, затем отступаем на шаг назад и снова стремимся пройти
вперед и так далее.
Путь, полученный методом поиска в глубину, в общем случае не является кратчайшим путем из вершины v в вершину u.
Поиск в ширину
При поиске в ширину, после посещения первой вершины, посещаются все соседние с
ней вершины. Потом посещаются все вершины, находящиеся на расстоянии двух ребер от
начальной. При каждом новом шаге посещаются вершины, расстояние от которых до
начальной на единицу больше предыдущего. Чтобы предотвратить повторное посещение
вершин, необходимо вести список посещенных вершин. Для хранения временных данных,
необходимых для работы алгоритма, используется очередь – упорядоченная последовательность элементов, в которой новые элементы добавляются в конец, а старые удаляются из
начала.
Таким образом, основная идея поиска в ширину заключается в том, что сначала исследуются все вершины, смежные с начальной вершиной (вершина с которой начинается обход). Эти вершины находятся на расстоянии 1 от начальной. Затем исследуются все вершины
на расстоянии 2 от начальной, затем все на расстоянии 3 и т.д. Обратим внимание, что при
7
этом для каждой вершины сразу находятся длина кратчайшего маршрута от начальной вершины.
Алгоритм поиска в ширину
Шаг 1. Всем вершинам графа присваивается значение непосещенная. Выбирается первая вершина, помечается как посещенная и заносится в очередь.
Шаг 2. Считывается и удаляется первая вершина из очереди. Все ее соседние вершины
заносятся в очередь.
Шаг 3. Повторяется шаг 2 до тех пор, пока очередь не пуста.
Рис. Демонстрация алгоритма поиска в ширину
Пример:
Порядок включения вершин при использовании метода поиска в ширину: v1, v2, v3, v4,
v7, v8, v6, v5,v9.
//Описание функции поиска в ширину
void BFS(int n, int **graph, bool *used, int Node)
{
queue <int> list;
int i;
// начальная инициализация
list.push(Node);
used[Node] = true;
while (!list.empty())
{
Node=list.front();
list.pop();
cout << Node + 1 << ' ';
for (i = 0 ; i < n ; i++)
// просмотр всех вершин, связанных с вершиной Node
if (graph[Node][i]!=0 && !used[i]) // если вершина ранее не просмотрена
{
list.push(i);
used[i] = true;
}
}
}
Для приведенного выше графа (заданного с помощью матрицы смежности) получим 1
2 4 5 3 6 7.
Сложность поиска в ширину при использовании матрицы смежности приводит к
оценке O(n2)
Чаще всего поиск в ширину используется для нахождения кратчайшего пути от одной вершины X до другой – Y: нужно запустить поиск в ширину из вершины X и при до8
бавлении новой вершины в очередь смотреть, не является ли она вершиной Y. Если программа не завершит работу в условном цикле, это значит, что пути из X в Y не существует.
Задание
Для графа указать порядок включения
a
вершин в множество достижимых вершин,
2
1
если поиск начинается с вершины 1, в
b
первую очередь отдается предпочтение вершине с меньшим номером, и используется
f
d
3
c
а) метод поиска в глубину
б) метод поиска в ширину.
6
e
Ответ:
Поиск в глубину: 1, 2, 3, 4, 6, 5.
h
4
Поиск в ширину: 1, 2, 5, 3, 4, 6.
5
i
Поиск количества компонент связности графа
Использование обхода в глубину для поиска компонент связности графа:
void komp_sv(int n, int **graph, bool *used, int &k)
{
for (int i=0;i<n;i++)
// перебираем все вершины графа
if (!used[i])
// текущая вершина не помечена
{
DFS(n,graph,used,i);
// запуск поиска в глубину
k++;
// увеличение числа найденных
компонент связности
}
}
Задача нахождения кратчайшего пути. Алгоритм Дейкстры
Пусть есть ориентированный граф, у которого все дуги имеют неотрицательные метки
(стоимости дуг), а одна вершина определена как источник. Задача состоит в нахождении
стоимости кратчайших путей от источника ко всем другим вершинам графа G (здесь длина
пути определяется как сумма стоимостей дуг, составляющих путь). Эта задача часто называется задачей нахождения кратчайшего пути с одним источником.
Пример. Рассмотрим работу алгоритма Дейкстры на примере графа, показанного на
рисунке. Пусть требуется найти расстояния от 1-й вершины до всех остальных. Кружками
обозначены вершины, линиями — пути между ними (ребра графа).
9
В кружках обозначены номера вершин, над Первый шаг. Минимальную метку имеет
ребрами обозначена их «цена» — длина пути. вершина 1. Ее соседями являются вершины
Рядом с каждой вершиной красным обозначе- 2, 3 и 6.
на метка — длина кратчайшего пути в эту
вершину из вершины 1
Первый по очереди сосед вершины 1 — верАналогичную операцию проделывашина 2, потому что длина пути до нее мини- ем с двумя другими соседями 1-й вершины
мальна. Длина пути в нее через вершину 1 — 3-й и 6-й.
равна кратчайшему расстоянию до вершины 1
+ длина ребра, идущего из 1 в 2, то есть 0 + 7
= 7. Это меньше текущей метки вершины 2,
поэтому новая метка 2-й вершины равна 7.
10
Все соседи вершины 1 проверены. Текущее
минимальное расстояние до вершины 1 считается окончательным и пересмотру не подлежит (то, что это действительно так, впервые доказал Дейкстра). Вычеркнем её из
графа, чтобы отметить, что эта вершина посещена.
Второй шаг. Шаг алгоритма повторяется.
Снова пытаемся уменьшить метки
Снова находим «ближайшую» из непосещен- соседей выбранной вершины, пытаясь пройных вершин. Это вершина 2 с меткой 7.
ти в них через 2-ю. Соседями вершины 2
являются 1, 3, 4.
Первый (по порядку) сосед вершины
2 — вершина 1. Но она уже посещена, поэтому с 1-й вершиной ничего не делаем.
11
Следующий сосед вершины 2 — вершина 4. Если идти в неё через 2-ю, то длина
такого пути будет = кратчайшее расстояние до
2 + расстояние между вершинами 2 и 4 = 7 +
15 = 22. Поскольку 22<∞, устанавливаем метку вершины 4 равной 22.
Ещё один сосед вершины 2 — вершина 3.
Если идти в неё через 2, то длина такого пути будет = 7 + 10 = 17. Но текущая метка
третьей вершины равна 9<17, поэтому метка
не меняется.
Все соседи вершины 2 просмотрены, Третий шаг. Повторяем шаг алгоритма,
замораживаем расстояние до неё и помечаем выбрав вершину 3. После её «обработки»
её как посещенную.
получим такие результаты:
Повторяем шаг алгоритма для оставшихся вершин (Это будут по порядку 6, 4 и 5).
12
Завершение выполнения алгоритма. Алгоритм заканчивает работу, когда вычеркнуты
все вершины. Результат его работы: кратчайший путь от вершины 1 до 2-й составляет 7, до
3-й — 9, до 4-й — 20, до 5-й — 20, до 6-й — 11.
Поскольку алгоритм Дейкстры всякий раз выбирает для обработки вершины с
наименьшей оценкой кратчайшего пути, можно сказать, что он относится к «жадным» алгоритмам.
Пример
Граф задан матрицей смежности.
1 2 3 4 5
1 0

2 0
3 0

4 0

5 0
Ответ: 60
#include <iostream>
#include <fstream>
#include <set>
using namespace std;
void Dijkstra(int n, int **Graph, int Node1, int Node2)
{
set <int> S;//множество непросмотренных вершин
int *D = new int[n];//метки вершин
int i, j, m, p, k;
int Max_Sum = 0;
Node1--;//так как нумерация с 0
13
10 0 30 100 

0 50 0 0 
0 0 0 10 

0 20 0 60 
0 0 0 0 
Node2--;//так как нумерация с 0
for (i = 0 ; i < n ; i++)
for (j = 0 ; j < n ; j++)
Max_Sum += Graph[i][j];//вычисление суммы элементов (максимально возможное значение)
for (i = 0 ; i < n ; i++)
for (j = 0 ; j < n ; j++)
if (Graph[i][j] == 0)
Graph[i][j] = Max_Sum;//если путь к вершине отсутствует, то считаем его равным
максимальному значению
for (i=0;i<n;i++)
{
D[i]=Max_Sum;//ставим все метки равными максимальному значению
S.insert(i);//заносим вершины в множество непросмотренных
}
p=Node1;//текущая просматриваемая вершина
D[Node1]=0;//путь к первому элементу равен 0
while (p!=Node2) //пока просматриваемая вершина не совпала с конечной
{
for (i=0;i<n;i++) //перебираем все вершины
if (S.find(i)!=S.end()) //если вершина не просмотрена
{
if (Graph[p][i]<Max_Sum) //вершина смежна с текущей
if (D[i]> D[p]+Graph[p][i]) //метка вершины больше суммы метки просматриваемой
вершины и длины пути из нее до данной вершины
{
D[i]= D[p]+Graph[p][i]; //замена метки на меньшее значение
}
}
S.erase(p);//удаляем из множества вершину как просмотренную
m=Node2;//m - переменная, где будет храниться номер ближайшей смежной вершины (в начале
считаем таковой конечную вершину)
for (i=0;i<n;i++)//перебираем все вершины
if (S.find(i)!=S.end()) //если вершина не просмотрена
if (D[m]>D[i]) m=i; //ищем ближайшую смежную вершину
p=m;
//переходим к ближаайше смежной вершине
}
cout<<D[Node2]; //вывод результата
}
void main()
{
int n,i,j,k1,k2;
ifstream f;
f.open("a.txt",ios::in);
cin>>n; //количество ребер
cin>>k1;//начальная вершина
cin>>k2;//конечная вершина
int **a;
a=new int *[n];//задаем массив из n элементов, каждый из которых является адресом
строки
for (i=0;i<n;i++)
a[i]=new int[n]; //выделяем память под каждую строку массива
for (i=0;i<n;i++)
for (j=0;j<n;j++)
f>>a[i][j];
Dijkstra(n, a, k1,k2 );
f.close();
}
14
Топологическая сортировка
Топологической сортировкой называют порядок нумерации вершин ориентированного графа, при котором любое ребро идет из вершины с меньшим номером в вершину с
большим.
Задача может быть сформулирована следующим образом: перечислить вершины графа в таком порядке, чтобы для любого ребра графа его начальная вершина была перечислена
раньше конечной.
1
2
1
3
3
2
4
4
При помощи топологической сортировки строится корректная последовательность
выполнения действий, всякое из которых может зависеть от другого: последовательность
прохождения учебных курсов студентами, установки программ при помощи пакетного менеджера.
Топологическая сортировка существует для ациклических графов и не существует для
циклических.
1. Помечаем все вершины графа как неиспользованные. Проставляем степени вершин
(количество входящих дуг)
2. Ищем неиспользованную вершину, в которую не входит ни одна дуга (степень равна 0). Такая вершина всегда существует, если в графе нет контуров. Если их несколько, то
перебираем их по очереди и для каждой выполняем пункты 3 и 4.
3. Помечаем найденную вершину как использованную, печатаем ее номер.
4. Удаляем из графа все дги с началом в этой вершине (обнуляется соответствующая
строка матрицы смежности).
5. Переходим к шагу 2.
Задание 1.
Выполнить для графа, заданного с помощью матрицы инцидентности, топологическую сортировку.
a
b
c
d
e
f
g
h
i
j
k
1 1
1
0
0
0
1
0
1
0
1
0 


2  1 0
1
0
1
0
1
0
0
0
0 
3 0
0
0 1 0
0 1 0
0
0
0 


4 0
0
0
0 1 1 0
0
0
0
0 
5 0  1  1 1
0
0
0
0
0
0
1 


6 0
0
0
0
0
0
0 1 1 0
0 


7 0
0
0
0
0
0
0
0
1
0  1
8  0
0
0
0
0
0
0
0
0  1 0 
Шаг 1. Проставляем степени вершин. Неиспользованная вершина, в которую не входит ни одно ребро – вершина №1. Записываем ее номер: 1. Удаляем из графа все ребра с
началом в этой вершине.
15
0
1
1
2
3
2
1
8
4
2
1
7
6
5
2
2
Шаг 2. Проставляем степени вершин. Неиспользованные вершины, в которые не входит ни одно ребро – вершины №2 и №8. Записываем номера: 2, 8 (предпочтение отдается
вершине с меньшим номером). Удаляем из графа все ребра с началом в этой вершине.
0
2
3 2
0
8
4
1
1
7
6
5
1
1
Шаг 3. Проставляем степени вершин. Неиспользованные вершины, в которые не входит ни одно ребро – вершины №4 и №5. Записываем номера: 4, 5 (предпочтение отдается
вершине с меньшим номером). Удаляем из графа все ребра с началом в этой вершине.
3
1
4
1
0
7
6
1
5
0
Шаг 4. Проставляем степени вершин. Неиспользованные вершины, в которые не входит ни одно ребро – вершины №3 и №7. Записываем номера: 3, 7 (предпочтение отдается
вершине с меньшим номером). Удаляем из графа все ребра с началом в этой вершине.
16
3
0
0
7
6
1
Шаг 5. Проставляем степени вершин. Неиспользованная вершина, в которую не входит ни одно ребро – вершина №6. Записываем номер: 6. Удаляем из графа все ребра с началом в этой вершине.
6
0
Ответ: 1 | 2, 8| 4, 5| 3, 7| 6.
Каркас. Алгоритм Прима
Остовное дерево или каркас графа - подграф графа, который:
1) содержит все вершины графа,
2) является деревом (не содержит циклов).
Задача. Найти минимальное остовное дерево в неориентированном взвешенном графе
G.
Алгоритм (метод Прима):
1) Выберем в графе G ребро минимальной длины. Вместе с инцидентными ему двумя
вершинами оно образует подграф G2 графа G. Положим i:=2.
2) Если i=n(G), то задача решена и Gi – искомое минимальное остовное дерево графа G.
Иначе переходим к шагу 3.
3) Строим граф Gi+1. Для этого добавим к графу Gi новое ребро минимальной длины
из оставшихся, которое инцидентно какой-либо вершине графа Gi и одновременно вершине,
не содержащейся в Gi. Вместе с этим ребром включаем в Gi+1 и эту инцидентную ему вершину. Присваиваем i:=i+1 и переходим к шагу 2.
Пример.
Найдем минимальное остовное дерево в неориентированном взвешенном графе, изображенном на рис.
Обозначим ребро, соединяющее вершины vi и vj через xij.
Для удобства использования приведенного выше алгоритма решения поставленной
задачи, составим матрицу длин ребер. В рассматриваемом графе количество вершин n=8,
17
следовательно, матрица длин ребер графа G будет иметь размерность 8×8 и выглядеть следующим образом:
Согласно приведенному выше алгоритму, выбираем ребро минимальной длины
(c47=1) и вместе с инцидентными ему двумя вершинами относим к графу G2. Таким образом,
.
Длина дерева G2 будет равна l(G2)=c47=1. Поскольку
, продолжаем работу алгоритма. По четвертой и седьмой строкам ищем минимальное число (за исключением использованной единицы) − ребро минимальной длины, инцидентное либо вершине v4, либо
v7. Выбираем ребро x46 с длиной c46=3 и вместе с вершиной v6 добавляем к графу G2, обозначая его теперь как G3:
, при этом l(G3)=c47+c46=1+3=4.
Аналогично находим графы:
,
Поскольку i=8=n(G), работа алгоритма заканчивается.
Таким образом, искомое минимальное остовное дерево графа G − граф G8, длина которого равна 22.
18
Related documents
Download