Bellmanův–Fordův algoritmus

Bellmanův–Fordův algoritmus počítá nejkratší cestu v ohodnoceném grafu z jednoho uzlu do uzlu dalšího (do ostatních uzlů), kde mohou být některé hrany ohodnoceny i záporně. Dijkstrův algoritmus tento problém řeší sice v kratším čase, ale vyžaduje nezáporné ohodnocené hrany. Proto se Bellmanův–Fordův algoritmus používá i pro grafy se záporně ohodnocenými hranami.

Algoritmus je používán například ve směrovacím protokolu RIP.

Popis algoritmu

V případě grafů se záporně ohodnocenými hranami není Dijkstrův algoritmus obecně vždy použitelný. Proto nasazujeme Bellmanův–Fordův algoritmus, který stejně jako Dijkstrův algoritmus využívá metodu relaxace hran, jež zjišťuje aktuálně nastavenou hodnotu nejkratší vzdálenosti od uzlu . Jestliže je zjištěno, že hodnota v uzlu je vyšší než hodnota z nynějšího uzlu plus ohodnocení hrany z nynějšího uzlu do uzlu, v kterém bychom chtěli změnit jeho hodnotu, pak tuto hodnotu změníme (snížíme). Hlavní rozdíl oproti Dijkstrovu algoritmu spočívá ve způsobu průchodu grafu. Jelikož Dijkstrův algoritmus, jestliže projdeme všechny následníky jednoho uzlu, tak tento uzel „uzavře“ a poté ho už neupravuje. Toto se v Bellmanově–Fordově algoritmu neděje, jelikož tyto uzly neuzavírá takto ihned, ale prochází několikrát všechny uzly a upravuje postupně hodnoty vzdáleností nejkratších cest.

Časová složitost

Časová složitost algoritmu je , kde je počet vrcholů a  počet hran.

Implementace

Obecná implementace v pseudokódu

bellman-ford(vrcholy, hrany, zdroj)

// krok 1: inicializace grafu
for each v in vrcholy
  if v=zdroj then v.vzdálenost := 0
             else v.vzdálenost := nekonečno
  v.předchůdce := null

// krok 2: opakovaně relaxovat hrany
for i from 1 to size(vrcholy)-1
  for each h in hrany    // h je hrana z u do v
    u := h.počátek
    v := h.konec
    if u.vzdálenost + h.délka < v.vzdálenost
      v.vzdálenost := u.vzdálenost + h.délka
      v.předchůdce := u

// krok 3: kontrola záporných cyklů
for each h in hrany
  u := h.počátek
  v := h.konec
  if u.vzdálenost + h.délka < v.vzdálenost
    error "Graf obsahuje záporný cyklus."

První cyklus inicializuje graf – nastaví všem vrcholům kromě zdroje nekonečné vzdálenosti, zdroji nulovou. Druhý cyklus upravuje relaxací hodnoty vzdáleností nejkratších cest mezi zdrojem a ostatními uzly. Pokud třetí cyklus zjistí, že některá už určená hodnota nejkratší cesty mezi uzly by mohla být ještě zkrácena (snížena hodnota vzdálenosti nejkratší cesty), graf obsahuje záporně ohodnocený cyklus. V takovém případě postrádá úkol nalezení minimální cesty smysl (opakovaným průchodem cyklem získáme neomezeně krátkou cestu) a hodnoty vykonstruované algoritmem nemůžeme brát za správné.

Implementace – Perl

# !/usr/bin/perl

use warnings;
use strict;

my $INFINITY = 10**24;

print "bellmanuv–forduv algoritmus\n";

