This is an old revision of the document!


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

Adobe is a global technology leader with a mission to change the world through personalized digital experiences. For over four decades, Adobe has transformed how individuals, teams, businesses, and enterprises create, manage, and deliver content across every surface and channel.

With AI at the core, tools like Adobe Firefly and AI-powered assistants make creativity faster, smarter, and more personalized—while keeping it safe and responsible. In Adobe Experience Cloud, AI powers real-time personalization, predictive insights, and automated customer journeys, helping brands deliver exceptional experiences at scale. At Adobe, creativity meets productivity, and AI is the catalyst for transformation.

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] și d[4][1])
-1234
10+∞-2+∞
2402+∞
3+∞+∞02
43-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
43-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
43-110
  • Drumurile minime sunt finale (cele menționate anterior - $d_4$).


Complexitate

  • complexitate temporală: $T = O(n^3)\ sau\ O(|V|^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 = ComputeH(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.

<note warning>

Î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

Pool probleme (pentru prezentări)

1) Number of Possible Sets of Closing Branches

Enunț: O companie are N sucursale și mai multe drumuri bidirecționale, fiecare având o anumită distanță. O configurație a sucursalelor care rămân deschise este considerată validă dacă distanța minimă dintre oricare două sucursale deschise nu depășește o limită dată. Sarcina este să găsești numărul total de configurații valide (inclusiv configurația în care toate sucursalele sunt închise).

Date de intrare: Un număr întreg N reprezentând numărul de sucursale, un număr întreg maxDistance reprezentând distanța maximă permisă și o matrice roads, unde fiecare element este un triplet [u, v, weight] ce reprezintă un drum bidirecțional și lungimea acestuia.

Date de ieșire: Un singur număr întreg reprezentând numărul total de configurații valide de sucursale.

Problema se poate testa la: \ LeetCode - Number of Possible Sets of Closing Branches

2) Minimum Cost to Convert String I

Enunț: Se dau două șiruri de caractere, source și target, de aceeași lungime, formate exclusiv din litere mici ale alfabetului englez, și trei vectori: original, changed și cost. O literă din original se poate transforma în litera corespunzătoare din changed plătind costul asociat din cost. Se cere găsirea costului minim total pentru a transforma șirul source în șirul target, știind că operațiile de transformare pot fi aplicate în lanț de oricâte ori.

Date de intrare: Șirurile de caractere source și target, împreună cu vectorii de transformare original, changed și cost.

Date de ieșire: Un singur număr întreg reprezentând costul minim pentru transformarea întregului șir, sau -1 dacă transformarea este imposibilă.

Problema se poate testa la: LeetCode - Minimum Cost to Convert String I

3) Roy-Floyd Invers (rfinv)

Enunț: Se dă o matrice de dimensiune N × N care reprezintă potențialele distanțe minime dintre oricare două noduri dintr-un graf orientat (adică rezultatul aplicării algoritmului Roy-Floyd). Se cere să se verifice dacă această matrice este validă și, în caz afirmativ, să se determine numărul minim de muchii pe care ar trebui să le aibă graful inițial pentru a genera exact aceste distanțe.

Date de intrare: Numărul de teste T. Pentru fiecare test, se dă numărul de noduri N, urmat de o matrice de dimensiune N × N ce reprezintă distanțele.

Date de ieșire: Pentru fiecare test, se va afișa pe o linie separată “NU” dacă nu se poate construi un astfel de graf. Dacă este posibil, se va afișa “DA”, urmat de un spațiu și de numărul minim de muchii ale grafului inițial.

Problema se poate testa la: Infoarena - rfinv

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-09.1778221053.txt.gz · Last modified: 2026/05/08 09:17 by radu.nichita
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