Laborator 10: Drumuri minime în grafuri: surse / destinații multiple. (2/2)




Changing the world through digital experiences is what Adobe’s all about. We give everyone - from emerging artists to global brands - everything they need to design and deliver exceptional digital experiences! We’re passionate about empowering people to create beautiful and powerful images, videos, and apps, and transform how companies interact with customers across every screen.

Obiective laborator

În laboratorul anterior am introdus contextul pentru Shortest-paths problem și Single-source shortest-paths problem. În laboratorul 10 vom continua cu All-pairs shortest-paths problem.

  • Înțelegerea conceptelor de cost asociat unei muchii, relaxare a unei muchii.
  • Prezentarea problemei drumului de cost minim (diverse variante).
  • Prezentarea algoritmilor pentru calculul drumurilor minime.

Shortest-paths problem: all-pairs

Vă rugăm să parcugeți Shortest-paths problem pentru a vă familiariza cu contextul, problema și notațiile folosite.

Concepte necesare:

  • cost muchie / edge cost
  • cost drum / path cost
  • problema drumurilor minime: surse / destinații multiple / all-pairs shortest-paths problem
  • relaxarea unei muchii / edge relaxation
  • reconstruirea unui drum / RebuildPath

Algoritmi

În acest laborator vom studia all-pairs shortest-paths problem. Pentru această problemă, vom prezenta 2 algoritmi:

  • Roy-Floyd: eficient pentru grafuri dense ($m >> n$ sau $|E| >> |V|$ - a.k.a. număr de muchii mult mai decât număr de noduri).
  • Johnson: eficient pentru grafuri rare ($n >> m$ sau $|V| >> |E|$ - a.k.a. număr de noduri mult mai mare decât număr de muchii).

Vom prezenta fiecare algoritm, îl vom analiza, iar la final vom vedea când îl vom folosi pe fiecare.

Puteți consulta capitolul All-Pairs Shortest Paths din Introduction to Algorithms [0] pentru mai multe detalii despre acești algoritmi.

Roy-Floyd

Algoritmul Roy-Floyd (Roy-Floyd / Floyd–Warshall algorithm) rezolvă shortest-paths problem în grafuri G = (V, E) care sunt dense.

Roy-Floyd - Pseudocod

// apply Roy-Floyd's algorithm for all-pairs shortest-paths problem
//
// nodes     = list of all nodes from G
// adj[node] = the adjacency list of node
//             example: adj[node] = {..., neigh, ...} => edge (node, neigh) of cost w[node][neigh]
//
//
// returns: d, p
//          d = distance matrix
//          p = parent matrix
//
Roy-Floyd(G=(nodes, adj)) {
  // STEP 1: initialize results
  // d[i][j] = minimum distance from i to j
  // p[i][j] = parent of node j, on shortest path from i to j
  for (i in nodes) {
    for (j in nodes) {
      d[i][j] = w[i][j];                          // edge cost (or infinity if missing)
      p[i][j] = (w[i][j] != infinity ? i : null); // parent (or null if missing)
    }
  }
 
  // STEP 2: For each intermediar node k,
  // try to update shortest path from i to j.
  for (k in nodes) {
    for (i in nodes) {
      for (j in nodes) {
        // Is (i -> ... -> k) reunion with (k -> ... -> j) shorter than (i -> ... -> j)?
        if (d[i][k] + d[k][j] < d[i][j]) {
          d[i][j] = d[i][k] + d[k][j];
          p[i][j] = p[k][j];
        }
      }
    }
  }
 
  return d, p;
}
 
// Usage example:
d, p = Roy-Floyd(G=(nodes, adj));
// 1. Use distances from d
// 2. Rebuild path from node to source using parents (p)
RebuildPath(source, destination, p);

Exemple

Exemplu Roy-Floyd

 Exemplu Roy-Floyd

Drumurile minime calculate de algoritmul Roy-Floyd sunt:

-1234
10-1-20
24024
35102
43-110

Explicație pas cu pas

Explicație pas cu pas

