Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Методичка по C# / Часть 16. Структуры данных

.pdf
Скачиваний:
205
Добавлен:
12.02.2015
Размер:
1.17 Mб
Скачать

Структуры данных

Обход в глубину

Обход в ширину

Рис. 41. Порядок просмотра вершин графа.

Задания

Выполните обходы графа с Рис. 37, полагая в качестве начальной:

1)вершину с номером 1;

2)вершину с номером 4.

Следует отметить, что модификации алгоритмов обхода легли в основу многих других эффективных алгоритмов на графах. Например, алгоритм обхода в ширину позволяет находить кратчайший путь между вершинами а и b, а также циклы в неориентированном графе.

Замечание

Программная реализация рассмотренных алгоритмов будет приведена позже.

Алгоритмы нахождения кратчайших путей

Пусть дан взвешенный орграф G, у которого все веса неотрицательные. Нашей задачей является нахождение такого пути от одной вершины к другой, для которого сумма весов по маршруту принимает минимальное значение. Мы назовем такой путь кратчайшим.

Следует отметить, что вес может представлять собой комплексную характеристику. Например, для графа моделирующего схему дорог, весом ребра/дуги может являться только расстояние от одной вершины до другой, а может и целый комплекс величин, таких как стоимость, время и расстояние.

Как мы говорили ранее, кратчайший путь между вершинами а и b в обычном графе можно найти с помощью модификации алгоритма обхода в ширину. Для взвешенного графа обычно применяют алгоритм Дейкстры, который позволяет решить более общую задачу нахождение кратчайшего пути от произвольной вершины, называемой источником, до всех остальных вершин. Если необходимо найти кратчайшие пути между всеми парами вершин во взвешенном графе, то используют алгоритм Флойда.

Рассмотрим алгоритм Дейкстры и Флойда более подробно.

Алгоритм Дейкстры

Для алгоритма Дейкстры предполагается, что все вершины графа G поименованы целыми числами, т.е., определено множество вершин V={0, 1, 2, …, n-1}, причем вершина v0 считается источником. Граф G определяется массивом Сn×n, где сi,j равно стоимости ребра/дуги (i, j), если ребро/дуга (i, j) существует; в противном случае сi,j = ∞.

Алгоритм строит множество S вершин, для которых кратчайшие пути от источника уже известны. На каждом шаге к множеству S добавляется та из оставшихся вершин, расстояние

Стр. 392 из 510

Структуры данных

до которой от источника меньше, чем до других оставшихся вершин. Если стоимости ребер/дуг неотрицательны, то можно быть уверенным, что кратчайший путь проходит только через множество S. Назовем такой путь особым. Дополнительно используются:

1)массив D размерности n, в который записываются длины кратчайших особых путей от источника к каждой вершине;

2)массив P размерности n, в котором в pi записывается вершина, непосредственно предшествующая вершине i в кратчайшем пути.

Рассмотрим сам алгоритм.

1.Полагаем S={v0}.

2.Инициализируем массив D: для u от 1 до n (за исключением u=v0) D[u]=C[v0, u].

3.Инициализируем массив P: для u от 1 до n (за исключением u=v0) P[u]=v0.

4.Для i от 1 до n-1:

a.выбираем из множества V\S такую вершину w, что D[w] минимально;

b.добавляем w к множеству S;

c.для каждой вершины u из множества V\S: если D[u]> D[w]+C[w, u], то

D[u]=D[w]+C[w, u] и P[u]=w;

После завершения работы алгоритма для каждого u¹v0 D[u] хранит кратчайший путь от источника v0 до вершины u; массив Р позволит восстановить путь от источника до вершины u.

В качестве примера рассмотрим граф, представленный на Рис. 42.

Рис. 42. Взвешенный орграф

35

75

35

 

 

 

Для этого графа C =

30

∞ .

 

 

 

 

20

 

 

30

 

40

В качестве v0 берем вершину с номером 4. Тогда:

S={4}

D[0] = 40, D[1] = , D[2] = , D[3] = 30. P[0] = P[1] = P[2] = P[3] = 4.

Для i = 1: w = 3;

S = {4, 3};

D[0] = 40 < D[3]+C[3, 0] = 30+; D[1] = < D[3]+C[3, 1] = 30+;

Стр. 393 из 510

Структуры данных

D[2] = > D[3]+C[3, 2] = 30+20 D[2] = 50, P[2] = 3. Для i = 2:

w = 0;

