Documente Academic
Documente Profesional
Documente Cultură
Algoritmica grafurilor
Recenzori:
Prof. univ. dr. habilitat Dumitru Todoroi
Conf. univ. dr. Elena Nechita
Prof. univ. dr. Victor Blănuţă
Talmaciu, Mihai
I, Talmaciu M.
Capitolul 1 Introducere 1
1.1. Definiţia unui graf 1
1. 2. Grade 3
1.3. Subgrafuri 3
1.4. Operaţii cu grafuri 3
1.5. Clase de grafuri 4
1.6. Drumuri şi circuite 5
1.7. Mulţimi separatoare, transversale şi mulţimi omogene 6
1.8. Algoritmi şi complexitate de calcul 7
Bibliografie 121
Capitolul 1
INTRODUCERE
1
Dacă S ⊆ V(G) atunci:
S este mulţime stabilă (sau independentă) a lui G dacă şi numai dacă
∀x, y ∈ V(G), x ≠ y : xy ∉ E(G) .
O stabilă S este maximală dacă nici o stabilă a lui G nu conţine
propriu S;
S(G) = {S | S este stabilă maximală în G} ;
α(G) = max{S | S ∈ S(G)} este numărul de stabilitate a lui G.
Sα (G) = {S | S ∈ S(G),| S | = α(G)} ;
S este o mulţime α - stabilă dacă şi numai dacă S ∈ Sα (G) .
Complementarul, G , al unui graf G este graful a cărui mulţime de
vârfuri este V(G), iar două vârfuri sunt adiacente în G dacă şi numai dacă
ele nu sunt adiacente în G.
Dacă Q ⊆ V(G) atunci:
Q este clică (său mulţime completă) a lui G dacă şi numai dacă Q este
mulţime stabilă în G ; o clică Q este maximală dacă nici o clică a lui G nu
conţine propriu Q;
C(G) = {Q | Q este clică maximală în G}; ,
ω (G) = max{|Q| | Q ∈ C(G) } este numărul clică a lui G;
Cω (G) ={Q | Q ∈ C(G) şi |Q| = ω (G)}; Q este o ω - clică dacă şi
numai dacă Q ∈ Cω (G) .
Dacă p ∈ N∗ , se numeşte p – colorare a vârfurilor lui G o aplicaţie
c : V(G) → {1,..., p}
cu proprietatea că c−1 (i) este o mulţime stabilă în G.
χ(G) este numărul cromatic a lui G, adică cel mai mic număr de
culori necesare pentru a colora vârfurile de felul că două vârfuri adiacente
distincte nu pot fi de aceeaşi culoare.
θ(G) = χ(G) este numărul de acoperire cu clici a lui G.
Două grafuri, G = (V(G),E(G)) şi H = (V(H),E(H)) se numesc
izomorfe, şi notăm aceasta prin G ≈ H (sau G ≅ H ) dacă există o bijecţie
ϕ : V(G) → V(H)
cu proprietatea că aplicaţia
ψ : E(G) → E(H) ,
definită pentru orice uv ∈ E(G) prin
ψ (uv) = ϕ(u)ϕ(v)
este o bijecţie.
2
1. 2. Grade
Gradul unui vârf v dintr-un graf G este numărul de muchii ale lui G
incidente cu v, care se notează cu d G (v) . Un vârf de grad zero se numeşte
izolat. Un vârf de grad unu se numeşte pendant .
1.3. Subgrafuri
Fie G un graf. Un graf H este un subgraf al lui G dacă V(H) ⊆ V(G) şi
E(H) ⊆ E(G). Dacă A este o submulţime nevidă a lui V(G) atunci subgraful
lui G indus de A este graful având mulţimea vârfurilor A, iar mulţimea
muchiilor constă din acele muchii ale lui G incidente cu două elemente din
A. Dacă A ⊆ V(G), v ∈ V, U ⊆ E(G),e ∈ E(G) atunci notăm: G A sau G(A) sau
[A]G sau [A] subgraful lui G indus de A; G – A = G(V(G) – A);
G – v = G – {v} ; G – U = (V(G),E(G) – U); G – e = G – {e}.
(
V UX )
x∈X G x = U x∈X V(X) × {x};
E (UX
x∈X G x ) = U x∈V(X) E(G x ) ∪
∪{[(a, x), (b, x ')] | x ≠ x ',[x, x '] ∈ E(X), a ∈ V(G x ), b ∈ V(G x ' )}.
3) Produsul lexicografic (compoziţia) (Harary) a două grafuri G1 şi G 2
este graful notat G = G1 [ G 2 ], unde V(G) = V( G1 ) × V( G 2 ) şi două vârfuri
(u1 , u 2 ) şi (v1 , v 2 ) ale lui G sunt adiacente dacă şi numai dacă
u1v1 ∈ E(G1 ) sau ( u1 = v1 şi u 2 v2 ∈ E(G 2 ) ).
4) Suma a două grafuri. Dacă luam X = K 2 , K 2 - adiacenţa grafurilor
oarecare G1 şi G 2 se mai numeşte suma celor două grafuri şi se notează cu
G1 + G 2 .
3
1.5. Clase de grafuri
Se numeşte graf complet de ordin n, graful notat, K n , unde
| V(K n ) | = n şi E(K n ) = P2 (V(K n )) , iar P2 (X) este mulţimea părţilor cu
două elemente ale lui X.
Se numeşte graf nul de ordin n, graful N n = K n .
Se numeşte ciclu de lungime n ( n ≥ 3 ) graful notat, Cn , unde
V(Cn ) = {1, 2,..., n} şi E(Cn ) = {12, 23,..., n − 1n, n1} . Deoarece vârfurile sunt
distincte, ciclul este elementar. Peste tot în carte vom folosi ciclu elementar.
De asemenea îl vom numi şi circuit
Se numeşte lanţ de ordin n, graful Pn = Cn − e (e ∈ E(Cn )) . Îl vom
numi şi drum. Dacă a = i, b = i + l, pentru orice i = l, ..., n – l şi e = ab atunci
spunem că avem un a – b drum (sau ab – drum).
Un circuit (drum) al unui graf G este un subgraf indus al lui G care
este el însuşi circuit (drum) în G. (Observăm că un circuit (drum) al unui graf
nu are corzi. O coardă într-un circuit (drum) al unui graf este o muchie cu
extremităţile vârfuri neconsecutive pe circuit (drum)).
Un graf G = (V,E) se numeşte n – partit, n ≥ 1 , dacă V se
partiţionează în n submulţimi V1 , V2 ,..., Vn nevide astfel încât
/ , ∀i = 1, n (adică, orice muchie din E este incidentă cu un
[Vi ]G = (Vi , 0)
vârf din Vi şi un vârf din Vj , unde i ≠ j ). Pentru n = 2, un astfel de graf se
numeşte bipartit. Un graf n – partit G se numeşte n – partit complet dacă
mulţimile partiţiei V1 , V2 ,..., Vn au proprietatea că pentru orice u din Vi şi
orice v din Vj , i ≠ j , uv este muchie a lui G. Un graf bipartit complet cu
mulţimile partiţiei V1 şi V2 , unde | V1 | = m şi | V2 | = n este notat K(m,n)
( K m,n ). Graful K(l,n) (sau K1,n ) se numeşte graf stea (star).
Un graf se numeşte perfect (Berge) dacă numărul cromatic şi
numărul clică sunt egale pentru orice subgraf indus al său.
Un graf G este minimal imperfect dacă şi numai dacă G nu este
perfect şi orice subgraf G – v (v din V(G)) este perfect.
O muchie e a lui G se numeşte α – critică (Olaru) dacă
α (G – e) = α (G) + 1. Notăm mulţimea muchiilor α – critice ale lui G prin
E c şi numim G c = (V, E c ) scheletul α – critic a lui G. Dacă E c = E, graful
se numeşte α - critic.
4
Un graf G se numeşte ( α , ω ) – partiţionabil (a se vedea Golumbic,
Olaru) dacă pentru fiecare v ∈ V(G), G – v admite o partiţie de α ω – clici şi
o partiţie de ω mulţimi α – stabile.
Un graf G = (V,E) se numeşte α – partiţionabil ( α – decompozabil)
(Olaru) dacă există o partiţie Z = {V1 ,..., Vm } a lui V, cu m > 1 astfel încât
∑ im=1 α(G i ) = α(G) , unde G i = G(Vi ), i = 1, m .
Z se numeşte α – partiţie a lui G, iar G i sunt α – componente.
Un graf G se numeşte ω - partiţionabil dacă G este α -
partiţionabil.
Dacă graful G are o α – partiţie astfel încât α – componentele sunt
clici atunci G este perfect α – partiţionabil ( α – decompozabil).
G este partiţionabil dacă este α – partiţionabil şi ω – partiţionabil.
Un graf G se numeşte tare stabil (Olaru, Antohe) dacă
α(G − Q) = α(G) ,
pentru orice clică proprie Q a lui G.
Un graf G se numeşte tare α – stabil (Olaru) dacă şi numai dacă
∀v ∈ V(G) şi orice clică Q a lui G, θ(G − v) = α(G) = α(G − Q) .
Cografurile sau grafurile P4 – libere (sau P4 – free), descoperite de
mai mulţi autori, printre care Lerchs sunt grafuri care nu conţin P4 indus.
Un graf G se numeşte gem – free dacă pentru fiecare vârf v, [NG(v)]
este graf P4 – free.
Un graf se numeşte paw dacă este izomorf cu
({a,b,c,d},{ab,bc,cd,db}).
Un graf izomorf cu P5 se numeşte house.
Un graf se numeşte bull dacă este izomorf cu
({a,b,c,d,e},{ab,bc,ca,bd,ce}).
Chvatal a introdus clasa grafurilor ordonabile perfect, care sunt
caracterizate prin existenţa unei ordini lineare < pe mulţimea vârfurilor astfel
încât nici un P4: abcd nu are a < b şi d < c.
Fie F o familie de mulţimi nevide. Graful intersecţie al lui F este
obţinut reprezentând fiecare mulţime din F printr-un vârf şi două astfel de
vârfuri determină o muchie dacă şi numai dacă mulţimile lor corespunzătoare
se intersectează.
5
Un graf este conex dacă există un drum între orice două vârfuri ale
sale. Un graf care nu este conex se numeşte neconex. Orice graf poate fi
exprimat unic ca o reuniune disjunctă de subgrafuri induse conexe şi
maximale cu această proprietate. Aceste subgrafuri se numesc componente
conexe ale grafului.
Un arbore este un graf conex şi fără circuite.
6
1.8. Algoritmi şi complexitate de calcul
Vom înţelege prin problemă algoritmică (Croitoru) o funcţie total
definită P : I → F , unde I este mulţimea informaţiilor iniţiale (intrărilor
problemei), iar F este mulţimea informaţiilor finale. Vom presupune că I şi F
sunt cel mult numărabile. Dacă i ∈ I este precizat, atunci determinarea lui
P(i) se numeşte instanţă a problemei P. Vom folosi pentru o instanţă notaţia
p şi prin abuz de notaţie vom scrie p ∈ P .
Pentru fiecare instanţă p ∈ P se poate asocia un număr natural g(p)
numit dimensiunea problemei.
Un algoritm care rezolvă problema P va porni de la o codificare a
unei instanţe oarecare a problemei P si va oferi o codificare a rezultatului.
Vom nota cu TA (p) timpul necesar algoritmului A pentru rezolvarea
instanţei p a problemei P.
Comportarea în cazul cel mai nefavorabil a algoritmului A pe o
intrare de dimensiune n este
TA (n) = sup{TA (p) | p ∈ P şi g(p) = n} .
Acest tip de analiză a algoritmilor ne asigură că, oricare ar fi o intrare
de dimensiune n, timpul de lucru este mărginit de TA (n) . De aceea, o
abordare naturală este să se studieze comportarea în medie a unui algoritm,
care presupune:
• precizarea unei distribuţii de probabilitate pe mulţimea instanţelor
p∈P;
• determinarea mediei variabilei aleatoare TA (p) :
TAmed (n) = M({TA (p) | p ∈ P şi g(p) = n}) .
Fie f : N → N . Atunci
O(f ) = {g | g : N → N, ∃c ∈ R, c > 0, ∃n 0 ∈ N : g(n) ≤ cf (n), ∀n ≥ n 0 } .
Un algoritm A cu proprietatea că TA(n) = O(p(n)), unde p este un
polinom în n se numeşte polinomial. Un algoritm care nu este polinomial se
numeşte exponenţial. O problema pentru care nu se cunosc algoritmi
polinomiali se numeşte intractabilă.
7
8
Capitolul 2
METODE DE CĂUTARE ŞI PROGRAMARE
2.1.1. Algoritmul
1. Introducerea nodului rădăcină în coadă.
2. Extragerea unui nod din capătul listei şi examinarea acestuia.
Dacă elementul căutat se identifică cu acest nod, se renunţă la
căutare şi se returnează rezultatul.
Altfel, se plasează toţi succesorii (nodurile „fii”) (neexaminaţi
încă) acestui nod la sfârşitul „cozii” (acesta în cazul în care
există)
3. Dacă „coada” este goală, fiecare nod al grafului a fost examinat -
se renunţă la căutare şi se întoarce la „not found”.
4. Repetă începând cu Pasul 2.
struct Vertex {
...
std::vector<int> out;
...
};
9
std::vector<Vertex> graph(vertices);
bool BFS(const std::vector<Vertex>& graph, int start, int end) {
std::queue<int> next;
std::vector<int> parent(graph.size(), 0);
parent[start] = -1;
next.push(start);
while (!next.empty()) {
int u = next.front();
next.pop();
if (u == end) return true;
for (std::vector<int>::const_iteratorj=graph[u].out.begin();
j != graph[u].out.end(); ++j)
{
// Look through neighbors.
int v = *j;
if (parent[v] == 0) {
// If v is unvisited.
parent[v] = u;
next.push(v);
}
}
}
return false;
}
10
Optimalitate. Pentru costul unitar pe muchii, bread-first search este
o metodă optimă. În general, breadth-first search nu este o metodă optimă, şi
aceasta deoarece returnează întotdeauna rezultatul cu cele mai puţine muchii
între nodul de start şi nodul vizat. Dacă graful este un graf ponderat, şi drept
urmare are costuri asociate fiecărei etape, această problemă se rezolvă
îmbunătăţind metoda breadth-first search astfel încât să se uniformizeze
costurile de căutare, identificate cu: costurile drumului. Totuşi, dacă graful
nu este ponderat, şi prin urmare toate costurile etapelor sunt egale,
breadth-first search va identifica cea mai apropiată şi optimă soluţie.
11
ramurilor asemănătoare. Complexitatea în timp a ambilor algoritmi este
proporţională cu numărul vârfurilor plus numărul muchiilor grafului
corespunzător (O(V + E )) .
Căutarea în adâncime se poate folosi şi la ordonarea liniară a
vârfurilor grafului (sau arborelui). Există trei astfel de posibilităţi:
■ O preordine reprezintă o listare a vârfurilor în ordinea în care au
fost vizitaţi prin intermediul algoritmului căutării în adâncime. Aceasta este
o modalitate naturală şi compactă de descriere a progresului căutării. O
preordine a unei expresii arbore este ceea ce numim expresie în notaţia
Polonezǎ.
■ O postordine reprezintă o listare în care cel din urmă vârf vizitat
este primul element al listei. O postordine a unui expresii arbore este de
fapt expresia în oglindă a expresiei în notaţie Polonezǎ.
■ O postordine inversată (în oglindă) este, de fapt, reversul
postordinii, i.e. o listare a vârfurilor în ordinea inversă a celei mai recente
vizite a vârfurilor in cauză. În căutarea unui arbore, postordinea inversată
coincide cu preordinea, însă, în general, diferă atunci când se caută un graf.
Spre exemplu când se caută graful:
12
PSEUDOCOD (recursiv)
dfs(v)
process(v)
mark v as visited
for all vertices i adjacent to v not visited
dfs(i)
O altă variantă
dfs(graph G)
{
list L = empty
tree T = empty
choose a starting vertex x
search(x)
while(L is not empty)
{
remove edge (v, w) from beginning of L
if w not yet visited
{
add (v, w) to T
search(w)
}
}
}
search(vertex v)
{
visit v
for each edge (v, w)
add edge (v, w) to the beginning of L
}
Aplicaţii
Iată câţiva algoritmi în care se foloseşte DFS:
■ Găsirea componentelor conexe.
■ Sortarea topologică.
■ Găsirea componentelor tare conexe.
13
Dintre soluţii va fi aleasă una care optimizează o funcţie de cost
p : P(A) → R
dată.
Metoda urmăreşte evitarea căutării tuturor submulţimilor (ceea ce ar
necesita un timp de calcul exponenţial), mergându-se "direct" spre soluţia
optimă. Nu este însă garantată obţinerea unei soluţii optime; de aceea
aplicarea metodei Greedy trebuie însoţită neapărat de o demonstraţie.
Distingem doua variante generale de aplicare a metodei Greedy:
Prima variantă alege în mod repetat câte un element oarecare al
mulţimii A şi îl adaugă soluţiei curente S numai dacă în acest mod se obţine
tot o soluţie. În a doua variantă procedura prel realizează o permutare a
elementelor lui A, după care elementele lui A sunt analizate în ordine şi
adăugate soluţiei curente S numai dacă în acest mod se obţine tot o soluţie.
Exemplu. Se consideră mulţimea de valori reale A = {a1 ,..., a n } .
Se caută submulţimea a cărei sumă a elementelor este maximă.
Vom parcurge mulţimea şi vom selecta numai elementele pozitive,
care vor fi plasate în vectorul soluţie s.
k←0
for i = 1,n
if ai > 0
then k ← k + 1; sk ← ai
write(s)
n = 20 n = 40 n = 60
n3 - - 0,2 sec
2n 1 sec 12,7 zile 366 secole
3n 58 min 3855 secole 1013 secole
14
Descrierea metodei Backtracking
Fie produsul cartezian X = X1 × ... × X n . Căutam x ∈ X cu ϕ(x) = 1 ,
unde ϕ : X → {0,1} este o proprietate definită pe X.
Din cele de mai sus rezultă că generarea tuturor elementelor
produsului cartezian X nu este acceptabilă.
Metoda backtracking încearcă micşorarea timpului de calcul. X este
numit spaţiul soluţiilor posibile, iar ϕ sintetizează condiţiile interne.
Vectorul X este construit progresiv, începând cu prima componentă.
Nu se trece la atribuirea unei valori lui x, decât dacă am stabilit valori pentru
x1 ,..., x k −1 şi ϕk −1 (x1 ,..., x k −1 ) = 1 . Funcţiile ϕk : X1 × ... × X n → {0,1} se
numesc condiţii de continuare şi sunt de obicei restricţiile lui ϕ la primele k
variabile. Condiţiile de continuare sunt strict necesare, ideal fiind să fie şi
suficiente.
Distingem următoarele cazuri posibile la alegerea lui xk:
1) "Atribuie şi avansează": mai sunt valori neanalizate din Xk şi
valoarea xk aleasă satisface ϕ k=> se măreşte k.
2) "Încercare eşuată": mai sunt valori neconsumate din Xk şi
valoarea xk aleasă dintre acestea nu satisface ϕ k=> se va relua,
încercându-se alegerea unei noi valori pentru xk.
3) "Revenire": nu mai există valori neconsumate din Xk (Xk
epuizată) ⇒ întreaga Xk devine disponibilă şi k <- k-1.
4) "Revenire după determinarea unei soluţii": este reţinută soluţia.
Reţinerea unei soluţii constă în apelarea unei proceduri retsol care
prelucrează soluţia şi fie opreşte procesul (dacă se doreşte o singură soluţie),
fie prevede k ← k-1 (dacă dorim să determinăm toate soluţiile).
Notăm prin Ck ⊂ X k mulţimea valorilor consumate din Xk.
Algoritmul este următorul:
Ci<- 0, ∀ i;
k<-l;
while k > 0
if k = n+1
then retsol (x); k<- k-1;
else if C ⊂ X
k k
then alege v ∈ X \ C ; C ← C ∪ {v} ;
k k k k
if ϕ (x ,..., x
k 1 k −1 , v) = 1
then x ← v ; k<- k+l;
k
else
/ ; k<- k-l;
else Ck<- 0
15
Pentru cazul particular X1 = ... = X n = {1,...,s} , algoritmul se
simplifică
k<-l; x i <-0, ∀ i = 1, …, n;
while k > 0
if k = n+1
then retsol (x); k<-k-l;
else if x < s
k
then x ← x + 1 ;
k k
if ϕ (x ,..., x ) = 1
k 1 k
then k<- k+l;
else
else x <-0; k<-k-l;
k
unde:
⎢p + u ⎥
- funcţia Interm întoarce un indice în intervalul p..u; de obicei m = ⎢ ;
⎣ 2 ⎥⎦
- funcţia Prel întoarce rezultatul subsecvenţei p .. u, dacă aceasta este
suficient de mică;
- funcţia Combin întoarce rezultatul asamblării rezultatelor parţiale r1 şi r2.
16
2.6. Metoda Branch and Bound
Prezentare generală
Metoda Branch and Bound se aplică problemelor care pot fi
reprezentate pe un arbore: se începe prin a lua una dintre mai multe decizii
posibile, după care suntem puşi în situaţia de a alege din nou dintre mai
multe decizii; vom alege una dintre ele etc. Vârfurile arborelui corespund
stărilor posibile în dezvoltarea soluţiei.
Deosebim două tipuri de probleme:
1) Se caută un anumit vârf, numit vârf rezultat, care nu are descendenţi.
2) Există mai multe vârfuri finale, care reprezintă soluţii posibile, dintre care
căutăm de exemplu pe cel care minimizează o anumită funcţie.
Deşi metoda este aplicabilă pe arbori. Există multe deosebiri, dintre
care menţionăm:
- ordinea de parcurgere a arborelui;
- modul în care sunt eliminaţi subarborii care nu pot conduce la o
soluţie;
- faptul ca arborele poate fi infinit (prin natura sa sau prin faptul că
mai multe vârfuri pot corespunde la o aceeaşi stare).
În general arborele de stări este construit dinamic.
Este folosită o listă L de vârfuri active, adică de stări care sunt
susceptibile de a fi dezvoltate pentru a ajunge la soluţie / soluţii. Iniţial, lista
L conţine rădăcina arborelui, care este vârful curent. La fiecare pas, din L
alegem un vârf (care nu este neapărat un fiu al vârfului curent!), care devine
noul vârf curent.
Când un vârf activ devine vârf curent, sunt generaţi toţi fiii săi, care
devin vârfuri active (sunt incluşi în L). Apoi din nou este selectat un vârf
curent.
Legat de modul prin care alegem un vârf activ drept vârf curent, deci
implicit legat de modul de parcurgere a arborelui, facem următoarele
remarci:
- căutarea în adâncime nu este adecvată, deoarece pe de o parte
arborele poate fi infinit, iar pe de altă parte soluţia căutată poate fi
de exemplu un fiu al rădăcinii diferit de primul fiu şi căutarea în
adâncime ar fi ineficientă: se parcurg inutil stări, în loc de a avansa
direct spre soluţie;
- căutarea pe lăţime conduce totdeauna la soluţie (dacă aceasta
există), dar poate fi ineficientă dacă vârfurile au mulţi fii.
Metoda Branch and Bound încearcă un "compromis" între cele două
căutări menţionate mai sus, ataşând vârfurilor active cate un cost pozitiv, ce
intenţionează sa fie o măsură a gradului de "apropiere" a vârfului de o
soluţie. Alegerea acestui cost este decisivă pentru a obţine un timp de
executare cât mai bun şi depinde de problema concretă, dar şi de abilitatea
17
programatorului.
Costul unui vârf va fi totdeauna mai mic decât cel al descendenţilor
(fiilor) săi.
De fiecare dată drept vârf curent este ales cel de cost minim (cel
considerat ca fiind cel mai "aproape" de soluţie).
Din analiza teoretică a problemei deducem o valoare lim care este o
aproximaţie prin adaos a minimului căutat: atunci când costul unui vârf
depaseste lim, vârful curent este ignorat: nu este luat în considerare şi deci
este eliminat întregul subarbore pentru care este rădăcină. Dacă nu
cunoaştem o astfel de valoare lim, o iniţializăm cu + ∞ .
Se poate defini o funcţie de cost ideală, pentru care c(x) este dat de:
• nivelul pe care se află vârful x dacă x este vârf rezultat;
• + ∞ dacă x este vârf final, diferit de vârf rezultat;
• min {c(y) | y fiu al lui x} dacă x nu este vârf final.
Această funcţie este ideală din două puncte de vedere:
- nu poate fi calculată dacă arborele este infinit; în plus,
chiar dacă arborele este finit, el trebuie parcurs în întregime, ceea
ce este exact ce dorim să evităm;
- dacă totuşi am cunoaşte această funcţie, soluţia poate
fi determinată imediat: plecăm din rădăcină şi coborâm mereu spre
un vârf cu acelaşi cost, până ajungem în vârful rezultat.
Neputând lucra cu funcţia ideală de mai sus, vom alege o
aproximaţie d a lui c, care trebuie să satisfacă condiţiile:
1) în continuare, dacă y este fiu al lui x avem
d(x) < d(y);
2) d(x) să poată fi calculată doar pe baza informaţilor din
drumul de la rădăcină la x;
3) este indicat ca d ≤ c pentru a ne asigura că dacă
d(x ) > lim, atunci şi c(x) > lim, deci x nu va mai fi
dezvoltat.
O primă modalitate de a asigura compromisul între căutările în
adâncime şi pe lăţime este de a alege funcţia d astfel încât, pentru o valoare
naturală k, să fie îndeplinită condiţia: pentru orice vârf x situat pe un nivel
nx şi orice vârf situat pe un nivel ny ≥ nx + k, să avem d(x) > d(y),
indiferent dacă y este sau nu descendent al lui x.
Condiţia de mai sus spune că niciodată nu poate deveni activ un vârf
aflat pe un nivel ny ≥ nx + k dacă în L apare un vârf situat pe nivelul nx,
adică nu putem merge "prea mult" în adâncime. Dacă aceasta condiţie este
îndeplinită, este valabilă următoarea propoziţie:
Propoziţie.
În ipoteza că este îndeplinită condiţia de mai sus şi dacă există
soluţie, ea va fi atinsă într-un timp finit, chiar dacă arborele este infinit.
18
Algoritmul Branch & Bound pentru probleme de optim
Să presupunem că dorim să determinăm vârful final de cost minim şi
drumul de la rădăcină la el. Fie lim aproximarea prin adaos considerată mai
sus.
Algoritmul este următorul (rad este rădăcina arborelui, iar ifinal este
vârful rezultat):
19
20
Capitolul 3
STRUCTURI DE DATE
3.1. Liste
Pentru a îmbunătăţi utilizarea memoriei sunt folosite structuri de date
înlănţuite. Acestea poartă numele de liste, fiind compuse dintr-o mulţime de
noduri între care sunt definite anumite legături. Se întâlnesc liste simplu
înlănţuite, liste dublu înlănţuite, liste circulare, structuri de liste şi liste
speciale care sunt formate din atomi şi din alte liste. În continuare se prezintă
listele simplu înlănţuite şi dublu înlănţuite.
lista=Anod;
nod=record
inf:integer;
leg:lista;
end;
L:lista;
21
variantă recursivă de creare a listei:
algoritm creare:lista;
{ read (A);
if (A=O) then creare:=NIL;
else
{ new(P);
P->inf:=A;
P->leg:=creare;
creare:=P;
}
}
algoritm listare(L:lista);
{ P:=L;
while (P<>NIL) do
{ write (P->inf,’ ‘);
P:=P->leg;
}
)
22
new (Q);
Q^.inf:=A;
Q^.succ:=P^.succ;
Q^.pred:=P;
Q^.succ^.pred:=Q;
P^.succ:=Q;
Q:=P;
P^.succ^.pred:=Q^.pred;
P^.pred^.succ:=Q^.succ;
dispose (Q) ;
algoritm listare1(P,Q:lista);
{ AUX:=P;
while (AUX<>NIL) do
{ write (AUX->inf,’ ‘);
AUX:=AUX->succ;
}
}
algoritm listare2(P,Q:lista);
{ AUX:=Q;
while (AUX<>NIL) do
{ write (AUX->inf, ‘ ‘) ;
AUX:=AUX->pred;
}
}
algoritm cautare(P,Q:lista):lista;
{ AUXl:=P;
AUX2:=Q;
while (AUXl<>AUX2) do
{ AUXl:=AUXl->succ;
if (AUXl=AUX2) then return (AUXl);
AUX2:=AUX2->pred;
if (AUXl=AUX2) then return (AUXl);
}
}
23
3.2. Arbori
3.2.1. Arbori liberi
Definiţie. Fie o mulţime V de noduri şi o mulţime E de muchii,
fiecare muchie legând două noduri distincte.
Se numeşte lanţ un şir X1, X2, … , XL de noduri pentru care oricare
două noduri consecutive sunt legate printr-o muchie. Dacă nodurile sunt
distincte, lanţul se numeşte elementar.
Se numeşte ciclu elementar un lanţ X1, X2, … , XL, X1 pentru care
lanţul X1, X2, … , XL este lanţ elementar.
Se numeşte arbore liber o pereche A = (V,E) cu proprietăţile:
l. Oricare două noduri distincte sunt legate printr-un lanţ.
2. Nu conţine cicluri elementare.
Propoziţie. Un arbore liber cu |V| = n noduri are exact n – l muchii.
Se poate demonstra prin inducţie după numărul n de noduri.
Propoziţie. Următoarele afirmaţii sunt echivalente:
l. A = (V, E) este arbore liber.
2. A = (V,E) cu |V| = n are exact n – 1 muchii şi nu conţine cicluri
elementare.
3. A = (V,E) cu |V| = n are exact n – 1 muchii şi oricare două noduri
sunt legate printr-un lanţ.
3.2.2. Arbori cu rădăcină
Definiţie. Se numeşte arbore cu rădăcină o mulţime de noduri şi
muchii în care: există un nod special numit rădăcină, iar celelalte noduri sunt
repartizate în k mulţimi disjuncte A1, A2, … , Ak care sunt la rândul lor
arbori cu rădăcină.
Observaţie.
1. Prin alegerea unui nod drept rădăcină, un arbore liber se poate
transforma în arbore cu rădăcină. Totodată, fiecărui nod al arborelui îi va fi
asociat un nivel. Nivelul rădăcinii se consideră a fi nivelul 1, iar un nod de pe
nivelul i are descendenţii direcţi pe nivelul i + 1.
2. Pentru fiecare nod, se consideră o ordine a mulţimilor A1, A2,… , Ak.
Spunem atunci că arborele cu rădăcină este ordonat.
Definiţie. Numim adâncimea unui arbore cu rădăcină nivelul maxim
pe care îl are un nod al acestui arbore.
Modalităţi statice de memorare
1. Putem memora arborele ca o expresie cu paranteze, în care prima
poziţie este eticheta rădăcinii, urmată, între paranteze, de lista subarborilor
respectivi.
2. Putem memora un vector de taţi. Vectorul are lungimea egală cu
numărul de noduri al arborelui, fiecare poziţie i memorând ascendentul direct
al nodului i, iar ascendentul rădăcinii (care nu există) se consideră a fi 0.
24
3.2.3. Arbori binari
Definiţie. Un arbore binar este un arbore cu rădăcină, în care orice
nod are cel mult doi descendenţi direcţi şi se face distincţia între
descendentul stâng şi descendentul drept.
Definiţie. Un arbore binar se numeşte arbore binar strict dacă fiecare
nod care are descendenţi direcţi are exact doi astfel de descendenţi.
Definiţie. Un arbore binar se numeşte arbore binar plin dacă are un
număr de n nivele şi pentru toate nodurile de pe nivele 1, 2, ... , n – l există
doi descendenţi direcţi.
Un arbore plin cu n nivele are
1 + 2 + ... + 2n −1 = 2n − 1
noduri.
Definiţie. Un arbore binar se numeşte arbore binar complet dacă pe
primele n – 1 niveluri are toate nodurile posibile, iar pe ultimul nivel n are o
parte din noduri, considerate pe orizontală în ordinea de la stânga la dreapta.
Modalităţi statice de memorare
1. Putem memora un arbore binar prin memorarea etichetei nodului
rădăcină şi folosind doi vectori ST şi DR ce memorează etichetele
descendenţilor direcţi stâng respectiv drept. Dacă nu există descendent
direct, pe poziţia respectiva se va memora valoarea 0.
2. Dacă arborele este arbore binar complet, sau apropiat de un arbore
binar complet putem folosi eficient un singur vector, în care legăturile stânga
şi dreapta sunt implicite.
algoritm preordine(A:arbore);
{ if (A<>NIL) then
{ write (A->INF) ;
preordine(A->ST);
preordine(A->DR);
}
}
Inordine
Parcurgerea în inordine vizitează, pentru fiecare subarbore, mai întâi
subarborele stâng, apoi rădăcina, apoi subarborele drept. Dacă arborele binar
respectiv este şi arbore de căutare, atunci parcurgerea în inordine vizitează
25
vârfurile în ordinea crescătoare a cheilor. Prezentăm algoritmul recursiv:
algoritm inordine(A:arbore);
{ if (A<>NIL) then
{ inordine(A->ST);
write(A->INF);
inordine(A->DR);
}
}
Postordine
Parcurgerea în postordine vizitează, pentru fiecare subarbore, mai
întâi subarborele său stâng, apoi subarborele său drept, apoi vârful rădăcină.
Parcurgerea în postordine se poate realiza recursiv astfel:
algoritm postordine(A:arbore);
{ if (A<>NIL) then
{ postordine(A->ST);
postordine(A->DR);
write(A->INF);
}
}
algoritm RSD_iterativ
{ stiva<-vida;
I :=RAD;
OK:=true;
while OK=true do
{ while (I<>NIL) do
{ write( I );
stiva<-I;
I:=ST[I);
}
if (stiva<>vida) then
{ I<-stiva;
I:=DR[I);
}
else OK:=false;
}
}
Parcurgerea pe nivele
Dându-se un arbore binar, să se viziteze vârfurile acestuia în ordinea
crescătoare a nivelelor. Acest lucru se realizează folosind o coadă auxiliară
de noduri vizitate dar neprelucrate. Algoritmul se încheie când coada devine
vidă, şi este o particularizare a parcurgerii în lăţime a unui graf.
26
Procedura este iterativă şi poate fi prezentată astfel:
algoritm nivele{A:arbore);
ST:stiva; P,U:intregi;
{ P:=O;U:=O;
U:=U+1;ST[U):=A;write {A->INF);
while (P<U) do
{ P:=P+1; NOD:=ST[P];
if {NOD->ST<>NIL) then
{ U:=U+1; ST[U):=NOD->ST;
write{NOD->ST->INF);}
if {NOD->DR<>NIL) then
{ U:=U+1; ST[U]:=NOD->DR;
write{NOD->DR->INF);}
}
}
27
28
Capitolul 4
PARCURGERI DE GRAFURI
29
Un drum D̂ x ∈ Dx cu l( D̂ x ) = d(x) se numeşte cel mai scurt drum
de la nodul sursă s la nodul x.
Observaţia 1. Pentru orice arc (x,y) ∈ A avem
d(y) ≤ d(x) + 1.
Într-adevăr, dacă Dx = 0/ atunci d(x) = ∞ şi inegalitatea se păstrează.
Dacă Dx ≠ 0/ atunci evident Dy ≠ 0/ . Fie D̂ x un cel mai scurt drum de la s
la x, deci l( D̂ x ) = d(x). Mulţimea Dy poate conţine un drum Dy, astfel încât
l(Dy) < d(x) + 1. Conform definiţiei distanţei avem d(y) ≤ l(Dy) şi rezultă că
d(y) < d(x) + 1. Avem egalitate când un drum cel mai scurt D̂ y de la s la y
are lungimea
d(y) = l( D̂ y ) = d(x) + 1,
de exemplu
D̂ y = D̂ x ∪ {(x,y)}.
În algoritmul parcurgerii BF (algoritmul PBF) se utilizează tabloul
lungime l care este unidimensional şi are n elemente. Mulţimea nodurilor
vizitate şi neanalizate V este organizată, ca structură de date, ca o coadă.
Algoritmul PBF este următorul:
Teorema 1 .
(1) Algoritmul PBF calculează elementele tabloului l astfel încât
d(y) ≤ l(y), y ∈ N ;
(2) Dacă la iteraţia k oarecare a algoritmului PBF avem V = (x1, ..., xr) în
această ordine, atunci l(x r ) ≤ l(x1 ) + 1 şi l(x i ) ≤ l(x i +1 ), i = 1, r − 1 .
30
Demonstraţie.
(1) Utilizăm inducţia după k, numărul de iteraţii ale ciclului WHILE.
Iniţial l(s) := 0, l(y) := ∞ pentru y∈U şi evident d(y) ≤ l(y), y ∈ N .
Presupunem că, la iteraţia k avem d(y) ≤ l(y) pentru y ∈ N . La iteraţia
k + 1 pot exista cazurile:
(Cl ) Există arc (x,y) admisibil ((x,y) ∈ A şi y ∈ U). În acest caz
l(y) = l(x) + 1
şi
d(y) ≤ d(x) + 1 ≤ l(x) + 1 = l(y)
(l(x) pentru x ∈ V nu se modifică). Deci pentru toate arcele (x,y) ∈ A şi
y ∈ U avem d(y) ≤ l(y) . Pentru celelalte noduri y, conform ipotezei
inducţiei, avem d(y) ≤ l(y) , deoarece la iteraţia k + 1, l(y) nu se mai
modifică.
(C2) Nu există arc (x,y) admisibil ((x,y) ∉ A sau y ∉ U). În acest caz la
iteraţia k + 1 nu se modifică nici un element l(y) şi conform ipotezei
inducţiei d(y) ≤ l(y) pentru y ∈ N .
(2) Utilizăm inducţia după k numărul de iteraţii ale ciclului WHILE.
Iniţial V := {s}. Deci x1 = a, xr = a şi l(s) < l(s) + 1, l(s) = l(s). Presupunem
că la iteraţia k avem l(xr) ≤ l(x1) + 1 şi l(xi) < l(xi+1), pentru i = 1, r − 1 . La
iteraţia k + 1 pot exista cazurile:
(Cl ) Există arc (x,y) admisibil ((x,y) ∈ A şi y ∈ U). În acest caz
V={x1, ..., xr, xr+1}, x1 = x, xr+1 = y. Astfel, l(xr+1) = l(y) = l(x) + 1 = l(x1)+1.
De asemenea, avem l(xr) ≤ l(xr) + 1 = l(x) + 1 = l(y) = l(xr+1) şi inegalităţile
l(xi) ≤ l(xi+1), i = 1, r − 1 au rămas nemodificate.
(C2) Nu există arc (x,y) admisibil ((x,y) ∉ A sau y ∉ U). În acest caz
V = {x2, ..., xr}. Avem l(xr) ≤ l(x1) + 1 ≤ l(x2) + 1 şi inegalităţile
l(xi) ≤ l(xi+1), i = 1, r − 1 au rămas nemodificate.
Teorema 2. Algoritmul PBF este convergent şi la terminarea
execuţiei determinăm mulţimea tuturor nodurilor care sunt accesibile
din nodul sursă s în digraful G = (N,A).
Demonstraţie. Din liniile (10), (11) şi (12) ale algoritmului
rezultă că toate nodurile introduse în V sunt eliminate după ce sunt
analizate. Deci după un număr finit de iteraţii se obţine V = 0/ şi
execuţia algoritmului se opreşte. Pentru a arăta că la terminarea
execuţiei, algoritmul determină mulţimea tuturor nodurilor care sunt
accesibile din nodul sursă s în digraful G = (N,A), trebuie să arătăm că
la terminarea execuţiei algoritmului mulţimea W este:
W = {y y ∈ N şi există drum de la s la y}.
Din liniile (10), (11) şi (12) ale algoritmului rezultă că în V sunt
31
introduse numai noduri y care sunt accesibile din s şi că după ce un nod
x ∈ V a fost analizat el este eliminat din V şi introdus în W. Deoarece
algoritmul se opreşte când V = 0/ rezultă că W conţine toate nodurile
y ∈ N care sunt accesibile din s şi introduse în V. Să arătăm că W
conţine toate nodurile y ∈ N care sunt accesibile din s. Prin reducere la
absurd să presupunem că există un drum D = (yl,y2, ... ,yk-l,yk) cu yi = s,
yk = y în G şi y∉ W. Rezultă că yk ∉ V. Deoarece yk ∉ V şi (yk-h,yk) ∈ A
deducem că yk-1 ∉V, astfel yk ar fi fost introdus în V. Continuând
procedeul vom deduce în final că s = y1 ∉ V. Aceasta contrazice faptul
că în linia (3) a algoritmului iniţializăm V:={s}. Rezulta că y ∈ V, deci y ∈ W
şi teorema este demonstrată.
Teorema 3.
Algoritmul PBF este convergent şi determină:
(1) mulţimea tuturor nodurilor care sunt accesibile din nodul sursă s;
(2) elementele tabloului l astfel încât l(y) = d(y) pentru y ∈ N.
Demonstraţie. Convergenţa şi punctul (1) se demonstrează la fel
ca Teorema 2. Punctul (2) se demonstrează prin inducţie după k numărul
iteraţiilor ciclului WHILE. Fie mulţimea Nk = {y ∈ N |d(y) = k}. Pentru k
= 0 avem N0 = {s} şi deci d(s) = l(s) = 0. Presupunem afirmaţia adevărată
pentru k. Afirmaţia pentru k + 1 rezultă cu uşurinţă, deoarece, în
conformitate cu Teorema 1 punctul (2), un nod y ∈ Nk+l este vizitat plecând
de la un nod x ∈ Nk numai după ce toate nodurile din Nk sunt vizitate. Deci,
dacă y ∈ Nk+l şi este vizitat explorând arcul (x,y), x ∈ Nk, atunci,
l(y) = l(x) + 1 = d(x) + 1 = k + 1 = d(y).
Teorema 4.
Algoritmul PBF are complexitatea O(m).
Demonstraţie. Din liniile (10) , (11) şi (12) ale algoritmului rezultă
că fiecare nod al digrafului G este introdus şi eliminat din V cel mult o dată.
Deoarece execuţia algoritmului se termină când V = 0/ deducem că
algoritmul execută cel mult 2n iteraţii. Fiecare arc (x,y)∈A este explorat cel
mult o dată pentru identificarea arcelor admisibile. Deci complexitatea
algoritmului PBF este O(m + n) = O(m).
În parcurgerea BF, dacă Np = W şi subgraful predecesor
Gp = (Np,Ap) este o arborescenţă atunci Gp se numeşte arborescenţă
parcurgere BPF.
Teorema 5.
Algoritmul PBF determină elementele tabloului p astfel încât
subgraful predecesor Gp = (Np,Ap) este o arborescenţă parcurgere BPF.
Demonstraţie. Din liniile (10), (11) şi (12) ale algoritmului rezultă
că p(y) := x numai dacă y este accesibil din s. Evident că la terminarea
execuţiei algoritmului avem Np = W. Din modul cum sunt definite Np şi Ap
32
rezultă că Gp este o arborescenţă. Deci subgraful predecesor Gp este o
arborescenţă parcurgere a digrafului G = (N,A).
Observaţia 2. Drumul unic de la nodul sursă s la un nod y din
arborescenţa parcurgere BF este un cel mai scurt drum de la nodul sursă s la
acelaşi nod y din digraful G, conform punctului (2) al Teoremei 3.
Observaţia 3. Algoritmul PBF sau PTBF se poate aplica şi
grafurilor neorientate. În acest caz, subgraful predecesor Gp = (Np,Ap)
este o arborescenţă. sau mai multe arborescenţe.
Observaţia 4. Un drum unic de la nodul sursă s la un nod y din
arborescenta parcurgere BF se poate determina cu următoarea procedură.
(1) PROCEDURA DRUM (G,s,y);
(2) BEGIN;
(3) se tipăreşte u;
(4) WHILE p (y)≠i-0 DO
(5) BEGIN
(6) x := p(y); se tipăreşte x;
(7) IF x≠s
(8) THEN y := x
(9) ELSE EXIT;
(10) END;
(11) END.
33
Exemplu 1.
Se aplică algoritmul PBF digrafului din figura 1 .
Figura 1
Iniţializări:
s = 1, U = {2,3,4,5,6},
V = {1}, W = 0/ , p = (0,0,0,0,0,0), l = (0, ∞ , ∞ , ∞ , ∞ , ∞ ).
Iteraţia 1:
x = 1, (1,2) ∈ A, 2 ∈ U: U = {3,4,5,6},
V = {1,2}, p = (0,1,0,0,0,0) ,
l = (0,1, ∞ , ∞ , ∞ , ∞ ); (1,3) ∈ A, 3 ∈ U: U = {4,5,6},
V = {1,2,3}, p= (0,1,1,0,0,0),
l = (0,1, 1, ∞ , ∞ , ∞ ); V = {2, 3}; W = {1}.
Iteraţia 2:
x = 2, (2,4) ∈ A, 4 ∈ U: U = {5,6}, V = {2,3, 4},
p = (0,1,1,2,0,0), l = (0,1,1,2, ∞ , ∞ ); (2,5) ∈ A,
5 ∈ U : U = {6}, V = {2,3,4,5} , p = (0,1,1,2,2,0) ,
l = (0,1,1,2,2, ∞ ) ; V = {3, 4, 5} ; W = {1, 2}.
Iteraţia 3:
x = 3, V = {4, 5}, W = {1, 2, 3}.
Iteraţia 4:
x = 4, (4, 6) ∈ A, 6 ∈ U: U = 0/ , V = {4, 5,6},
p = (0,1,1,2,2,4), l = (0,1,1,2,2,3); V = {5,6};
W = {1,2,3,4}.
Iteraţia 5:
x =5, V={6}, W = {1,2,3,4,5}.
Iteraţia 6:
x = 6, V = 0/ , W = {1,2,3,4,5,6}.
Np = {1,2,3,4,5,6} = W.
34
Figura 2
35
Din liniile (10), (11), (12) ale algoritmului PDF rezultă că elementul
t1(y) reprezintă momentul când y devine nod vizitat şi neanalizat şi
elementul t2(x) reprezintă momentul când x devine nod vizitat şi analizat.
Se va studia în continuare algoritmul parcurgerii totale DF
(algoritmul PTDF).
Algoritmul PTDF este următorul:
36
Teorema 8.
Algoritmul PTDF determină elementele tabloului p astfel încât
subgraful predecesor Gp = (Np,Ap) este o pădure parcurgere DF.
Demonstraţie. Se demonstrează analog ca Teorema 5.
Exemplul 2.
Se aplică algoritmul PTDF digrafului
Figura 3
Iniţializări:
s = 1,U = {2,3,4,5,6, ,8}, V = {1}, W = 0/ , p
p = (0,0,0,0,0,0,0,0), t = 1,
t1 = (1, ∞ , ∞ , ∞ , ∞ , ∞ , ∞ , ∞ ),
t2 = ( ∞ , ∞ , ∞ , ∞ , ∞ , ∞ , ∞ , ∞ ).
Iteraţia 1:
x = 1, (1,2) ∈ A, 2 ∈ U: U = {3,4,5,6,7,8}, V = {1,2},
p = (0,1,0,0,0,0,0,0), t = 2,
tl = (1,2, ∞ , ∞ , ∞ , ∞ , ∞ , ∞ , ∞ ).
Iteraţia 2:
x = 2, (2,3) ∈ A,3 ∈ U: U = {4,5,6,7,8}, V = {1,2,3},
p = (0,1,2,0,0,0,0,0), t = 3,
t1 = (1, 2, 3, ∞ , ∞ , ∞ , ∞ , ∞ ).
Iteraţia 3:
x = 3, (3,4) ∈ A,4 ∈ U: U = {5,6,7,8}, V = {1,2,3,4},
p = (0,1,2,3,0,0,0,0), t = 4, t1 = (1,2,3,4, ∞ , ∞ , ∞ , ∞ ).
Iteraţia 4:
x = 4: V = {1,2, 3}, W = {4}, t = 5,
t2 = ( ∞ , ∞ , ∞ ,5, ∞ , ∞ , ∞ , ∞ ).
Iteraţia 5:
x = 3: V = {1,2}, W = {4,3}, t = 6,
t2 = ( ∞ , ∞ ,6,5, ∞ , ∞ , ∞ , ∞ ).
Iteraţia 6:
x = 2, (2,5) ∈ A, 5 ∈ U: U = {6,7,8},
V = {1,2,5}, p = (0,1,2,3,2,0,0,0), t = 7,
tl = (1,2,3,4,7, ∞ , ∞ , ∞ ).
Iteraţia 7:
x = 5: V = {1,2}, W = {4,3,5}, t = 8,
t2 = ( ∞ , ∞ ,6,5,8, ∞ , ∞ , ∞ ).
37
Iteraţia 8:
x = 2 : V = {1}, W = {4,3,5,2}, t = 9,
t2 = ( ∞ ,9,6,5,8, ∞ , ∞ , ∞ ).
Iteraţia 9:
x = 1: V = 0, W = {4,3,5,2,l}, t = 10,
t2 = (10,9,6,5,8, ∞ , ∞ , ∞ ).
Actualizări:
s = 6, U = {7,8}, V = {6}, t = 11, t1 = (1,2,3,4,7,11, ∞ , ∞ )
Iteraţia 10:
x = 6, (6, 7)∈A, 7∈U : U = {8}, V = {6, 7},
p = (0,1,2,3,2,0,6,0), t = 12,
t1 = (1,2,3,4,7,11,12, ∞ ).
Iteraţia 11:
x = 7 : V = {6}, W = {4, 3,5,2,1,7}, t = 13,
t2 = (10,9,6,5,8,00,13, ∞ ).
Iteraţia 12:
x = 6, (6,8) ∈ A,8 ∈ U: U = 0/ , V = {6,8}, p = (0,1,2,3,2,0,6,6),
t = 14, tl = (1,2,3,4,7,11,12,14).
Iteraţia 13:
x = 8 : V = {6}, W = {4, 3, 5, 2,1, 7,8}, t = 15,
t2 = (10,9,6,5,8, ∞ ,13,15).
Iteraţia 14:
x = 6: V = 0/ , W = {4,3,5,2,1,7,8,6}, t = 16,
t2 = (10,9,6,5,8,16,13,15).
4.3. Aplicaţii
4.3.1. Sortarea topologică
Teorema 9. Un digraf G = (N,A) este fără circuite dacă şi numai
dacă orice parcurgere totală DF a lui G nu produce arce de revenire.
Demonstraţie. Presupunem că digraful G este fără circuite. Prin
reducere la absurd presupunem ca o parcurgere totală DF a lui G produce
arce de revenire ( R ≠ 0/ ). Fie arcul (z, x) ∈ R . În acest caz nodul z este un
descendent al nodului x în pădurea parcurgere DF. Deci există un drum D de
o
la x la z în G. Atunci D = D ∪ {z,x} este un circuit în G şi aceasta
contrazice ipoteza că digraful G este fără circuite.
Reciproc, presupunem că o parcurgere totală DF a digrafului G nu
produce arce de revenire ( R ≠ 0/ ). Prin reducere la absurd presupunem că
o o
G conţine un circuit D . Fie x primul nod vizitat din D şi fie arcul
o o
(z,x) ∈ D . Deoarece nodurile x, z ∈ D rezultă că există un drum D de la x
38
o
la z. De asemenea x fiind primul nod vizitat din D rezultă că nodul z
devine un descendent al nodului x la pădurea PDF. Deci (z,x) este un arc
de revenire ce contrazice ipoteza R = 0/ .
Sortarea topologică a unui digraf G = (N,A) fără circuite constă
într-o ordonare liniară a nodurilor din N astfel încât dacă (x, y) ∈ A
atunci x apare înaintea lui y în ordonare.
Algoritmul sortare topologică (algoritmul ST) se obţine din
algoritmul PTDF făcând următoarele două completări:
(1) în partea de iniţializări (liniile (3)-(6)) se iniţializează o listă a
nodurilor;
(2) în linia (16) după calculul lui t2(X), nodul x se introduce la
începutul listei.
La terminarea algoritmului ST, lista furnizează sortarea topologică
a digrafului G = (N,A) fără circuite şi nodurile x sunt plasate în listă în
ordinea descrescătoare a timpilor t2(X).
39
(6) WHILE V≠ 0/ DO
(7) BEGIN
(8) se selectează cel mai nou nod x introdus în V;
(9) IF există muchie [x, y] ∈ A şi y ∈ V
(10) THEN U:= U-{y}; V:=V ∪ {y};N':=N' ∪ {y}
(11) ELSE V:= V-{x}; W:=W ∪ {x};
(12) END;
(13) se tipăresc p şi N';
(14) se selectează s ∈ U; U:=U-{s}; V:={s};
p:= p+1;N':={s};
(15) END;
(16) END.
Figura 4
40
Figura 5
Iniţializări:
s = 1, U = {2,3,4,5,6,7,8}, V = {1}, W = 0/ , p = 1, N'={1}.
Iteraţia 1:
x = 1, [1,2]∈A, 2∈U : U = {3,4,5,6,7,8}, V = {1,2}, N'={1,2}.
Iteraţia 2:
x = 2, [2,3]∈A, 3∈U: U={4,5,6,7,8}, V={1,2,3}, N'={1,2,3}.
Iteraţia 3:
x=3, [3,4]∈A, 4∈U: U={5,6,7,8}, V={1,2,3,4}, N'={1,2,3,4}.
Iteraţia 4:
x = 4: V = {1,2,3}, W = {4}.
Iteraţia 5:
x = 3: V = {1,2}, W = {4,3}.
Iteraţia 6:
x = 2, V = {1}, W = {4,3,2}.
Iteraţia 7:
x = 1 : V = 0/ , W = {4, 3, 2, 1}.
Se tipăresc:
p = 1 şi N' = {1,2,3,4}
Actualizări:
s = 5, U = {6,7,8}, V = {5}, p = 2, N' = {5}.
Iteraţia 8:
x = 5, [5,6] ∈ A, 6 ∈ U : U = {7,8}, V = {5,6}, N' = {5,6}.
Iteraţia 9:
x = 6, [6,8] ∈ A, 8 ∈ U : U = {7}, V = {5,6,8}, N' = {5,6,8}.
Iteraţia 10:
x = 6, [8, 7]∈A, 7∈U: U = 0/ , V = {5,6,8,7}, N'={5,6,8,7}.
Iteraţia 11:
x = 7 : V = {5,6,8}, W = {4,3,2,1,7}.
După încă trei iteraţii se obţine
V = 0/ , W = {4,3,2,1,7,8,6,5}
Se tipăresc
p = 2 şi N' = {5,6,8,7}
şi execuţia algoritmului se opreşte.
41
42
Capitolul 5
PROBLEME DE DRUM ÎN (DI)GRAFURI
Algoritmi
Cei mai importanţi algoritmi care rezolvă această problemă sunt:
• Algoritmul lui Dijkstra – rezolvă problema sursei unice, dacă
toate muchiile sunt ponderate pozitiv Acest algoritm poate
43
genera cele mai scurte drumuri de la un anumit punct de
placare s la toate celelalte noduri.
• Algoritmul Bellman-Ford – rezolvă problema sursei unice şi
pentru costuri negative ale muchiilor.
• Algoritmul de căutare A* - rezolvă problema drumurilor cele
mai scurte în cazul sursei unice, folosind euristica, în
încercarea accelerării căutării.
• Algoritmul Floyd-Warshall – rezolvă problema celor mai
scurte drumuri corespunzătoare tuturor perechilor.
• Algoritmul lui Johnson - rezolvă problema celor mai scurte
drumuri corespunzătoare tuturor perechilor; poate fi mai rapid
ca Algoritmul Floyd-Warshall, în cazul grafurilor rare.
Aplicaţii
Algoritmii ce rezolvă problema celui mai scurt drum se aplică, în
mod evident, pentru a găsi, în mod automat, adrese între diferite locaţii
fizice, cum ar fi spre exemplu instrucţiuni legate de şofat oferite de GPS –
uri sau programele web de mapare (Mapquest).
Dacă reprezentăm, spre exemplu, o maşină abstractă nedeterministă
sub forma unui graf, în care vârfurile descriu state, iar muchiile descriu
posibile tranziţii, algoritmii de identificare a celui mai scurt drum pot fi
folosiţi pentru a găsi o secvenţă optimală de alegeri, astfel încât să ajungă
într-un stat prestabilit, sau pentru a minimiza timpul necesar pentru a ajunge
în acel stat.
44
5.1.1. Arborele Steiner
45
fiind minimă, astfel încât oricare două puncte sunt interconectate, fie printr-
un segment de linie, fie via alte puncte, respectiv alte segmente de dreaptă.
Pentru Problema Euclidean Steiner, punctele adăugate grafului
(Punctele Steiner) trebuie să aibă gradul trei, iar cele trei muchii incidente
corespunzătoare trebuie să formeze trei unghiuri de 120 de grade. Rezultă că
numărul maxim de Puncte Steiner pe care le poate avea un Arbore Steiner
este de N-2, unde N reprezintă numărul iniţial de puncte considerate.
Se poate încă generaliza până la Problema Metrică a Arborelui
Steiner. Fiind dat un graf ponderat G(S,E,w) ale cărui vârfuri corespund unor
puncte în spaţiul metric, iar „costul” muchiilor este reprezentat de distanţele
în spaţiu, se cere să se găsească un arbore de lungime totală minimă, ai cărui
vârfuri constituie o supermulţime a mulţimii S, mulţime a vârfurilor grafului
G.
Versiunea cea mai generală o constituie Arborele Steiner în
grafuri: Fiind dat un graf ponderat G(V,E,w) şi o submulţime de vârfuri
S ⊆ V găsiţi un arbore de cost minim care include toate nodurile mulţimii S.
Problema Metrică a Arborelui Steiner corespunde problemei
Arborelui Steiner în grafuri, unde graful are un număr infinit de noduri, toate
fiind puncte în spaţiul metric.
Problema arborelui Steiner are aplicaţii în design-ului reţelelor.
Majoritatea versiunilor Problemei Arborelui Steiner sunt NP – complete, i.e.,
gândite ca fiind computaţional-dificile. În realitate, una dintre acestea se
număra printre cele 21 de probleme iniţiale ale lui Karp, NP – complete.
Unele cazuri restrictive pot fi rezolvate într-un timp polinomial. În practică
se folosesc algoritmii euristici.
O aproximare comună a Problemei Arborelui Euclidian Steiner este
reprezentată de calcularea arborelui parţial de cost minim Euclidian.
46
funcţia de cost w : E → [0, ∞) ; astfel, w(u , v) reprezintă costul muchiei (u,v).
Costul unei muchii poate fi închipuit ca o generalizare a distanţei între aceste
două vârfuri. Costul unui drum între două vârfuri este dat de suma tuturor
costurilor muchiilor componente. Pentru o pereche dată de vârfuri s şi t din
V, algoritmul găseşte drumul de cost minim între s şi t (i.e. cel mai scurt
drum). Algoritmul poate fi folosit, în aceeaşi măsură pentru depistarea
drumurilor de cost minim între vârful sursă s şi toate celelalte vârfuri ale
grafului.
Descrierea algoritmului
Algoritmul funcţionează reţinând, pentru fiecare vârf v, costul d [v ] al
celui mai scurt drum găsit până în acel moment între s şi v. Iniţial, această
valoare este 0, pentru vârful sursă s ( d [s ] = 0 ), respectiv infinit pentru restul
vârfurilor, sugerând faptul că nu se cunoaşte nici un drum către aceste noduri
(vârfuri) ( d [v ] = ∞ pentru fiecare v din V, exceptând s). La finalul
algoritmului, d [v ] va reprezenta costul celui mai scurt drum de la s la v – sau
infinit, dacă nu există un astfel de drum.
Algoritmul presupune existenţa a două mulţimi de vârfuri S şi Q.
Mulţimea S conţine toate vârfurile pentru care se cunoaşte valoarea d [v ] ,
valoare ce corespunde costului celui mai scurt drum, iar mulţimea Q conţine
toate celelalte vârfuri . Mulţimea S este, iniţial,goală (nu are elemente), iar cu
fiecare pas un vârf din mulţimea Q devine element al mulţimii S. Acest vârf
este ales astfel încât d [v ] să corespundă celei mai mici valori. Odată cu
„mutarea” vârfului u în mulţimea S, algoritmul „relaxează” fiecare muchie
de forma (u,v). Aceasta înseamnă că, pentru fiecare vecin al lui v sau al lui u,
algoritmul verifică dacă poate optimiza drumul (la v) cunoscut ca fiind cel
mai scurt până la acel moment, urmând drumul cel mai scurt de la sursa s la
u, traversând în cele din urmă muchie (u, v). Dacă acest nou drum este mai
bun (în sensul unui cost mai mic), algoritmul actualizează d [v ], atribuindu-i
valoarea mai mică.
47
Pseudocodul
În algoritmul ce urmează, u := extract_min(Q) caută vârful u în
mulţimea vârfurilor Q, care are cea mai mică valoare asociată dist[u]. Vârful
este scos din mulţimea Q şi returnat utilizatorului. length(u, v) calculează
distanţa între cele două vârfuri vecine u şi v alt de pe linia 10 reprezintă
lungimea drumului de la rădăcină la v, dacă ar fi să treacă prin u. Dacă acest
drum este mai scurt decât drumul considerat în momentul respectiv ca fiind
cel mai scurt, acel drum curent este înlocuit cu acest alt drum.
1 function Dijkstra(Graph, source):
2 for each vârf v in Graph:
3 dist[v] := infinity
4 previous[v] := undefined
5 dist[source] := 0
6 Q := copy(Graph)
7 while Q is not empty:
8 u := extract_min(Q)
9 for each vecin v of u:
10 alt = dist[u] + length(u, v)
11 if alt < dist[v]
12 dist[v] := alt
13 previous[v] := u
Dacă, însă ne interesează doar un drum mai scurt între vârfurile sursă
şi ţintă, căutarea poate înceta la punctul 9 dacă u=target. Acum putem „citi”
cel mai scurt drum de la sursă la ţintă prin iterare:
1 S := empty sequence
2 u := target
3 while este definit previous[u]
4 inserează u la începutul of S
5 u := previous[u]
48
anumit vârf de început, atunci fiecare drum de la acel vârf către oricare alt
vârf, în noul graf, va fi cel mai scurt între nodurile respective în graful
original, iar toate drumurile de aceiaşi lungime din garful original vor fi
prezente în graful rezultant. Astfel, pentru a găsi aceste drumuri scurte între
oricare două vârfuri date vom folosi algoritmul de găsire a drumului în noul
graf, asemenea depth-first search (căutării în adâncime).
Timpul de rulare
Timpul de rulare al algoritmului lui Dijkstra într-un graf cu |E| muchii
şi |V| noduri poate fi exprimat ca o funcţie de E şi V , folosind notaţia O.
Cea mai simplă implementare a algoritmului lui Dijkstra stochează
vârfurile mulţimii Q într-o listă de legătură ordinară sau într-un tablou, iar
operaţia Extract-Min(Q) este o simplă căutare liniară a vârfurilor mulţimii Q.
2
În acest caz, timpul de rulare este O( V + E ) .
Pentru cazul grafurilor rare, adică, grafuri cu un număr de muchii
2
mult mai mic decât V , algoritmul Dijkstra se poate implementa într-un
mod mult mai eficient, prin stocarea grafului sub forma listelor de adiacenţă
şi folosirea heap binar sau heap Fibonaci pe post de coadă cu priorităţi în
implementarea funcţiei Extract-Min. Cu heap binar algoritmul necesită un
timp de rulare de ordinul O(( E + V ) log V ) (dominat de către O( E log V )
presupunând că E ≥ V − 1 ), iar heap Fibonaci îmbunătăţeşte acest timp la
O( E + V log V ) .
49
numim cel mai scurt drum, având în vedere că valoarea descreşte de fiecare
dată când ciclul este traversat.)
Algoritmul A* este o generalizare a algoritmului Dijkstra, care
reduce mărimea subgrafului care urmează să fie explorat, aceasta în cazul în
care sunt disponibile informaţii adiţionale, menite să micşoreze „distanţa”
către ţintă.
Procesul care stă la baza algoritmului lui Dijkstra este similar
procesului greedy, folosit în cazul algoritmului lui Prim.
Scopul algoritmului lui Prim îl constituie găsirea arborelui parţial de cost
minim corespunzător unui graf.
50
consideraţii structurale derivate din costurile pozitive, această abordare
directă se extinde la cazul general.
Timpul de rulare al algoritmului Bellman – Ford este de ordinul
O(|E|).
procedure BellmanFord(list vertices, list edges, vertex source)
// Pasul 1: Iniţializarea grafului
for each vertex v in vertices:
if v is source then v.distance := 0
else v.distance := infinity
v.predecessor := null
// Pasul 2: Relaxarea repetitivă a muchiilor
for i from 1 to size(vertices):
for each edge uv in edges:
u := uv.source
v := uv.destination //uv este muchia de la u la v
if v.distance > u.distance + uv.weight:
v.distance := u.distance + uv.weight
v.predecessor := u
// Depistarea ciclurilor negative
for each edge uv in edges:
u := uv.source
v := uv.destination
if v.distance > u.distance + uv.weight:
error "Graful cinţine un ciclu negativ"
Demonstraţia corectitudinii
Corectitudinea algoritmului poate fi arătată cu ajutorul inducţiei.
Propoziţia care va fi demonstrată prin inducţie este dată de următoarea:
Lemă.
După i repetiţii ale buclei for:
• Dacă Distance(u) nu este infinită, atunci este egală cu lungimea unui
anumit drum de la s la u;
• Dacă există un drum de la s la u cu cel mult i muchii, atunci
Distance(u) corespunde cel mult lungimii celui mai scurt drum de la s la u
cu cel mult i muchii.
Demonstraţie.
Pentru etapa I, considerăm i = 0 şi momentul apriori ciclului for
considerându-l ca fiind executat pentru prima dată. Apoi, pentru vârful sursă,
source.distance=0, ceea ce este corect. Pentru alte vârfuri u,
u.distance=infinity, ceea este deopotrivă corect deoarece nu există nici un
drum de la sursă la u cu 0 muchii.
Pentru pasul inductiv, demonstrăm pentru început prima parte.
Considerând un moment în care distanţa la un vârf este dată de:
v.distance:=u.distance+uv.weight. Prin presupunere inductivă, u.distance
este lungimea unui drum oarecare de la sursă la u. Astfel,
51
u.distance+uv.weight este lungimea drumului de la sursă la v, care nu
părăseşte drumul de la sursă la u şi ajunge la v.
Pentru cea de-a doua parte, considerăm cel mai scurt drum de la
sursă la u cu cel mult i muchii. Fie v ultimul vârf înaintea lui u pe acest
drum. Atunci, porţiunea de drum de la sursă la v este cel mai scurt drum de
la sursă la v cu cel mult i-1 muchii. Prin presupunere inductivă, v.distance,
după i-1 cicluri, are cel mult lungimea acestui drum. De aceea,
uv.weight+v.distance are cel mult lungimea drumului de la s la u. La ciclul
cu numărul i, u.distance este comparat cu uv.weight+v.distance, şi se
egalează cu această cantitate dacă uv.weight+v.distance este mai mică. De
aceea, după i cicluri, u.distance are cel mult lungimea celui mai scurt drum
de la sursă la u, drum ce foloseşte cel mult i muchii.
Când i egalează numărul vârfurilor grafului, fiecare drum va fi cel
mai scurt între toate vârfurile, doar dacă nu există cicluri negative. Dacă
există totuşi un ciclu ponderat negativ şi accesibil de la sursă, atunci dat fiind
un drum oarecare, există unul mai scurt, deci nu există un cel mai scurt
drum. Altfel, cel mai scurt drum nu va include nici un ciclu (deoarece
ocolirea ciclului ar presupune scurtarea drumului), pentru ca fiecare drum
mai scurt să viziteze fiecare nod cel mult o dată, iar numărul de muchii
corespunzător să fie mai mic decât numărul vârfurilor grafului.
Aplicaţii în rutare
O variantă distribuită a algoritmului Bellman – Ford se foloseşte în
protocoalele de rutare distanţă-vector, de exemplu Protocolul de Rutare a
Informaţiei (RIP)(Routing Information Protocol). Algoritmul constă din
următorii paşi:
1. Fiecare nod calculează distanţa între “sine” şi toate celelate noduri şi
stochează această informaţie ca un tabel.
2. Fiecare nod îşi trimite tabelul corespunzător tuturor celorlalte noduri.
3. În momentul în care un nod primeşte un astfel de tabel de la vecinii
săi, calculează cele mai scurte căi către toate celelalte noduri şi actualizează
propriul tabel astfel încât să fie reflectate toate schimbările survenite.
Marele dezavantaj al algoritmului Bellman-Ford în aceste condiţii
constă în:
• Măsurarea incorectă
• Schimbările în topologia reţelei nu sunt reflectate în timp util,
odată cu actualizarea succesivă a tabelelor nodurilor.
• Numărarea la infinit (proces ce survine ca urmare a eşecului
transmiterii tabelelor)
Implementare
Următorul program implementează algoritmul Bellman-Ford în C.
52
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
/* Să considerăm INFINIT-ul o valoare întreagă, pentru a nu
interveni confuzia în valorarea reală, chiar şi cea negativă*/
#define INFINITY ((1 << 14)-1)
typedef struct {
int source;
int dest;
int weight;
} Edge;
void BellmanFord(Edge edges[], int edgecount, int nodecount, int
source)
{
int *distance = (int*) malloc(nodecount * sizeof(*distance));
int i, j;
for (i=0; i < nodecount; i++)
distance[i] = INFINITY;
/* distanţa nodului sursă este presetată ca fiind nulă */
distance[source] = 0;
for (i=0; i < nodecount; i++) {
for (j=0; j < edgecount; j++) {
if (distance[edges[j].source] != INFINITY) {
int new_distance = distance[edges[j].source] +
edges[j].weight;
if (new_distance < distance[edges[j].dest])
distance[edges[j].dest] = new_distance;
}
}
}
for (i=0; i < edgecount; i++) {
if (distance[edges[i].dest] > distance[edges[i].source] +
edges[i].weight) {
puts("S-au detectat cicluri cu muchii ponderate negativ
(cu costuri negative)!");
free(distance);
return;
}
}
for (i=0; i < nodecount; i++) {
printf("Cea mai scurtă distanţă dintre nodurile %d şi %d este
%d\n",
source, i, distance[i]);
}
free(distance);
return;
}
int main(void)
{
/* Acest test ar trebui să genereze distanţele 2, 4, 7, -2, and
0. */
Edge edges[10] = {{0,1, 5}, {0,2, 8}, {0,3, -4}, {1,0, -2},
{2,1, -3}, {2,3, 9}, {3,1, 7}, {3,4, 2},
{4,0, 6}, {4,2, 7}};
BellmanFord(edges, 10, 5, 4);
return 0;
}
53
5.1.6. Algoritmul de căutare A∗
În ştiinţa calculatoarelor, A∗ este un algoritm de căutare a grafurilor
de tipul “best-first”, care găseşte drumul de cost de minim de la un nod
iniţial la un nod “ţintă” (din una sau mai multe ţinte posibile).
Foloseşte o funcţie euristică distanţă-plus-cost (notată de regulă cu
f ( x ) ) pentru a determina ordinea în care sunt vizitate nodurile arborelui.
Euristic-ul distanţă-plus-cost reprezintă o sumă de două funcţii: funcţia
cost-drum (notată de obicei cu g ( x ) , care poate fi, sau, nu euristică) şi o
“estimare euristică” admisibilă a distanţei către ţintă (notată de regulă cu
h( x ) ). Funcţia cost-drum g ( x ) determină costul de la nodul de start la nodul
curent.
Având în vedere faptul că h( x ) , parte a funcţiei f ( x ) , trebuie să fie
euristic admisibilă, trebuie să se “subestimeze” distanţa către ţintă. Astfel,
pentru o aplicaţie ca rout-area, h( x ) ar putea reprezenta distanţa în linie
dreaptă la ţintă, ţinând cont şi de faptul că, din punct de vedere fizic, este cea
mai mică distanţa posibilă între oricare două noduri.
Algoritmul a fost descris pentru prima dată în anul 1968 de către
Peter Hart, Nils Nilsson, respectiv Bertram Raphael. Algoritmul era numit
algoritmul A. Având în vedere faptul că se face apel doar la comportamentul
optimal pentru un anumit euristic, a fost numit A∗ .
Descrierea algoritmului
A∗ caută toate drumurile de la nodul de start, oprindu-se în
momentul în care s-a găsit drumul cel mai scurt la nodul ţintă. Ca toţi
algoritmii de căutare informaţionali, cercetează mai întâi drumurile ce par a
conduce la ţintă. Ceea ce prezintă A∗ în plus faţă de căutarea greedy de tip
best-first este reprezentat de faptul că ia în considerare distanţa deja parcursă
Începând cu un anumit nod (iniţial), algoritmul extinde nodul cu cea
mai mică valoare a lui f ( x ) - nodul care are cel mai mic cost-per-beneficiu.
A∗ menţine o mulţime de soluţii parţiale - noduri frunză neextinse -, stocată
într-o coadă cu priorităţi. Prioritatea asociată unui drum x este determinată de
funcţia f ( x ) = g ( x ) + h( x ) . Funcţia “continuă” până când o ţintă are o valoare
corespunzătoare f ( x ) mai mică decât a oricărui nod din coadă (sau până
când arborele va fi fost parcurs în totalitate). Multe alte ţinte pot fi trecute cu
vederea dacă există un drum care putea conduce la o „ţintă” având „costul”
mai mic.
Cu cât f ( x ) are o valoare mai mică, cu atât prioritatea este mai mare
(astfel, s-ar putea folosi o min-heap pentru a implementa coada)
54
function A*(start,goal)
var closed := the empty set
var q := make_queue(path(start))
while q is not empty
var p := remove_first(q)
var x := the last node of p
if x in closed
continue
if x = goal
return p
add x to closed
for each y in successors(x)
enqueue(q, p, y)
return failure
55
caz, probarea prioritară a nodurilor din imediata apropiere a „destinaţiei” ar
putea implica un cost ridicat în ceea ce priveşte timpul implicat.
Cazuri speciale
În general vorbind, depth-first search şi bredth-first search reprezintă
două cazuri speciale (particulare) ale algoritmului A∗ . Algoritmul lui
Dijkstra, un alt exemplu de algoritm de tip best-first search (căutare
prioritară), reprezintă un caz special al A∗ , unde h( x ) = 0 ∀x . Pentru
depth-first search (parcurgerea în adâncime), putem considera că există un
„contabilizator” C, iniţializat cu o valoare foarte mare. De fiecare dată când
se procesează un nod îi ataşăm C corespunzător tuturor vecinilor săi astfel
descoperiţi. După fiecare astfel de assign-are, micşorăm „contabilizatorul” C
cu o unitate. Astfel, cu cât un nod este „descoperit” mai repede, cu atât
valoarea h(x) corespunzătoare este mai mare.
De ce A∗ este „admisibil” şi optimal din punct de vedere
computaţional
A∗ este atât admisibil, iar, pe de altă parte, implică şi mai puţine
noduri decât orice alt algoritm de căutare având acelaşi euristic, aceasta
deoarece A∗ porneşte de la cost aproximativ „optim” al drumului ce
parcurge toate nodurile, către „ţintă” („optim” însemnând că acel cost final
va fi cel puţin la fel de mare cu cel estimat).
Când A∗ finalizează căutarea, a găsit, prin definiţie, un drum al cărui
cost actual este mai mic decât costul estimat al oricărui alt drum ce parcurge
nodurile. Având în vedere, însă, faptul că aceste estimări sunt optimiste, A∗
poate ignora toate aceste noduri „deschise”. Cu alte cuvinte, A∗ nu va omite
niciodată posibilitatea existenţei unui drum având un cost mai mic, fiind
astfel admisibil.
Să presupunem acum că un algoritm oarecare de căutare A finalizează
căutarea găsind un drum al cărui cost nu este mai mic decât cel estimat.
Algoritmul A nu poate exclude posibilitatea existenţei unui drum al cărui
cost prin acel nod să fie mai scăzut, bazându-se pe informaţia euristică pe
care o deţine. Astfel, atât timp cât A poate considera mai puţine noduri decât
A* , nu poate fi admisibil. Deci, A∗ reprezintă algoritmul de căutare cu cele
mai puţine noduri ce poate fi considerat ca fiind admisibil.
Complexitate
Complexitatea în timp a lui A∗ depinde de euristic. Potrivit celui mai
sumbru scenariu, numărul nodurilor „extinse” este de ordin exponenţial, în
ceea ce priveşte lungimea soluţiei (cel mai scurt drum), însă este de ordin
polinomial atunci când funcţia euristică h satisface următoarea condiţie:
56
| h(x) − h∗ (x) |≤ O(log h∗ (x))
unde h∗ reprezintă euristicul optimal, i.e. costul exact ce-l implică drumul
de la x la „ţintă”. Cu alte cuvinte, eroarea corespunzătoare lui h nu ar trebui
să crească mai rapid decât logaritmul „euristicului perfect” h∗ , ce returnează
distanţa reală de la x la „ţintă”.
O chestiune şi mai problematică a A∗ decât cea legată de
complexitatea în timp, o constituie uzul de memorie. În cel mai rău caz, ar
trebui să memoreze un număr exponenţial de noduri. S-au elaborat mai multe
variante ale algoritmului A∗ astfel încât să poată face faţă acestei probleme,
printre care amintim: „adâncirea” iterativă A∗ (ID A∗ ), memoria–graniţă
∗ ∗
(la limită) A (M A ), respectiv varianta simplificată a memoriei –graniţă (la
limită) A∗ (SM A∗ ) şi best-first search varianta recursivă (RBFS).
57
Există două candidate la statutul de cel mai scurt drum, şi anume: fie
adevăratul cel mai scurt drum, ce foloseşte doar noduri ale mulţimii (1…k),
fie există un anume drum ce uneşte i de k+1, pe acest k+1 de j, ce este mai
bun. Ştim că cel mai bun drum de la i la j, care foloseşte doar nodurile
mulţimii (1…k) este definit de shortestPath(i,j,k), şi este evident faptul că
dacă ar exista un drum mai bun de la i la k+1, respectiv la j, atunci lungimea
acestui drum ar reprezenta concatenarea celui mai scurt drum de la i la k+1
(folosind vârfuri ale mulţimii (1…k)), respectiv a celui mai scurt drum de la
k+1 la j (folosindu-se deopotrivă vârfurile mulţimii (1…k)).
Astfel, putem defini shortestPath(i,j,k) în termenii următoarei
formule recursive:
shortestPath(i, j, k) = min(shortestPath(i, j, k − 1) + shortestPath(i, k, k − 1) +
+shortestPath(k, j, k − 1));
shortestPath(i, j, 0) = edgeCost(i, j);
Această formulă constituie „inima” lui Floyd Warshall. Algoritmul
funcţionează calculând mai întâi shortestPath(i,j,1) pentru toate perechile de
tipul (i,j), folosind acest rezultat, ulterior, pentru a calcula shortestPath(i,j,2)
pentru toate perechile de tipul (i,j), etc. Acest proces continuă până când
k = n, iar drumul cel mai scurt corespunzător tuturor perechilor (i,j), folosind
nodurile intermediare, va fi fost găsit.
Pseudocodul
În mod convenabil, când se calculează cazul de ordinul k, se poate
rescrie informaţia salvată la calculul corespunzător etapei k-1. Acesta
înseamnă că algoritmul foloseşte memorie pătratică. (A se lua în considerare
condiţiile de iniţializare!):
58
18 end
19 end
20 endproc
59
maxim între două vârfuri. Aceasta reprezintă că, în loc să
considerăm minimul ca în cazul pseudocodului de mai sus, vom
fi interesaţi de maxim. Costurile muchiilor constituie restricţii în
ceea ce priveşte fluxul. Costurile drumului reprezintă „blocaje”.
Astfel, operaţia de sumare de mai sus este înlocuită cu operaţia
corespunzătoare minimului.
• Testarea bipartiţiei unui graf neorientat.
60
O(| V |2 log | V | + | V || E |) : algoritmul foloseşte un timp de ordinul
O(| V || E |) pentru etapa Bellman-Ford a algoritmului, respectiv de ordinul
O (| V | log | V | + | E |) pentru fiecare din cele |V| apelări ale algoritmului lui
Dijkstra. Astfel, când graful este rar, timpul total poate fi mai rapid decât cel
corespunzător algoritmului Floyd-Warshall, care rezolvă aceeaşi problemă
într-un timp de ordinul O(| V |3 ) .
61
Remarcăm:
1) Egalitatea min-max din enunţul teoremei este interesantă şi conduce
la rezultate importante, în cazuri particulare.
2) Teorema se poate demonstra şi algoritmic ca o consecinţă a teoremei
fluxului maxim - secţiunii minime.
Forma echivalentă (a teoremei de mai sus) care a fost enunţată şi
demonstrată iniţial de Menger este:
Teoremă.
Fie G = (V,E) un (di)graf şi s, t ∈ V, astfel încât s ≠ t, st ∉ E. Există k
drumuri intern disjuncte de la s la t în graful G dacă şi numai dacă
îndepărtând mai puţin de k vârfuri diferite de s şi t, în graful rămas există un
drum de la s la t.
Notăm că două drumuri sunt intern disjuncte dacă nu au vârfuri
comune cu excepţia extremităţilor.
Am definit un graf G p-conex ( p ∈ N* ) dacă G = Kp sau dacă |G| > p
şi G nu poate fi deconectat prin îndepărtarea a mai puţin de p vârfuri.
Avem şi rezultatul.
Corolar.
Un graf G este p-conex dacă G = Kp sau ∀st ∈ E(G) există p
drumuri intern disjuncte de la s la t în G.
Determinarea numărului k(G) de conexiune a grafului G (cea mai
mare valoare a lui p pentru care G este p-conex) se reduce deci la
determinarea lui
max p({s},{t};G)
st∈E(G)
problemă care se poate rezolva în timp polinomial.
Un caz particular interesant al teoremei 1, se obţine atunci când G
este un graf bipartit iar X şi Y sunt cele două clase ale bipartiţiei:
Teoremă. (Konig)
Dacă G = (S,R;E) este un graf bipartit, atunci cardinalul maxim al
unui cuplaj (o mulţime independentă de muchii) este egal cu cardinalul
minim al unei mulţimi de vârfuri incidente cu toate muchiile grafului.
62
Teoremă. (Dirac)
Dacă G = (V,E) este un graf p-conex, p≥2, atunci prin orice p vârfuri
ale sale trece un circuit.
Pe baza acestei teoreme, se poate demonstra o condiţie suficientă de
hamiltonietate.
Teoremă.
Fie G p-conex. Dacă α(G)≤p atunci G este hamiltonian.
63
circuit Hamiltonian. Similar, graful Petersen este 3-conex, conţine mulţimi
neindependente cu mai mult de patru vârfuri şi nu are circuit Hamiltonian.
Demonstraţie.
Fie G ce satisface ipoteza Teoremei 1. Evident, G conţine un circuit;
fie C cel mai lung circuit. Dacă G nu are circuit Hamiltonian, atunci există
un vârf x, x ∉ C. Deoarece G este s-conex, există s drumuri începând din x şi
terminând în C, care sunt perechi disjunctive despărţite din x şi partajează cu
C chiar în vârfurile lor terminale x1,x2,…,xs. Pentru ∀ i=1,2,…,s , fie yi
succesorul lui xi într-un ciclu ordonat fix al lui C. Nici un yi nu este adiacent
cu x – altfel am putea înlocui muchiile xiyi în C prin drumul de la xi la yi în
afara lui C (către x) şi am obţine un circuit mai lung. Cu toate acestea, G nu
conţine mulţimi independente cu s+1 vârfuri şi deci există o muchie yiyj.
Şterge muchiile xiyi, xjyj din C şi adăugă muchia yiyj împreună cu drumul de
la xi la xj în afara lui C. În acest sens obţinem un circuit mai lung decât C,
ceea ce este o contradicţie.
Fie G un graf cu n vârfuri , n ≥ 3 . G nu conţine vârfuri cu grad mai
1
mic decât k unde k este un întreg astfel încât k ≥ ( n + 2 ) . Atunci G ori are
3
un circuit Hamiltonian, ori este separabil, ori are k+1 vârfuri independente.
Ca o consecinţă simplă a teoremei 1 obţinem:
Teorema 2.
Fie G un graf s-conex fără mulţimi independente de s+2 vârfuri.
Atunci G are un circuit Hamiltonian.
Demonstraţie.
Într-adevăr, dacă G satisface ipoteza Teoremei 2, atunci G+x (graful
obţinut din G prin adăugarea lui x şi reunindu-l cu toate vârfurile lui G)
satisface ipoteza Teoremei 1 cu s+1 în loc de s. Aşadar G+x are un circuit
Hamiltonian şi G are un drum Hamiltonian. Graful bipartite complet
K(s,s+2) arată că Teorema 2 este evidentă.
Tehnica utilizată în demonstrarea Teoremei 1 ne dă de asemenea
Teorema 3.
Fie G un graf s-conex ce nu conţine s vârfuri independente. Atunci G
este Hamiltonian – conex (i.e. fiecare pereche de vârfuri este unită printr-un
drum Hamiltonian).
64
Fie G = (V,U) un graf conex neorientat. Fiecărei muchii i se ataşează
un cost strict pozitiv. Ca urmare, graful va fi reprezentat prin matricea
costurilor C, având drept componente:
⎧ ≠ 0, dacă muchia (i, j) există;
ci, j = ⎨
⎩0, dacă nu există muchia (i, j);
Costul unui ciclu este definit ca sumă a costurilor ataşate muchiilor
componente.
Definiţie. Se numeşte ciclu hamiltonian un ciclu care trece exact o
singura dată prin fiecare vârf.
Pentru determinarea ciclurilor Hamiltoniene vom folosi metoda
backtracking. Astfel, dacă N = card(V), atunci o soluţie oarecare a problemei
se poate scrie sub forma unui vector X = (x1,x2,...,xN+1) .
Condiţiile de continuitate ce trebuie satisfăcute în construcţia soluţiei
sunt:
- x1 = xN+1;
- xi ≠ xj, ∀ (i,j) cu i ≠ j;
- (xi, xi+1)∈U, ∀ i∈ {1,...,N}.
Pentru a nu obţine de mai multe ori acelaşi ciclu, se poate fixa x1=1.
Fie alese x1,..., xk-1 cu k∈{2,...,N}. Atunci, condiţiile de continuitate, care
stabilesc dacă o valoare a lui xk poate conduce la o soluţie posibilă, sunt
următoarele:
- ∃ muchie între vârfurile xk-1 şi xk, cu xk ∉{x1,..., xk-1}
- xN trebuie să îndeplinească şi condiţia ca (xN, x1)∈U.
65
if c[N,1]< µ then
x[N+1]=1
write x
endif
else
k=k+1
x[k]=1
endif
endif
repeat
return
end
Procedura PROC1(c,N,X,k,v)
v = 0
if c[x[k-1],x[k]] = µ then
return
endif
for i=2, k-1
if x[k] = x[i] then
return
endif
repeat
v = 1
return
end
Nu se cunosc condiţii necesare şi suficiente direct verificabile pentru
a stabili dacă într-un graf dat există un ciclu hamiltonian.
Are loc următorul rezultat
Teoremă.
Într-un graf cu cel puţin 3 vârfuri, cu proprietatea că gradul fiecărui
N
vârf este ≥ , unde N = |V|, există un ciclu hamiltonian.
2
Demonstraţie.
Fie G=(V,U) un graf cu proprietatea din enunţ. Să presupunem prin
absurd că el nu conţine nici un ciclu hamiltonian. Fie H=(V,D) graful obţinut
din G prin adăugarea de noi muchii între vârfurile neadiacente atâta timp cât
acest lucru este posibil, fără ca astfel să se obţină vreun ciclu hamiltonian.
N
Bineînţeles că şi în H fiecare vârf are gradul ≥ .
2
Graful H nu este complet, deoarece într-un graf complet există
evident un ciclu hamiltonian. Există deci două vârfuri i,j ∈ V, neadiacente în
H. Printr-o renumerotare a vârfurilor, putem considera că i = 1 şi j = N.
Din modul de construcţie al grafului H rezultă că adăugarea muchiei
(1, N) ar conduce la apariţia unui ciclu hamiltonian.
Rezultă deci că există în H un lanţ L = {1, i1, ..., iN - 2, N} cu
{i1, ..., iN-2} = {2,...,N-1}.
66
Fie i j ,..., i j vârfurile adiacente vârfului 1.
1 k
N
Evident k ≥ .
2
Vârful N nu este adiacent cu nici unul dintre vârfurile i j ,..., i j
I −1 k −1
pentru că dacă N ar fi adiacent cu i j atunci s-ar obţine următorul ciclu
s −1
hamiltonian: sublanţul lui L ce uneşte pe 1 cu ij , muchia
s −1
(i j , N ) sublanţul din L ce uneşte pe N cu i j , muchia (i j , I ) , ceea ce nu
s −1 s s
este posibil. Rezultă că vârful N nu poate fi adiacent decât cu
N N
N −( k +1) ≤ N − −1 = −1
2 2
vârfuri, ceea ce duce la o contradicţie, deoarece fiecare vârf din graful H are
N
cel puţin gradul , prin ipoteză.
2
if (c[x[k]][x[k-1]]==inf) return 0;
/* ultimul nod trebuie sa fie vecin cu primul */
67
{
printf("Scrieti numele nodului %d: ",i);
scanf("%s",&nume[i]);
}
for (i=1; i<=n-1; i++)
for (j=i+1; j<=n; j++)
{
printf("Care este costul legaturii de la %s la
%s (0=infinit) ? ",nume[i],nume[j]);
scanf("%d",&c[i][j]);
if (c[i][j]==0) c[i][j]=inf;
c[j][i]=c[i][j];
}
x[1]=1;
k=2;
x[k]=1;
cost_minim=inf;
while (k>1)
{
cont=0;
while ((x[k]<n) && (!cont))
{
x[k]++;
cont=PotContinua();
}
if (cont)
if (k==n)
{
cost_curent=0;
for (i=1; i<n; i++)
cost_curent+=c[x[i]][x[i+1]];
cost_curent+=c[x[n]][x[1]];
if
(cost_curent<cost_minim)
{
cost_minim=cost_curent;
for (i=1; i<=n; i++)
y[i]=x[i
];
}
}
else x[++k]=1;
else --k;
}
printf("\n\nCircuitul de cost minim este: \n");
for (i=1; i<=n; i++)
printf("%s -> ", nume[y[i]]);
printf("%s", nume[y[1]]);
printf("\nCostul sau este : %d\n",cost_minim);
getch();
}
68
5.6. Arborele parţial de cost minim
69
P2) Unicitatea
Dacă fiecare muchie are un cost distinct, atunci există un unic arbore
parţial de cost minim. Demonstraţia acestui lucru este trivială şi se poate face
folosind inducţia.
P3) Subgraful de cost minim
Dacă costurile sunt nenegative, atunci un arbore parţial de cost
minim reprezintă, de fapt, subgraful de cost minim ce „conectează” toate
nodurile, ţinând cont şi de faptul că subgrafurile ce conţin cicluri au,
implicit, o valoare totală mai mare
P4) Proprietatea ciclului
Pentru orice ciclu C al grafului, dacă costul unei muchii e ∈ C este
mai mare ca valoarea tuturor celorlalte muchii din C, atunci această muchie
nu poate aparţine unui MST (Minimal Spanning Tree (Arbore Parţial de
Cost Minim)).
P5) Proprietatea tăieturii
Pentru orice tăietură C din graf, dacă costul unei muchii e din C este
mai mic decât toate costurile celorlalte muchii din C, atunci această muchie
aparţine tuturor MST – urilor (Arborilor parţiali de cost minim)
corespunzători grafului. Într-adevăr, presupunând contrariul, i.e., e nu
aparţine unui MST T1, atunci adăugând e lui T1 va rezulta un ciclu, care
,implicit, trebuie să mai conţină o altă muchie e2 din T1 în tăietura C.
Înlocuirea muchiei e2 cu e ar da naştere unui arbore având un cost mai mic.
Algoritmi
Primul algoritm de găsire a unui arbore parţial de cost minim a fost
conceput de către omul de ştiinţă ceh Otakar Borüvka în anul 1926
(algoritmul lui Borüvka). Astăzi se folosesc, cu precădere doi algoritmi, şi
anume: Algoritmul lui Prim, respectiv Algoritmul lui Kruskal. Toţi aceşti trei
algoritmi sunt algoritmi „greedy” ,al căror timp de rulare este de ordin
polinomial, astfel că problema găsirii unor astfel da arbori se încadrează în
clasa P. Un alt algoritm „greedy”, ce-i drept nu prea folosit, este algoritmul
reverse - delete, opusul algoritmului lui Kruskal.
Cel mai rapid algoritm de găsire a arborelui parţial de cost minim a
fost elaborat de către Bernard Chazelle, şi se baza pe cel al lui Borüvka.
Timpul său de rulare este de ordinul O(eα(e, v)) , unde e reprezintă numărul
muchiilor, v reprezintă numărul vârfurilor, iar α este funcţionala clasică,
inversa funcţiei Ackermann. Funcţia α creşte foarte încet, astfel că în
scopuri practice poate fi considerată o constantă nu mai mare ca 4; aşadar
algoritmul lui Chazelle se apropie (d.p.d.v. al timpului de rulare) de O(e) .
Care este cel mai rapid algoritm ce poate rezolva această problemă?
Aceasta este una din cele mai vechi întrebări deschise a ştiinţei
calculatoarelor. Există, în mod cert, o limită inferioară liniară, având în
70
vedere faptul că trebuie să examinăm cel puţin toate costurile. Dacă aceste
costuri ale muchiilor sunt întregi, atunci algoritmii determinişti sunt
caracterizaţi de un timp de rulare de ordinul O(e) . Pentru valorile generale,
David Karger a propus un algoritm aleatoriu al cărui timp de rulare a fost
preconizat ca fiind liniar.
Problema existenţei unui algoritm determinist al cărui timp de rulare
să fie liniar, în cazul costurilor oarecare, este încă deschisă. Cu toate acestea,
Seth Pettie şi Vijaya Ramachandran au găsit un posibil algoritm determinist
optimal pentru arborele parţial de cost minim, complexitatea computaţională
a acestuia fiind necunoscută.
Mai recent, cercetătorii şi-au concentrat atenţia asupra rezolvării
problemei arborelui parţial de cost minim de o manieră „paralelă”. De
exemplu, lucrarea pragmatică, publicată în anul 2003 „Fast Shared-Memory
Algorithms for Computing the Minimum Spanning Forest of Sparse Graphs"
a lui David A. Bader şi a lui Guojing Cong, demonstrează un algoritm care
poate calcula MST de cinci ori mai rapid pe 8 procesoare decât un algoritm
secvenţial optimizat. Caracteristic, algoritmii paraleli se bazează pe
algoritmul lui Borüvka – algoritmul lui Prim, dar mai ales cel al lui Kruskal
nu au aceleaşi rezultate în cazul procesoarelor adiţionale.
Au fost elaboraţi mai mulţi astfel de algoritmi de calculare a arborilor
parţiali de cost minim corespunzători unui graf „mare”, care trebuie stocat pe
disc de fiecare dată. Aceşti algoritmi de stocare externă, aşa cum sunt
descrişi în "Engineering an External Memory Minimum Spanning Tree
Algorithm", a lui Roman Dementiev et al., pot ajunge să opereze cel puţin de
două până la cinci ori mai lent ca un algoritm tradiţional in-memory; ei
pretind că „problemele aferente unui arbore parţial de cost minim masiv, ce
ocupă mai multe hard disk-uri, pot fi rezolvate pe un PC.” Se bazează pe
algoritmi de sortare - stocare externă eficienţi şi pe tehnici de contracţie a
grafului, folosite în scopul reducerii eficiente a mărimii acelui graf .
71
Descriere
Algoritmul măreşte în permanenţă dimensiunea arborelui iniţial, care
conţine un singur vârf, astfel încât la sfârşitul parcurgerii algoritmului acesta
(n.arborele) să se fi extins la toate nodurile.
• Date de intrare: Un graf conex ponderat G = (V,E)
• Iniţializare: V ′ = {x}, unde x este un nod arbitrar din V,
E′ = { }
• Repetă până când V ′ = V :
• Alegeţi o muchie (u,v) din E, având valoare minimă,
astfel încât u este din V ′ iar v nu aparţine mulţimii V ′
(dacă există mai multe muchii ce au aceeaşi valoare,
alegerea uneia dintre ele este arbitrară)
• Adăugaţi v la V ′ , adăugaţi (u,v) mulţimii E ′
• Date de ieşire: G = (V ′, E ′ ) este arborele parţial de cost
minim
Complexitate în timp
Complexitatea în timp (total) pentru:
-
căutarea cu ajutorul matricei de adiacenţă este |V|2;
- heap binar (ca în pseudocodul de mai jos) şi lista de adiacenţă este:
O((|V| + |E|) log(|V|)) = |E| log(|V|)
- heap Fibonaci şi lista de adiacenţă este:
|E| + |V| log(|V|)
O simplă implementare ce foloseşte o matrice de adiacenţă pentru
reprezentarea grafului, şi care cercetează un tablou de costuri pentru a găsi
muchia de cost minim ce urmează a fi adăugată, necesită un timp de rulare
de ordinul O(|V|2). Folosind o structură de date simplă de tip heap binar,
respectiv o reprezentare cu ajutorul listei de adiacenţă, se poate demonstra că
algoritmul lui Prim rulează într-un timp de ordinul
O(| E | log | V |) ,
unde |E| reprezintă numărul muchiilor, iar |V| reprezintă numărul vârfurilor.
Apelând la mai sofisticata heap Fibonacci, acest timp de rulare poate fi redus
până la
O(| E | + | V | log | V |) ,
semnificativ mai rapid pentru grafurile destul de dense (|E| este
Ω(| V | log | V |) ).
72
Exemple.
Cel de-al doilea nod ales este nodul cel mai apropiat lui D: A, cu un cost de
5.
Nevizitaţi: C, G
Fringe: B, E, F
Mulţimea soluţiilor: A, D
Următorul nod ales este acela situat în imediata apropiere fie a nodului D fie
a lui A. B se află la „o depărtare” 9 de D, respectiv 7 faţă de A, E la 15, iar F
la 6. 6 este cea mai mică valoare, astfel că vom marca vârful F şi arcul DF.
Nevizitaţi: C
Fringe: B, E, G
Mulţimea soluţiilor: A, D, F
73
Se marchează nodul B, care se află la o „distanţă” 7 de A. De data aceasta
arcul DB va fi colorat cu roşu , deoarece atât nodul B cât şi nodul D au fost
marcate, deci nu mai pot fi folosite.
Nevizitaţi: null
Fringe: C, E, G
Mulţimea soluţiilor: A, D, F, B
74
Vârful G este singurul vârf rămas. Se află la o ”distanţa” 11 de F, respectiv 9
faţă de E. E este mai aproape, astfel că îl marcăm împreună cu arcul EG. La
acest moment toate vârfurile vor fi fost marcate, arborele parţial de cost
minim fiind marcat cu verde. În acest caz, are o valoare de 39.
Nevizitaţi: null
Fringe: null
Mulţimea soluţiilor: A, D, F, B, E, C, G
Pseudocodul
Iniţializare
Date de intrare: Un graf, o funcţie ce returnează „costurile”
muchiilor – funcţia costurilor, respectiv un nod iniţial.
Starea iniţială a tuturor vârfurilor: „nevizitaţi”, mulţimea iniţială de
vârfuri ce urmează a fi adăugaţi arborelui, plasându-le într-o Min-heap cu
scopul de extrage „distanţa minimă” din graful minim.
for each vertex in graph
set min_distance of vertex to ∞
set parent of vertex to null
set minimum_adjacency_list of vertex to empty list
set is_in_Q of vertex to true
set distance of initial vertex to zero
add to minimum-heap Q all vertices in graph.
Algoritm
În descrierea algoritmului de mai sus:
cel mai apropiat vârf este Q[0], acum ultima „adăugare”
fringe este v în Q unde distanţa lui v < ∞ după extragerea celui mai
apropiat vârf
nevizitat este v din Q pentru care distanţa corespunzătoare v = ∞ ,
după extragerea celui mai apropiat vârf
75
Complexitatea în timp: |V| pentru buclă, log(|V|) pentru funcţia de
întoarcere
while latest_addition = remove minimum in Q
set is_in_Q of latest_addition to false
add latest_addition to (minimum_adjacency_list of (parent of
latest_addition))
add (parent of latest_addition) to (minimum_adjacency_list of
latest_addition)
Demonstraţia corectitudinii
Fie P un graf conex, ponderat.
La fiecare iteraţie a algoritmului lui Prim, trebuie să se găsească o
muchie ce leagă un vârf ce aparţine subgrafului de un altul din afara acestuia.
Având în vedere faptul că P este conex, va exista întotdeauna un drum către
orice vârf.
Ceea ce rezultă în urma parcurgerii algoritmului lui Prim este un
arbore Y, explicaţia fiind dată de faptul că muchia şi vârful adăugate lui Y
sunt conexe. Fie Y1 un arbore parţial de cost minim al lui Y.
Dacă Y1 = Y atunci Y este un arbore parţial de cost minim.
Altfel, fie e prima muchie adăugată la „construcţia” lui Y, muchie ce
nu este în Y1 , şi fie V mulţimea vârfurilor conectate prin intermediul
muchiilor adăugate înaintea muchiei e. Atunci unul dintre vârfurile ce
compun muchia e se va găsi în V iar celălalt nu. Ţinând cont de faptul că Y1
este un arbore parţial al lui P, există un drum în Y1 ce uneşte aceste două
vârfuri. Pe măsură ce se parcurge acest drum, trebuie să se găsească o
muchie f ce uneşte un vârf din V de un altul ce nu se găseşte în V. Acum, la
momentul iteraţiei în care e este adăugată lui Y, există posibilitatea ca şi f să
fi fost adăugată, acest lucru fiind posibil în eventualitatea deţinerii unui cost
mai mic decât cel al muchiei e.
76
Dat fiind faptul că f nu a fost adăugată deducem că
w ( f ) ≥ w (e) .
Fie Y2 graful obţinut prin înlăturarea muchiei f, respectiv adăugarea
muchiei e din Y1 .
Se arată uşor faptul că Y2 este conex, are acelaşi număr de muchii ca
şi Y1 , iar costul total al muchiilor constituente nu-l depăşeşte pe cel al lui Y1 ,
astfel că este de asemenea un arbore parţial de cost minim al lui P, conţinând
muchia e şi toate celelalte muchii ce o precedau în construcţia V.
Repetând paşii anteriori vom putea obţine un arbore parţial de cost
minim al lui P, identic cu Y.
Aceasta demonstrează că Y este un arbore parţial de cost minim.
77
■ se şterge o muchie având valoarea cea mai mică din
mulţimea S
■ dacă acea muchie leagă doi arbori diferiţi, atunci adaugă
aceşti arbori pădurii, combinându-i într-unul singur
■ altfel, elimină muchia respectivă
Acest algoritm a apărut pentru prima dată în Proceedings of the
American Mathematical Society, în anul 1956, şi a fost scris de către Joseph
Kruskal.
Funcţionare
Ţinând cont de faptul că |E| reprezintă numărul muchiilor grafului, iar
|V| reprezintă numărul vârfurilor grafului, se poate demonstra că timpul de
rulare al algoritmului lui Kruskal este de ordinul O(| E | log | E |) , sau,
echivalent, de ordinul O(| E | log | V |) , în cazul structurilor de date simple.
Aceşti timpi de rulare sunt echivalenţi deoarece:
■ |E| este cel mult | V |2 , iar
log | V |2 = 2 log | V |
este
O(log | V |) .
■ Dacă ignorăm vârfurile izolate, care vor constitui propriile
componente ale arborilor parţiali de cost minim, | V |≤ 2 | E | , astfel că
log | V | este O(log | E |) .
Putem obţine aceasta astfel: se sortează, pentru început, muchiile,
după costuri folosind o sortare de comparare, al cărui timp de rulare este de
ordinul O(| E | log | E |) ; acest lucru permite pasului „elimină o muchie de
cost minim din S ” să opereze într-un timp constant. În continuare, folosim o
mulţime de separaţie a structurilor de date, astfel ca să se poată contabiliza
vârfurile şi apartenenţa la anumite componente. Este nevoie de execuţia a
O(| E |) operaţii, pentru „găsirea” operaţiilor şi a unei posibile uniuni pentru
fiecare muchie în parte. Chiar şi o simplă structură de date de tip mulţime de
separaţie, cum ar fi pădurile pot executa O(| E |) operaţii într-un timp de
ordinul O(| E | log | V |) . Astfel, timpul total este
O(| E | log | E |) = O(| E | log | V |) .
Dacă muchiile sunt deja sortate sau pot fi sortate într-un timp liniar,
algoritmul poate folosi structuri de date de tip mulţime de separaţie mult mai
sofisticate pentru a rula într-un timp de ordinul O(| E || α(V) |) , unde α
reprezintă inversul unei funcţii ponderate-singular Ackermann, ce creşte
foarte încet.
78
Exemplu
AD şi CE sunt cele mai „scurte” arce, având lungimea 5, iar AD a fost ales
arbitrar, astfel că apare colorat
Oricum, CE este , la momentul actual, cel mai scurt arc care nu formează
buclă, de lungime 5, astfel că este colorat.
79
Următoarele cele mai scurte arce sunt AB şi BE, ambele având lungimea 7.
Se alege în mod arbitrar AB, şi se colorează. Arcul BD se colorează cu roşu,
deoarece ar forma o buclă ABD dacă ar fi ales.
80
Fie Y1 un arbore parţial de cost minim. Dacă Y = Y1 atunci Y este un
arbore parţial de cost minim. Altfel, fie e prima muchie considerată de către
algoritm, muchie ce este în Y dar nu este în Y1 . Y1 ∪ e conţine un ciclu,
deoarece nu se poate adăuga o muchie unui arbore parţial astfel încât să
continuăm să avem un arbore. Acest ciclu conţine o altă muchie f ,care, în
etapa în care e a fost adăugată, nu a fost luată în considerare. Aceasta din
pricina faptului că în acest caz e nu ar fi conectat arbori diferiţi, ci două
ramuri ale aceluiaşi arbore. Astfel, Y2 = Y1 ∪ e \ f este, de asemenea, un
arbore parţial. Valoarea sa totală este mai mică sau cel mult egală cu
valoarea totală a lui Y1 . Aceasta se întâmplă deoarece algoritmul „vizitează”
muchia e înaintea muchiei f, şi drept urmare w ( e ) ≤ w ( f ) . Dacă se întâmplă
ca aceste valori să fie egale, considerăm următoarea muchie e, ce se găseşte
în Y dar nu în Y1 . Dacă nu mai sunt astfel de muchii, valoarea lui Y este egală
cu cea a lui Y1 , deşi sunt caracterizaţi de mulţimi diferite de muchii, iar Y este
de asemenea un arbore parţial de cost minim. În cazul în care valoarea lui Y2
este mai mică decât valoarea lui Y1 , putem concluziona că acesta din urmă
( Y1 ) nu este un arbore parţial de cost minim, iar presupunerea conform căreia
există muchii e, f cu w ( e ) < w ( f ) este falsă. De aceea, Y este un arbore
parţial de cost minim (egal cu Y1 sau cu o altă mulţime de muchii, dar având
aceeaşi valoare).
Pseudocodul
1 function Kruskal(G)
2 for each vertex v in G do
3 Define an elementary cluster C(v) ← {v}.
4 Initialize a priority queue Q to contain all edges in G,
using the weights as keys.
5 Define a tree T ← Ø //T will ultimately
contain the edges of the MST
6 // n is total number of vertices
7 while T has fewer than n-1 edges do
8 // edge u,v is the minimum weighted route from/to v
9 (u,v) ← Q.removeMin()
10 // prevent cycles in T. add u,v only if T does not already
contain an edge consisting of u and v.
// Note that the cluster contains more than one vertex only
if an edge containing a pair of
// the vertices has been added to the tree.
12 Let C(v) be the cluster containing v, and let C(u) be the
cluster containing u.
13 if C(v) ≠ C(u) then
14 Add edge (v,u) to T.
15 Merge C(v) and C(u) into one cluster, that is, union C(v) and
C(u).
16 return tree T
81
82
Capitolul 6
PROBLEME DE FLUXURI ÎN REŢELE
83
⎛ ⎞
0 = ∑ ⎜ ∑ x ji − ∑ x ij ⎟ = ∑ ∑ x ji − ∑ ∑ x ij +
⎜
i ≠ s, t ⎝ j∈V j∈V
⎟ i ≠ s, t j ≠ s,t i ≠ s, t j ≠ s, t
⎠
⎛ ⎞ ⎛ ⎞
+ ∑ x si + ∑ x ti − ∑ x is − ∑ x it = − ⎜ ∑ x is − ∑ x si ⎟ − ⎜ ∑ x it − ∑ x ti ⎟ ,
i ≠ s, t i ≠ s, t i ≠ s,t i ≠ s, t ⎝i i ⎠ ⎝i i ⎠
Definiţie. Dacă x este un flux în reţeaua R = (G,s,t,c) se numeşte
valoarea fluxului x numărul
v(x) = ∑ x jt − ∑ x tj .
j∈V j∈V
v(x) se poate interpreta ca fiind fluxul net care ajunge în ieşirea reţelei sau
(conform egalităţii obţinute mai sus) fluxul net care iese din intrarea reţelei.
În orice reţea R = (G,s,t,c) există un flux, fluxul nul x ij = 0 ∀ij , de
valoare 0.
Problema fluxului maxim
Dată R = (G,s,t,c) o reţea, să se determine un flux de valoare maximă.
Problema se poate formula, ca o problemă de programare liniară:
⎧max v
⎪
⎪
⎪∑ x ji − ∑ x ij = 0, ∀i ≠ s, t
⎪j j
⎪
⎨∑ x js − ∑ x sj = − v
⎪j j
⎪
⎪∑ x jt − ∑ x tj = v
⎪j j
⎪0 ≤ x ij ≤ cij ∀ij
⎩
Definiţie. Dacă P este un drum în G , multigraful suport al digrafului
G, şi e = vi v j este o muchie a lui P atunci:
- dacă e corespunde arcului vi v j al lui G, e se numeşte arc direct al
drumului P;
- dacă e corespunde arcului v j vi al lui G, atunci e se numeşte arc
invers.
Definiţie. Fie R = (G,s,t,c) şi x flux în R. Se numeşte C-drum (în R
relativ la fluxul x) un drum D în G cu proprietatea că ∀ij ∈ E(D) :
x ij < cij dacă ij este arc direct;
x ji > 0 dacă ij este arc invers.
84
Dacă D este un C – drum şi ij ∈ E(D) , se numeşte capacitate
reziduală a lui ij (relativ la C – drumul D) numărul
⎧⎪cij − x ij , ij arc direct în D
r(ij) = ⎨
⎪⎩ x ji , ij arc invers în D.
Capacitatea reziduală a drumului D este:
r(D) = min r(e) .
e∈E(D)
Definiţie. Se numeşte drum de creştere a fluxului x, în reţeaua R =
(G,s,t,c), un C-drum de la s la t.
Se arată că:
Lemă.
Dacă D este un drum de creştere a fluxului x în reţeaua R = (G,s,t,c),
atunci x1 = x ⊗ r(D) definit prin
⎧x , ij ∉ E(D)
⎪⎪ ij
1
x ij = ⎨ x ij + r(D), ij ∈ E(D), ij arc direct în D
⎪
⎪⎩ x ij − r(D), ji ∈ E(D), ji arc invers în D
85
Să observăm că ∀i ∈ S şi ∀j ∈ T avem:
- dacă ij ∈ E atunci x ij = cij şi
- dacă ji ∈ E atunci x ji = 0
1: x 0 ← 0; i ← 0;
2: while ( ∃P drum de creştere relativ la x i ) do
i
{
x i +1 ← x i ⊗ r(Pi ) ;
i + +
}
86
construită în demonstraţia teoremei antecedente satisface împreună cu x
enunţul teoremei.
Pentru cazul capacităţilor raţionale algoritmul descris satisface
această condiţie.
Pentru cazul capacităţilor reale există un algoritm, datorat lui
Edmonds şi Karp.
Algoritmul lui Ford şi Fulkerson pentru aflarea unui flux de
valoare maximă
Se va folosi un procedeu de etichetare a vârfurilor reţelei, în vederea
depistării drumurilor de creştere a fluxului curent x. Dacă nu există drumuri
de creştere, fluxul va fi de valoare maximă.
Eticheta atribuită unui vârf j ∈ V are trei componente (e1 , e2 , e3 )
unde e1 ∈ V ∪ {0} , e2 ∈{direct, invers}; e3 ∈ R + şi au următoarea
semnificaţie:
- dacă e2 = direct şi e1 = i atunci
∃ un C-drum P de la s la j cu ultimul arc ij, arc direct şi r(P) = e3 ;
- dacă e2 = invers şi e1 = i atunci
∃ un C-drum P de la s la j cu ultimul arc ij, arc invers şi r(P) = e3 .
Iniţial, se etichetează sursa s cu eticheta (0, . , ∞) . Celelalte vârfuri
primesc etichetă prin ”cercetarea” vârfurilor deja etichetate:
Dacă i este un vârf etichetat, atunci ∀j ∈ V :
- dacă j neetichetat,
ij ∈ E şi x ij < cij
atunci j se etichetează
e = (i, direct, min(e3 [i], cij − x ij )) ;
- dacă j neetichetat,
ji ∈ E şi x ji > 0
atunci j se etichetează
e = (i,invers, min(e3 [i], x ji )) .
Evident, în acest fel se respectă semnificaţia celor trei componente
ale etichetelor.
Numim această procedură etichetare(i).
Atunci când în procedura de etichetare s-a atribuit etichetă vârfului t,
s-a obţinut un drum de creştere P a fluxului curent, de capacitate reziduală
r(P) = e3 [t] şi ale cărui vârfuri se depistează în O(n) explorând prima
componentă a etichetelor. Modificarea fluxului x ⊗ r(P) se execută în acest
mers înapoi, tot în O(n).
87
Pentru noul flux se reia procedura de etichetare.
Dacă toate vârfurile etichetate au fost cercetate şi nu s-a reuşit
etichetarea vârfului t, rezultă că fluxul curent nu admite drumuri de creştere,
este deci de valoare maximă, iar dacă S = mulţimea vârfurilor etichetate
atunci (S, V − S) este o secţiune de capacitate minimă.
Descrierea algoritmului
Complexitatea algoritmului
Pentru fiecare creştere a fluxului, sunt necesare cel mult 2m (m = |E|)
inspecţii de arce în vederea etichetării.
Dacă toate capacităţile sunt întregi atunci vor fi necesare cel mult v
(v = valoarea fluxului maxim) creşteri succesive. Rezultă că algoritmul are
complexitatea O(mv).
Dacă U este o margine superioară a capacităţilor arcelor atunci
v ≤ (n − 1)U
((n − 1)U este o margine superioară a capacităţii secţiunii
({s}, V −{s})),
deci algoritmul are complexitatea
O(nmU).
Dezavantajele algoritmului sunt legate de neconvergenţa în cazul
capacităţilor iraţionale (deşi practic, în implementări nu este cazul), şi de
faptul ca mărimile capacităţilor influenţează comportarea sa, acestea
neconstituind o măsură a volumului datelor de intrare.
88
6.2. Fluxuri de cost minim
Fie R = (G,s,t,c) o reţea şi x un flux de la s la t în R.
Considerăm a : E → R o funcţie de cost care asociază fiecărui arc
ij ∈ E , a(ij) = a ij costul (transportului unei unităţi de flux) pe arcul ij.
Costul fluxului x se defineşte ca fiind
a(x) = ∑ a ij x ij .
i, j
Problema fluxului de cost minim
Dată R o reţea, v ∈ R + şi a : E → R funcţie de cost, să se determine
x 0 flux în R astfel încât
a(x 0 ) = min {a(x) x flux în R, v(x) = v} .
Observăm că, dacă v nu depăşeşte valoarea fluxului maxim în reţeaua
R, atunci problema are întotdeauna soluţii, a(x) fiind liniară, iar mulţimea
fluxurilor de valoare dată v fiind mărginită şi închisă în R m .
Definiţie. Fie x un flux în R = (G,s,t,c) şi a : E → R o funcţie de
cost.
Dacă P este un C-drum în R relativ la fluxul x, atunci costul drumului
P se defineşte
a(P) = ∑ a ij − ∑ a ji .
ij∈P ij∈P
ij direct ij invers
Dacă C este un C-drum închis, a(C) se calculează după aceeaşi
formulă, după stabilirea unui sens de parcurgere a lui C (este posibil ca
ambele sensuri de parcurgere ale lui C să satisfacă definiţia unui C-drum).
1. Din definiţia dată, rezultă că dacă P este drum de creştere relativ la
fluxul x, atunci x1 = x ⊗ r(P) este un flux de valoare v(x1 ) = v(x) + r(P) şi
de cost a(x) + r(P) ⋅ a(P) .
2. Dacă C este un C-drum închis relativ la x, atunci x1 = x ⊗ r(C) este
un flux de valoare v(x1 ) = v(x) şi de cost a(x1 ) = a(x) + r(C) ⋅ a(C) .
Dacă a(C) < 0 atunci x1 este un flux de aceeaşi valoare ca şi x, dar de
cost strict mai mic.
Teoremă.
Un flux de valoare v este de cost minim dacă şi numai dacă nu admite
C - drumuri închise de cost negativ.
Demonstraţie.
Necesitatea este evidentă din observaţia anterioară.
89
Suficienţa. Fie x un flux de valoare v, care nu admite C - drumuri
închise de cost negativ.
Fie x∗ un flux de valoare v, de cost minim astfel încât
Δ(x, x∗ ) = min{Δ (x, x ') x ' flux de valoare v şi cost minim}
{ }
unde Δ (x, x ') = | ij x ij ≠ x 'ij | .
0 ≤ x ij < x ∗ij ≤ cij (astfel, raţionamentul este similar). Din legea de conservare
a fluxurilor rezultă că
∃jk ∈ E astfel încât 0 ≤ x jk < x∗jk ≤ c jk
sau
∃kj ∈ E astfel încât 0 ≤ x∗kj < x kj ≤ c jk .
Repetând acest raţionament, deoarece numărul vârfurilor este finit, se
va obţine C, un C-drum închis relativ la x în R.
Considerând sensul invers de parcurgere pe C se obţine un C-drum
C ' , închis relativ la x ∗ .
Deoarece a(C) ≥ 0 din ipoteză, iar a( C ' ) = – a(C), rezultă, din
necesitatea teoremei ( x∗ este de cost minim), că a(C) = 0.
Modificând fluxul x∗ cu δ( C ' ) pe C ' , unde
δ(C ') = min{ min x kj − x∗kj , min x∗jk − x jk }
kj direct în C ' kj invers în C '
se obţine un flux x ' cu
v(x ') = v(x∗ ) = v , a(x ') = a(x∗ ) + δ(C ') ⋅ a(C ') = a(x∗ ) ,
deci de cost minim, dar, cu Δ(x, x ') < Δ(x, x∗ ) , contradicţie.
Deci Δ(x, x∗ ) = 0 .
Teoremă.
Dacă x este un flux de valoare v şi de cost minim iar P0 este un drum
de creştere, astfel încât
a(P0 ) = min{a(P) P drum de creştere relativ la x}
atunci x1 = x ⊗ r(P0 ) este un flux de valoare v(x1 ) = v + r(P0 ) şi de cost
minim.
90
Linia demonstraţiei este următoarea: presupunând prin reducere la
absurd că x1 nu este de cost minim, atunci x1 admite un C – drum închis C
de cost negativ. Cum x era flux de cost minim rezultă că E(C) ∩ E(P0 ) ≠ 0/ .
Dacă ij ∈ E(C) ∩ E(P0 ) , atunci va rezulta că P0 ∪ C − ij conţine un
drum de creştere relativ la x de cost mai mic decât P0 .
Un drum de creştere de cost minim poate fi depistat cu ajutorul
algoritmilor de drum minim.
Dacă x este un flux în R şi a : E → R este funcţia de cost atunci
considerând a ij = ∞ dacă ij ∉ E (caz în care x ij = 0 ), construim
⎧ a ij , x ij < cij şi x ji = 0
⎪
⎪⎪ min{a ij , − a ji }, x ij < cij şi x ji > 0
a ij = ⎨
⎪ − a ji , x ij = cij şi x ji > 0
⎪∞, x ij = cij şi x ji = 0
⎪⎩
Un drum de pondere minimă de la s la t în raport cu ponderile aij
corespunde unui drum minim de creştere în R relativ la fluxul x.
Un circuit de pondere negativă în raport cu ponderile aij corespunde
unui C – drum închis în R relativ la x, de cost negativ.
Rezultă, următorul algoritm pentru rezolvarea problemei fluxului de
cost minim.
Algoritm generic de rezolvare a problemei fluxului de cost minim
{
0: Se consideră x = ( x ) un flux cu valoarea v ' ≤ v ;
ij
{x poate fi fluxul nul sau un flux y determinat cu
ajutorul algoritmului de flux maxim şi apoi
considerând x = v
y}
v(y)
1: while ( ∃ circuite de pondere < 0 relativ la a ) do
ij
{ determină un astfel de circuit;
modifică fluxul pe acest circuit
}
2: while v(x) < v do
{ aplică un algoritm de drum minim în raport cu
ponderile a pentru depistarea unui C – drum
ij
P de cost minim;
x ← x ⊗ min(r(P), v − v(x))
}
}
91
Complexitatea pentru pasul 2 este O( n 3 v ), dacă se pleacă de la
fluxul nul. Se poate dovedi că pasul 1 se poate implementa astfel ca numărul
iteraţiilor să fie O( nm 2 log n ).
În continuare se prezintă o nouă descriere a algoritmului lui Ford şi
Fulkerson şi se aplică acest algoritm pe un exemplu.
exceptând s şi t. Fluxul ce intră într-un nod trebuie să fie egal cu cel ce iese
din nodul respectiv.
Aceasta înseamnă că fluxul prin reţea este ceea ce numim „flux
legal” după fiecare etapă parcursă a algoritmului. Definim reţeaua
reziduală G f (V , E f ) ca fiind reţeaua cu capacitate
c f (u , v ) = c(u, v ) − f (u, v )
şi fără flux. A se ţine cont de faptul că nu se ştie cu certitudine că
E = Ef ,
deoarece se poate întâmpla ca, trimiţând flux pe (u, v) acesta să se blocheze
(satureze), însă dă naştere unei noi muchii (v, u) în reţeaua reziduală.
Algoritmul Ford-Fulkerson
Date de intrare: Graful G cu capacitatea c, nodul sursă s şi
nodul ţintă t.
Date de ieşire: Un flux maxim f de la s la t
92
1. f ( u, v ) ← 0 pentru toate muchiile (u, v)
2. Cât timp există un drum P de la s la t, astfel încât
c f (u, v) > 0 pentru toate muchiile ( u, v ) ∈ P :
1. Găsirea c f { }
( p ) = min c f ( u, v ) ( u, v ) ∈ p
2. Pentru fiecare muchie ( u, v ) ∈ P
1. f ( u, v ) ← f ( u, v ) + c f ( p)
(Se trimite flux de-a lungul drumului)
2. f ( v, u ) ← f ( v, u ) − c f ( p)
(Fluxul ar putea fi „întors” ulterior)
93
Drum Capacitate Fluxul în reţea rezultat
min(cf(A,B),cf(B,C),cf(C,D))
= min(c(A,B) f(A,B),
c(B,C) − f(B,C),
A,B,C,D
c(C,D) − f(C,D))
= min(1000 − 0,
1 − 0,1000− 0) = 1
min(cf(A,C),cf(C,B),cf(B,D))
= min(c(A,C) − f(A,C),
c(C,B) − f(C,B),
A,C,B,D
c(B,D) − f(B,D))
= min(1000 − 0,
0 − ( − 1),1000 − 0) = 1
94
Capitolul 7
NUMĂRUL DE STABILITATE ŞI
DENSITATEA UNUI GRAF
95
m ≥ z k +1 (n) ⇒ ω(G(n, m)) ≥ k + 1 ?
Pentru a răspunde la această întrebare să considerăm:
n = t⋅k + r,
⎡n ⎤
unde t = ⎢ ⎥ , 0 ≤ r ≤ k − 1 şi să construim graful Tnk în felul următor:
⎣k ⎦
Graful Tnk are n vârfuri repartizate în clasele S1 ,S2 ,...,Sk cu
⎧ t + 1, i = 1, 2,..., r
| Si | = ⎨
⎩ t, i = r + 1,..., k,
iar două vârfuri diferite vor fi unite printr-o muchie dacă şi numai dacă
aparţin la clase diferite.
Se constată uşor că numărul muchiilor grafului Tnk este:
k(k − 1)t 2 r(r − 1)
f k (n) = + r(k − 1)t +
2 2
k −1 2 2 ⎛ r ⎞
= (n − r ) + ⎜ ⎟ .
2k ⎝ 2⎠
96
acest k egalează numărul vârfurilor, sau dacă este mai mic cu o unitate ca
acesta, dar nu dacă reprezintă jumătate din numărul vârfurilor.
O problemă mult mai uşor de rezolvat este aceea a găsirii unei
mulţimi independente maximale, care să nu fie conţinută în nici o altă astfel
de mulţime independentă. Pornim de la un singur vârf. Găsim un vârf
neadiacent celui considerat iniţial şi-l adăugăm mulţimii respective, apoi
găsim un alt nod neadiacent niciunui vârf menţionat anterior ş.a.m.d. până
când nu mai găsim astfel de noduri. La acel moment mulţimea este maximal
independentă. Se cunosc algoritmi mult mai complecşi, de listare a tuturor
mulţimilor independente maximale, dar, în general, numărul unor astfel de
mulţimi poate fi foarte mare.
Demonstraţia NP – complet
Se poate observa uşor că problema este de tipul NP (aparţine clasei
NP), ţinând cont de faptul că, dacă avem o submulţime de vârfuri, putem
verifica dacă există muchii între oricare din două vârfuri într-un timp
polinomial. Pentru a arăta că problema este de tipul dificultate – NP
(NP – dificil), vom folosi o „reducere” a unei alte probleme de tipul
NP – complet.
Presupunem că se cunoaşte deja rezultatul lui Cook, conform căruia
problema satisfacerii booleene este NP – completă. Orice formulă booleană
poate fi redusă, în mod eficient, la forma sa normală conjunctivă
(conjunctive normal form CNF). În forma normală conjunctivă:
■ Formula este o conjuncţie (and ( şi)) de propoziţii.
■ Fiecare propoziţie reprezintă o disjuncţie (or(sau)) de literali.
■ Fiecare literal reprezintă fie o variabilă fie negaţia acesteia.
Spre exemplu, următoarea formulă constituie o formă CNF, unde ~
denotă negaţia:
( x1 sau ~ x2 sau ~ x3 ) şi ( x1 sau x2 sau x4 )
O astfel de formulă este satisfăcătoare dacă putem atribui valori
adevărat / fals fiecărei variabile în parte astfel încât cel puţin un literal din
fiecare propoziţie să aibă valoarea de adevăr adevărat. Spre exemplu, orice
atribuire a lui x2 cu valoarea de adevăr fals, respectiv a lui x4 cu valoarea de
adevăr adevărat satisface formula mai sus amintită.
În cele ce urmează, se va prezenta o reducere de tipul mai mulţi - la
unul (timp - polinomial), de la CNF- la problema mulţimii independente.
Mai întâi, se ataşează câte unu nod pentru fiecare literal din formulă; se
includ duplicate ale vârfurilor pentru apariţii multiple. Se trasează o muchie
între:
1. Oricare doi literali, fiecare reprezentând negaţia celuilalt.
2. Oricare doi literali, ce se află în aceeaşi propoziţie.
97
Astfel, în exemplul de mai sus, x2 ar fi adiacent cu ~ x2 , primul x1 ar
fi adiacent cu ~ x2 , iar cel de-al doilea x1 ar fi adiacent cu x4 .
98
Graf conţinând clică de mărime 3
Clica într-un graf reprezintă o mulţime de vârfuri adiacente perechi,
adică subgraful indus, care este un graf complet.
În cele din urmă, problema clicii constă în determinarea existenţei
unei clici într-un graf, a cărei mărime să fie cel puţin k (ordin predefinit).
Odată identificate k sau mai multe vârfuri ce formează o clică, este trivial să
demonstrăm acest lucru, fapt ce-i justifică complexitatea NP. Problema
corespunzătoare de optimizare, problema clicii maxime, constă în găsirea
celei mai mari clici din graf.
Complexitatea NP a problemei clicii derivă din complexitatea NP a
problemei mulţimii independente, deoarece există o clică de mărime k (cel
puţin) dacă şi numai dacă există o mulţime independentă de mărime k (cel
puţin) în graful complementar. Acest lucru este uşor de observat, ţinând cont
de faptul că dacă un subgraf este complet, atunci subgraful complementar nu
are muchii.
Algoritmi
Un algoritm „primitiv” de găsire a clicilor într-un graf constă în
examinarea fiecărui subgraf ce conţine cel puţin k vârfuri, şi îndeplinirea
condiţiei de formare a unei clici. Algoritmul este polinomial (complexitate
polinomială) dacă acel k coincide cu numărul vârfurilor, sau cu o constantă
mai puţin, dar nu dacă k este, să spunem, jumătate din numărul total al
vârfurilor. Numărul total al clicilor de mărime k a unui graf de mărime |V|
este egal cu
⎛| V |⎞ | V |!
⎜ ⎟= .
⎝ k ⎠ k!(| V | −k)!
În mod euristic am începe prin considerarea fiecărui nod ca fiind o
clică, iar mai apoi să se unească aceste clici în altele mai mari până când
acest lucru nu mai este posibil.
Două clici A şi B pot fuziona dacă fiecare vârf din clica A este
adiacent fiecărui vârf din clica B. Aceasta presupune un cost, în ceea ce
priveşte timpul, liniar (liniar în numărul muchiilor), dar poate eşua în găsirea
unei clici mari, întrucât două sau mai multe părţi a clicii mari vor fi fost
fuzionat anterior, prin intermediul unor vârfuri care nu aparţin clicii.
Se găseşte, totuşi, cel puţin o clică maximală, care nu este conţinută
în nici o altă clică mai mare.
99
Unele cazuri mai speciale, pot fi rezolvate într-un timp mai scurt
decât cel exponenţial. Pentru k = 3, algoritmul are o complexitate O(n1,41 ) ,
unde n reprezintă numărul muchiilor.
S ∪ {x k + 1} ∈ S∗k + 1 , dacă S ⊆ Yk + 1
sau dacă S ⊆/ Yk + 1 punem S ∈ S∗k + 1 şi {x k + 1} ∪ (S ∩ Yk + 1) ∈ S∗k + 1
atunci şi numai atunci când S ∩ Yk + 1 ∈ I 'k .
∗
Familia Sk + 1 conţine numai mulţimile specificate mai sus.
100
mulţime stabilă a grafului Gk+1 şi că orice mulţime stabilă maximală a acestui
graf aparţine familiei S∗k +1 .
Prima parte a afirmaţiei rezultă din modul de construcţie al familiei
S∗k +1 , deoarece S∗k +1 conţine mulţimi sau submulţimi din Sk (care fiind
stabile în Gk, sunt stabile şi în graful Gk+1) la care se adaugă elementul xk+1
dacă şi numai dacă xk+1 nu este adiacent în Gk+1 cu nici unul dintre
elementele mulţimii sau submulţimii considerate.
Fie acum S o mulţime stabilă maximală a grafului Gk+1. Dacă
x k +1 ∉ S atunci S ∈ S k şi S ⊆/ Yk +1 . Rezultă, conform pasului 3 al
algoritmului (alternativa a doua) că S ∈ S∗k +1 .
Dacă x k +1 ∈ S atunci T = S − {x k +1} este stabilă în graful Gk şi
T ⊆ Yk +1 .
Dacă T ∈ S k , atunci
S = T ∪ {x k +1} ∈ S∗k +1 .
În cazul când T ∉ S k există o mulţime T ' ∈ S k cu T ⊂ T ' . Avem
T ⊆ T '∩ Yk +1
şi din cauza maximalităţii lui S rezultă că
T = T '∩ Yk +1 , T ' ⊆/ Yk +1 , T '∩ Yk +1 ∈ I 'k .
Deci S = (T '∩ Yk +1 ) ∪ {x k +1} ∈ S∗k +1 , şi astfel, teorema este
demonstrată.
Exemplu.
Să se determine familia S7 pentru graful din figură:
1. X1 = {1}, S1 = {1} (k = 1)
Y2 = {1}
2. I1 = {{1}} = I '1
3. S∗2 = {{1, 2}}
101
4. S2 = {{1,2}}
X2 = {1,2}, S2 = {{1,2}} (k = 2)
Y3 = {1}
2. I2 = {{1}} = I '2
3. Avem {1,2} ⊆/ Y3 şi {1,2} ∩ Y3 ∈ I '2 . Deci S∗3 = {{1, 2},{1,3}} .
4. S3 = S∗3
Y4 = {2}
2. I3 = {{2}, 0/ }
I '3 = {{2}}
3. S∗4 = {{1, 2},{2, 4},{1,3}}
4. S 4 = S∗4
Y5 = 0/
2. I4 = { 0/ , 0/ , 0/ , 0/ }, I '4 = {0}
/
3. S∗5 = {{1, 2},{5},{2, 4},{5},{1,3},{5}}
4. S5 = {{1, 2},{2, 4},{1,3},{5}}
Y6 = {1,2,3,4,5}
2. I5 = {{1,2},{2,4},{1,3},{5}} = I '5
3. S∗6 = {{1, 2, 6},{2, 4, 6},{1,3, 6},{5, 6}}
S6 = {{1, 2, 6},{2, 4, 6},{1,3, 6},{5, 6}}
Y7 = {1,2,3,4,5}
2. I6 = {{1,2},{2,4},{1,3},{5}} = I '6
3. S∗7 = {{1, 2, 6},{1, 2, 7},{2, 4, 6},{2, 4, 7},{1,3, 6},
{1,3, 7},{5, 6},{5, 7}} = S7 .
Deoarece k = n – 1 = 6, algoritmul se opreşte. Familia mulţimilor
stabile maximale în graful G considerat este S7, iar numărul de stabilitate
este α(G) = 3 .
Observaţii.
a) Determinare familiei Cn a clicilor maximale în graful G revine,
pe baza observaţiei b) anterioare, la determinarea familiei mulţimilor stabile
maximale în graful G (complementarul grafului G).
b) Determinarea familiei Sn şi a familiei Cn este necesară printre
altele în problema găsirii unei acoperiri minimale a mulţimii vârfurilor unui
graf cu mulţimi stabile (problema colorării vârfurilor) şi respectiv cu clici.
102
Capitolul 8
PROBLEME DE DESCOMPUNERI ÎN GRAFURI
103
Propoziţia 2.
Grafurile α - critice nu au cutset care să fie clică.
Propoziţia 3.
Dacă G este un graf α - decompozabil atunci există o
α - descompunere în componente α - indecompozabile care sunt tare
stabile.
Teorema 1.
Pentru un graf G cu α (G) ≥ 2, următoarele afirmaţii sunt echivalente:
(i) G este minimal imperfect (adică este minimal critic);
(ii) G este minimal tare stabil;
(iii) G este minimal cu: χ(G − x) < χ(G), ∀x ∈ V(G) .
{(i) echivalent (ii): E. Olaru, (i) echivalent (iii): W. Wessel }.
Propoziţia 4.
Un graf G este perfect dacă şi numai dacă singurele subgrafuri
α - indecompozabile ale lui G sunt clici.
Propoziţia 5.
Un graf G este perfect α - decompozabil dacă şi numai dacă el
conţine un graf parţial α - critic şi perfect α - decompozabil.
Propoziţia 6.
Un graf G α - critic este perfect α - decompozabil dacă şi numai
dacă componentele sale conexe sunt clici.
Remarcăm că orice graf admite cel puţin un graf parţial α - critic. Un
criteriu de decompozabilitate este următorul.
Propoziţia 7.
Un graf G este α - decompozabil dacă şi numai dacă el are cel puţin
un graf parţial α - critic decompozabil.
104
Observăm că un graf decompozabil poate fi prezentat în diverse
forme ca S-descompunere de subgrafuri indecompozabile. Este, deci necesar
a considera altfel S-descompunerea, proprie (Olaru, Antohe).
Definiţie. O S-descompunere în subgrafuri indecompozabile este
proprie dacă ea conţine un număr minim de S-componente indecompozabile
banale.
Teorema 2. (Olaru, Antohe).
Orice graf finit decompozabil admite o S-descompunere proprie,
unică până la un izomorfism.
Definiţie. Un subgraf [A] a unui graf G este numit
coindecompozabil dacă A este omogenă şi este maximală cu această
proprietate.
Teorema 3. (Olaru, Antohe).
Orice graf (finit sau nu) admite o unică S-descompunere în care
S-componentele sunt coindecompozabile.
105
Un graf neorientat G = (V,E), care prin orientarea fiecărei muchii
obţinem graful orientat (V,F) care satisface următoarea condiţie: dacă ab ∈ F
şi bc ∈ F implică ac ∈ F ( ∀a, b, c ∈ V ), se numeşte graf de comparabilitate
(Golumbic, Shamir).
Teorema 4.
Fie G = G 0 [G1 ,..., G n ] , unde Gi sunt grafuri neorientate disjuncte.
Atunci G este graf de comparabilitate dacă şi numai dacă Gi i = 0, l, ..., n este
graf de comparabilitate.
Definiţie. Un graf se numeşte decompozabil dacă el poate fi
exprimat ca o compoziţie nebanală de subgrafuri induse; altfel, el se numeşte
indecompozabil.
Pentru orice graf există compoziţia banală: G = K1[G].
Formal, G = (V,E) este decompozabil dacă există o partiţie
V = V1 + ... + Vn a vârfurilor în submulţimi nevide disjuncte două câte două
cu 1 < r < |V| astfel încât G = G R [G V ,..., G V ] pentru orice mulţime de
1 v
reprezentanţi R = {x1 ,..., x n } , xi din Vi. O astfel de partiţie induce o
descompunere proprie a lui G.
G are o mulţime partitivă nebanală dacă şi numai dacă G este
decompozabil.
8.1.4. G-descompunerea
Un graf orientat este o pereche G = (V,E), unde V este o mulţime
nevidă, iar E este o mulţime de perechi ordonate de elemente distincte ale lui
V. Elementele lui E se numesc arce.
Pentru un graf orientat G = (V,E), notăm cu G −1 = (V, E −1 ) , graful
invers, unde E −1 = {(x, y) | (y, x) ∈ E} .
Fie G = (V,E) un graf. Un graf orientat H =(V,F) (având aceeaşi
mulţime de vârfuri ca şi G) cu proprietatea că F ∩ F−1 = 0/ şi F ∪ F−1 = E se
numeşte orientarea grafului G. (H se obţine asociind o orientare fiecărei
muchii a lui G).
În acest paragraf prezentăm un algoritm pentru G - descompunere.
Fie R o relaţie binară pe mulţimea muchiilor unui graf neorientat
G = (V,E), definită astfel:
abRxy dacă şi numai dacă ori a = x şi by∉E ori b = y şi ax∉E.
Închiderea reflexivă şi tranzitivă R' a lui R defineşte o relaţie de
echivalenţă pe E, ale cărei clase de echivalenţă se numesc clase de implicaţii.
Dacă A este o clasă de implicaţie atunci  = A ∪ A −1 este închiderea
simetrică a lui A.
106
Pentru G = (V ,E), un graf, o partiţie a lui E în k mulţimi:
E=B ˆ + ... + B
ˆ se numeşte G-descompunere dacă B sunt clase de
1 k i
ˆ +B
implicaţii din graful G = (V, B ˆ ˆ ), ∀i = 1, k .
+ ... + B
i i +1 k
O secvenţă de muchii [x1 y1 ,..., x n y n ] se numeşte schemă de
descompunere pentru G dacă există o G-descompunere E = B ˆ + ... + B
ˆ
1 k
astfel încât x i yi ∈ Bi , i = 1, k .
Are loc următorul algoritm de descompunere:
begin
fie E1 := E
i := 1
loop:
Alege arbitrar o muchie ei = x i yi ∈ Ei .
Determină clasele de implicaţii Bi din Ei conţinând xiyi
ˆ
Fie Ei + 1 := Ei − B i
If Ei + 1 = 0/ then
Fie k := i
stop
else
Incrementează i cu 1
go to loop
end if
end
107
Condiţia a doua poate fi reformulată astfel. Fiecare bloc Ei verifică:
pentru fiecare i ori x ~ Ei ori x ~/ Ei pentru ∀x ∈ V − Ei , adică Ei este
modul.
R. McConnell şi Spinrad au descoperit un algoritm O(m+n) pentru
partiţionarea vârfurilor.
Vârful v separă (splitează) un bloc B ori de cate ori v este utilizat
pentru a divide B în blocurile B ∩ N(v) şi B − N(v) .
Descompunerea modulară (Jamison, Olariu) sau descompunerea
substituţie (descoperită independent de mai mulţi autori printre care R. H.
Mohring şi F. J. Radamacher) înseamnă partiţionarea unui graf G în
subgrafuri, fiecare din ele este un modul într-un subgraf al lui G.
În particular, graful însuşi şi mulţimile cu un singur vârf sunt
considerate module.
Algoritmul următor partiţionează un graf în O(mlogn) timp.
Algoritmul alege un vârf v care ori
i) nu a fost folosit înainte ca element de splitare ori
| B' |
ii) v ∈ B, | B | ≤ , B' care a conţinut v ultima dată când v a fost ales
2
ca element de separare ori
iii) este într-un bloc care nu va fi separat în viitor.
Vârful v este folosit să separe fiecare bloc care nu conţine v.
Separarea cauzată de v poate fi executată în O(|N(v) |).
Pentru a separa pe B, prima dată verificăm dacă v nu este în B; apoi
loca1izăm vecinii lui v într-un nou bloc B' , care este păstrat "legat" de B
până la sfârşitul separării cauzată de v.
Dacă nici un vârf nu satisface criteriul i) sau ii) pentru alegerea
elementului de separare, orice vârf din cel mai mare bloc activ C rămas, va
satisface criteriul iii). Orice alt vârf v a încercat să separe C, deoarece v a
fost separat de C la un moment dat şi este acum într-un bloc care este cel
mult de lungimea lui C.
Algoritmul propriu-zis:
Fie partiţia iniţială ( B1,..., B j );
Pentru fiecare bloc Bi execută
lastused[Bi] = cea mai mare valoare;
{ folositultimadata[Bi] = cea mai mare valoare}
READY = (B1,..., B j);
Cât timp (G este nevid) execută
Cât timp (READY ≠ 0/ ) execută
C = orice bloc din READY;
lastused[C] = |C|;
pentru fiecare vârf x din C execută
separă toate blocurile exceptând C în funcţie de
adiacenţa la x;
108
pentru fiecare bloc B separat în B, B2 prin x execută
lastused[B2] = lastused[B];
dacă |B| ≤ lastused[B]/2 atunci
adaugă B la READY;
dacă |B2| ≤ lastused[B2]/2 atunci
adaugă B2 la READY;
C = cel mai mare bloc rămas;
returnează C ca parte a partiţiei finale;
foloseşte fiecare vârf din C pentru a separa celelalte
blocuri;
G = G – C.
109
Arborele de descompunere este produs prin combinarea modulelor
găsite în timpul celor două faze.
Fie un modul M. Daca există un descendent direct c al lui M care nu
formează muchii cu nici un alt descendent direct al lui M din G, ştergem c
din M şi adăugăm un nod etichetat P cu c drept un descendent direct şi M
drept celălalt descendent direct. Similar, dacă există un descendent direct c '
al lui M, extremitate a tuturor muchiilor cu toţi ceilalţi descendenţi direcţi ai
lui M, ştergem c' din M şi adăugăm un nod S cu c' drept un descendent direct
şi M drept celălalt descendent direct. Aceasta ia O(n+m) timp. Unim arborii
găsiţi în fiecare din cele două stagii astfel. Traversăm arborele în ordinea
primul în adâncime. Dacă întâlnim un nod serie (care corespunde unui modul
M astfel încât [M]G este neconex) sau paralel (care corespunde unui modul
astfel încât [M]G este neconex) x, care are un ascendent direct p de acelaşi
tip, luăm toţi descendenţii direcţi ai lui x descendenţi direcţi ai lui p.
În final, combinăm modulele găsite în timpul fiecăreia din cele două
faze într-un singur arbore de descompunere, T. Fie T1 şi T2 arborii găsiţi în
timpul celor două faze. Fie toate nodurile lui T1 şi T2 în ordine
nedescrescătoare a numărului de descendenţi direcţi vârfuri pendante. Când
un modul M este întâlnit, adăugăm M dacă M nu este deja parte a arborelui
combinat. Pentru a cunoaşte dacă un modul M cu k descendenţi vârfuri
pendante este deja în arbore este suficient a verifica dacă cel mai mic număr
de vârfuri din M este deja într-un modul de lungime k. Dacă memorăm cel
mai mic număr de vârfuri din fiecare modul şi modificăm variabila i pentru
un vârf v când v este vârful cu cel mai mic număr dintr-un modul de lungime
i, acest pas ia timp constant pe modul. Adăugarea unui modul M la T se face
astfel. Orice descendent direct c al lui M se află în T şi pentru orice rădăcină
r din arborele curent care conţine un descendent direct al lui M, r devine un
descendent direct al lui M în arbore. Orice metodă naturală de a marca un
arbore, pentru ca un drum să nu fie traversat de două ori, ne permite să
combinăm doi arbori în O(n) timp.
La sfârşitul acestui paragraf, prezentăm următorul rezultat (Olariu).
Pentru un graf G următoarele două afirmaţii sunt echivalente:
(i) G nu are nici un subgraf indus izomorf cu grafurile cu secvenţa de
grade (2,2,2,3,3), (1,1,2,3,3), (2,2,3,3,4,4);
(ii) pentru fiecare subgraf indus H a lui G, cel puţin una din
următoarele afirmaţii sunt adevărate:
(a) H conţine o mulţime omogenă;
(b) ω(H) ≤ 2 .
110
8.2. Descompunerea slabă a grafurilor
8.2.1. Introducere
Fie G = (V,E) un graf neorientat, fără bucle şi muchii multiple. În
diverse probleme de teoria grafurilor, cu precădere în construcţia unor
algoritmi de recunoaştere apare frecvent un anumit tip de partiţie a mulţimii
vârfurilor în trei clase A, B, C astfel încât A să inducă graf conex, iar C să fie
total adiacent cu B şi total neadiacent cu A. Aşa se întâmplă, de exemplu, cu
construcţia cografurilor pornind de la K1,2 şi substituind vârfurile cu
cografuri. Introducerea noţiunii de descompunere slabă (C. Croitoru) şi
studiul proprietăţilor ei ne permite să obţinem alte rezultate de acest tip, cum
ar fi: caracterizarea cografurilor cu coarbori ( rezultat cunoscut, obţinut de
Lerchs, dar pentru care obţinem demonstraţie mai uşoară). De asemenea,
caracterizăm grafurile K1,3 –free şi dăm un algoritm de recunoaştere şi un
altul pentru determinarea unui cuplaj de cardinal maxim în această clasă de
grafuri. De asemenea, alte proprietăţi se obţin pentru grafuri triangulate,
garfuri {P4, C4}- free, grafuri paw – free, grafuri confidenţial conexe.
O parte din acest capitol a fost prezentat la ROSYCS 2000 Iaşi.
111
Demonstraţie. Deoarece graful G este incomplet, α (G ) ≥ 2 , există
vârfurile x ≠ y , neadiacente. Fie
A0 = {x}; B0 = N ( x); C0 = N ( x) .
Avem y ∈ C0 . Dacă N ( x) ~ N ( x) atunci A = A0 . Dacă nu, fie
x1 ∈ N ( x), y1 ∈ N ( x) astfel încât x 1ℵ y 1 .
Pentru A1 = A0 ∪ {x1},[ A1 ] este conex, deoarece [ A0 ] este conex şi
x1 ∈ N ( x) .
N ( A1 ) = ( N ( A0 ) − {x1}) ∪ ( N ( x1 ) ∩ C0 ). y1 ∈ N ( A1 )
(deoarece x 1ℵ y 1 şi y1 ∈ N ( x) şi y 1ℵ A0 ).
Deci
N ( A1 ) ≠ ∅ .
Dacă
N ( A1 ) ~ N ( A1 )
atunci [V − A1 ] = [ N ( A1 )]G + [ N ( A1 )]G . Presupunem că s-a construit Ai ,
Bi=N(Ai) , Ci = N ( Ai ) .
Dacă Bi ~ Ci atunci A = Ai. Dacă nu, fie xi +1 ∈ Bi şi yi +1 ∈ Ci cu
x i + 1ℵ y i + 1 .
Notăm
Ai +1 = Ai ∪ {xi +1}; Bi +1 = N ( Ai +1 ); Ci +1 = N ( Ai +1 ) .
[Ai+1] este conex, deoarece [Ai] este conex şi xi +1 ∈ N ( Ai ) .
yi +1 ∈ N ( Ai +1 ) (deoarece x i + 1ℵ y i + 1 şi yi +1 ∈ N ( Ai ) şi y i + 1ℵ A i ).
Deci
N ( Ai +1 ) ≠ ∅ .
Dacă Bi +1 ~ Ci +1 atunci A=Ai+1 şi
[V − Ai +1 ]G = [ N ( Ai +1 )]G + [ N ( Ai +1 )]G .
Deoarece
A0 ⊂ A1 ⊂ ... ⊂ Ai ⊂ ... ⊂ V
şi V < ∞ rezultă ∃p ∈ N astfel încât N ( Ap ) ~ N ( Ap ) şi deci A = Ap este
componenta slabă cu proprietatea din enunţ.
Propoziţia 3.
Fie G = (V,E) un graf conex şi incomplet şi A ⊂ V . Atunci A este
componentă slabă a lui G dacă şi numai dacă G(A) este conex şi
N ( A) ~ N ( A) .
112
Demonstraţie. Presupunem că există n ∈ N ( A) şi n ∈ N ( A) astfel
încât nn ∉ E (G ) .
Fie A′ = A ∪ {n} şi N ′ = ( N ( A) − {n}) ∪ ( N (n) ∩ N ( A)) .
[ A′]G este conex, N ( A′) = N ′ şi V (G ) − ( A′ ∪ N ( A′)) ⊇ {n } . Deci
N ( A′) ≠ V (G ) − A′ , contrazicând maximalitatea lui A.
Invers.
Fie G(A) conex şi N ( A) ~ N ( A) . Arătăm că G(A) este componentă
slabă. Fie A′ ⊃ A , componentă slabă.
∅ ≠ A′ − A ⊆ N ( A) (deoarece Aℵ N ( A ) şi G ( A′) conex).
Fie n ∈ A′ − A . Atunci N ( A) ⊆ N (n) . Deci N ( A′) ≠ ∅ , contrazicând
definiţia componentei slabe.
Definiţia 4. O partiţie de forma ( A, N ( A),V − N [ A]) , unde A este
mulţime slabă o numim descompunere slabă în raport cu A a grafului G.
Numim A componentă slabă, N(A) mulţime separatoare minimală, iar V-
N[A] mulţime îndepărtată (remote set).
Propoziţia 5.
Dacă G=(V,E) este un graf conex şi incomplet atunci mulţimea
vârfurilor V admite o descompunere slabă (A,B,C) astfel încât G(A) este
componentă slabă şi G(V-A)=G(B)+G(C).
{Demonstraţia este cea dată în propoziţia 2}.
Observaţia 6. Fie G=(V,E) un graf conex şi incomplet. Dacă A este
mulţime slabă atunci A ≠ V .
{ A mulţime slabă şi G conex rezultă A ≠ V , altfel N G ( A) = ∅,V − A = ∅ ,
adică N G ( A) = V − A }.
Fie G un graf conex şi WG={A: A mulţime slabă în G}.
Observaţia 7. Fie G=(V,E) un graf conex. WG are cel puţin un
element dacă şi numai dacă G nu-i complet.
{Dacă G nu este complet atunci ∃ v ∈ V (G ) astfel încât N G ( A) ≠ V − A .
Deci {v} este mulţime slabă, adică {v} ∈ WG . Arătăm implicaţia inversă.
Presupunem că există A mulţime slabă în G. Atunci N G ( A) ≠ V − A . Deci
N G ( A) ≠ ∅ . Deci ∃a ∈ A∃b ∈ N G ( A) astfel încât ab ∉ E (G ) . Deci G nu este
complet.}
Fie WG0 = { A | A ∈ WG , A maximală în raport cu incluziunea }.
Mai numim componentele slabe ale unui graf G şi frunze. Mulţimea
frunzelor o notăm cu LG. Deci LG = WG0 .
113
Remarca 8. Fie G=(V,E) un graf conex şi incomplet. Dacă A ∈ WG0
atunci A este cutset (mulţime separatoare) în G .
{În G − A, R = N ( A) şi N sunt mulţimi nevide de vârfuri total
neadiacente}
Remarca 9. Fie G=(V,E) un graf conex şi incomplet. Dacă A ∈ WG0
atunci N G ( A) este cutset (mulţime separatoare) în G.
{În G − A, R = N ( A) şi A sunt mulţimi nevide de vârfuri total
neadiacente}
Demonstraţia dată în propoziţia 3. oferă un algoritm polinomial
pentru construirea unei descompuneri slabe pentru un graf conex şi
incomplet.
114
{ 2 = α (G ) ≥ α ([ A]) + α ([ R]) ≥ 1 + 1 = 2 .}.
În continuare reamintim următorul rezultat.
Lema 12.
Dacă G este un graf conex, C este cutset, K1,K2,…,Kp (p≥ 2) sunt
componentele conexe ale lui G-C atunci:
(i) N ( K i ) ⊆ C , ∀i = 1,..., p;
(ii) C este cutset minimal dacă şi numai dacă N ( K i ) = C , ∀i = 1,..., p
În încheierea acestei secţiuni caracterizăm grafurile G cu proprietatea
A ~ N , unde (A,N,R) este descompunere slabă.
Fie G=(V,E) un graf, X ⊂ V şi k ∈ N* .
Numim vecinătatea de ordin k a lui X, mulţimea
N G ( X ) = { a | a ∉ X , ∃ un drum indus în G, P, de lungime k de la x ∈ X la
k
115
demonstrat un astfel de lucru. În ultimă instanţă, pentru a da crezare
demonstraţiei, trebuie să se încreadă în corectitudinea compilării şi a
execuţiei hardware.
Istoric
Se ridică problema folosirii a cinci sau mai multe culori.
În anul 1890, Heawood a demonstrat că toate grafurile planare sunt
cinci-colorabile.
Între 1960 şi 1970 matematicianul german Heinrich Heesch a
dezvoltat metode de utilizare a calculatorului în scopul găsirii acelei
demonstraţii atât râvnite.
Nu mai devreme de anul 1976 s-a demonstrat ipoteza celor patru
culori a lui, de către Kenneth Appel şi Wolfgang Haken la Universitatea din
Illinois. Au fost asistaţi, în ceea ce priveşte algoritmica, de John Koch.
Dacă s-ar fi întâmplat ca ipoteza privind cele patru culori să fie falsă,
ar fi existat cel puţin o hartă cu cele mai puţine regiuni posibile care să
necesite cinci culori pentru colorare. Demonstraţia a arătat că un astfel de
contraexemplu minimal nu poate exista.
Odată cu demonstraţia teoremei, au fost elaboraţi algoritmi eficienţi
de 4-colorare a hărţilor, necesitând doar O (n 2 ) timp de rulare, unde n
reprezintă numărul de noduri. În anul 1996, Neil Robertson, Daniel P.
Sanders, Paul Seymour şi Robin Thomas au creat un algoritm cu timpul
pătratic.
În anul 2004 Benjamin Werner şi Georges Gonthier au formalizat o
demonstraţie a teoremei.
Determinarea suficienţei folosirii a trei culori în colorarea optimă a
unei hărţi, este de complexitate NP, ceea ce indică faptul că nu vom avea o
soluţie „rapidă”. Determinarea posibilităţii de 4-colorare a unui graf general
(posibil ne-planar) are de asemenea complexitatea NP.
Expunere formală în teoria grafurilor
Pentru a expune formal teorema, este mai simplu să o reformulăm în
teoria grafurilor. Teorema afirmă că vârfurile fiecărui graf planar pot fi
colorate cu ajutorul a cel mult patru culori, astfel ca două vârfuri adiacente
oarecare să nu aibă aceeaşi culoare. Sau, pe scurt: „orice graf planar este
patru-colorabil”. În cazul acesta, fiecare regiune a hărţii este înlocuită cu un
vârf, iar două vârfuri sunt conectate prin intermediul unei muchii dacă şi
numai dacă regiunile corespunzătoare „împart” un segment de graniţă.
Generalizări
S-ar putea pune problema colorării suprafeţelor ce nu sunt neapărat
plane. Problema corespunzătoare sferei este echivalentă cu cea din plan.
Pentru suprafeţele închise (orientate sau neorientate), de clasă pozitivă,
116
numărul maxim p de culori necesare depinde de caracteristica Euler χ a
suprafeţelor conform formulei:
⎡ 7 + 49 − 24 χ ⎤
p=⎢ ⎥,
⎣⎢ 2 ⎦⎥
unde parantezele indică partea întreagă a funcţiei. Singura excepţie în ceea ce
priveşte această formulă o reprezintă „sticla lui Klein”, a cărei caracteristică
a lui Euler are valoarea 0 şi necesită 6 culori. Aceasta a fost denumită iniţial
ipoteza Heawood, fiind demonstrată ca şi Teorema colorării hărţii de către
Gerhard Ringel şi J. T. W. Youngs în anul 1968.
Alternativ, pentru o suprafaţă orientată, formula poate depinde de
parametrul g (parametru ce caracterizează clasa (genul) suprafeţei):
⎡ 7 + 1 + 48 g ⎤
p=⎢ ⎥.
⎣⎢ 2 ⎦⎥
O 3-colorare este suficientă acestui graf, însă, dacă s-ar folosi mai puţine
culori ar rezulta noduri (vârfuri) adiacente de aceeaşi culoare.
Găsirea numărului minim de culori necesare colorării unui graf
oarecare are dificultate NP
117
8.3.3. Numărul cromatic
Cel mai mic număr de culori necesare colorării unui graf se numeşte
numărul cromatic χ corespunzător. De exemplu, numărul cromatic al unui
graf complet K n cu n vârfuri (un graf cu o muchie între oricare două
vârfuri), este χ (K n ) = n .
Un graf căruia îi poate fi atribuită o k-colorare (corespunzătoare) este
k-colorabil, şi este k-cromatic dacă numărul său cromatic este chiar k.
Problema găsirii unei colorări minime a unui graf are o dificultate-
NP. Problema deciziei corespunzătoare (există o colorare care foloseşte cel
mult k culori?) are o complexitate de ordinul NP, reprezentând, la origine,
una din cele 21 de probleme ale lui Karp NP-complete. Rămâne
NP-completă chiar şi în cazul grafurilor planare de grad cel mult 4, aşa cum
a fost demonstrat de către Garey şi Johnson în 1974, chiar dacă în cazul
grafurilor planare este trivială (n. demonstraţia) pentru k>3 (acest lucru
datorându-se teoremei celor patru culori). Există, totuşi, unii algoritmi de
aproximare eficienţi, care folosesc programarea semidefinită.
Proprietăţi ale χ (G ) :
1. χ(G) = 1 dacă şi numai dacă G este total neconex.
2. χ(G) ≥ 3 dacă şi numai dacă G are un ciclu impar (echivalent, dacă
G nu este bipartit).
3. χ(G) ≥ ω(G).
4. χ(G) ≤ Δ(G)+1.
5. χ(G) ≤ Δ(G) pentru G conex, doar dacă nu cumva G este un graf
complet sau un ciclu impar (Teorema lui Brook).
6. χ(G) ≤ 4, pentru orice graf planar G. Acest faimos rezultat este numit
Teorema celor patru culori.
Aici, Δ(G ) reprezintă gradul maxim, iar ω (G ) , numărul clică.
118
Algoritmi predefiniţi
Algoritmii de colorare pot fi divizaţi în două categorii:
9 Algoritmi optimali de colorare (de exemplu, Algoritmul lui Zykov,
metoda ramificării şi mărginirii, etc. )
9 Algoritmi care nu asigură un rezultat cu cele mai puţine culori
posibile. În această categorie se pot încadra algoritmii secvenţiali,
algoritmii euristici, algoritmi globali aleatori, algoritmi
metaeuristici, respectiv algoritmii genetici.
119
Exemple
120
BIBLIOGRAFIE
[1] Aho, Hopcroft and Ullman, The Design and Analysis of Computer’s
Algorithms, Addison Wesley, 1975.
[2] L. Babel, S. Olariu, On the Structure of Graphs with Few P4s, Work
supported by NSF grants CCR-9407180 and CCR-95-22093, as well as by
ONR grandt N00014-1-95-0779, Discrete Applied Mathematics, 1998.
[7] V. Chvatal, Star – cutest and Perfect Graphs, Journal of Comb. Th.,
B, 39, 189-199 (1985).
121
[13] D. G. Corneil, Y. Perl, L. K. Stewart, A linear recognition
algorithmic for cographs, SIAM.J. Comput., 14(l985), 926-934.
122
[26] H. Lerchs, On clique-kernel structure of graphs, Department of
Computer Science, University of Toronto, Toronto, Ontario, October 1972.
123
[38] E. Olaru, Gh. Suciu, Dauerstabile und Kritisch imperfekte Graphen,
An.Univ. Timişoara, vol. XVII, fasc.l, 1970, 59-64.
124