În exemplul atașat, avem un graf orientat cu următoare configurație:

  • n = 4, m = 5
  • Funcția de cost w are valorile menționate pe muchii.
  • Avem mai multe drumuri de cost diferite între diverse perechi de noduri din graf.
  • Construim tabloul d al distanțelor minime:
    • d[u][v] = w[u][v], dacă există muchia $(u, v)$.
    • d[u][v] = +∞, dacă NU** există muchia $(u, v)$.
    • d[u][u] = 0, convenție.
    • Obținem matricea d_0:
-1234
10+∞-2+∞
2403+∞
3+∞+∞02
4+∞-1+∞0
  • Rulăm algoritmul Roy-Floyd, vom scrie matricea pentru fiecare $ k = 1, 2, 3, 4$.
    • $d_1$ (matricea dupa primul pas din algoritm; se modifică doar d[2][3])
-1234
10+∞-2+∞
2402+∞
3+∞+∞02
4+∞-1+∞0
  • $d_2$ (matricea dupa primul pas din algoritm; se modifică doar d[4][3])
-1234
10+∞-2+∞
2402+∞
3+∞+∞02
4+∞-110
  • $d_3$ (matricea dupa primul pas din algoritm; se modifică doar d[1][4] și d[2][4])
-1234
10+∞-20
24024
3+∞+∞02
4+∞-110
  • $d_4$ (matricea dupa primul pas din algoritm; se modifică doar d[1][2], d[3][1] și d[3][2])
-1234
10-1-20
24024
35102
4+∞-110
  • Drumurile minime sunt finale (cele menționate anterior - $d_4$).


Complexitate

  • complexitate temporală: $T = O(n^3)\ sau\ O(|E|^3)$
  • complexitate spațială : $S = O(1)$

Detalii (analiză + optimizări)

Detalii (analiză + optimizări)

  • complexitate temporală: Se aplică recurența discutată anterior care pentru fiecare nod intermediar k, încearcă să actualizeze drumul minim de la i la j. Cele 3 foruri dau complexitatea temporară.
  • complexitate spațială : Nu stocăm tablouri auxilare.


Johnson

Algoritmul lui Johnson (Johnson’s algorithm) rezolvă shortest-paths problem în grafuri G = (V, E) care sunt rare.

Ideea de la care pornește acest algoritm este de a rula cel mai rapid algorithm pentru shortest-paths single source, adică algoritmul lui Dijkstra, pentru fiecare sursă (nod) din graf. Dacă toate costurile sunt pozitive, putem face direct acest lucru. Dacă însă există costuri negative, nu putem aplica Dijkstra pe acest graf. Algoritmul lui Johnson face o preprocesare (în ComputeH) și calculează un graf echivalent, în care toate costurile sunt pozitive. Pe acest graf se poate aplica Dijkstra și să se afle toate distanțele. Ulterior se face translatarea inversă și se obțin distanțele în graful inițial.

Observație: Dacă se știe că toate costurile din graf sunt pozitive (nenegative), algoritmul lui Johnson se poate înlocui cu rularea directă a algoritmului Dijkstra pentru toate nodurile din graf.

Johnson - Pseudocod

// apply Johnson's algorithm for all-pairs shortest-paths problem
//
// nodes     = list of all nodes from G
// adj[node] = the adjacency list of node
//             example: adj[node] = {..., neigh, ...} => edge (node, neigh) of cost w[node][neigh]
//
// returns: has_cycle, d, p
//          has_cycle = negative cycle detection flag (true if found)
//          d = distance matrix (defined only if has_cycle == false)
//          p = parent matrix (defined only if has_cycle == false)
//
Johnson(G=(nodes, adj)) {
  // STEP 1: Compute adjustment distances h (using Bellmand-Ford).
  has_cycle, h = ComputerH(G);
  if (has_cycle) {
    return true, null, null;
  }
 
  // STEP 2: Update all costs in G to obtain all costs nonnegative.
  foreach ((u, v) in edges) {
    if (w[u][v] != infinity) {
      w[u][v] = w[u][v] + (h[u] - h[v]);
    }
  }
 
  // STEP 3: Now all costs are nonnegative, so we can apply Dijsktra.
  // Start Dijkstra for each source u, saving just distances.
  foreach (u in nodes) {
    d_dijkstra, p_dijkstra = Dijkstra(u, G);
 
    // STEP 4: Compute distance (u, v) on initial graph.
    foreach (v in nodes) {
      d[u][v] = d_dijkstra[v] + (h[v] - h[u]);
      p[u][v] = p_dijkstra[v];
    }
  }
 
  return false, d, p;  // no negative cycles detected
}
 
