На этом шаге мы рассмотрим алгоритм Флойда.
Этот алгоритм более общий по сравнению с алгоритмом Дейкстры, так как он находит кратчайшие пути между любыми двумя узлами сети. В этом алгоритме сеть представлена в виде квадратной матрицы с n строками и n столбцами. Элемент (i, j) равен расстоянию dij от узла i к узлу j, которое имеет конечное значение, если существует дуга (i, j), и равен бесконечности в противном случае.
Покажем сначала основную идею метода Флойда. Пусть есть три узла i, j и k и заданы расстояния между ними (рис. 1). Если выполняется неравенство dij + djk < dik, то целесообразно заменить путь i -> k путем i -> j -> k. Такая замена (далее ее будем условно называть треугольным оператором) выполняется систематически в процессе выполнения алгоритма Флойда.
Рис.1. Треугольный оператор
Алгоритм Флойда требует выполнения следующих действий.
Шаг 0. Определяем начальную матрицу расстояния D0 и матрицу последовательности узлов S0. Диагональные элементы обеих матриц помечаются знаком "-", показывающим, что эти элементы в вычислениях не участвуют. Полагаем k = 1:
Рис.2. Начальная ситуация
Основной шаг k. Задаем строку k и столбец k как ведущую строку и ведущий столбец. Рассматриваем возможность применения треугольного оператора ко всем элементам dij матрицы Dk-1. Если выполняется неравенство dik + dkj < dij, (i <> k, j <> k, i <> j), тогда выполняем следующие действия:
Поясним действия, выполняемые на k-м шаге алгоритма, представив матрицу Dk-1 так, как она показана на рисунке 3. На этом рисунке строка k и столбец k являются ведущими. Строка i - любая строка с номером от 1 до k - 1, а строка p - произвольная строка с номером от k + 1 до n. Аналогично столбец j представляет любой столбец с номером от 1 до k - 1, столбец q - произвольный столбец с номером от k + 1 до n. Треугольный оператор выполняется следующим образом. Если сумма элементов ведущих строки и столбца (показанных в квадратах) меньше элементов, находящихся в пересечении столбца и строки (показанных в кружках), соответствующих рассматриваемым ведущим элементам, то расстояние (элемент в кружке) заменяется на сумму расстояний, представленных ведущими элементами:
Рис.3. Иллюстрация алгоритма Флойда
После реализации n шагов алгоритма определение по матрицам Dn и Sn кратчайшего пути между узлами i и j выполняется по следующим правилам.
Рис.4. Пример сети
Шаг 0. Начальные матрицы D0 и S0 строятся непосредственно по заданной схеме сети. Матрица D0 симметрична, за исключением пары элементов d35 и d53, где d53 равно бесконечности, поскольку невозможен переход от узла 5 к узлу 3:
Рис.5. Начальное состояние
Шаг 1. В матрице D0 выделены ведущие строка и столбец (k = 1). Двойной рамкой представлены элементы d23 и d32, единственные среди элементов матрицы D0, значения которых можно улучшить с помощью треугольного оператора. Таким образом, чтобы на основе матриц D0 и S0 получить матрицы D1 и S1, выполняем следующие действия.
Матрицы D1 и S1 имеют следующий вид:
Рис.6. Матрицы D1 и S1
Шаг 2. Полагаем k = 2; в матрице D1 выделены ведущие строка и столбец. Треугольный оператор применяется к элементам матрицы D1 и S1, выделенным двойной рамкой. В результате получаем матрицы D2 и S2:
Рис.7. Матрицы D2 и S2
Шаг 3. Полагаем k = 3; в матрице D2 выделены ведущие строка и столбец. Треугольный оператор применяется к элементам матрицы D2 и S2, выделенным двойной рамкой. В результате получаем матрицы D3 и S3:
Рис.8. Матрицы D3 и S3
Шаг 4. Полагаем k = 4, ведущие строка и столбец в матрице D3 выделены. Получаем новые матрицы D4 и S4:
Рис.9. Матрицы D4 и S4
Шаг 5. Полагаем k = 5, ведущие строка и столбец в матрице D4 выделены. Никаких действий на этом шаге не выполняем; вычисления закончены.
Конечные матрицы D4 и S4 содержат всю информацию, необходимую для определения кратчайших путей между любыми двумя узлами сети. Например, кратчайшее расстояние между узлами 1 и 5 равно d15 = 12.
Для нахождения соответствующих маршрутов напомним, что сегмент маршрута (i, j) состоит из ребра (i, j) только в том случае, когда sij = j. В противном случае узлы i и j связаны, по крайней мере, через один промежуточный узел. Например, поскольку s15 = 4 и s45 = 5, сначала кратчайший маршрут между узлами 1 и 5 будет иметь вид 1->4->5. Но так как s14 не равно 4, узлы 1 и 4 в определенном пути не связаны одним ребром (но в исходной сети они могут быть связаны непосредственно). Далее следует определить промежуточный узел (узлы) между первым и четвертым узлами. Имеем s14 = 2 и s24 = 4, поэтому маршрут 1->4 заменяем 1->2->4. Поскольку s12 = 2 и s24 = 4, других промежуточных узлов нет. Комбинируя определенные сегменты маршрута, окончательно получаем следующий кратчайший путь от узла 1 до узла 5: 1->2->4->5. Длина этого пути равна 12 километрам.
Приведем текст программы, реализующей алгоритм Флойда.
#include <iostream.h> #define TRUE 1 #define FALSE 0 #define MaxNodes 5 //Количество вершин. //Описание типа узла стека. typedef struct Zveno *svqz; typedef struct Zveno { int Element; svqz Sled; }; class Spisok { private: int Mas[MaxNodes][MaxNodes]; //Матрица весов дуг. int DD[MaxNodes][MaxNodes]; //Матрица расстояний. int SS[MaxNodes][MaxNodes]; //Матрица последовательных узлов. svqz Stack; //Указатель на рабочий стек. void UDALENIE (svqz *, int *); void W_S (svqz *, int); void Small_Put (int,int); public: Spisok() {Stack = NULL;} void Vvod_Ves(); void Reshenie (); }; void main() { Spisok A; A.Vvod_Ves(); A.Reshenie(); } void Spisok::Small_Put (int one, int two) //Нахождение кратчайшего пути. { svqz St=NULL; //Указатель на вспомогательный стек. svqz UkZv; int Flag=FALSE; //Флаг построения кратчайшего пути. int elem1,elem2,k; //Помещение в стек конечной и начальной вершин. W_S (&Stack,two); W_S (&Stack,one); while (!Flag) { //Извлекли верхних два элемента. UDALENIE(&Stack,&elem1); UDALENIE(&Stack,&elem2); if (SS[elem1][elem2]==elem2) //Если есть путь... if (elem2==two) //и это конечный узел... { Flag = TRUE; //то кратчайший путь найден. W_S (&St,elem1); W_S (&St,elem2); } else //и это не конечный узел... { W_S (&St,elem1); //В вспомогательный стек. W_S (&Stack,elem2); //Обратно в рабочий стек. } else //Если пути нет. { W_S (&Stack,elem2); //Обратно в рабочий стек. k = SS[elem1][elem2]; W_S (&Stack,k); //Запомнить промежуточную вершину. W_S (&Stack,elem1); //Обратно в рабочий стек. } } UkZv = St; while ( UkZv != NULL ) { cout << (UkZv->Element+1) << " "; UkZv = UkZv->Sled; } cout << endl; } void Spisok::W_S (svqz *stk, int Elem) //Помещение Elem в стек stk. { svqz q=new (Zveno); (*q).Element = Elem; (*q).Sled = *stk; *stk = q; } void Spisok::UDALENIE (svqz *stk, int *Klad) //Удаление звена из стека, заданного указателем *stk. //Значение информационного поля удаляемого звена сохраня- //ется в параметре Klad. { svqz q; if (*stk==NULL) cout<<"Попытка выбора из пустого стека!\n"; else { *Klad = (**stk).Element; q = *stk; *stk = (**stk).Sled; delete q; } } void Spisok::Vvod_Ves() //Ввод матрицы весов дуг заданного графа. { cout << "Вводите элементы матрицы весов дуг по строкам:\n"; for (int i=0;i<MaxNodes;i++) for (int j=0;j<MaxNodes;j++) { cout << "Введите Mas[" << (i+1) << "," << (j+1) << "]: "; cin >> Mas[i][j]; } } void Spisok::Reshenie() { int one,two; int i,j; //Инициализация. for (i=0;i<MaxNodes;i++) for (j=0;j<MaxNodes;j++) { if (Mas[i][j]>0) SS[i][j]=j; else SS[i][j]=0; DD[i][j]=Mas[i][j]; } cout << "\nНачальная вершина: "; cin >> one; one--; cout << "Конечная вершина: "; cin >> two; two--; int ved=0; while (ved<MaxNodes) { for (i=0;i<MaxNodes;i++) for (j=0;j<MaxNodes;j++) if (i!=j && i!=ved && j!=ved && DD[i][ved]>0 && DD[ved][j]>0) if (DD[i][ved]+DD[ved][j]<DD[i][j] || DD[i][j]==0) { DD[i][j]=DD[i][ved]+DD[ved][j]; SS[i][j]=ved; } ved++; } i=one; if (SS[i][two]!=two && SS[i][two]!=0) while (SS[i][two]!=two) { j=SS[i][two]; while (SS[i][j]!=j) j=SS[i][j]; i=j; } cout << "\nКратчайший путь (в обратном порядке): "; Small_Put (one, two); cout << "Длина минимального пути между этими вершинами: " << DD[one][two]; }
Результат работы этой программы для сети, изображенной на рисунке 4, будет следующим:
Рис.10. Результат работы приложения
На следующем шаге мы рассмотрим задачу о максимальном потоке.