my $graf = {
  pocatek => 'a',
  hrany => [
	{ from => 'a', to => 'b', capacity => 10 },
	{ from => 'b', to => 'c', capacity => -20 },
	{ from => 'c', to => 'd', capacity => 10 },
	{ from => 'a', to => 'd', capacity => 10 },
	{ from => 'd', to => 'e', capacity => -5 },
	{ from => 'e', to => 'f', capacity => 10 },
	{ from => 'f', to => 'g', capacity => -5 },
	{ from => 'g', to => 'h', capacity => 10 },
	{ from => 'h', to => 'i', capacity => -30 },
	{ from => 'i', to => 'j', capacity => 10 },
	{ from => 'i', to => 'b', capacity => -100 },
	{ from => 'a', to => 'i', capacity => 10 },
  ],
  vrcholy => [qw(a b c d e f g h i j)],
};

my %distance = ();
my %predek = ();

my ($vrchol, $hrana);

foreach $vrchol ( @{ $graf->{vrcholy} } )
{
	$distance{ $vrchol } = $INFINITY;
}
$distance{ $graf->{pocatek} } = 0;

foreach $vrchol ( @{ $graf->{vrcholy} } )
{
	foreach $hrana ( @{ $graf->{hrany} } )
	{
		if( $distance{ $hrana->{from} } != $INFINITY ) {
			my $new_distance = $distance{ $hrana->{from} } + $hrana->{capacity};
			if( $new_distance < $distance{ $hrana->{to} } )
			{
				$distance{ $hrana->{to} } = $new_distance;
				$predek{ $hrana->{to} } = $hrana->{from};
			}
		}
	}
}

foreach $hrana ( @{ $graf->{hrany} } )
{
	if ( $distance{ $hrana->{to} } > $distance{ $hrana->{from} } + $hrana->{capacity} )
	{
		print "Negative edge weight cycles detected!\n";
		exit(1);
	}
}

foreach $vrchol ( @{ $graf->{vrcholy} } )
{
	print "The shortest distance between nodes "
		. $graf->{pocatek} . " and $vrchol is " . $distance{$vrchol}
		. "\n";
	# vypis cestu
	my @cesta = ();
	my $p = $vrchol;
	while( $p ) {
		push @cesta, $p;
		$p = $predek{$p};
	}
	print " " . join(" -> ", reverse( @cesta )) . "\n";
}

exit(0);

Implementace – C

# include <limits.h>
# include <stdio.h>
# include <stdlib.h>

/* Let INFINITY be an integer value not likely to be
   confused with a real weight, even a negative one. */
# define INFINITY ((1 << 14)-1)

typedef struct {
    int source;
    int dest;
    int weight;
} Edge;

void BellmanFord(Edge edges[], int edgecount, int nodecount, int source)
{
    int *distance = malloc(nodecount * sizeof *distance);
    int i, j;
    for (i=0; i < nodecount; ++i)
      distance[i] = INFINITY;
    distance[source] = 0;

    for (i=0; i < nodecount; ++i) {
        for (j=0; j < edgecount; ++j) {
            if (distance[edges[j].source] != INFINITY) {
                int new_distance = distance[edges[j].source] + edges[j].weight;
                if (new_distance < distance[edges[j].dest])
                  distance[edges[j].dest] = new_distance;
            }
        }
    }

    for (i=0; i < edgecount; ++i) {
        if (distance[edges[i].dest] > distance[edges[i].source] + edges[i].weight) {
            puts("Negative edge weight cycles detected!");
            free(distance);
            return;
        }
    }

    for (i=0; i < nodecount; ++i) {
        printf("The shortest distance between nodes %d and %d is %d\n",
            source, i, distance[i]);
    }
    free(distance);
    return;
}

int main(void)
{
    /* This test case should produce the distances 2, 4, 7, -2, and 0. */
    Edge edges[10] = {{0,1, 5}, {0,2, 8}, {0,3, -4}, {1,0, -2},
                      {2,1, -3}, {2,3, 9}, {3,1, 7}, {3,4, 2},
                      {4,0, 6}, {4,2, 7}};
    BellmanFord(edges, 10, 5, 4);
    return 0;
}

Externí odkazy