S = {4, 3, 0};

D[1] = > D[0]+C[0, 1] = 40+35 D[1] = 75, P[1] = 0; D[2] = 50 < D[0]+C[0,2] = 40+.

Для i = 3: w = 3;

S = {5, 4, 1, 3};

D[2] = 75 < D[3]+C[3][2] = 50+30. Для i = 4:

w = 1;

S = {5, 4, 1, 3, 2}.

В итоге:

D[0] = 40, D[1] = 75, D[2] = 50, D[3] = 30; P[0] = 4, P[1] = 0, P[2] = 3, P[3] = 4.

Если теперь мы хотим определить стоимость кратчайшего пути от источника v0=4 до вершины 2, то нужно обратиться к D[2]. В данном случае кратчайший путь равен 50. Сам путь соответствует последовательности вершин 4, 3, 2, которую можно восстановить по массиву Р (т.к. Р[2]=3, то вершине 2 в кратчайшем пути предшествует вершина 3; в свою очередь P[3]=4, следовательно вершине 3 предшествует вершина 4).

Задание

Выполните поиск кратчайшего пути (и его длины) для графа, представленного на Рис. 40 с источником v0=3.

Алгоритм Флойда

Для алгоритма Флойда предполагается, что все вершины графа G поименованы целыми неотрицательными числами, т.е., задано множество вершин V={0, 1, 2, …, n-1}. Граф G определяется массивом An×n, в котором вычисляются длины кратчайших путей. Вначале ai,j равно стоимости ребра/дуги (i, j) для всех i¹j. Если ребро/дуга (i, j) не существует, то ai,j=¥. Каждый диагональный элемент равен нулю. Для восстановления кратчайшего пути используется матрица Pn×n. Вначале pi,j= -1 для всех i¹j, т.е., кратчайший путь из вершины i в вершину j состоит из одного ребра/дуги (i, j).

Над матрицей А выполняется n итераций. После итерации с номером k ai,j содержит значение наименьшей длины путей из вершины i в вершину j, которые не проходят через вершины с номером, большим k. Другими словами, между концевыми вершинами пути i и j могут находиться только вершины, номера которых меньше или равны k.

На k-той итерации для вычислении матрицы А применяется следующая формула:

Ak[i, j]=min(Ak-1[i, j], Ak-1[i, k]+Ak-1[k, j])

Другими словами, для вычисления Ak[i, j] проводится сравнение величины Ak-1[i, j] (т.е., стоимости пути от вершины i к вершине j без участия вершины k, или другой вершины с более высоким номером) с величиной Ak-1[i, k]+Ak-1[k, j] (т.е., стоимости пути от вершины i до вершины k плюс стоимость пути от вершины k до вершины j). Если путь через вершину k «дешевле», чем Ak-1[i, j], то величина Ak[i, j] изменяется, и запоминается текущая вершина,

т.е., Pk[i, j]=k.

В качестве примера рассмотрим граф, приведенный на Рис. 42. Для этого графа матрицы А и Р последовательно примут следующие значения:

Стр. 394 из 510

Структуры данных

Начальное состояние:

А

0

35

75

 

 

 

 

 

 

 

35

0

 

 

 

 

 

 

 

30

0

 

 

 

 

 

 

 

20

0

 

 

 

 

 

 

 

40

30

0

 

 

 

 

 

 

Итерации:

А0

0

35

75

 

35

0

110

 

30

0

 

20

0

 

40

75

30

0

А1

0

35

75

 

35

0

110

 

65

30

0

140

 

20

0

 

40

75

30

0

А2

0

35

75

 

35

0

110

 

65

30

0

140

 

85

50

20

0

 

40

75

30

0

А3

0

35

95

75

 

35

0

130

110

 

65

30

0

140

 

85

50

20

0

 

40

75

50

30

0

P

-1

-1

-1

-1

-1

 

-1

-1

-1

-1

-1

 

-1

-1

-1

-1

-1

 

-1

-1

-1

-1

-1

 

-1

-1

-1

-1

-1

P0

-1

-1

-1

-1

-1

 

-1

-1

-1

0

-1

 

-1

-1

-1

-1

-1

 

-1

-1

-1

-1

-1

 

-1

1

-1

-1

-1

P1

-1

-1

-1

-1

-1

 

-1

-1

-1

0

-1

 

1

-1

-1

1

-1

 

-1

-1

-1

-1

-1

 

-1

1

-1

-1

-1

P2

-1

-1

