RAČUNALNIŠTVO IN INFORMACIJSKE TEHNOLOGIJE OSNOVE ALGORITMOV NIKOLA GUID Fakulteta za elektrotehniko, računalništvo in informatiko Maribor, 2011 Kazalo 2 Zmanjšaj-in-vladaj 2-1 2.1 Urejanje z vrivanjem . . . . . . . . . . . . . . . . . . . . . . . . . . 2-3 2.2 Iskanje z razvijanjem v širino . . . . . . . . . . . . . . . . . . . . . 2-6 2.3 Iskanje z razvijanjem v globino . . . . . . . . . . . . . . . . . . . . 2-13 Poglavje 2 Zmanjšaj-in-vladaj Strategija zmanjšaj-in-vladaj (decrease-and-conquer ) izkorišča odnos med rešitvijo danega primerka problema in rešitvijo manjšega primerka istega problema [Levitin, 2007]. Po določitvi tega odnosa problem rešujemo bodisi od zgoraj navzdol (rekurzivno) bodisi od spodaj navzgor (brez rekurzije). Obstajajo tri glavne variante pristopa zmanjšaj-in-vladaj: 1. zmanjšaj za konstanto, 2. zmanjšaj za konstantni faktor in 3. zmanjšaj za spremenljivo velikost. 1. Zmanjšaj za konstanto V varianti zmanšaj za konstanto se velikost primerka zmanjša za isto konstanto pri vsaki iteracijo algoritma. Tipično je ta konstanta enaka 1. Zgled 2.1. Problem je izračun potence an za n > 0. Odnos med rešitvijo primerka velikosti n in primerka velikosti n − 1 je določen s formulo: an = an−1 · a, kar pomeni, da velikost problema zmanjšamo za 1. Funkcijo f (n) = an lahko izračunamo bodisi od zgoraj navzdol s pomočjo rekurzivne enačbe: ½ f (n − 1) · a, ˇce n > 1 f (n) = (2.1) a, ˇce n = 1 bodisi od spodaj navzgor, tako da množimo a med sabo (n − 1)-krat. ♦ 2. Zmanjšaj za konstantni faktor Varianta zmanšaj za konstantni faktor zmanjšuje problem pri vsaki iteraciji za konstantni faktor. V večini aplikacij je ta faktor dva. 2-2 Zgled 2.2. Vrnimo se na problem izračuna potence an za n > 0. Če je primerek velikosti n izračun an , je primerek polovične velikosti izračun an/2 , kar pomeni, da velikost problema zmanjšamo za dvakrat. Med njima velja naslednja relacija: an = (an/2 )2 . Slednja relacija velja samo, če je n sodo število. Če je n liho število, moramo rezultat na desni strani enačbe pomnožiti še z a. Povzamimo celotno formulo: n/2 2 ˇce je n sodo in pozitivno ˇstevilo (a ) , n n/2 2 (2.2) a = (a ) · a, ˇce je n liho in veˇcje od 1 a, ˇce n = 1 Če izračunamo an rekurzivno po formuli 2.2, lahko pričakujemo časovno zahtevnost O(log n), ker se pri vsaki iteraciji velikost reducira za najmanj polovico. Izračunajmo npr. a16 . Najprej moramo izračunati a8 (saj je a16 = (a8 )2 ), nato a4 (saj je a8 = (a4 )2 ), nato a2 (saj je a4 = (a2 )2 ) in na koncu še a1 (saj je a2 = (a1 )2 ). Skupaj smo imeli 4 izračune, kar ustreza vrednosti log2 16 = 4. ♦ Po strategiji deli-in-vladaj se problem prevede na reševanje dveh podproblemov velikosti n/2: ½ bn/2c dn/2e a ·a , ˇce n > 1 n a = (2.3) a, ˇce n = 1 Algoritem, ki temelji na formuli 2.3, je mnogo počasnejši od algoritma, temelječega na formuli 2.2. 3. Zmanjšaj za spremenljivo velikost V varianti zmanjšaj za spremenljivo velikost se velikost zmanjšanja spreminja pri vsaki iteraciji. Zgled 2.3. Evklidov algoritem za izračun največjega skupnega delitelja je primer te variante. Algoritem temelji na formuli gcd(m, n) = gcd(n, m mod n). Argumenti na desni strani so vedno manjši od tistih na levi strani. Manjši niso niti za konstanto niti za konstantni faktor. ♦ 2.1 Urejanje z vrivanjem 2.1 2-3 Urejanje z vrivanjem Uporabimo strategijo zmanjšaj-in-vladaj (varianta zmanjšaj za konstanto, ki je ena) pri urejanju zaporedja A[1..n]. Predpostavimo, da je manjše zaporedje A[1..n − 1] že urejeno, tako da velja A[1] ≤ A[2] ≤ . . . ≤ A[n − 1]. Če hočemo urediti zaporedje A[1..n], je potrebno samo postaviti element A[n] na pravo mesto (pred prvim večjim elementom), saj so vsi drugi elementi že urejeni po velikosti. Na tej ideji urejanja temelji algoritem urejanje z vrivanjem (insertion sort), ki je učinkovit algoritem za urejanje manjšega števila elementov. Čeprav ideja urejanja z vrivanjem temelji na rekurziji, algoritem implementiramo lažje od spodaj navzgor, tj. iterativno. Delovanje algoritma lahko ponazorimo z urejanjem kart. Ko igralec prejme sveženj kart, jih mora urediti običajno po številkah (po moči ali pomembnosti posameznih kart). Igralec (desničar) drži karte v levi roki, z desno pa vriva karte na pravilno mesto. Denimo, da se igralec odloči, da bo vrednost njegovih kart tekla z leve proti desni. Tedaj vzame običajno v desno roko drugo karto, gledano z leve, in jo pusti na mestu, če je ta večja od leve karte, sicer jo postavi pred levo karto. Zatem vzame tretjo karto z leve in jo vrine v že urejeno zaporedje levo od te karte. V splošnem j-to karto z leve vrine v urejeno zaporedje A[1..j − 1], pri čemer je j = 2, 3, . . . , n, kjer je n velikost zaporedja (polja) A. Delovanje algoritma opisuje naslednji psevdokod (Rezultat je urejeno zaporedje, ki je postavljeno v isto polje A): UREJANJE-Z-VRIVANJEM(A) 1 for j ← 2 to n % vrini izbrano vrednost A[j] v urejeno zaporedje A[1..j − 1] 2 do kljuc ← A[j] 3 i←j−1 4 while i > 0 and A[i] > kljuc 5 do A[i + 1] ← A[i] % večjo številko prestavi desno 6 i←i−1 7 A[i + 1] ← kljuc % ključ postavi na pravo mesto Zgled 2.4. Dano je zaporedje A = h7, 1, 3, 8, 0, 2i. Kako deluje na njem procedura UREJANJE-Z-VRIVANJEM, kaže slika 2.1. 1. Vrstica 1: Najprej vstopimo v prvi cikel zanke for, tako da postavimo j = 2. 2. Vrstica 2: Postavimo kljuc = A[j] = A[2] = 1. 3. Vrstica 3: Postavimo i = j − 1 = 1 2-4 2.1 Urejanje z vrivanjem 7 1 3 8 0 2 1 7 3 8 0 2 1 3 7 8 0 2 1 3 7 8 0 2 0 1 3 7 8 2 0 1 2 3 7 8 končano Slika 2.1: Delovanje procedure UREDI-Z-VRIVANJEM 4. Vrstica 4: Vstopimo v zanko while. V prvi iteraciji te zanke sta oba pogoja izpolnjena (i > 0 (1>0) in A[1] > kljuc (7>1). 5. Vrstica 5: Izvedi: A[2] = A[1] = 7. 6. Vrstica 6: Dekrementiraj i: i = 0. 7. Vrstica 4: Vstopimo v zanko while. Ker ne velja več i > 0, izstopimo iz zanke. 8. Vrstica 7: Sedaj postavimo ključ na pravo mesto: A[1] = kljuc = 1. Trenutni rezultat je zaporedje A = h1, 7, 3, 8, 0, 2i. 9. Vrstica 1: Vstopimo v drugi cikel zanke for, tako da postavimo j = 3. 10. Vrstica 2: Postavimo kljuc = A[j] = A[3] = 3. 11. Vrstica 3: Postavimo i = j − 1 = 2. 12. Vrstica 4: Vstopimo v zanko while. V prvi iteraciji te zanke sta oba pogoja izpolnjena (i > 0 (2>0) in A[2] > kljuc (7>3)). 13. Vrstica 5: A[3] = A[2] = 7. 14. Vrstica 6: Dekrementiraj i: i = 1. 2-5 2.1 Urejanje z vrivanjem 15. Vrstica 4: Vstopimo v drugo iteracijo zanke while. Drugi pogoj ni izpolnjen (A[1] ni večje od kljuc (ne velja 1>3)), zato izstopimo iz zanke. 16. Vrstica 7: Sedaj postavimo ključ na pravo mesto: A[2] = kljuc = 3. Trenutni rezultat je zaporedje A = h1, 3, 7, 8, 0, 2i. 17. Vrstica 1: Vstopimo v tretji cikel zanke for, tako da postavimo j = 4. 18. Vrstica 2: Nadaljujemo na zgoraj opisan način do končne ureditve zaporedja. Najprej postavimo na pravo mesto element 8, nato 0 in na koncu element 2, saj se algoritem izvede do j = 6. Rezultat algoritma je urejeno zaporedje A = h0, 1, 2, 3, 7, 8i (slika 2.1). ♦ Časovna zahtevnost procedure UREJANJE-Z-VRIVANJEM Zanka for se izvede ne glede na urejenost zaporedja (n − 1)-krat. Izvajanje notranje zanke while je odvisno od urejenosti zaporedja. Če je zaporedje urejeno nepadajoče, se ne izvede nikoli. Tedaj dobimo najugodnejši primer oz. spodnjo mejo časovne zahtevnosti: T (n) = Ω(n). Če je zaporedje urejeno nerastoče, se notranja zanka izvede na vsakem koraku (j − 1)-krat. Tedaj dobimo zgornjo mejo časovne zahtevnosti: T (n) = O(n2 ). V splošnem primeru urejenosti zaporedja se da določiti (precej zahtevno!) poprečno časovno zahtevnost: TA (n) = O(n2 ). 2.2 Iskanje z razvijanjem v širino 2.2 2-6 Iskanje z razvijanjem v širino Iskanje z razvijanjem v širino (breadth-first search) je eden najpreprostejših algoritmov za preiskovanje grafa. V danem grafu G = (V, E) podamo izhodišče (source vertex ) ali začetno vozlišče s. Algoritem sistematično preiskuje povezave v G in odkriva vsako vozlišče, ki je dosegljivo iz s. Izračuna razdaljo (najmanjše število povezav) iz s do vseh dosegljivih vozlišč. Določa tudi drevo razvijanja v širino s korenom s in z vsemi dosegljivimi vozlišči. Vsaka pot iz s do vozlišča v je najkrajša pot iz s v v, če so uteži vseh povezav enake. Algoritem deluje na neusmerjenem in usmerjenem grafu. Osnovna računska operacija je razvoj (expansion) vozlišča, kar pomeni, da tvorimo vse njegove naslednike (ki še niso bili tvorjeni), preden začnemo obravnavati drugo vozlišče. Pri napredovanju algoritma si pomagamo z barvami. Vozlišče ima lahko belo, sivo ali črno barvo. Na začetku so vsa vozlišča bela, kasneje postanejo siva in črna. Siva barva pomeni, da je vozlišče tvorjeno (generated [Nilsson, 1980]) oz. odkrito (discovered [Cormen et al., 2007]). Črna barva nekega vozlišča pomeni, da so bili tvorjeni in obravnavani vsi njegovi sosedi. V žargonu teorije grafov to pomeni, da je bilo vozlišče razvito. Dani algoritem lahko smatramo kot primer uporabe strategije zmanjšaj-invladaj z varianto zmanjšaj za konstanto, ki je ena (na vsakem koraku razvijamo eno vozlišče). Začnemo z razvijanjem začetnega vozlišča. Algoritem se zaključi, ko smo razvili vsa vozlišča grafa. Barva vozlišča u je shranjena v spremenljivki barva[u]. Očeta od vozlišča u označuje spremenljivka oce[u]. Razdaljo od začetnega vozlišča s do vozlišča u hranimo v spremenljivki d[u]. Algoritem uporablja vrsto Q s strežnim pravilom FIFO. Vrsta služi za upravljanje množice sivih (tvorjenih) vozlišč. 2.2 Iskanje z razvijanjem v širino 2-7 ISKANJE-Z-RAZVIJANJEM-V-SIRINO(G, s) 1 for vsako vozlišče u ∈ V − {s} 2 do barva[u] ←BELA 3 d[u] ← ∞ 4 oce[u] ← NIL 5 barva[s] ←SIVA 6 d[s] ← 0 7 oce[s] ← NIL 8 Q←∅ 9 VSTAVI-V-VRSTO(Q, s) 10 while Q 6= ∅ % dokler vrsta Q ni prazna 11 do u ← IZLOCI-IZ-VRSTE(Q) % izloči u iz vrste Q % tvori vse sosede od vozlišča u, ki imajo belo barvo 12 for vsako vozlišče v ∈ Adj[u] % iz seznama sosedov Adj[u] 13 do if barva[v] = BELA 14 then barva[v] ← SIVA 15 d[v] ← d[u] + 1 16 oce[v] ← u 17 VSTAVI-V-VRSTO(Q, v) 18 barva[u] ← ČRNA % vozlišče u je že razvito Vrstice 1–4 v proceduri ISKANJE-Z-RAZVIJANJEM-V-SIRINO pobarvajo vsako vozlišče u razen vozlišče s belo, postavijo d[u] v neskončnost in očeta od u (oce[u]) na NIL. V vrstici 5 se pobarva začetno vozlišče s sivo. Vrstica 6 postavi d[s] na nič, vrstica 7 pa postavi očeta od s na NIL. Potem ko se v vrstici 8 izprazni vrsta Q, se v vrstici 9 vstavi v vrsto Q vozlišče s. V vrsti Q so venomer samo siva vozlišča. Glavno zanko (while) programa predstavljajo vrstice od 10–18. Zanka se izvaja, dokler je še kakšno sivo vozlišče v vrsti Q. Vrstica 11 izbere sivo vozlišče iz glave vrste Q. Zanka for v vrsticah 12–17 obravnava vsako vozlišče v iz seznama sosedov vozlišča u. Če je vozlišče v belo, potem še ni bilo tvorjeno in algoritem ga obdela v vrsticah od 14–17. Najprej vozlišče pobarva sivo, nato postavi razdaljo d[v] na vrednost d[u] + 1, postavi očeta od v (oce[v] = u) in prenese v na rep vrste Q. Ko so pregledana vsa vozlišča iz seznama sosedov vozlišča u, u pobarvamo črno (vrstica 18). 2.2 Iskanje z razvijanjem v širino 2-8 Zgled 2.5. Dan je neusmerjen graf na sliki 2.2a, na katerem želimo izvesti algoritem ISKANJE-Z-RAZVIJANJEM-V-SIRINO. Predpostavimo, da na tekočem koraku poiščemo najprej sinove, ki imajo manjšo številko vozlišča. Naj bo izhodišče vozlišče 2. Algoritem deluje na naslednji način: 1. Vrstice 1–4: Vsa vozlišča razen začetno vozlišče 2 pobarvaj belo, postavi njihovo razdaljo na vrednost ∞ in njihove očete na NIL. 2. Vrstice 5–9: Pobarvaj vozlišče 2 s sivo barvo, postavi njegovo razdaljo na vrednost 0, njegovega očeta na NIL in po izpraznitvi vrste Q postavi vozlišče 2 v vrsto Q. Stanje v tej točki prikazuje slika 2.2b. 3. Vrstica 10: Vstop v prvi cikel zanke while. Vozlišče 2 izloči iz vrste Q. Vozlišče u postane vozlišče 2. V zanki for (vrstice 12–17) obravnavaj oba soseda od 2, tj. 1 in 5, ki sta pobarvana belo (pobarvaj ju sivo, določi njuni razdalji in njuna očeta). Postavi ju tudi v vrsto Q. V vrstici 18 pobarvaj vozlišče 2 črno. Stanje na koncu tega cikla prikazuje slika 2.2c. 4. Vrstica 10: Vstop v drugi cikel zanke while. Vozlišče u postane vozlišče 1, ki je vozlišče v glavi vrste Q. V zanki for (vrstice 12–17) obravnavaj soseda od 1, tj. vozlišče 4, ki je pobarvano belo (pobarvaj ga sivo, določi njegovo razdaljo in očeta). Postavi vozlišče 4 v vrsto Q. Njegov sosed je tudi vozlišče 2, ki pa je pobarvano črno, zato ga ne obravnavaj. V vrstici 18 pobarvaj vozlišče 1 črno. Stanje na koncu tega cikla prikazuje slika 2.2d. 5. Vrstica 10: Vstop v tretji cikel zanke while. Vozlišče u postane vozlišče 5, ki je vozlišče v glavi vrste Q. V zanki for (vrstice 12–17) obravnavaj oba soseda od 5, tj. vozlišči 3 in 6, ki sta pobarvana belo (pobarvaj ju sivo, določi njuno razdaljo, njuna očeta in ju postavi v vrsto Q). Njegov sosed je tudi vozlišče 4, ki je pobarvano sivo, in vozlišče 2, ki pa je pobarvano črno, zato ju ne obravnavaj. V vrstici 18 pobarvaj vozlišče 5 črno. Stanje na koncu tega cikla prikazuje slika 2.2e. 6. Vrstica 10: Vstop v četrti cikel zanke while. Vozlišče u postane vozlišče 4, ki je vozlišče v glavi vrste Q. Vstopi v zanko for (vrstice 12–17), ki pa se ne izvede, saj vozlišče 4 nima belih sosedov. Vozlišče 4 ima dva soseda, 2 in 5, in oba sta črna. Zato takoj vstopi v vrstico 18 in pobarvaj vozlišče 4 črno. Stanje na koncu tega cikla prikazuje slika 2.2f. 7. Vrstica 10: Vstop v peti cikel zanke while. Vozlišče u postane vozlišče 3, ki je vozlišče v glavi vrste Q. Vstopi v zanko for (vrstice 12–17), ki pa se ne izvede, saj vozlišče 3 nima belih sosedov. Vozlišče 3 ima dva soseda: sosed 5 je črn, sosed 6 je siv. Zato takoj vstopi v vrstico 18 in pobarvaj vozlišče 3 črno. Stanje na koncu tega cikla prikazuje slika 2.2g. 2.2 Iskanje z razvijanjem v širino 2-9 8. Vrstica 10: Vstop v šesti cikel zanke while. Vozlišče u postane vozlišče 6, ki je vozlišče v glavi vrste Q. Vstopi v zanko for (vrstice 12–17), ki pa se ne izvede, saj vozlišče 6 nima belih sosedov. Vozlišče 6 ima dva soseda, tj. vozlišče 3 in 5, in oba sta črna. Zato takoj vstopi v vrstico 18 in pobarvaj vozlišče 6 črno. Stanje na koncu tega cikla prikazuje slika 2.2h. 9. Vrstica 10: Ker je vrsta Q sedaj prazna, se algoritem ISKANJE-Z-RAZVIJANJEM-V-SIRINO zaključi. 2.2 Iskanje z razvijanjem v širino 2-10 Slika 2.2: Delovanje algoritma ISKANJE-Z-RAZVIJANJEM-V-SIRINO na neusmerjenem grafu. Povezave drevesa so poudarjene. Znotraj kroga, ki predstavlja vozlišče, je prikazana številka vozlišča, zunaj ob vozlišču pa razdalja d do izhodišča 2. a) neusmerjen graf, b) stanje po vrstici 9, c)–h) stanje po koncu ustrezne iteracije zanke while. 2-11 2.2 Iskanje z razvijanjem v širino Procedura ISKANJE-Z-RAZVIJANJEM-V-SIRINO določi vsakemu vozlišču u ∈ V njegovega očeta oce[u] = v, pri čemer je tudi v ∈ V . Nadalje izračuna še razdaljo d[u] od vozlišča s do vsakega ostalega vozlišča u ∈ G. Razdalja d[u] se meri v številu povezav med vozliščem s in u. Iz danih očetov in razdalj (preglednica 2.1) lahko rekonstruiramo drevo razvijanja v širino (slika 2.3). Koren predstavlja začetno vozlišče s. ♦ Preglednica 2.1: Rezultati procedure ISKANJE-Z-RAZVIJANJEM-V-SIRINO: vrstni red razvoja vozlišč, očetje in razdalje vozlišč. zap. št. razvoja 1 2 3 4 5 6 vozlišče u 2 1 5 4 3 6 oce[u] d[u] NIL 2 2 1 5 5 0 1 1 2 2 2 Slika 2.3: Drevo razvijanja v širino. Številka ob vozlišču predstavlja zaporedno številko razvoja. 2-12 2.2 Iskanje z razvijanjem v širino Časovna zahtevnost Operacija vstavljanja in izločanja iz vrste za eno vozlišče zahteva O(1) časa, za vsa vozlišča pa O(|V |) časa. Seznam sosedov za vsako vozlišče se prebira samo enkrat. Ker je vsota dolžin vseh seznamov sosedov velikosti Θ(|E|), se porabi za prebiranje seznamov sosedov skupaj O(|E|) časa. Inicializacija algoritma ISKANJE-Z-RAZVIJANJEM-V-SIRINO zahteva O(|V |) časa. Tako je skupna časovna zahtevnost algoritma enaka: T (n) = O(|V | + |E|). Torej algoritem ISKANJE-Z-RAZVIJANJEM-V-SIRINO ima časovno zahtevnost linearno odvisno od velikosti seznama sosedov. Če je graf podan z matriko sosednosti, je časovna zahtevnost algoritma: T (n) = O(|V |2 ). 2.3 Iskanje z razvijanjem v globino 2.3 2-13 Iskanje z razvijanjem v globino Iskanje z razvijanjem v globino (depth-first search) preiskuje graf v globino, dokler je možno. V danem grafu G = (V, E) podamo izhodišče (source vertex ) ali začetno vozlišče s. Algoritem sistematično preiskuje povezave v G in odkriva vsako vozlišče, ki je dosegljivo iz s. Izračuna razdaljo iz s do vseh dosegljivih vozlišč. Določa tudi drevo razvijanja v globino s korenom s in z vsemi dosegljivimi vozlišči. Algoritem deluje na neusmerjenem in usmerjenem grafu. Osnovna računska operacija je razvoj (expansion) vozlišča, kar pomeni, da tvorimo vse njegove naslednike (ki še niso bili tvorjeni), preden začnemo obravnavati drugo vozlišče. Pri napredovanju algoritma si pomagamo z barvami. Vozlišče ima lahko belo, sivo ali črno barvo. Na začetku so vsa vozlišča bela, kasneje postanejo siva in črna. Siva barva pomeni, da je vozlišče tvorjeno. Črna barva nekega vozlišča pomeni, da so bili tvorjeni in obravnavani vsi njegovi sosedi. V žargonu teorije grafov to pomeni, da je bilo vozlišče razvito. Dani algoritem lahko obravnavamo kot primer uporabe strategije zmanjšaj-invladaj z varianto zmanjšaj za konstanto, ki je ena (na vsakem koraku razvijamo eno vozlišče). Algoritem se zaključi, ko smo razvili vsa vozlišča grafa. Barva vozlišča u je shranjena v spremenljivki barva[u]. Očeta od vozlišča u označuje spremenljivka oce[u]. Razdaljo od začetnega vozlišča s do vozlišča u hranimo v spremenljivki d[u]. Algoritem uporablja sklad S s strežnim pravilom LIFO. Sklad služi za upravljanje množice sivih (tvorjenih) vozlišč. 2.3 Iskanje z razvijanjem v globino 2-14 ISKANJE-Z-RAZVIJANJEM-V-GLOBINO(G, s) 1 for vsako vozlišče u ∈ V − {s} 2 do barva[u] ←BELA 3 d[u] ← ∞ 4 oce[u] ← NIL 5 barva[s] ←SIVA 6 d[s] ← 0 7 oce[s] ← NIL 8 S←∅ 9 POTISNI-NA-SKLAD(S, s) 10 while S 6= ∅ % dokler sklad S ni prazen 11 do u ← POVLECI-IZ-SKLADA(S) % izloči u iz sklada S % tvori vse sosede od vozlišča u, ki imajo belo barvo 12 for vsako vozlišče v ∈ Adj[u] % iz seznama sosedov Adj[u] 13 do if barva[v] = BELA 14 then barva[v] ← SIVA 15 d[v] ← d[u] + 1 16 oce[v] ← u 17 POTISNI-NA-SKLAD(S, v) 18 barva[u] ← ČRNA % vozlišče u je že razvito Vrstice 1–4 v proceduri ISKANJE-Z-RAZVIJANJEM-V-GLOBINO pobarvajo vsako vozlišče u razen vozlišče s belo, postavijo d[u] v neskončnost in očeta od u (oce[u]) na NIL. V vrstici 5 se pobarva začetno vozlišče s sivo. Vrstica 6 postavi d[s] na nič, vrstica 7 pa postavi očeta od s na NIL. Ko izpraznimo sklad S (vrstica 8), postavimo vozlišče s v sklad S (vrstica 9). V skladu S so samo siva vozlišča. Glavno zanko (while) programa predstavljajo vrstice od 10–18. Zanka se izvaja, dokler je še kakšno sivo vozlišče v skladu S. Vrstica 11 izbere sivo vozlišče z vrha sklada S. Zanka for v vrsticah 12–17 obravnava vsako vozlišče v iz seznama sosedov vozlišča u. Če je vozlišče v belo, potem še ni bilo tvorjeno in algoritem ga obdela v vrsticah od 14–17. Najprej vozlišče pobarva sivo, nato postavi razdaljo d[v] na vrednost d[u] + 1, postavi očeta od v (oce[v] = u) in prenese v na vrh sklada S. Ko so pregledana vsa vozlišča iz seznama sosedov vozlišča u, se pobarva vozlišče u črno (vrstica 18). 2.3 Iskanje z razvijanjem v globino 2-15 Zgled 2.6. Dan je neusmerjen graf na sliki 2.2a, na katerem želimo izvesti algoritem ISKANJE-Z-RAZVIJANJEM-V-GLOBINO. Predpostavimo, da na tekočem koraku poiščemo najprej sinove, ki imajo manjšo številko vozlišča. Naj bo izhodišče vozlišče 2. Slika 2.4 prikazuje celotno delovanje algoritma: 1. Vrstice 1–4: Vsa vozlišča razen začetno vozlišče 2 pobarvaj belo, postavi njihovo razdaljo na vrednost ∞ in njihove očete na NIL. 2. Vrstice 5–9: Pobarvaj vozlišče 2 sivo, postavi njegovo razdaljo na vrednost 0, njegovega očeta na NIL in postavi 2 v sklad S. Stanje v tej točki prikazuje slika 2.4a. 3. Vrstica 10: Vstop v prvi cikel zanke while. Vozlišče u postane vozlišče 2. V zanki for (vrstice 12–17) obravnavaj oba soseda od 2, tj. vozlišče 1 in 5, ki sta pobarvana belo (pobarvaj ju sivo, določi njuni razdalji in njuna očeta). Postavi ju tudi v sklad S. V vrstici 18 pobarvaj vozlišče 2 črno. Stanje na koncu tega cikla prikazuje slika 2.4b. 4. Vrstica 10: Vstop v drugi cikel zanke while. Vozlišče u postane vozlišče 5, ki je vozlišče na vrhu sklada S. V zanki for (vrstice 12–17) obravnavaj sosede od 5, tj. 3, 4 in 6, ki so pobarvani belo (pobarvaj jih sivo, določi njihove razdalje in očeta). Njegov sosed je tudi vozlišče 2, ki pa je pobarvano črno, zato ga ne obravnavaj. Postavi vozlišča 2, 3 in 6 na vrh sklada S. V vrstici 18 pobarvaj vozlišče 5 črno. Stanje na koncu tega cikla prikazuje slika 2.4c. 5. Vrstica 10: Vstop v tretji cikel zanke while. Vozlišče u postane vozlišče 6, ki je vozlišče na vrhu sklada S. Zanka for (vrstice 12–17) se ne izvede, ker vozlišče 6 nima belih sosedov. Sosed 3 je siv, sosed 5 pa je črn, zato ju ne obravnavaj. V vrstici 18 pobarvaj vozlišče 6 črno. Stanje na koncu tega cikla prikazuje slika 2.4d. 6. Vrstica 10: Vstop v četrti cikel zanke while. Vozlišče u postane vozlišče 4, ki je vozlišče na vrhu sklada S. Zanka for (vrstice 12–17) se ne izvede, ker vozlišče 4 nima belih sosedov. Sosed 1 je siv, sosed 5 pa je črn, zato ju ne obravnavaj. V vrstici 18 pobarvaj vozlišče 4 črno. Stanje na koncu tega cikla prikazuje slika 2.4e. 7. Vrstica 10: Vstop v peti cikel zanke while. Vozlišče u postane vozlišče 3, ki je vozlišče na vrhu sklada S. Zanka for (vrstice 12–17) se ne izvede, ker vozlišče 3 nima belih sosedov. Soseda 5 in 6 sta črna, zato ju ne obravnavaj. V vrstici 18 pobarvaj vozlišče 3 črno. Stanje na koncu tega cikla prikazuje slika 2.4f. 8. Vrstica 10: Vstop v šesti cikel zanke while. Vozlišče u postane vozlišče 1, ki je edino vozlišče v skladu S. Zanka for (vrstice 12–17) se ne izvede, ker 2.3 Iskanje z razvijanjem v globino 2-16 vozlišče 1 nima belih sosedov. Soseda 2 in 4 sta črna, zato ju ne obravnavaj. V vrstici 18 pobarvaj vozlišče 1 črno. Stanje na koncu tega cikla prikazuje slika 2.4g. 9. Vrstica 10: Ker je sklad S sedaj prazen, se algoritem ISKANJE-Z-RAZVIJANJEM-V-GLOBINO zaključi. 2-17 2.3 Iskanje z razvijanjem v globino 0 ¥ 0 ¥ 3 2 1 1 1 1 5 S 5 ¥ 2 4 ¥ ¥ 3 2 4 ¥ 6 ¥ 6 ¥ b) a) 0 1 0 2 2 3 11 6 4 S 3 1 1 5 2 4 S 5 1 2 2 3 1 1 1 4 S 3 1 1 5 2 4 6 2 6 2 d) c) 0 11 0 2 2 1 3 1 1 2 2 3 11 1 5 2 2 4 S 1 5 S 3 1 1 2 4 6 6 2 f) e) 0 2 2 3 1 1 1 5 S 2 2 4 6 g) Slika 2.4: Delovanje algoritma ISKANJE-Z-RAZVIJANJEM-V-GLOBINO na neusmerjenem grafu. Znotraj kroga, ki predstavlja vozlišče, je prikazana številka vozlišča, zunaj ob vozlišču pa razdalja d do izhodišča 2. a) stanje po vrstici 9, b)–g) stanje po koncu ustrezne iteracije zanke while. 2-18 2.3 Iskanje z razvijanjem v globino Procedura ISKANJE-Z-RAZVIJANJEM-V-GLOBINO določi vsakemu vozlišču u ∈ V njegovega očeta oce[u] = v, pri čemer je tudi v ∈ V . Nadalje izračuna še razdaljo d[u] od vozlišča s do vsakega ostalega vozlišča u ∈ G. Razdalja d[u] se meri v številu povezav med vozliščem s in u. Iz danih očetov in razdalj (preglednica 2.2) lahko rekonstruiramo drevo razvijanja v globino (slika 2.5). Koren predstavlja začetno vozlišče s. ♦ Preglednica 2.2: Rezultati procedure ISKANJE-Z-RAZVIJANJEM-V-GLOBINO: vrstni red razvoja vozlišč, očetje in razdalje vozlišč d[u]. zap. št. razvoja 1 2 3 4 5 6 vozlišče u 2 5 6 4 3 1 oce[u] d[u] NIL 2 5 5 5 2 0 1 2 2 2 1 Slika 2.5: Drevo razvijanja v globino. Številka ob vozlišču predstavlja zaporedno številko razvoja. 2-19 2.3 Iskanje z razvijanjem v globino Časovna zahtevnost Operaciji POTISNI-NA-SKLAD in POVLECI-IZ-SKLADA za eno vozlišče zahtevata O(1) časa, za vsa vozlišča pa O(|V |) časa. Vse ostalo je enako kot pri algoritmu ISKANJE-Z-RAZVIJANJEM-V-SIRINO. Tako je skupna časovna zahtevnost algoritma enaka, če je graf podan s seznamom sosedov: T (n) = O(|V | + |E|), kar pomeni linearno odvisnost od velikosti seznama sosedov. Če je graf podan z matriko sosednosti, je časovna zahtevnost algoritma: T (n) = O(|V |2 ). Literatura Aho, A. V., Hopcroft, J. E., and Ullman, J. D. (1974). The Design and Analysis of Computer Algorithms. Addison-Wesley, Reading. Cormen, T. H., Leiserson, C. E., and Rivest, R. L. (2007). Introduction to Algorithms. Druga izdaja, MIT Press, Cambridge. Horowitz, E., Sahni, S., and Rajasekaran, S. (1998). Computer Algorithms. Computer Science Press, New York. Kleinberg, J. and Tardos, E. (2006). Algorithm Design. Parson Education, Inc., New York. Kononenko, I. (1996). Načrtovanje podatkovnih struktur in algoritmov. Fakulteta za računalništvo in informatiko, Ljubljana. Kozak, J. (1997). Podatkovne strukture in algoritmi. Društvo matematikov, fizikov in astronomov Slovenije, Ljubljana. Levitin, A. (2007). The Design and Analysis of Algorithms. Druga izdaja, Pearson Education, Inc., Boston. Manber, U. (1989). Introduction to Algorithms. A Creative Approach. AddisonWesley, Reading. Nilsson, N. J. (1980). Principles of Artificial Intelligence. Tioga. Sedgewick, R. (2003). Boston. Algorithms in Java. Third Edition. Addison-Wesley, Vilfan, B. (1998). Osnovni algoritmi. Fakulteta za računalništvo in informatiko, Ljubljana.
© Copyright 2024