ComputeH(G=(nodes, adj)){
  // STEP 0: Create a new **temporary** graph
  // * add a new node
  source = new node;
  new_nodes = nodes + { source };
  new_adj = adj;
  // * add a new edge (source, node) with cost 0 for all existing nodes
  for (node in nodes) {
    new_adj[source].push_back(node);
    w[source][node] = 0;
  }
 
  // STEP 1: Run Bellman-Ford on the new graph. Save just flag and distances.
  has_cycle, h, _ = Bellmann-Ford(source, new_G=(new_nodes, new_adj)))
  if (has_cycle) {
    return true, null; // negative cycle detected
  }
 
  return false, d;
}
 
 
// Usage example:
has_cycle, d, p = Johnson(source, G=(nodes, adj));
if (has_cycle) {
  print "Has Cycle!"
  STOP.
} else {
  // 1. Use distances from d
  // (e.g. d[node] = distance from source to node)
  //
  // 3. Rebuild path from node to source using parents (p)
  RebuildPath(source, destination, p);
}

Exemple

Exemplu Johnson

În această secțiune exemplificăm grafic cum rulează algoritmul lui Johnson pe un graf dat.

Explicație pas cu pas

Explicație pas cu pas

 Exemplu Johnson 1/3

În exemplul atașat, avem un graf orientat cu următoare configurație:

  • n = 5, m = 8
  • Funcția de cost w are valorile menționate pe muchii.
  • Avem mai multe drumuri de cost diferite între diverse perechi de noduri din graf. (Observăm că nu avem ciclu de cost negativ, deci are sens să rulăm un algoritm de drumuri minime).
  • STEP 1: Adăugăm un nod fictiv (exemplu nodul 6). Îl vom uni de fiecare nod din graful inițial (1, 2, 3, 4, 5) cu muchie de cost 0. Obținem graful din figura următoare:

 Exemplu Johnson - 2/3

  • Pe acest graf putem rula o dată algoritmul Bellman-Ford, considerând sursă noul nod adăugat.
  • Obținem vectorul de distanțe $h[node] = distanța\ de\ la\ nodul\ fictiv\ (6)\ la\ nodul\ node$
node123456
h[node]-1-7-40-20
  • STEP 2: Revenim la graful inițial (cel cu 5 noduri, 8 muchii) și îi alterăm costurile:
    • $w[1][4] = w[1][4] + (h[1] - h[4]) = 2 + [ (-1) - (0) ] = 1$
    • $w[2][1] = w[2][1] + (h[2] - h[1]) = 6 + [ (-7) - (-1) ] = 0$
    • $w[2][3] = w[2][3] + (h[2] - h[3]) = 3 + [ (-7) - (-4) ] = 0$
    • $w[3][1] = w[3][1] + (h[3] - h[1]) = 4 + [ (-4) - (-1) ] = 1$
    • $w[3][4] = w[3][4] + (h[3] - h[4]) = 5 + [ (-4) - (0) ] = 1$
    • $w[4][2] = w[4][2] + (h[4] - h[2]) = -7 + [ (0) - (-7) ] = 0$
    • $w[4][5] = w[4][2] + (h[4] - h[2]) = -2 + [ (0) - (-2) ] = 0$
    • $w[5][4] = w[5][4] + (h[5] - h[4]) = -1 + [ (-2) - (-4) ] = 1$
  • Obținem graful din următoarea figură:

 Exemplu Johnson - 2/3

  • STEP 3: Deoarece toate costurile sunt pozitive, putem rula Dijkstra pe rând, pentru fiecare sursă source = 1, 2, 3, 4, 5 din graf.
  • STEP 3: $ source = 1 $
    • Vectorul de distanțe este d_dijkstra atașat mai jos.
    • Distanțele față de nodul 1 pe graful inițial sunt:
      • $d[1][1] = 0$
      • $d[1][2] = d_{dijkstra}[2] + (h[2] - h[1]) = 1 + [ (-7) - (-1) ] = -5$
      • $d[1][3] = d_{dijkstra}[3] + (h[3] - h[1]) = 1 + [ (-4) - (-1) ] = -2$
      • $d[1][4] = d_{dijkstra}[4] + (h[4] - h[1]) = 1 + [ (0) - (-1) ] = 2$
      • $d[1][5] = d_{dijkstra}[5] + (h[5] - h[1]) = 1 + [ (-2) - (-1) ] = 0$