-1

-1

-1

 

-1

-1

-1

0

-1

 

1

-1

-1

1

-1

 

2

2

-1

-1

-1

 

-1

1

-1

-1

-1

P3

-1

-1

3

-1

-1

 

-1

-1

3

0

-1

 

1

-1

-1

1

-1

 

2

2

-1

-1

-1

 

-1

0

3

-1

-1

Стр. 395 из 510

Структуры данных

А4

0

35

95

75

P4

-1

-1

3

-1

-1

 

 

 

 

 

 

 

 

 

 

 

 

 

35

0

130

110

 

-1

-1

3

0

-1

 

 

 

 

 

 

 

 

 

 

 

 

 

65

30

0

140

 

1

-1

-1

1

-1

 

 

 

 

 

 

 

 

 

 

 

 

 

85

50

20

0

 

2

2

-1

-1

-1

 

 

 

 

 

 

 

 

 

 

 

 

 

40

75

50

30

0

 

-1

0

3

-1

-1

 

 

 

 

 

 

 

 

 

 

 

 

Если теперь мы хотим определить стоимость кратчайшего пути от вершины 4 до вершины 2, то нужно обратиться к А[4, 2]. В данном случае кратчайший путь равен 50. Сам путь соответствует последовательности вершин 4, 3, 2, который можно восстановить по массиву Р.

Задание

Примените алгоритм Флойда к графу, представленному на Рис. 40.

Программная реализация АТД «граф»

Реализацию АТД «граф» мы будем осуществлять с помощью следующего класса Graph:

using System; using System.IO; namespace Example

