All-Pairs Shortest paths Algoritmiek
All Pairs Shortest Paths Kortste pad tussen alle paren knopen naar A R U 1 2 9 4 5 6 2 A U 5 1 van 4 R Algoritmiek
Hoe zouden jullie het doen? Algoritmiek
Simpel idee Single source shortest path vanuit iedere knoop Zonder lengtes BFS: O(n (n+a)) Alleen niet-negatieve lengtes Dijkstra: O(n (a + n log n)) Negatieve lengtes, maar geen negatieve cycles Bellman-Ford: O(n (na)) Algoritmiek
n keer Bellman-Ford direct (1) In plaats van n aanroepen, doe alles in één dynamisch-programmerings algoritme Schrijf: Tq(i,j) als lengte van het kortste pad van knoop i naar knoop j dat hooguit q pijlen gebruikt Herinner correctheidsbewijs: Bellman-Ford berekent eigenlijk Tq(s,j) voor iedere q, j Algoritmiek
n keer Bellman-Ford direct (2) Tq(i,j) als lengte van het kortste pad van knoop i naar knoop j dat hooguit q pijlen gebruikt Basisgeval:T1(i,j) = 0 als i=j L(i,j) als er een pijl (i,j) bestaat ¥ anders Bellman-Ford aanpak: Tq+1(i,j) = min{ Tq(i,j), min(k,j) in A{Tq(i,k)+L(k,j)} } q: iteratienummer, min: te vergelijken met relaxatie Algoritmiek
Voorbeeld Algoritmiek
n keer Bellman-Ford direct (3) Tq(i,j) als lengte van het kortste pad van knoop i naar knoop j dat hooguit q pijlen gebruikt Tq+1(i,j) = min{Tq(i,j), min(k,j) in A{Tq(i,k)+L(k,j)} } Correctheid: kortste pad P met hooguit q+1 pijlen is een kortste pad met hooguit q pijlen of er is een knoop k met (k,j) in A z.d.d. P een kortste pad is van i naar k met (hooguit) q pijlen gevolgd door pijl (k,j) Sub-pad optimaliteit Door inductie en dat we min(k,j) in A nemen, weten we dat Tq+1 correct berekend wordt Algoritmiek
n keer Bellman-Ford direct (3) Tq(i,j) als lengte van het kortste pad van knoop i naar knoop j dat hooguit q pijlen gebruikt Tq+1(i,j) = min{Tq(i,j), min(k,j) in A{Tq(i,k)+L(k,j)} } n iteraties In iedere iteratie: voor iedere knoop i, voor iedere knoop j, kijk naar alle ingaande buren k van j Hoe snel is dit? O(n4) met simpele analyse O(n2a) is preciezer Algoritmiek
n keer Bellman-Ford direct (3) Tq(i,j) als lengte van het kortste pad van knoop i naar knoop j dat hooguit q pijlen gebruikt Tq+1(i,j) = min{Tq(i,j), min(k,j) in A{Tq(i,k)+L(k,j)} } Ruimtegebruik: O(n4), O(n3), of O(n2)? Truuk: om Tq+1 te berekenen hebben we alleen Tq nodig, en niet Tq-1, Tq-2, … Dus O(n2) ruimte Algoritmiek
Een algemener algoritme Tq(i,j) als lengte van het kortste pad van knoop i naar knoop j dat hooguit q pijlen gebruikt Tq(i,j) = minknoop k {Tp(i,k) + Tq-p(k,j) } voor een vaste keuze van p Correctheid: in kortste pad P met hooguit q pijlen bestaat er knoop k z.d.d. P een kortste pad van i naar k is met (hooguit) p pijlen gevolgd door kortste pad van k naar j met (hooguit) q-p pijlen Sub-pad optimaliteit Door inductie en doordat we min over alle knopen nemen, weten we dat Tq correct berekend wordt Algoritmiek
Voorbeeld Algoritmiek
Een algemener algoritme Tq(i,j) als lengte van het kortste pad van knoop i naar knoop j dat hooguit q pijlen gebruikt Tq(i,j) = minknoop k {Tp(i,k) + Tq-p(k,j) } voor een vaste keuze van p Wat is een goede keuze van p? p = q/2. Idee: pad halveren (cf. binair zoeken) Dan alleen nodig Tn-1, T(n-1)/2, T(n-1)/4, T(n-1)/8, … Hoeveel iteraties? log n, dus looptijd O(n3 log n) Algoritmiek
Kan het sneller? We hebben nu O(n2a) en O(n3 log n) Hoe snel hangt af van ijlheid van de graaf Hoe doen we dit sneller? Vorige algoritme, maar met soort van snelle matrix-vermenigvuldiging Pad halveren blijft Of: slimmere keuze van de knoop in het midden Floyd-Warshall algoritme Algoritmiek
Floyd-Warshall algoritme Algoritmiek
Floyd-Warshall: idee Nummer de knopen 1, 2, …, n Kijk naar kortste pad P van knoop i naar knoop j Laat k de knoop van P zijn met hoogste nummer P[i,k] is kortste pad van i naar k dat alleen knopen met nummer kleiner dan k gebruikt; zelfde P[k,j] Sub-pad optimaliteit k i j Algoritmiek
Floyd-Warshall: algoritme Nummer de knopen 1, 2, …, n. Dk(i,j) = lengte van het kortste pad van i naar j, waarbij het pad alleen knopen i, j, 1, 2, …, k mag gebruiken Merk op: d(i,j) = Dn(i,j) D2(3,5) = 4 D4(3,5) = 2 D1(1,5) = ¥ D2(1,5) = 20 D3(1,5) = 9 D4(1,5) = D5(1,5) = 7 10 10 2 5 1 4 5 1 3 4 1 Algoritmiek
Floyd-Warshall: algoritme Initialisatie: k = 0 Als (i,j) in A, dan D0(i,j) = L(i,j) Als (i,j) geen pijl in A, dan: D0(i,j) = ¥ k > 0: Dk(i,j) = min{ Dk-1(i,j), Dk-1(i,k) + Dk-1(k,j) } Helpt de nieuwe knoop k? Dk-1(i,k) Dk-1(k,j) k i j Dk-1(i,j)
Floyd-Warshall: correctheid Dk(i,j) = lengte van het kortste pad van i naar j, waarbij het pad alleen knopen i, j, 1, 2, …, k mag gebruiken Dk(i,j) = min{ Dk-1(i,j), Dk-1(i,k) + Dk-1(k,j) } Kortste pad gebruikt knoop k niet of wel; in het tweede geval splitst het pad zich in kortste paden i ~> k en k ~> j die alleen knopen met nummer hooguit k-1 gebruiken k i j
Floyd-Warshall: implementatie Initialiseer 3-dimensionaal array D(0…n, 1…n, 1…n) op ¥ ( D(k,i,j) geeft Dk(i,j) weer ) For i=1 to n do For j=1 to n do if (i,j) in A then D(0,i,j) = L(i,j); For k=1 to n do D(k,i,j) = min { D(k-1,i,j), D(k-1,i,k)+D(k-1,k,j) } Antwoorden staan in D(n,*,*) Algoritmiek
Floyd-Warshall: analyse Triviaal: O(n3) tijd Ruimte: O(n3) Kunnen we minder ruimte gebruiken? Algoritmiek
Floyd-Warshall: implementatie Initialiseer 3-dimensionaal array D(0…n, 1…n, 1…n) op ¥ ( D(k,i,j) geeft Dk(i,j) weer ) For i=1 to n do For j=1 to n do if (i,j) in A then D(0,i,j) = L(i,j); For k=1 to n do D(k,i,j) = min { D(k-1,i,j), D(k-1,i,k)+D(k-1,k,j) } Antwoorden staan in D(n,*,*) Om D(k,*,*) te berekenen, gebruiken we alleen D(k-1,*,*) en niet D(k-2,*,*) etc Algoritmiek
Floyd-Warshall: minder geheugen Maak 2-dimensionale arrays D(1…n, 1…n), C(1…n,1…n) Initaliseer D voor pijlen For k=1 to n do For i=1 to n do For j=1 to n do C(i,j) = min { D(i,j), D(i,k)+D(k,j) } D(i,j) = C(i,j) Antwoorden staan in D For i=1 to n do For j=1 to n do if (i,j) in A then D(0,i,j) = L(i,j); Standaard techniek voor geheugenbesparing Algoritmiek
20 10 5 ¥ 1 4 20 10 5 ¥ 1 4 20 30 25 10 10 2 5 1 4 5 1 3 4 1 1 10 5 ¥ 20 1 4 30 25 10 5 6 9 ¥ 1 4 20 30 25 26 3 10 5 6 7 ¥ 1 2 20 30 25 26 2 4 10 5 6 7 30 35 36 22 32 1 2 21 31 26 20 25 5 Algoritmiek
Eigenschappen C(i,j) = min{ D(i,j), D(i,k)+D(k,j) } 10 5 6 7 ¥ 1 2 20 30 25 26 Eigenschappen Merk op C(k,k) = D(k,k) = 0 Informatie nodig in ronde k verandert niet in ronde k Waarde van C(i,j) wordt 1 keer geschreven in ronde k D(i,j) wordt 1 keer uitgelezen en wordt dan in C(i,j) geschreven, BEHALVE D(*,k) en D(k,*) worden telkens uitgelezen Maar: C(i,k) = min {D(i,k), D(i,k)+D(k,k) }=D(i,k) Dus: in plaats van 2 kunnen we met 1 matrix werken. 10 5 6 7 30 35 36 22 32 1 2 21 31 26 20 25 C(i,j) = min{ D(i,j), D(i,k)+D(k,j) } Algoritmiek
Floyd-Warshall: minste geheugen Initialiseer 2-dimensionale array D(1…n, 1…n) op ¥ For i=1 to n do For j=1 to n do if (i,j) in A then D(i,j) = L(i,j); For k=1 to n do D(i,j) = min { D(i,j), D(i,k)+D(k,j) } Antwoorden staan in D Algoritmiek
Floyd-Warshall: analyse Triviaal: O(n3) tijd Ruimte: O(n2) Minder ruimte niet zinnig Uitvoer heeft O(n2) ruimte nodig Algoritmiek
Floyd-Warshall: constructief Hoe bepalen we het pad? Houd bij welke knoop we gebruiken Zelfde idee voor Dijkstra, Bellman-Ford, etc. Extra matrix P P(v,w) geeft een knoop die ligt tussen v en w op een kortste pad van v naar w Tijdens algoritme: P(v,w) geeft zo’n knoop van een pad van v naar w met lengte de huidige waarde D(v,w) Algoritmiek
Floyd-Warshall: constructief Maak 2-dimensionale arrays D(1…n, 1…n) en P(1…n,1…n) Initaliseer D met pijlen For i=1 to n do For j=1 to n do P(i,j) = “#” For k=1 to n do If D(i,j) > D(i,k)+D(k,j) Then D(i,j) = D(i,k) + D(k,j); P(i,j) = k For i=1 to n do For j=1 to n do if (i,j) in A then D(i,j) = L(i,j); Algoritmiek
Construeer pad van i naar j Pathconstruct (P, D, i, j) if D(i,j) = ¥ then return “Er is geen pad” if P(i,j) = “#” then return pad met 1 pijl: (i,j). k = P(i,j); Pad1 = Pathconstruct(P,D,i,k); Pad2 = Pathconstruct(P,D,k,j); Plak paden Pad1 en Pad2 achterelkaar, en return dat pad. Algoritmiek
Pijlen met negatieve lengte Algoritme werkt correct als er geen negatieve cycle is Als wel een negatieve cycle: er komt een v met D(v,v) < 0 -2 ¥ -1 1 -1 -2 3 2 1 1 -1 4 -2 ¥ -1 1 -2 -3 ¥ -1 1 -2 -3 ¥ -1 1 -2 -3 ¥ -1 1 D(4,3)+D(3,4) = – 2 + 1 = – 1 Algoritmiek
“Huiswerk” Bewijs dat Floyd-Warshall een negatieve waarde geeft voor D(v,v) voor alle knopen v die in een negatieve cycle zitten Algoritmiek
Link doen met Dijkstra’s algoritme Johnson’s algoritme Algoritmiek
Dijkstra: ter herinnering Dijkstra’s algoritme werkt niet met negatieve pijl-lengtes Zelfs niet als we een constante optellen bij iedere pijl-lengtes v 5 s -20 1 t x 2 s v t -1 1 Algoritmiek
Johnson’s algoritme voor APSP Basis-idee: Dijkstra’s algoritme aanroepen vanuit iedere knoop Verrassend: werkt voor negatieve pijl-lengtes Truuk: Tel een waarde bij iedere pijl op die niet constant is, maar goed gekozen wordt Algoritmiek
Johnson: pijl-lengte (1) Kies een waarde p(u) voor iedere knoop u Op een slimme manier (zien we zo) p is een potentie functie Bereken L’(u,v) = p(u) + L(u,v) – p(v) Bewering: Als Q een pad is van s naar t, dan geldt L’(Q) = p(s) + L(Q) – p(t) 4 1 1 u v -2 Algoritmiek
Johnson: pijl-lengte (2) L’(u,v) = p(u) + L(u,v) – p(v) Bewering: Als Q een pad is van s naar t, dan geldt L’(Q) = p(s) + L(Q) – p(t) Stel Q is s = v0 v1 … vk = t L’(Q) = Σi L’(vi-1, vi) = Σi p(vi-1) + L(vi-1, vi) – p(vi) = Σi L(vi-1, vi) + Σi p(vi-1) – p(vi) = L(Q) + p(v0) – p(vk) = L(Q) + p(s) – p(t) Algoritmiek
Johnson: pijl-lengte (3) L’(u,v) = p(u) + L(u,v) – p(v) Bewering: Als Q een pad is van s naar t, dan geldt L’(Q) = p(s) + L(Q) – p(t) Gevolgen: Als Q en R paden van s naar t zijn, dan L’(Q) ≤ L’(R) d.e.s.d.a L(Q) ≤ L(R) Een kortste pad onder L is een kortste pad onder L’, en visa versa Algoritmiek
Johnson: pijl-lengte (4) L’(u,v) = p(u) + L(u,v) – p(v) Een kortste pad onder L is een kortste pad onder L’, en visa versa Truuk: functie p kiezen zodat L’ niet-negatief is Algoritmiek
Johnson: keuze van p Voeg een nieuwe knoop x toe Pijl (x,u) voor iedere knoop u L(x,u) = 0 Bereken d(x,u) voor iedere knoop u Zet p(u) = d(x,u) Bewering: L’(u,v) = p(u) + L(u,v) – p(v) ≥ 0 Want p(u) + L(u,v) ≥ p(v) Pad x ~> u -> v is een x ~> v pad, kortste pad is minstens zo kort G Algoritmiek
Johnson: algoritme G x Voeg een nieuwe knoop x toe, pijl (x,u) voor iedere knoop u, L(x,u) = 0 Bereken p(u) = d(x,u) voor iedere knoop u Bereken L’(u,v) = p(u) + L(u,v) – p(v) Bereken dL’(u,v) door n keer Dijkstra’s algoritme Mag omdat L’(u,v) ≥ 0 voor alle pijlen (u,v) Bereken d(u,v) = dL’(u,v) – p(u) + p(v) Herinner: Als Q een pad is van s naar t, dan geldt L’(Q) = p(s) + L(Q) – p(t) Gebruik Bellman-Ford
Johnson: correctheid Correctheid van pijl-lengtes al beargumenteerd Nieuwe pijl-lengtes zijn niet-negatief, dus we mogen Dijkstra gebruiken Graaf G plus knoop x bevat geen negatieve cycles als G geen negatieve cycles bevat, dus we mogen Bellman-Ford gebruiken Kortste pad onder nieuwe pijl-lengtes komt overeen met kortste pad onder oude pijl-lengtes Algoritmiek
Johnson: analyse Bellman-Ford om potentie-functie uit te rekenen O(na) n keer Dijkstra O(n (a + n log n) ) Totaal: O(na + n2 log n) Algoritmiek
Reflectie op APSP Algoritmiek
Reflectie: theorie Algoritmes: O(n3) en O(na + n2 log n) Kan het sneller? Ja, voor speciale klassen van grafen Sneller dan O(n2) niet, vanwege formaat uitvoer In het algemeen? Veel andere problemen met algoritme met looptijd O(n3) zijn ‘vergelijkbaar’ Master-vak: Network Science & Analysis Algoritmiek
Samenvatting Single source/target Dijkstra’s algoritme: O((n+a) log n) of O(a + n log n) Alleen niet-negatieve lengtes Bellman-Ford: O(na) Ook negatieve lengtes, geen negatieve cycles All pairs: O(n3) of O(na + n2 log n) Kan ook negatieve lengtes aan; detecteert negatieve cycles Single pair: veel toepassingen, maar er zijn in O-notatie geen snellere algoritmen bekend Wel in de praktijk (o.a. bidirectioneel zoeken, werken met schattingen, …) Algoritmiek