node12345
d_dijkstra[node]01111
node12345
d[1][node]0-5-220
  • STEP 3: $ source = 2 $
    • Vectorul de distanțe este d_dijkstra atașat mai jos.
    • Distanțele față de nodul 2 pe graful inițial sunt:
      • $d[2][1] = d_{dijkstra}[1] + (h[1] - h[2]) = 0 + [ (-1) - (-7) ] = 6$
      • $d[2][2] = 0$
      • $d[2][3] = d_{dijkstra}[3] + (h[3] - h[2]) = 0 + [ (-4) - (-7) ] = 3$
      • $d[2][4] = d_{dijkstra}[4] + (h[4] - h[2]) = 1 + [ (0) - (-7) ] = 8$
      • $d[2][5] = d_{dijkstra}[5] + (h[5] - h[2]) = 1 + [ (-2) - (-7) ] = 6$
node12345
d_dijkstra[node]00011
node12345
d[2][node]60386
  • STEP 3: $ source = 3 $
    • Vectorul de distanțe este d_dijkstra atașat mai jos.
    • Distanțele față de nodul 3 pe graful inițial sunt:
      • $d[3][1] = d_{dijkstra}[1] + (h[1] - h[3]) = 1 + [ (-1) - (-4) ] = 4$
      • $d[3][2] = d_{dijkstra}[2] + (h[2] - h[3]) = 1 + [ (-7) - (-4) ] = -2$
      • $d[3][3] = 0$
      • $d[3][4] = d_{dijkstra}[4] + (h[4] - h[3]) = 1 + [ (0) - (-4) ] = 5$
      • $d[3][5] = d_{dijkstra}[5] + (h[5] - h[3]) = 1 + [ (-2) - (-4) ] = 3$
node12345
d_dijkstra[node]11011
node12345
d[3][node]4-2053
  • STEP 3: $ source = 4 $
    • Vectorul de distanțe este d_dijkstra atașat mai jos.
    • Distanțele față de nodul 4 pe graful inițial sunt:
      • $d[4][1] = d_{dijkstra}[1] + (h[1] - h[4]) = 0 + [ (-1) - (0) ] = -1$
      • $d[4][2] = d_{dijkstra}[2] + (h[2] - h[4]) = 0 + [ (-7) - (0) ] = -7$
      • $d[4][3] = d_{dijkstra}[4] + (h[3] - h[4]) = 0 + [ (-4) - (0) ] = -4$
      • $d[4][4] = 0$
      • $d[4][5] = d_{dijkstra}[5] + (h[5] - h[4]) = 0 + [ (-2) - (0) ] = 2$
node12345
d_dijkstra[node]00000
node12345
d[4][node]-1-7-402
  • STEP 3: $ source = 5 $
    • Vectorul de distanțe este d_dijkstra atașat mai jos.
    • Distanțele față de nodul 4 pe graful inițial sunt:
      • $d[5][1] = d_{dijkstra}[1] + (h[1] - h[5]) = 2 + [ (-1) - (-2) ] = 3$
      • $d[5][2] = d_{dijkstra}[2] + (h[2] - h[5]) = 2 + [ (-7) - (-2) ] = -3$
      • $d[5][3] = d_{dijkstra}[4] + (h[3] - h[5]) = 1 + [ (-4) - (-2) ] = -1$
      • $d[5][4] = d_{dijkstra}[5] + (h[4] - h[5]) = 2 + [ (0) - (-2) ] = 4$
      • $d[5][5] = 0$
node12345
d_dijkstra[node]22120
node12345
d[5][node]3-3-140
  • STOP! Am obținut toate distanțele $d[u][v]$ cerute!


Complexitate

  • complexitate temporală: $T = O(n * m * log (n))\ sau\ O(|V| * |E| * log (|V|))$
  • complexitate spațială : $S = O(n + m)\ sau \ O(|V| + |E|)$

