This is an old revision of the document!
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.
În laboratorul 8 vom introduce contextul pentru Shortest-paths problem și vom studia Single-source shortest-paths problem, iar în laboratorul 9 vom continua cu All-pairs shortest-paths problem.
Vă rugăm să parcugeți Shortest-paths problem pentru a vă familiariza cu contextul, problema și notațiile folosite.
Concepte necesare:
În acest laborator vom studia single-source/destination shortest-paths problem. Pentru această problemă, vom prezenta 2 algoritmi:
Vom prezenta fiecare algoritm, îl vom analiza, iar la final vom vedea când îl vom folosi pe fiecare.
Puteți consulta capitolul Single-Source Shortest Paths din Introduction to Algorithms [0] pentru mai multe detalii despre acești algoritmi.
Algoritmul lui Edsger Wybe **Dijkstra** (Dijkstra’s algorithm) rezolvă shortest-paths problem în grafuri G = (V, E) cu costurile muchiilor nenegative ($w[u][v] \ge 0$).
// apply Dijkstra's algorithm from source // // source = the source for the computing distances // nodes = list of all nodes from G // adj[node] = the adjacency list of node // example: adj[node] = {..., neigh, ...} => edge (node, neigh) // // returns: d, p // d = distance vector // p = parent vector // Dijsktra(source, G=(nodes, adj)) { // STEP 0: initialize results // d[node] = distance from source to node // p[node] = parent of node on the shortest path from source to node foreach (node in nodes) { d[node] = +oo; // distance not yet computed p[node] = null; // parent not yet found } // STEP 1: initialize a priority queue pq = {}; // STEP 2: add the source(s) into q d[source] = 0; // distance from source to source p[source] = null; // source never has parent pq.push( (source, d[source]) ); // STEP 3: start relaxation loop using the node(s) from pq while (!pq.empty()) { // STEP 3.1: extract the next node (having the minimum estimate distance) node = pq.pop_min(); // [optional] STEP 3.2: print/use the node // STEP 3.3: relax all edges (node, neigh) foreach (neigh in adj[node]) { if (d[node] + w[node][neigh] < d[neigh]) { // try to relax edge (node, neigh) d[neigh] = d[node] + w[node][neigh]; // update the new distance from source to neigh p[neigh] = node; // save parent pq.push( (neigh, d[neigh]) ); // replace distance for neigh in pq } } } return d, p; } // Usage example: d, p = Dijkstra(source, G=(nodes, edges)); // 1. Use distances from d // (e.g. d[node] = distance from source to node) // // 2. Rebuild path from node to source using parents (p) RebuildPath(source, destination, p);
Acest algoritm menține un set de noduri (de exemplu, un min heap / priority queue) cu distanța minimă față de sursă fiind estimată. Cât timp mai există noduri pentru care distanța minimă față de sursă nu este finalizată, se extrage nodul $node$ pentru care estimarea este minimă și această se consideră finalizată. Deoarece acum distanța de la nodul sursă la $u$ este finală, se relaxează toate muchiile care pornesc din $node$, care sunt de forma $(node, neigh)$.
Pentru ca acest algoritm alege mereu “cel mai apropiat nod de sursă” spunem că Algoritmul lui Dijsktra este de tip greedy. Demonstrația se poate găsi în Introduction to algorithms.
Drumurile minime calculate de algoritmul lui Dijkstra sunt:
| node | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| d[node] | 0 | 1 | 1 | 3 | 3 | 1 | 3 | 4 | $+∞$ |
| p[node] | null | 1 | 1 | 3 | 4 | 1 | 6 | 5 | null |
Un nod scos din coadă în algoritmul lui Dijkstra are distanță calculată și finală. Ca această abordare greedy să fie corectă, trebuie ca toate costurile din graf să fie nenegative.
Algoritmul Bellman-Ford a fost inițial propus de Alfonso Shimbel (1955), dar publicat (1956-1958) și numit ulterior după Richard E. **Bellman** și Lester Randolph **Ford** Jr. . Acest algoritm rezolvă shortest-paths problem în grafuri G = (V, E) cu costurile muchiilor oarecare ($w[u][v]$ poate fi și negativ), dar fără cicluri de cost negativ (în grafurile de cost negativ nu putem defini costul unui drum care conține un ciclu).
Algoritmul detectează prezența ciclurilor de costuri negative în graf. Dacă nu există cicluri de cost negativ în graf, acesta furnizează vectorul de distanțe $d$ și vectorul de părinți $p$ (ca și Dijkstra).
// apply Bellman-Ford's algorithm from source // // source = the source for the computing distances // nodes = list of all nodes from G // edges = list of all edges from G // // returns: has_cycle, d, p // has_cycle = negative cycle detection flag (true if found) // d = distance vector (defined only if has_cycle == false) // p = parent vector (defined only if has_cycle == false) // Bellman-Ford(source, G=(nodes, edges)) { // STEP 0: initialize results // d[node] = distance from source to node // p[node] = parent of node on the shortest path from source to node foreach (node in nodes) { d[node] = +oo; // distance not yet computed p(node) = null; // parent not yet found } // STEP 1: set distance and parent for source node d[source] = 0; // distance from source to source p[source] = null; // source never has parent // STEP 2: do |nodes| - 1 relaxations for all edges in G for (i = 1 : |nodes| - 1) { foreach ((node, neigh) in edges) { if (d[node] + w[node][neigh] < d[neigh]) { // try to relax edge (node, neigh) d[neigh] = d[node] + w[node][neigh]; // update the new distance from source to neigh p[neigh] = node; // save parent } } } // STEP 3: check if edge relaxations can still be made foreach ((node, neigh) in edges) { if (d[node] + w[node][neigh] < d[neigh]) { // try to relax edge (node, neigh) // negative cycle detected! return true, null, null; } } // STEP 4: no cycle detected return false, d, p; } // Usage example: has_cycle, d, p = Bellman-Ford(source, G=(nodes, edges)); if (has_cycle) { print "Has Cycle!" STOP. } else { // 1. Use distances from d // (e.g. d[node] = distance from source to node) // // 2. Rebuild path from node to source using parents (p) RebuildPath(source, destination, p); }
Drumurile minime calculate de algoritmul lui Bellman-Ford sunt:
| node | 1 | 2 | 3 | 4 | 5 |
| d[node] | 0 | -2 | -2 | -3 | -2 |
| p[node] | null | 4 | 1 | 3 | 4 |
Dacă graful are un ciclu de cost negativ, se va putea plimba pe acel ciclu la infinit, reactulizând distanțele nodurilor implicate.
Scheletul de laborator se găsește pe pagina pa-lab::skel/lab09.
Î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ă:
Se dă un graf orientat cu n noduri și m arce. Graful are pe arce costuri pozitive (nenegative).
Folosiți Dijkstra pentru a găsi costul minim (lungimea minimă) a unui drum de la o sursă dată (source) la toate celelalte $n - 1$ noduri din graf.
Restricții și precizări:
1s2s
Rezultatul se va returna sub forma unui vector d cu n + 1 elemente.
Convenție:
$d[0]$ nu este folosit, deci va fi initializat cu 0! (am pastrat indexarea nodurilor de la 1).
S-a rulat anterior un algoritm oarecare de calculare a drumurilor de lungime minimă într-un graf cu n noduri și m arce folosind sursa source. Se cunoaște vectorul de părinți $p$ rezultat:
Se cere să se reconstituie drumul de la nodul source la nodul destination.
Restricții și precizări:
1s2s
Rezultatul se va returna sub forma unui vector path: $path = (source, ..., destination)$.
Se dă un graf orientat cu n noduri și m arce. Graful are pe arce costuri oarecare (pozitive sau negative).
Folosiți Bellman-Ford pentru a găsi costul minim (lungimea minimă) a unui drum de la o sursă dată (source) la toate celelalte $n - 1$ noduri din graf. Se va folosi aceeași convenție de reprezentare a vectorului de distanțe $d$.
Restricții și precizări:
1s2s
Enunț: Se consideră n orașe conectate prin zboruri. Fiecare zbor este definit de un oraș sursă, un oraș destinație și un preț. Să se determine cel mai ieftin traseu de la un oraș de plecare (src) la un oraș destinație (dst) care utilizează cel mult k escale.
Date de intrare: Numărul de orașe n, o listă de muchii orientate cu ponderi (zborurile și prețurile lor), orașul de plecare src, orașul destinație dst și numărul maxim permis de escale k.
Date de ieșire: Un singur număr întreg reprezentând costul minim al drumului. Dacă nu există niciun traseu valid care să respecte condiția numărului de escale, se va returna -1.
Problema se poate testa la: LeetCode - Cheapest Flights Within K Stops
Enunț: Se consideră o rețea formată din n noduri. Există o listă de conexiuni orientate, unde fiecare conexiune are o pondere ce reprezintă timpul necesar pentru ca un semnal să parcurgă distanța de la sursă la destinație. Se trimite un semnal dintr-un nod specificat k. Să se determine timpul minim necesar pentru ca toate cele n noduri din rețea să primească semnalul.
Date de intrare: O listă de muchii orientate cu ponderi pozitive, numărul total de noduri n și nodul de start k.
Date de ieșire: Timpul minim după care toate nodurile sunt atinse. Dacă este imposibil ca semnalul să ajungă la toate cele n noduri, se va afișa -1.
Problema se poate testa la: LeetCode - Network Delay Time
Enunț: Se consideră un joc desfășurat pe un graf orientat cu n camere și m tuneluri. Fiecare tunel are un scor asociat (pozitiv sau negativ). Să se determine scorul maxim ce poate fi obținut pe un traseu de la camera 1 la camera n.
Date de intrare: Prima linie conține numerele întregi n și m. Următoarele m linii conțin trei numere întregi a, b și x, reprezentând un tunel de la camera a la camera b cu scorul x.
Date de ieșire: Scorul maxim posibil. Dacă se poate obține un scor infinit de mare (există un ciclu de scor pozitiv pe un drum care conectează nodul 1 de nodul n), se va afișa -1.
Problema se poate testa la: CSES - High Score
Enunț: Se consideră parcurgerea unui traseu de la orașul 1 la orașul n. Se oferă un singur cupon de reducere ce poate fi utilizat o singură dată pentru a înjumătăți prețul unui singur zbor (prețul devine c / 2 rotunjit în jos). Să se determine costul minim total pentru a ajunge la destinație folosind cuponul în mod optim.
Date de intrare: Prima linie conține numerele n (orașe) și m (zboruri). Următoarele m linii descriu zborurile orientate sub forma a b c (sursă, destinație, preț).
Date de ieșire: Un singur număr întreg reprezentând costul minim de la orașul 1 la orașul n.
Problema se poate testa la: CSES - Flight Discount
Enunț: Se dă o matrice de dimensiune n x n unde fiecare celulă reprezintă altitudinea terenului. Nivelul apei crește uniform în timp, fiind egal cu t la momentul t. Deplasarea între celule vecine este permisă doar dacă altitudinea ambelor celule este mai mică sau egală cu nivelul actual al apei. Să se determine timpul minim t necesar pentru a ajunge din celula (0, 0) în celula (n-1, n-1).
Date de intrare: O matrice 2D cu n x n valori întregi reprezentând altitudinile.
Date de ieșire: Valoarea minimă a lui t care permite existența unui drum valid.
Problema se poate testa la: LeetCode - Swim in Rising Water
Enunț: Se consideră un graf neorientat cu n noduri, unde fiecare muchie are asociată o probabilitate de succes (între 0 și 1). Probabilitatea unui drum este produsul probabilităților tuturor muchiilor de pe acel traseu. Să se determine probabilitatea maximă de a ajunge de la un nod de start la un nod de destinație.
Date de intrare: Numărul de noduri n, lista de muchii neorientate, lista probabilităților corespunzătoare, nodul de start și nodul final.
Date de ieșire: Un număr real reprezentând probabilitatea maximă de succes.
Problema se poate testa la: LeetCode - Path with Maximum Probability
La acest laborator, asistentul va alege 1-2 probleme din secțiunea extra.
[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.