Algorytmy i struktury danych/Algorytmy grafowe - najlżejsze ścieżki

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania

Ten wykład poświęcimy wprowadzeniu do algorytmów grafowych. W tym celu rozwiążemy jedno następujący problem ścieżkowy.

Najlżejsze ścieżki z jednym źródłem

W tym wykładzie pisząc graf będziemy mieli zawsze na uwadze graf spójny i nieskierowany. Tradycyjnie przez n będziemy oznaczali liczbę wierzchołków w grafie, a przez m liczbę jego krawędzi. Dla każdego grafu mamy zawsze n1mn(n1)/2.

Niech G=(V,E) będzie grafem, a w:E{1,2,...} funkcją przypisująca krawędziom dodatnie liczby całkowite zwane wagami krawędzi.

Ciąg wierzchołków p=<v0,v1,,vk> nazywamy ścieżką w G wtedy i tylko wtedy, gdy Parser nie mógł rozpoznać (błąd składni): {\displaystyle v_i–v_{i+1}} jest krawędzią w E, dla każdego i=1,2,,k1. Liczbę k nazywamy długością ścieżki p.

Wagą ścieżki p nazywamy sumę wag jej krawędzi i oznaczamy także przez w(p). Przyjmujemy, że waga ścieżki o długości 0 wynosi 0.

Przez 𝒩 będziemy oznaczali zbiór sąsiadów wierzchołka v w grafie G.

Problem (wag) najlżejszych ścieżek z jednym źródłem definiuje się nastepująco.

Dane:
  G=(V,E) – spójny, nieskierowany graf
  w:E{1,2,} – funkcja przypisująca krawędziom   dodatnie liczby całkowite
  s – wyróżniony wierzchołek w G, zwany źródłem
Wynik:
  dla każdego wierzchołka v waga w*(v) najlżejszej ścieżki łączącej v z s.

W tym wykładzie zajmiemy się tylko wyznaczaniem wag najlżejszych ścieżek. Znalezienie sposobu wyznaczania takich ścieżek pozstawimy jako ćwiczenie dla czytelników. Problem najlżejszych scieżek jest problemem bardzo naturalnym. Jeśli mapę drogową potraktować jako graf, w którym wagi to długości odcinków drogowych na mapie, a źródło jest wyróżnionym miastem na mapie (na przykład stolicą państwa), to rozwiązanie problemu najlżejszych ścieżek da nam długości najkrótszych dróg od miasta-źródła do pozostałych miast na mapie.

Znaczenie problemu najlżejszych ścieżek jest dużo ważniejsze niż by to wynikało z jego zastosowań. Poszukując wydajnego algorytmu dla rozwiązania tego problemu postaramy się przedstawić cały proces projektowania, a następnie implementowania algorytmu ze szczegtólnym uwzględnieniem doboru odpowiednich struktur danych w celu osiągnięcia lepszej wydajności implementacji.

Nasze rozważania rozpoczniemy od schematu algorytmu poszukiwania najlżejszych ścieżek, a następnie zajmiemy się implementacją poszczególnych elementów tego schematu. Rozwiązanie, które zaproponujemy pochodzi od wybitnego holenderskiego informatyka Edgara Dijkstry.

Zastanówmy się w jaki sposób obliczać wagi najlżejszych ścieżek. Pomoże nam w tym następujace proste spostrzeżenie:

Niech <v=v0,v1,,vk=s> będzie najlżejszą ścieżką z wierzchołka v, do źródła s. Wówczas w*(v)>w*(v1)>>w*(vk)=0.

Spostrzeżenie to mówi, że gdybyśmy obliczali wagi najlżejszych ścieżek od najlżejszej do najcięższej, to dla każdego wierzchołka v różnego od źródła, sąsiad v na najlżejszej ścieżce z v do s miałby swoją wagę już obliczoną i tym sąsiadem byłby wierzchołek (może być wiecej niż jeden wybór, dla którego waga najlżejszej ścieżki do s powiększona o wagę krawędzi łączącej go z v jest najmniejsza.

Możemy teraz przystąpić do schematu algorytmu. Algorytm obliczania najlżejszych ścieżek będzie działał w n1 fazach. W trakcie działania algorytmu L będzie zbiorem tych wierzchołków dla których wagi najlżejszych scieżek prowadzące do s zostały już policzone. Przez R będziemy oznaczali zbiór pozostałych wierzchołków. Podczas obliczeń z kazdym wierzchołkiem v będziemy mieli związana nieujemną wagę w[v]. Dla każdego wierzchołka vL waga w będzie wagą najlżejszej scieżki łączacej v z s, czyli w[v]=w*(v). Dla każdego wierzchołka v z R, w[v] będzie równe wadze najlżejszej ścieżki z v do s, na której wszystkie wierzchołki poza v muszą należeć do L. Może się zdarzyć, że taka ścieżka nie istnieje. Wówczas przyjmujemy, że w[v] jest równe - umownej wartości większej od wszystkich wag występujących w obliczeniach. Jedna faza algorytmu będzie polegała na przeniesieniu do L wierzchołka z R o najmniejszej wadze w, a nstępnie poprawieniu wag w dla wierzchołków, które pozostały w R. Zauważmy, że wagi w mogą się zmienić tylko sąsiadom w grafie wierzchołka przeniesionego do L, którzy pozostali w R. Oto schemat algorytmu obliczania najlżejszych ścieżek. Nazwiemy go schematem Dijkstry.

Algorytm Schemat Dijkstry


 1  //Inicjacja
 2  L:={s}; R:=V{s};
 3  w[s]:=0; 
 4  for each v \in R do
 5    if vsE then
 6      w[v]:=w(vs)
 7    else
 8      w[v]:=;
 9  //właściwe obliczenia
 10 for i:=1 to n1 do 
 11 begin
 12   u:= wierzchołek z R o minimalnym w[u];
 13   R:=R{u}; 
 14   L:=L+{u};
 15   for each vin𝒩(𝓊) do
 16     if w[u]+w(uv)<w[v] then
 17       w[v]:=w[u]+w(uv);
 18 end;
 20 //dla każdego vV, w[v]=w*(v)

Dlaczego powyższy opis obliczania wag najlzejszych ścieżek nazwaliśmy schematem Dijkstry, a nie algorytmem Dijkstry. Zauważmy, że żeby powyższy schemat móc zaimplementować na komputerze, a także dokonać analizy złożoności obliczeniowej musimy doprecyzować wiele elementów schematu. Należą do nich

- implementacja grafu - implementacja zbiorów L i P i operacji na nich (inicjacja, usuwanie i dodawanie wierzchołka, znajdowanie wierzchołka o minimalnej wadze, zmiana wagi wierzchołka).

Dopiero scisłe określenie tych elementów da nam pełną implementację schematu, a tym samym algorytm, który będzie mógł być poddany analizie.