Detalii (analiză + optimizări)

Detalii (analiză + optimizări)

  • complexitate temporală:
    • ComputeH:
      • Construire graf nou - $O(n + m))$.
      • Aplicare Bellman-Ford pe noul graf - $O(n * m)$.
    • Update edges - pasul se face în $O(m)$.
    • Rularea Dijkstra pentru fiecare nod din graf - este complexitatea de la rulare Dijkstra pentru un singur nod sursă (a.k.a. $O(m log n)$), multiplicată cu numărul de noduri.
    • În final ajungem la un total de $O(n * m * log(n) + n * m + n + m) = O(n * m + log(n))$.
  • complexitate spațială : Se construiește un alt graf (new_node - n, new_adj - m), se produc câțiva vectori temporari de lungime n (h, d_dijkstra, p_dijkstra).
  • optimizare: Deoarece Dijsktra se poate optimiza (vezi laborator anterior), putem obține $O(n^2 * log n + n * m)$ complexitatea pentru ultima etapă. Complexitatea finală este $O(n ^ 2 log (m) + n * m)$.


TLDR

  • Pentru cazul shortest-path single source am studiat în laboratorul anterior algoritmul lui Dijkstra / algoritmul Bellman-Ford.
  • Pentru cazul shortest-path all-pairs, discuția se facă după numărul de muchii din graf:
    • graf dens: aplicăm Roy-Floyd și obținem $O(n ^ 3)$.
    • graf rar: aplicăm Johnson și obținem $O(n * m * log (n))$.

Exerciții

Scheletul de laborator se găsește pe pagina pa-lab::skel/lab10.

Înainte de a rezolva exercițiile, asigurați-vă că ați citit și înțeles toate precizările din secțiunea Precizari laboratoare 07-12.

Prin citirea acestor precizări vă asigurați că:

  • știți convențiile folosite
  • evitați buguri
  • evitați depunctări la lab/teme/test

Roy-Floyd

Se dă un graf orientat cu n noduri. Graful are costuri strict pozitive.

Se dă matricea ponderilor - w, se cere matricea drumurilor minime - d, aplicând algoritmul Roy-Floyd.

Restricții și precizări:

  • $ n <= 100 $
  • $ 0 <= c <= 1.000$, unde c este costul unui arc.
    • Dacă nu există arc între o pereche de noduri x și y, distanța de la nodul x la nodul y din matricea ponderilor va fi 0.
    • Dacă după aplicarea algoritmului nu se găsește drum pentru o pereche de noduri x și y, se va considera distanța dintre ele egală cu 0 (se stochează în matricea distantelor valoarea 0).
    • Drumul de la nodul i la nodul i are lungime 0 (prin convenție).
  • timp de execuție
    • C++: 1s
    • Java: 8s

Johnson

Se dă un graf orientat cu n noduri. Graful are costuri oarecare (pot fi și negative).

Se dă lista de adiacență cu costurile aferente, se cere matricea drumurilor minime - d, aplicând algoritmul Johnson.

Restricții și precizări:

  • $ n <= 1000 $
  • $ m <= 25000 $
  • $ -1000 <= c <= 1.000$, unde c este costul unui arc.
    • Dacă nu există arc între o pereche de noduri x și y, distanța de la nodul x la nodul y din matricea ponderilor va fi 0.
    • Dacă după aplicarea algoritmului nu se găsește drum pentru o pereche de noduri x și y, se va considera distanța dintre ele egală cu 0 (se stochează în matricea distantelor valoarea 0).
    • Drumul de la nodul i la nodul i are lungime 0 (prin convenție).
    • Dacă graful conține un ciclu de cost negativ, se va afișa mesajul: Ciclu negativ!
  • timp de execuție
    • C++: 1s
    • Java: 8s

BONUS

La acest laborator, asistentul va alege 1-2 probleme din secțiunea extra.

Extra

Referințe

[0] Chapters Single-Source Shortest Paths / All-Pairs Shortest Paths, “Introduction to Algorithms”, Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein.

pa/laboratoare/laborator-10.txt · Last modified: 2022/05/03 14:07 by darius.neatu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0