Автор работы: Пользователь скрыл имя, 11 Января 2014 в 04:39, реферат
1. Графы являются моделью представления данных, основанных на отношениях между элементами множеств.
2. Для представления графов используется несколько способов: список ребер, матрица смежности, матрица инцидентности.
3. Для организации поиска на графах используются обходы в глубину и в ширину.
4. Реализацию обходов можно осуществлять рекурсивными и нерекурсивными алгоритмами.
5. От вида графа и способа его представления зависит временная сложность выполнения алгоритма.
Введение………………………………………………………………………... 3
1 Алгоритмы обхода графа……………………………………………………. 5
1.1 Поиск в глубину…………………………………………………….. 6
1.2 Поиск в ширину……………………………………………………... 7
2 Алгоритмы нахождения кратчайшего пути………………………………... 9
2.1 Алгоритм Дейкстры………………………………………………… 10
2.2 Алгоритм Флойда…………………………………………………… 12
2.3 Переборные алгоритмы…………………………………………….. 15
Заключение…………………………………………………………………….. 20
Шаг 4. Вес всех невыделенных вершин пересчитывается по формуле: вес невыделенной вершины есть минимальное число из старого веса данной вершины, суммы веса текущей вершины и веса ребра, соединяющего текущую вершину с невыделенной.
Шаг 5. Среди невыделенных вершин ищется вершина с минимальным весом. Если таковая не найдена, то есть вес всех вершин равен бесконечности, то маршрут не существует. Следовательно, выход. Иначе, текущей становится найденная вершина. Она же выделяется.
Шаг 6. Если текущей вершиной оказывается конечная, то путь найден, и его вес есть вес конечной вершины.
Шаг 7. Переход на шаг 4.
В программной реализации алгоритма Дейкстры построим множество S вершин, для которых кратчайшие пути от начальной вершины уже известны. На каждом шаге к множеству S добавляется та из оставшихся вершин, расстояние до которой от начальной вершины меньше, чем для других оставшихся вершин. При этом будем использовать массив D, в который записываются длины кратчайших путей для каждой вершины. Когда множество S будет содержать все вершины графа, тогда массив D будет содержать длины кратчайших путей от начальной вершины к каждой вершине.
Помимо указанных массивов будем использовать матрицу длин C, где элемент C[i,j] –длина ребра (i,j), если ребра нет, то ее длина полагается равной бесконечности, то есть больше любой фактической длины ребер. Фактически матрица C представляет собой матрицу смежности, в которой все нулевые элементы заменены на бесконечность.
Для определения самого кратчайшего пути введем массив P вершин, где P[v] будет содержать вершину, непосредственно предшествующую вершине v в кратчайшем пути (рис. 7).
Рис. 7. Демонстрация
алгоритма Дейкстры
//Описание функции алгоритма Дейкстры
void Dijkstra(int n, int **Graph, int Node){
bool *S = new bool[n];
int *D = new int[n];
int *P = new int[n];
int I, j;
int Max_Sum = 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++){
S[i] = false;
P[i] = Node;
D[i] = Graph[Node][i];
}
S[Node] = true;
P[Node] = -1;
for ( I = 0 ; I < n – 1 ; i++ ){
int w = 0;
for ( j = 1 ; j < n ; j++ ){
if (!S[w]){
if (!S[j] && D[j] <= D[w])
w = j;
}
else w++;
}
S[w] = true;
for ( j = 1 ; j < n ; j++ )
if (!S[j])
if (D[w] + Graph[w][j] < D[j]){
D[j] = D[w] + Graph[w][j];
P[j] = w;
}
}
for ( I = 0 ; I < n ; i++ )
printf(“%5d”,D[i]);
cout << endl;
for ( I = 0 ; I < n ; i++ )
printf(“%5d”,P[i]+1);
cout << endl;
delete [] P;
delete [] D;
delete [] S;
}
Сложность алгоритма Дейкстры зависит от способа нахождения вершины, а также способа хранения множества непосещенных вершин и способа обновления длин.
Если для представления графа использовать матрицу смежности, то время выполнения этого алгоритма имеет порядок O(n2), где n – количество вершин графа.
2.2 Алгоритм Флойда
Рассматриваемый алгоритм иногда называют алгоритмом Флойда-Уоршелла. Алгоритм Флойда – Уоршелла является алгоритмом на графах, который разработан в 1962 году Робертом Флойдом и Стивеном Уоршеллом. Он служит для нахождения кратчайших путей между всеми парами вершин графа.
Метод Флойда непосредственно основывается на том факте, что в графе с положительными весами ребер всякий неэлементарный (содержащий более 1 ребра) кратчайший путь состоит из других кратчайших путей.
Этот алгоритм более общий по сравнению с алгоритмом Дейкстры, так как он находит кратчайшие пути между любыми двумя вершинами графа.
В алгоритме Флойда используется матрица A размером nxn, в которой вычисляются длины кратчайших путей. Элемент A[i,j] равен расстоянию от вершины i к вершине j, которое имеет конечное значение, если существует ребро (i,j), и равен бесконечности в противном случае.
Основная идея алгоритма. Пусть есть три вершины i, j, k и заданы расстояния между ними. Если выполняется неравенство A[i,k]+A[k,j]<A[i,j], то целесообразно заменить путь i->j путем i->k->j. Такая замена выполняется систематически в процессе выполнения данного алгоритма.
Шаг 0. Определяем начальную матрицу расстояния A0 и матрицу последовательности вершин S0. Каждый диагональный элемент обеих матриц равен 0, таким образом, показывая, что эти элементы в вычислениях не участвуют. Полагаем k = 1.
Основной шаг k. Задаем строку k и столбец k как ведущую строку и ведущий столбец. Рассматриваем возможность применения замены описанной выше, ко всем элементам A[i,j] матрицы Ak-1. Если выполняется неравенство , тогда выполняем следующие действия:
Таким образом, алгоритм Флойда делает n итераций, после i-й итерации матрица А будет содержать длины кратчайших путей между любыми двумя парами вершин при условии, что эти пути проходят через вершины от первой до i-й. На каждой итерации перебираются все пары вершин и путь между ними сокращается при помощи i-й вершины (рис. 8).
Рис. 8. Демонстрация
алгоритма Флойда
//Описание функции алгоритма Флойда
void Floyd(int n, int **Graph, int **ShortestPath){
int I, j, k;
int Max_Sum = 0;
for ( I = 0 ; I < n ; i++ )
for ( j = 0 ; j < n ; j++ )
Max_Sum += ShortestPath[i][j];
for ( I = 0 ; I < n ; i++ )
for ( j = 0 ; j < n ; j++ )
if ( ShortestPath[i][j] == 0 && I != j )
ShortestPath[i][j] = Max_Sum;
for ( k = 0 ; k < n; k++ )
for ( I = 0 ; I < n; i++ )
for ( j = 0 ; j < n ; j++ )
if ((ShortestPath[i][k] + ShortestPath[k][j]) <
ShortestPath[i][j])
ShortestPath[i][j] = ShortestPath[i][k] +
ShortestPath[k][j];
}
Заметим, что если граф неориентированный, то все матрицы, получаемые в результате преобразований симметричны и, следовательно, достаточно вычислять только элементы, расположенные выше главной диагонали.
Если граф представлен матрицей смежности, то время выполнения этого алгоритма имеет порядок O(n3), поскольку в нем присутствуют вложенные друг в друга три цикла.
2.3 Переборные алгоритмы
Переборные алгоритмы по сути своей являются алгоритмами поиска, как правило, поиска оптимального решения. При этом решение конструируется постепенно. В этом случае обычно говорят о переборе вершин дерева вариантов. Вершинами такого графа будут промежуточные или конечные варианты, а ребра будут указывать пути конструирования вариантов.
Рассмотрим переборные алгоритмы, основанные на методах поиска в графе, на примере задачи нахождения кратчайшего пути в лабиринте.
Постановка задачи.
Лабиринт, состоящий из проходимых и непроходимых клеток, задан матрицей A размером mxn. Элемент матрицы A[i,j]=0, если клетка (i,j) проходима. В противном случае .
Требуется найти длину кратчайшего пути из клетки (1, 1) в клетку (m, n).
Фактически дана матрица смежности (только в ней нули заменены бесконечностями, а единицы – нулями). Лабиринт представляет собой граф.
Вершинами дерева вариантов в данной задаче являются пути, начинающиеся в клетке (1, 1). Ребра – показывают ход конструирования этих путей и соединяют два пути длины k и k+1, где второй путь получается из первого добавлением к пути еще одного хода.
Перебор с возвратом
Данный метод основан на методе поиска в глубину. Перебор с возвратом считают методом проб и ошибок ("попробуем сходить в эту сторону: не получится – вернемся и попробуем в другую"). Так как перебор вариантов осуществляется методом поиска в глубину, то целесообразно во время работы алгоритма хранить текущий путь в дереве. Этот путь представляет собой стек Way.
Также необходим массив Dist, размерность которого соответствует количеству вершин графа, хранящий для каждой вершины расстояние от нее до исходной вершины.
Пусть текущей является некоторая клетка (в начале работы алгоритма – клетка (1, 1) ). Если для текущей клетки есть клетка-сосед Neighbor, отсутствующая в Way, в которую на этом пути еще не ходили, то добавляем Neighbor в Way и текущей клетке присваиваем Neighbor, иначе извлечь из Way.
Приведенное выше описание дает четко понять, почему этот метод называется перебором с возвратом. Возврату здесь соответствует операция "извлечь из Way ", которая уменьшает длину Way на 1.
Перебор заканчивается, когда Way пуст и делается попытка возврата назад. В этой ситуации возвращаться уже некуда (рис. 9).
Way является текущим путем, но в процессе работы необходимо хранить и оптимальный путь OptimalWay.
Усовершенствование алгоритма можно произвести следующим образом: не позволять, чтобы длина Way была больше или равна длине OptimalWay. В этом случае, если и будет найден какой-то вариант, он заведомо не будет оптимальным. Такое усовершенствование в общем случае означает, что как только текущий путь станет заведомо неоптимальным, надо вернуться назад. Данное улучшение алгоритма позволяет во многих случаях сильно сократить перебор.
Рис. 9. Демонстрация
алгоритма перебора с возвратом
/*Описание функции
void Backtracking(int n, int m, int **Maze){
int Begin, End, Current;
Begin = (n - 1) * m;
End = m - 1;
int *Way, *OptimalWay;
int LengthWay, LengthOptimalWay;
Way = new int[n*m];
OptimalWay = new int[n*m];
LengthWay = 0;
LengthOptimalWay = m*n;
for (int i = 0 ; i < n*m ; i++ )
Way[i] = OptimalWay[i] = -1;
int *Dist;
Dist = new int[n*m];
for (int i = 0 ; i < n ; i++ )
for (int j = 0 ; j < m ; j++ )
Dist[i * m + j] = ( Maze[i][j] == 0 ? 0 : -1 );
Way[LengthWay++] = Current = Begin;
while ( LengthWay > 0 ){
if(Current == End){
if (LengthWay < LengthOptimalWay){
for (int i = 0 ; i < LengthWay ; i++ )
OptimalWay[i] = Way[i];
LengthOptimalWay = LengthWay;
}
if (LengthWay > 0) Way[--LengthWay] = -1;
Current = Way[LengthWay-1];
}
else{
int Neighbor = -1;
if ((Current/m - 1) >= 0 && !Insert(Way, Current - m) &&
(Dist[Current - m] == 0 || Dist[Current - m] > LengthWay)
&& Dist[Current] < LengthOptimalWay)
Neighbor = Current - m;
else
if ((Current%m - 1) >= 0 && !Insert(Way,Current - 1)&&
(Dist[Current - 1]== 0 || Dist[Current - 1] > LengthWay)
&& Dist[Current] < LengthOptimalWay )
Neighbor = Current - 1;
else
if ((Current%m + 1) < m && !Insert(Way,Current + 1) &&
(Dist[Current + 1]== 0 || Dist[Current + 1] > LengthWay)
&& Dist[Current] < LengthOptimalWay )
Neighbor = Current + 1;
else
if ((Current/m + 1) < n && !Insert(Way,Current + m) &&
(Dist[Current + m]== 0 || Dist[Current + m] > LengthWay)
&& Dist[Current] < LengthOptimalWay )
Neighbor = Current + m;
if ( Neighbor != -1 ){
Way[LengthWay++] = Neighbor;
Dist[Neighbor] = Dist[Current] + 1;
Current = Neighbor;
}
else {
if (LengthWay > 0) Way[--LengthWay] = -1;
Current = Way[LengthWay-1];
}
}
}
if ( LengthOptimalWay < n*m )
cout << endl << "Yes. Length way=" << LengthOptimalWay<< endl;
else cout << endl << "No" << endl;
}
Волновой алгоритм
Этот переборный алгоритм, который основан на поиске в ширину, состоит из двух этапов:
Распространение волны и есть собственно поиск в ширину, при котором клетки помечаются номером шага метода, на котором клетка посещается. При обратном ходе, начиная с конечной вершины, идет восстановление пути, по которому в нее попали путем включения в него клеток с минимальной пометкой (рис. 10). Важной особенностью является то, что восстановление начинается с конца (с начала оно зачастую невозможно).
Рис. 10. Демонстрация
волнового алгоритма
Заметим, что перебор методом поиска в ширину по сравнению с перебором с возвратом, как правило, требует больше вспомогательной памяти, которая необходима для хранения информации, чтобы построить путь при обратном ходе и пометить посещенные вершины. Однако он работает быстрее, так как совершенно исключается посещение одной и той же клетки более чем один раз.
Заключение
1. Графы являются моделью представления данных, основанных на отношениях между элементами множеств.
2. Для представления графов используется несколько способов: список ребер, матрица смежности, матрица инцидентности.
3. Для организации поиска на графах используются обходы в глубину и в ширину.
4. Реализацию обходов можно осуществлять рекурсивными и нерекурсивными алгоритмами.
5. От вида графа и способа его представления зависит временная сложность выполнения алгоритма.
6. Нахождение кратчайшего пути на сегодняшний день является актуальной задачей.
7. К наиболее эффективным алгоритмам нахождения кратчайшего пути в графах относятся алгоритм Дейкстры, алгоритм Флойда и переборные алгоритмы. Эти алгоритмы эффективны при достаточно небольших количествах вершин.
8. В реализации алгоритма Дейкстры строится множество вершин, для которых кратчайшие пути от начальной вершины уже известны. Следующие шаги основаны на добавлении к имеющемуся множеству по одной вершине с сохранением длин оптимальных путей.
9. Сложность алгоритма Дейкстры зависит от способа нахождения вершины, а также способа хранения множества непосещенных вершин и способа обновления длин.
10. Метод Флойда основывается на факте, что в графе с положительными весами ребер всякий неэлементарный кратчайший путь состоит из других кратчайших путей.
11. Если граф представлен матрицей смежности, то время выполнения алгоритма Флойда имеет порядок O(n3).