Алгоритм Беллмана — Форда

История алгоритма связана сразу с тремя независимыми математиками: Лестером Фордом, Ричардом Беллманом и Эдвардом Муром. Форд и Беллман опубликовали алгоритм в 1956 и 1958 годах соответственно, а Мур сделал это в 1957 году. И иногда его называют алгоритмом Беллмана – Форда – Мура. Метод используется в некоторых протоколах дистанционно-векторной маршрутизации, например в RIP (Routing Information Protocol – Протокол маршрутной информации).

Также как и алгоритм Дейкстры, алгоритм Беллмана — Форда вычисляет во взвешенном графе кратчайшие пути от одной вершины до всех остальных. Он подходит для работы с графами, имеющими ребра с отрицательным весом. Но спектр применимости алгоритма затрагивает не все такие графы, ввиду того, что каждый очередной проход по пути, составленному из ребер, сумма весов которых отрицательна (т. е. по отрицательному циклу), лишь улучшает требуемое значение. Бесконечное число улучшений делает невозможным определение одного конкретного значения, являющегося оптимальным. В связи с этим алгоритм Беллмана — Форда не применим к графам, имеющим отрицательные циклы, но он позволяет определить наличие таковых, о чем будет сказано позже.

Решить задачу, т. е. найти все кратчайшие пути из вершины s до всех остальных, используя алгоритм Беллмана — Форда, это значит воспользоваться методом динамического программирования: разбить ее на типовые подзадачи, найти решение последним, покончив тем самым с основной задачей. Здесь решением каждой из таких подзадач является определение наилучшего пути от одного отдельно взятого ребра, до какого-либо другого. Для хранения результатов работы алгоритма заведем одномерный массив d[]. В каждом его i-ом элементе будет храниться значение кратчайшего пути из вершины s до вершины i (если таковое имеется). Изначально, присвоим элементам массива d[] значения равные условной бесконечности (например, число заведомо большее суммы всех весов), а в элемент d[s] запишем нуль. Так мы задействовали известную и необходимую информацию, а именно известно, что наилучший путь из вершины s в нее же саму равен 0, и необходимо предположить недоступность других вершин из s. По мере выполнения алгоритма, для некоторых из них, это условие окажется ложным, и вычисляться оптимальные стоимости путей до этих вершин из s.

Задан граф G=(V, E), n=|V|, а m=|E|. Обозначим смежные вершины этого графа символами v и u (vÎV и uÎV), а вес ребра (v, u) символом w. Иначе говоря, вес ребра, выходящего из вершины v и входящего в вершину u, будет равен w. Тогда ключевая часть алгоритма Беллмана — Форда примет следующий вид:

Для i от 1 до n-1 выполнять

Для j от 1 до m выполнять

Если d[v]+w(v, u)<d[u] то

d[u]←d[v]+w(v, u)

На каждом n-ом шаге осуществляются попытки улучшить значения элементов массива d[]: если сумма, составленная из веса ребра w(v, u) и веса хранящегося в элементе d[v], меньше веса d[u], то она присваивается последнему.

Код программы на C++:

 

#define inf 100000

struct Edges

{ int u, v, w; };

const int Vmax=1000;

const int Emax=Vmax*(Vmax-1)/2;

int i, j, n, e, start;

Edges edge[Emax];

int d[Vmax];

 

void bellman_ford(int n, int s) //алгоритм Беллмана-Форда

{

int i, j;

for (i=0; i<n; i++) d[i]=inf;

d[s]=0;

for (i=0; i<n-1; i++)

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

if (d[edge[j].v]+edge[j].w<d[edge[j].u])

d[edge[j].u]=d[edge[j].v]+edge[j].w;

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

if (d[i]==inf)

cout<<endl<<start<<"->"<<i+1<<"="<<"Not";

else cout<<endl<<start<<"->"<<i+1<<"="<<d[i];

}

 

void main() //главная функция

{

int w;

cout<<"Количество вершин > "; cin>>n;

e=0;

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

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

{

cout<<"Вес "<<i+1<<"->"<<j+1<<" > "; cin>>w;

if (w!=0)

{

edge[e].v=i;

edge[e].u=j;

edge[e].w=w;

e++;

}

}

cout<<"Стартовая вершина > "; cin>>start;

cout<<"Список кратчайших путей:";

bellman_ford(n, start-1);

}

 

Код программы на Pascal:

 

const

inf=100000;

Vmax=1000;

Emax=Vmax*(Vmax-1) div 2;

type Edges=record

u, v, w: integer;

end;

Var

i, j, e, n, w, start: integer;

edge: array[1..Emax] of Edges;

d: array[1..Vmax] of integer;

 

procedure FB(n, s: integer); {алгоритм Беллмана-Форда}

begin

for i:=1 to n do d[i]:=inf;

d[s]:=0;

for i:=1 to n-1 do

for j:=1 to e-1 do

if d[edge[j].v]+edge[j].w<d[edge[j].u] then

d[edge[j].u]:=d[edge[j].v]+edge[j].w;

for i:=1 to n do

if d[i]=inf then

writeln(start, '->', i, '=', 'Not')

else writeln(start, '->', i, '=', d[i]);

end;

 

begin {основной блок программы}

write('Количество вершин > ');

read(n);

e:=1;

for i:=1 to n do

for j:=1 to n do

begin

write('Вес ', i, '->', j, ' > ');

read(w);

if w<>0 then

begin

edge[e].v:=i;

edge[e].u:=j;

edge[e].w:=w;

e:=e+1;

end;

end;

write('Стартовая вершина > '); read(start);

writeln('Список кратчайших путей:');

FB(n, start);

end.

 

Здесь граф представлен упрощенным списком ребер, который формируется по мере ввода пользователем матрицы весов. Основная часть алгоритма Беллмана – Форда (проверка условия) выполняется m*(n-1) раз, т. к. число повторений внешнего цикла равно n-1, а внутреннего – m. Отказ от n-ой итерации целесообразен, поскольку алгоритм справляется со своей задачей за n-1 шаг, но запуск внешнего цикла n раз позволит выявить наличие отрицательного цикла в графе. Проверить это можно, например, при помощи следующей модификации:

 

bool x=true;

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

{

if (i!=n-1)

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

if (d[edge[j].v]+edge[j].w<d[edge[j].u])

d[edge[j].u]=d[edge[j].v]+edge[j].w;

if (i==n-1)

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

if (d[edge[j].v]+edge[j].w<d[edge[j].u])

x=false;

}

if (!x) cout<<endl<<"Граф содержит отриц. циклы"<<endl;

 

Здесь внешний цикл выполняется n раз (в C++ принято начальным указывать значение 0, поэтому для данного кода n-1 раз), и если на n-ой стадии будет возможно улучшение, то это свидетельствует о наличие отрицательного цикла.