{

public class Graph

{

//вложенный класс для скрытия данных и алгоритмов private class Node

{

private int[,] array; //матрица смежности

//индексатор для обращения к матрице смежности public int this [int i, int j]

{

get

{

return array[i,j];

}

set

{

array[i,j] = value;

}

}

//свойство для получения числа строк/столбцов //матрицы смежности

public int Size

{

get

{

return array.GetLength(0);

}

}

//вспомогательный массив: если i-ый элемент массива равен //true, то i-ая вершина еще не просмотрена; если i-ый

Стр. 396 из 510

Структуры данных

//элемент равен false, то i-ая вершина просмотрена private bool[] nov;

//метод помечает все вершины графа как непросмотреные public void NovSet()

{

for (int i=0; i<Size; i++ )

{

nov[i] = true;

}

}

//конструктор вложенного класса, инициализирует матрицу // смежности и вспомогательный массив

public Node(int[,] a)

{

array = a;

nov = new bool[a.GetLength(0)];

}

//реализация алгоритма обхода графа в глубину public void Dfs(int v)

{

//просматриваем текущую вершину

Console.Write("{0} ", v);

nov[v] = false; //помечаем ее как просмотренную // в матрице смежности просматриваем строку с номером v for (int u = 0; u < Size; u++)

{

//если вершины v и u смежные, к тому же вершина u //не просмотрена,

if (array[v,u] != 0 && nov[u])

{

Dfs(u); // то рекурсивно просматриваем вершину

}

}

}

//реализация алгоритма обхода графа в ширину public void Bfs(int v)

{

Queue q = new Queue(); // инициализируем очередь

q.Add(v);

//помещаем вершину v в очередь

nov[v] = false;

// помечаем вершину v как просмотренную

while (!q.IsEmpty)

// пока очередь не пуста

{

 

 

 

v = q.Take(); //извлекаем вершину из очереди

Console.Write("{0} ", v);

//просматриваем ее

//находим все вершины

for (int u = 0; u < Size; u++)

{

// смежные с данной и еще не просмотренные if (array[v,u] != 0 && nov[u])

{

//помещаем их в очередь

Стр. 397 из 510

Структуры данных

q.Add(u);

//и помечаем как просмотренные nov[u] = false;

}

}

}

}

//реализация алгоритма Дейкстры

public long[] Dijkstr(int v, out int []p)

{

nov[v] = false; // помечаем вершину v как просмотренную //создаем матрицу с

int[,] c = new int [Size,Size]; for (int i = 0; i < Size; i++)

{

for (int u = 0; u < Size; u++)

{

if (array[i,u] == 0 || i == u)

{

c[i,u] = int.MaxValue;

}

else

{

c[i,u] = array[i,u];

}

}

}

//создаем матрицы d и p long[]d = new long [Size]; p = new int [Size];

for (int u = 0; u < Size; u++)

{

if (u != v)

{

d[u] = c[v,u]; p[u] = v;

}

}

for (int i = 0; i < Size-1; i++) // на каждом шаге цикла

{

//выбираем из множества V\S такую вершину w,

//что D[w] минимально

long min = int.MaxValue; int w = 0;

for (int u = 0; u < Size; u++)

{

if (nov[u] && min > d[u])

{

min = d[u]; w = u;

}

}

Стр. 398 из 510

Структуры данных

nov[w] = false; //помещаем w в множество S //для каждой вершины из множества V\S определяем //кратчайший путь от источника до этой вершины for (int u = 0; u < Size; u++)

{

long distance = d[w] + c[w,u]; if (nov[u] && d[u] > distance)

{

d[u] = distance; p[u] = w;

}

}

}

//в качестве результата возвращаем массив кратчайших //путей для заданного источника

return d;

}

//восстановление пути от вершины a до вершины b для //алгоритма Дейкстры

public void WayDijkstr(int a, int b, int[] p, ref Stack items)

{

items.Push(b); //помещаем вершину b в стек

if (a == p[b]) //если предыдущей для вершины b является //вершина а, то

{

items.Push(a); //помещаем а в стек и завершаем //восстановление пути

}

else //иначе метод рекурсивно вызывает сам себя для //поиска пути от вершины а до вершины, //предшествующей вершине b

{

WayDijkstr(a, p[b], p, ref items);

}

}

//реализация алгоритма Флойда public long[,] Floyd(out int [,]p)

{

int i,j,k;

//создаем массивы р и а

long[,] a = new long[Size, Size]; p = new int[Size, Size];

for (i = 0; i < Size; i++)

{

for (j = 0; j < Size; j++)

{

if (i == j)

{

a[i,j] = 0;

}

Стр. 399 из 510

Структуры данных

else

{

if (array[i,j] == 0)

{

a[i,j] = int.MaxValue;

}

else

{

a[i,j] = array[i,j];

}

}

p[i,j] = -1;

}

}

//осуществляем поиск кратчайших путей for(k = 0; k < Size; k++)

{

for (i = 0; i < Size; i++)

{

for (j = 0; j < Size; j++)

{

long distance = a[i, k] + a[k,j]; if (a[i,j] > distance)

{

a[i,j] = distance; p[i,j] = k;

}

}

}

}

return a; //в качестве результата возвращаем массив //кратчайших путей между всеми парами вершин

}

//восстановление пути от вершины a до вершины в //для алгоритма Флойда

public void WayFloyd(int a, int b, int[,] p, ref Queue items)

{

int k = p[a,b];

//если k != -1, то путь состоит более чем из двух вершин //а и b, и проходит через вершину k, поэтому

if (k != -1)

{

//рекурсивно восстанавливаем путь между вершинами

//а и k

WayFloyd(a, k, p, ref items);

items.Add(k); //помещаем вершину к в очередь //рекурсивно восстанавливаем путь между вершинами

//k и b

WayFloyd(k,b,p,ref items);

}

}

} //конец вложенного клаcса

Стр. 400 из 510

Структуры данных

private Node graph;//закрытое поле, реализующее АТД «граф»

public Graph(string name) //конструктор внешнего класса

{

using (StreamReader file = new StreamReader(name))

{

int n = int.Parse(file.ReadLine()); int[,] a = new int[n, n];

for (int i = 0; i < n; i++)

{

string line = file.ReadLine(); string[] mas = line.Split(' '); for (int j = 0; j < n; j++)

{

a[i, j] = int.Parse(mas[j]);

}

}

graph = new Node(a);

}

}

//метод выводит матрицу смежности на консольное окно public void Show ()

{

for (int i = 0; i < graph.Size; i++)

{

for (int j = 0; j < graph.Size; j++)

{

Console.Write("{0,4}", graph[i,j]);

}

Console.WriteLine();

}

}

public void Dfs(int v)

{

//помечаем все вершины графа как непросмотренные graph.NovSet();

graph.Dfs(v); //запускаем алгоритм обхода графа в глубину

Console.WriteLine();

}

public void Bfs(int v)

{

//помечаем все вершины графа как непросмотренные graph.NovSet();

graph.Bfs(v); //запускаем алгоритм обхода графа в ширину

Console.WriteLine();

}

public void Dijkstr(int v)

{

//помечаем все вершины графа как непросмотренные graph.NovSet();

int[] p;

Стр. 401 из 510