Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pa:laboratoare:laborator-06 [2018/04/09 22:00]
radu.iacob [Referinte]
pa:laboratoare:laborator-06 [2023/03/15 16:52] (current)
radu.nichita
Line 1: Line 1:
-====== Laborator 06: Minimax ​====== +====== Laborator 06: Parcurgerea grafurilor. Aplicații (1/2) ======
- +
-Responsabili:​ +
-  * [[neatudarius@gmail.com|Darius Neațu]] +
-  * [[visanr95@gmail.com|Radu Vișan]] +
-  * [[cristb@gmail.com|Cristian Banu]]+
  
 ===== Obiective laborator ===== ===== Obiective laborator =====
-  * Insusirea unor cunostinte de baza despre **teoria jocurilor** precum si despre **jocurile de tip joc de suma zero (suma nula, zero-sum games)** 
-  * Insusirea abilitatii de rezolvare a problemelor ce presupun cunoasterea si exploatarea conceptului de joc de suma zero (zero-sum game); 
-  * Insusirea unor cunostinte elementare despre algoritmii necesari rezolvarii unor probleme de joc de suma zero (zero-sum game). 
  
-===== Precizari initiale ===== +  * Înțelegerea conceptele ​de grafreprezentare și parcugere 
-<note warning>​ +  * Studierea unor aplicații pentru parcurgeri 
-Un curs foarte bine explicat este pe canalul ​de YouTube de la MIT. Va sfatuim sa vizionati integral [[https://​www.youtube.com/​watch?​v=STjW3eH0Cik&​t=2480s&​list=PLqUJoA5RTkI7UbJXJaHX80FotR1QJWHJt&​index=1 | Search: GamesMinimax, and Alpha-Beta]] inainte sa parcurgeti materialul de pe ocw. +
-</​note>​+
  
 ===== Importanţă – aplicaţii practice ===== ===== Importanţă – aplicaţii practice =====
  
-Algoritmul **Minimax** si variantele sale imbunatatite (**Negamax**,​ **Alpha-Beta** etc.) sunt folosite in diverse ​domenii precum teoria jocurilor (Game Theory), teoria jocurilor combinatorice (Combinatorial Game Theory – CGT), teoria deciziei (Decision Theory) si statistica. ​+Grafurile ​sunt utile pentru a modela ​diverse ​probleme și au numeroase aplicații practice:
  
-Astfel, diferite variante ale algoritmului sunt necesare in proiectarea si implementarea ​de aplicatii legate ​de inteligenta artificiala,​ economie, dar si in domenii precum stiinte politice sau biologie.+  * Rețele ​de calculatoare (ex: stabilirea unei topologii fără bucle / arbore ​de acoperire) 
 +  * Pagini Web (exalgoritmi de căutare -  Google PageRank ) 
 +  * Rețele sociale (ex. sugestii de prietenie pe Facebook) 
 +  * Hărți cu drumuri (ex. drum minim între două localități) 
 +  * Modelare grafică (ex. arbori de parționare) 
 +  * Rețele de transport (ex. flux)
  
-===== Descrierea problemei și a rezolvărilor===== 
  
-Algoritmii Minimax permit abordarea unor probleme ce tin de teoria jocurilor combinatorice. CGT este o ramura a matematicii ce se ocupa cu studierea jocurilor in doi (two-player games), in care participantii isi modifica rand pe rand pozitiile in diferite moduri, prestabilite de regulile jocului, ​pentru ​a indeplini una sau mai multe conditii ​de castig+===== Grafuri ===== 
 +Puteți consulta capitolul "​Elementary Graph Algorithms"​ din "​Introduction to Algorithms"​ [0] pentru mai multe definiții formale. Această secțiune sumarizează principalele notații folosite în laboratoarele ​de PA.
  
-Exemple de astfel de jocuri sunt: sah, go, dame (checkers), X si O (tic-tac-toe) etc. + ==== Definiții ====
  
-CGT nu studiaza jocuri ce presupun implicarea unui element aleator ​(sansain derularea jocului precum pokerblackjackzaruri etc. Astfel decizia abordarii unor probleme rezolvabile prin metode de tip Minimax se datoreaza in principal simplitatii atat conceptualecat si raportat la implementarea propriu-zisa.+>> Un **graf** G se definește ca fiind o pereche ​(V, E), unde **V = {node / un nod oarecare din graf}**iar **E = {(xy) / (x, y) muchie in graf}**.
  
-==== Minimax ==== +>> Un graf este **neorientat** dacă relațiile dintre noduri sunt **bidirecționale**oricare ar fi $(x, y)$ în $E$, există și $(y, x)$ în $E$. Relațiile se numesc **muchii**.
-Strategia pe care se bazeaza ideea algoritmului ​este ca jucatorii implicati adopta urmatoarele strategii:+
  
-  ​*Jucatorul 1 (**maxi**) va incerca mereu sa-si **maximizeze** propriul castig prin mutarea pe care o are de facut; +>> Un graf este **orientat** dacă relațiile dintre noduri sunt **unidirecționale**: $(x, y)$ este în $E$ nu implică neapărat $(y, x)$ în $E$. Relațiile se numesc ​**arce**.
-  *Jucatorul 2 (**mini**va incerca mereu sa **minimizeze** castigul jucatorului 1 la fiecare mutare.+
  
-De ce merge astfel ​de abordare? Dupa cum se preciza ​la inceputdiscutia ​se axeaza pe jocuri de suma zero (zero-sum game). Acest lucru garanteaza, printre altele, ca orice castig al Jucatorului 1 este egal cu modulul sumei pierdute de Jucatorul 2. Cu alte cuvinte cat pierde Jucator 2, atat castiga Jucator 1. Invers, cat pierde Jucator 1, atat castiga Jucator 2. Sau+>> O **componentă conexă (CC)** este submulțime maximală ​de noduri, cu proprietatea că oricare ar fi două noduri x și y din aceasta, există drum de la x la y. Pentru grafuri orientateo componentă conexă ​se numește ** componentă tare conexă ​(CTC)**.
  
-$$Win_{Player_1} = | Loss_{Player_2} |$$ +>> Un graf **aciclic** este un graf (orientat/​neorientat) care nu conține cicluri. 
-$$| Loss_{Player_1} | Win_{Player_2}$$+==== Reprezentare ==== 
 +Problemele care se modelează folosind grafuri, de obicei, presupun explorarea spațiului. O parcurgere explorează fiecare nod al grafului, exact o singură dată, pornind de la un nod ales, numit în continuare nod sursă (EN: **source**). Modul de reprezentare ar grafului, poate influența performanța unei parcurgeri/​unui algoritm.
  
-=== Reprezentarea spatiului solutiilor ===+Un graf poate fi modelat în mai multe moduri (folosind mai multe notații):
  
-In general spatiul solutiilor pentru un joc in doi de tip zero-sum se reprezinta ca un **arbore**, fiecarui nod fiindu-i asociata ​stare a jocului in desfasurare ​(game state). Pentru exemplul nostru de X si O putem considera urmatorul arbore ​partial ​de solutiice corespunde primelor mutari ale lui X, respectiv O+    ​printr-o pereche de mulțimi $G = (V, E)
 +      * $V$ = {v / v este un nod în graf} = mulțimea nodurile grafului ​(EN: nodes / vertices) 
 +      * $E$ = {e / $e=(xy)$ este o muchie în graf între nodurile x și y} = mulțimea muchiile/​arcelor (ENedges), fiecare muchie stabilind o relație de vecinătate între doua noduri. ​
  
-{{ :​pa:​laboratoare:​5.1_cr.png |}}+    * printr-o pereche $G = (nodes, a)$ 
 +      * $nodes$ = {node / node este un nod în graf 
 +      * $a[x][y] = 0/1$ 
 +        * **1** = există muchia/​arcul (x, y) 
 +        * **0** = **NU** există muchia/​arcul (x, y) 
  
-**Metodele ​de reprezentare a arborelui** variaza in functie ​de paradigma ​de programare aleasade limbajprecum si de gradul de optimizare avut in vedere.+    ​printr-o pereche ​de mulțimi $G = (nodes, adj)$ 
 +      ​$nodes$ = {node / node este un nod în graf}  
 +      ​$adj$   = {$adj[node]$ / unde $adj[node]$ este lista de adiacență a lui node} = reprezentarea grafului ca liste de adiacențe 
 +        * $adj[node] = {...neigh, ...}$ => există muchie/arc (node, neigh)
  
-Avand notiunile de baza asupra strategiei celor doi jucatori, precum si a reprezentarii spatiului solutiilor problemei, putem formula o prima varianta a algoritmului Minimax: 
  
-<spoiler Pseudocod Minimax> 
-<code cpp> 
-int evaluate(stare); ​  // functia returneaza un scor asociat cu starea 
-                       // functia returneaza mereu scorul din perspectiva lui maxi 
-    ​ 
-    ​ 
-void apply_move(move);​ // functia modifica starea curenta: executa miscarea move 
-void undo_move(move); ​ // functia restaureaza starea anterioara (de dinainte de executa lui move) 
-    ​ 
-// alege cea mai buna mutare pentru jucatorul maxi 
-// functia returneaza best_score pentru maxi 
-int maxi(int depth) { 
-    // daca jocul s-a terminat sau am atins nivelul maxim de recursivitate ales 
-    if (gameOver() || depth == 0 ) { 
-        return evaluate(); ​           // ne oprim si evaluam starea curenta 
-    } 
-            ​ 
-    int max = -oo;                    // jucatorul maxi doreste sa-si maximizeze ​ 
-            
-    // incercam pe rand fiecare care miscare posibila move    
-    for (move : all_moves) { 
-        apply_move(move); ​        // executa move 
  
-        // incercam sa simulam jocul mai departe:  +Reprezentarea în memorie a grafurilor se face, de obicei, cu **liste ​de adiacență**. Se pot folosi însă și alte structuri ​de date, care vor fi introduse pe parcurs.
-        // daca maxi face movece ar face mini? +
-        int score = mini(depth - 1); +
-         +
-        // dintre toate variantele pe care mini le permite (!!!), +
-        // maxi o va alege pe cea cu scor maxim +
-        if (score > max) { +
-            max = score; +
-        } +
-                     +
-        undo_move(move); ​        // restaureaza starea ​de dinainte ​de move +
-    }+
  
-    ​// cel mai bun scor pe care il poate obtine maxi este max +Cele mai uzuale notații din laboratoarele de grafuri sunt descrise în [[https://ocw.cs.pub.ro/courses/pa/​skel_graph | Precizări laboratoare 07-12]] (ex. $n$, $m$, $adj$, $adj\_trans$,​ $(x, y)$etc).
-    ​// (s-a tinut cont si de ce i-ar permite miniavand in vedere ca si el joaca optim) +
-    return max; +
-}+
  
-// alege cea mai buna mutare pentru jucatorul mini + 
-// functia returneaza best_score pentru mini +==== Colorare ==== 
-int mini(int depth{ +Algoritmii de parcugere se pot folosi de o colorare a nodurilor:​ 
-    ​// daca jocul s-a terminat sau am atins nivelul maxim de recursivitate ales + 
-    ​if ​(gameOver() || depth == 0 ) { +    * **white** ​(alb) = nod care nu a fost încă vizitat (nu este în coadă) 
-        ​return -evaluate();            // ne oprim si evaluam starea curenta+ 
 +    ​* **gray** (gri)  = nod care este în curs de vizitare (a fost adăugat în coadă) 
 + 
 +    * **black** (negru) = nod care a fost complet vizitat (node scos din coadă și pentru care s-a vizitat tot subarborele) 
 + 
 + 
 +==== Algoritmi ​de parcurgere ==== 
 +Problemă: Să se parcurgă un graf dat. Fiecare nod se parcuge ​(exact) o singură dată. 
 +Algoritmi:​ 
 + 
 +   * **BFS** 
 + 
 +   * **DFS** 
 + 
 +===== BFS - Parcurgerea în lățime ===== 
 + 
 +Parcurgerea în lățime **(Breadth-first Search - BFS)** este un algoritm de căutare în graf, în care, atunci când se ajunge într-un nod oarecare **node**, nevizitat, se vizitează toate nodurile nevizitate adiacente lui (notate pe rand cu **neigh**), apoi toate vârfurile nevizitate adiacente vârfurilor adiacente lui node, etc. 
 + 
 + 
 +Atenție! BFS depinde de nodul de start **source**. Plecând din acest nod, se vor vizita toate nodurile accesibile. De exemplu, într-un graf neorientat, aceste noduri accesibile formează o componentă conexă; în urma aplicării algoritmului BFS asupra fiecărei componente conexe a grafului, se obține un arbore de acoperire a întregului graf (prin eliminarea muchiilor pe care nu le folosim la parcurgere). Pentru a putea reconstitui acest arbore, se păstrează pentru fiecare nod dat identitatea părintelui său. În cazul în care nu exista o funcție de cost asociată muchiilor, BFS va determina și drumurile minime de la rădăcină la oricare nod. 
 + 
 + 
 +Pentru implementarea BFS se folosește o coadă.  
 + 
 + 
 +==== Algoritm ==== 
 +<code cppBFS> 
 +// do a BFS traversal from source 
 +// 
 +// source ​   ​the source for the BFS traversal 
 +// nodes     list of all nodes from G 
 +// adj[node] = the adjacency list of node 
 +//             ​example:​ adj[node] = {..., neigh, ...} => edge (node, neigh) 
 +BFS(source, G=(nodes, adj)) { 
 +    // STEP 0: initialize results 
 +    // d[node] = distance from source to node 
 +    // p[node] = parent of node in the BFS traversal started from source 
 +    // [optional] color[node] = white/​gray/​black 
 +    //                              * white = not yet visited 
 +    //                              * gray  = visit in progress 
 +    //                              * black = visited 
 +    foreach (node in nodes) { 
 +        ​d[node] = +oo;                          // distance not yet computed 
 +        p(node= null                        ​// parent not yet found 
 +        // [optional] color[node] = white;
     }     }
-            ​ 
-    int min = +oo;                    // jucatorul mini doreste sa minimizeze scorul lui maxi  
-            
-    // incercam pe rand fiecare care miscare posibila move    
-    for (move : all_moves) { 
-        apply_move(move); ​        // executa move 
  
-        ​// incercam sa simulam jocul mai departe:  +    ​// STEP 1initialize a queue 
-        // daca mini face move, ce ar face maxi? +    q = {} 
-        int score maxi(depth - 1); + 
-         +    ​// STEP 2: add the source(s) into q 
-        // dintre toate variantele pe care maxi le permite ​(!!!), +    ​d[source] ​0;                              // distance from source to source 
-        // mini o va alege pe cea cu scor minim +    p[source] = null;                           // the source never has a parent ​(because it's the root of the traversal) 
-        if (score min) { +    q.push(source); 
-            ​min ​score;+    // [optional] color[source] = gray; 
 + 
 +    ​// STEP 3: start traversal using the node(s) from q 
 +    while (!q.empty()) {                        // while still have nodes to explore 
 +        // STEP 3.1: extract the next node from queue 
 +        ​node = q.pop(); 
 + 
 +        // [optional] STEP 3.2: print/use the node 
 + 
 +        // STEP 3.3: expand/​visit the node 
 +        foreach (neigh in adj[node]) {          // for each neighbour 
 +            ​if (d[node] + 1 d[neigh]) {       // a smaller distance <=> color[neigh] == white 
 +                ​d[neigh] ​d[node] + 1        // update distance 
 +                p[neigh] = node;                // save parent 
 +                q.push(neigh); ​                 // add neigh to the queue of nodes to be visited 
 +                // [optional] color[neigh] = gray; 
 +            }
         }         }
-                     +         
-        undo_move(move); ​        ​// este o functie care incearca sa refaca +       ​// [optional] color[node] = black;
-                                 ​// starea de dinainte de aplicarea lui move+
     }     }
- 
-    // cel mai bun scor pe care il poate obtine mini este min 
-    // (s-a tinut cont si de ce i-ar permite maxi, avand in vedere ca si el joaca optim) 
-    return min; 
 } }
  
 </​code>​ </​code>​
-</​spoiler>​ 
  
-=== Argumentarea utilizarii unei adancimi maxime ===+<​note>​ 
 +Liniile cu **[optional]** se referă la logica de colorare menționată anterior, care se poate omite (dacă nu se dorește acest rezultat). 
 +</​note>​
  
-Datorita ​**spatiului ​de solutii mare**, de multe ori coplesitor ca volum, o inspectare completa a acestuia nu este fezabila si devine impracticabila din punctul de vedere al timpului consumat ​sau chiar a memoriei alocate ​(se vor discuta aceste aspecte in paragraful legat de complexitate)+==== Complexitate ====  
 +  ​*cu liste de adiacență:​ $O(n + m)$ sau $O(|V| + |E|)$ 
 +  ​*cu matrice ​de adiacență:​ $O(n^2)$ ​sau  $ O(|V|^2)$
  
-Astfel, de cele mai multe ori este preferata o abordare care parcurge arborele numai pana la o anumita **adancime maxima („depth”)**. Aceasta abordare permite examinarea arborelui destul de mult pentru a putea lua decizii minimalist coerente in desfasurarea jocului. ​+===== DFS - Parcurgerea în adâncime =====
  
-Totusi, ​**dezavantajul major** este ca pe termen lung se poate dovedi ​ca decizia luata la adancimea depth nu este global favorabila jucatorului in cauza.+Parcurgerea în adâncime ​**(Depth-First Search - DFS)** pornește de la un nod dat (**node**), care este marcat ​ca fiind în curs de procesare. Se alege primul vecin nevizitat al acestui nod (**neigh**), ​se marchează și acesta ​ca fiind în curs de procesare, apoi și pentru acest vecin se caută primul vecin nevizitat, și așa mai departe. În momentul în care nodul curent ​nu mai are vecini nevizitati, se marchează că fiind deja procesat și se revine la nodul anterior. Pentru acest nod se caută primul vecin nevizitat. Algoritmul se repetă până când toate nodurile grafului au fost procesate.
  
-De asemenea, se observa recursivitatea indirecta. Prin conventie acceptam ca **inceputul ​algoritmului** sa fie cu functia maxi. Astfel, se analizeaza succesiv diferite stari ale jocului din punctul ​de vedere al celor doi jucatori pana la adancimea depthRezultatul intors este scorul final al miscarii celei mai bune.+În urma aplicării ​algoritmului ​DFS asupra fiecărei componente conexe a grafului, se obține pentru fiecare dintre acestea câte un arbore ​de acoperire (prin eliminarea muchiilor pe care nu le folosim ​la parcurgere)Pentru a putea reconstitui acest arbore, păstram pentru fiecare nod dat identitatea părintelui sau.
  
-==== Negamax ====+Pentru fiecare nod se vor reține:  
 +  * $start[node]$ ​timestamp-ul / timpul descoperirii 
 +  * $finish[node]$ ​timestamp-ul / timpul finalizării 
 +  * $p[node]$ ​părintele din parcugerea DFS a lui node 
 + 
  
-Negamax este o varianta a minimaxce se bazeaza pe urmatoarea observatie: intr-un joc cu suma zero castigul unui jucator este egal cu modulul sumei pierdute ​de celalalt jucator si invers+Spre deosebire de BFSpentru implementarea DFS se folosește o stivă (abordare **LIFO** în loc de **FIFO**). În practică, stiva nu va fi reținută explicit - ci ne vom baza pe recursivitate.
  
-Intr-adevar putem spune ca jucatorul mini incearca de fapt sa maximizeze in modul suma pierduta de maxi. Astfel putem formula urmatoarea implementare ce profita de observatia de mai sus. 
  
-Notaputem exprima aceasta observatie si pe baza formulei **max(ab) = -min(-a-b)**.+====  Algoritm ==== 
 +<code cpp | DFS> 
 +// do a DFS traversal from all nodes 
 +// 
 +// nodes     = list of all nodes from G 
 +// adj[node] = the adjacency list of node 
 +//              exampleadj[node] = {..., neigh, ...} => edge (nodeneigh) 
 +// 
 +DFS(G=(nodesadj)) { 
 +    // STEP 0: initialize results 
 +    // p[node] ​    = parent of node in the BFS traversal started from source 
 +    // start[node] = the timestamp (the order) when we started visiting the node subtree 
 +    // finish[node] = the timestamp (the order) when we finished visiting the node subtree 
 +    // [optional] color[node] = white/​gray/​black 
 +    //                              ​white = not yet visited 
 +    //                              ​gray  = visit in progress 
 +    //                              * black = visited 
 +    foreach (node in nodes) { 
 +        p[node] = null;                             // parent not yet found 
 +        // [optional] color[node] = white; 
 +    }
  
-<spoiler Pseudocod Negamax>​ +    timestamp = 0                                 // the first timestamp before the DFS traversal 
-<code cpp> + 
-int evaluate(stare)  ​// functia returneaza un scor asociat cu starea +    ​foreach ​(node in nodes{ 
-                       // functia returneaza mereu scorul din perspectiva jucatorului CURENT! +        ​if ​(p[node] == null) {                      // or [optional] color[node] ​== white 
-    ​ +            ​DFS_RECURSIVE(node, G, p, timestamp) 
-     +        }
-void apply_move(move); // functia modifica starea curenta: executa miscarea move +
-void undo_move(move); ​ // functia restaureaza starea anterioara (de dinainte de executa lui move) +
-     +
-// alege cea mai buna mutare pentru jucatorul CURENT (EU) +
-// functia returneaza best_score +
-int negamax(int depth) { +
-    ​// daca jocul s-a terminat sau am atins nivelul maxim de recursivitate ales +
-    if (gameOver() || depth == 0 ) { +
-       return evaluate();            // ne oprim si evaluam starea curenta+
     }     }
-             +}
-    int max = -oo;                    // jucatorul CURENT doreste sa-si  maximizeze scorul  +
-            +
-    // incercam pe rand fiecare care miscare posibila move    +
-    for (move : all_moves) { +
-        apply_move(move); ​        // executa move+
  
-        // incercam sa simulam jocul mai departe:  +DFS_RECURSIVE(nodeG=(node, adj), p, ref timestamp) { 
-        // daca jucatorul CURENT face face movece ar face ADVERSARUL?​ +    start[node] = ++timestamp                     // start visiting its subtree 
-        int score -negamax(depth - 1);  // cel mai bine pentru el,  +    // [optional] color[node] = gray; 
-                                          // este cel mai rau pentru mine si invers + 
-       ​ +    ​for ​(neigh in adj[node]{                      ​// for each neighbour 
-        // dintre toate variantele pe care ADVERSARUL le permite ​(!!!)+        if (p[neigh] == null) {                     // or [optional] color[neigh] = white; 
-        ​// EU (jucatorul CURENT) o voi alege pe cea cu scor maxim +            ​p[neigh] ​node                       // save parent 
-        if (score > max) { +            DFS_RECURSIVE(neigh,​ G, p, timestamp); ​ // continue traversal
-            ​max score;+
         }         }
-                    ​ 
-        undo_move(move); ​        // restaureaza starea de dinainte de move 
     }     }
  
-    // cel mai bun scor pe care il pot obtine EU este max +    ​finish[node] = ++timestamp; ​                    // finish visiting its subtree 
-    // (s-a tinut cont si de ce mi-ar permite ADVERSARUL, avand in vedere ca si el joaca optim) +    // [optional] color[node] = black;
-    return max;+
 } }
 </​code>​ </​code>​
-</​spoiler>​ 
  
 <​note>​ <​note>​
-Se observa direct avantajele acestei formulari fata de Minimax-ul standard prezentat ​anterior:+Liniile cu **[optional]** se referă la logica ​de colorare menționată ​anterior, care se poate omite (dacă nu se dorește acest rezultat). 
 +</​note>​ 
 + 
 +==== Complexitate ==== 
 +  *cu liste de adiacență:​ $O(n + m)$ sau $O(|V| + |E|)$ 
 +  *cu matrice de adiacență:​ $O(n^2)$ sau  $ O(|V|^2)$ 
 + 
 + 
 + 
 +==== Tipuri de muchii/arce în parcurgerea DFS ==== 
 +>> **Arborele de parcurgere DFS** cuprinde toate nodurile din graf împreună cu muchiile/​arcele pe vizitate de DFS. Dacă graful nu este conex / tare conex, se obțin mai mulți arbori care formează o **pădure de arbori DFS**. 
 +<​note>​ 
 +Arborele DFS nu este unic - depinde de ordinea în care nodurile sunt stocate în listele de adiacență. 
 + 
 +Exemplu: 
 +  * dacă în lista lui 1 avem nodurile 2 și 3, atunci când se va vizita 1, prima oară se încearcă vizitarea lui 2, apoi a lui 3. 
 +  * dacă în lista lui 1 avem nodurile 3 și 2, atunci când se va vizita 1, prima oară se încearcă vizitarea lui 3, apoi a lui 2.
  
-  * **claritatea** si **eleganta** sporita a codului 
-  * usurinta in **intretinerea** si **extinderea** functionalitatii 
 </​note>​ </​note>​
  
-Din punctul ​de vedere al complexitatii temporale, Negamax nu difera absolut deloc de Minimax ​(ambele examineaza acelasi numar de stari in arborele de solutii). +Putem folosi o parcurgere DFS pentru a clasifica tipurile ​de muchii ​(toate muchiile din graf) relativ la arborele ​DFS curent.  
 +>> **tree-edge** (**T**) / muchie ​de arbore = muchie **(x, y)** care conectează un nod x de copilul său y din arbore
  
-Putem concluziona ca este de **preferat** o implementare ce foloseste ​**negamax** fata de una bazata pe minimax in rezolvarea unor probleme ce tin de aceasta tehnica.+>> **back-edge** (**B**) / muchie înapoi = muchie ​**(x, y)** care conectează un nod x de un strămoș y (ambele noduri sunt în curs de vizitare)
  
-==== Alpha-beta pruning ====+Pentru **graf orientat**, mai există încă 2 tipuri de muchii (arce): 
 +>> **forward-edge** (**F**) / muchie înainte /muchie de înaintare ​muchie **(x, y**) care nu este **tree-edge** ​ și care conecteză un nod **x** de un descendent **y** . 
  
-Pana acum s-a discutat despre algoritmii Minimax / NegamaxAcestia sunt algoritmi exhaustivi ​(**exhausting search algorithms**). Cu alte cuvinte, ei gasesc solutia optima examinand intreg spatiul de solutii al problemei. Acest mod de abordare ​este extrem de ineficient in ceea ce priveste efortul de calcul necesar, mai ales considerand ca extrem de multe stari de joc inutile sunt explorate ​(este vorba de acele stari care nu pot fi atinse datorita incalcarii principiului de maximizare ​castigului la fiecare runda).+<​note>​ 
 +În graful neorientat aceasta nu are sensÎntr-un graf neorientat, **(x, y)** și **(y, x)** reprezintă același lucruDacă x ar fi strămoș a lui y (x nu este părintele lui y, căci altfel **(x, y)** ar fi **tree-edge**), mai întâi se va încerca din y să se ajungă în x (moment în care spunem că muchia **(y, x)** este **back-edge**),​ ulterior la revenirea din recursivitate se va încerca din x să se ajungă în y (moment în care ar trebui să spunem că muchia **(x, y)** este **forward-edge**). Deoarece există o singură muchie **(x, y)** sau **(y, x)**, nu putem pune 2 categorii, ​așa că rămâne prima categorie găsită: **back-edge**.
  
-O imbunatatire substantiala a minimax/​negamax este **Alpha-beta pruning** (**taiere alfa-beta**). Acest algoritm incearca sa optimizeze mini/​negamax profitand de o observatie importanta: ​**pe parcursul examinarii arborelui de solutii se pot elimina intregi subarbori, corespunzatori unei miscari mdaca pe parcursul analizei gasim ca miscarea m este mai slaba calitativ decat cea mai buna miscare curenta.**+Într-un graf **orientat** muchiile ​**(x, y)** și **(yx)** sunt diferite, deci pot avea tipuri diferite! 
 +</​note>​
  
-Astfelconsideram ca pornim cu o prima miscare M1Dupa ce analizam ​aceasta ​miscare in totalitate si ii atribuim ​un scorcontinuam sa analizam miscarea M2. Daca in analiza ulterioara gasim ca adversarul are cel putin o miscare care transforma M2 intr-o miscare mai slaba decat M1 atunci ​orice alte variante ce corespund miscarii M2 (subarbori) nu mai trebuie analizate.+>> **cross-edge** (**C**) / muchie de traversare = muchie **(xy)** care conectează un nod x de un nod y din alt subarbore 
 +<​note>​ 
 +În graful neorientat ​aceasta ​nu are sens. Într-un graf neorientatdacă **(x, y)** ar fi **cross-edge**, înseamnă că aceasta conectează pe x de un nod y din alt subarbore (care a fost deja vizitat!). Dacă nodul y a fost deja vizitat, iar graful este neorientat, ​atunci ​s-ar fi **înaintat** pe muchia **(y, x)** din momentul vizitării nodului y. Acest lucru ar face muchia **(y, x)** un **tree-edge**. Prin urmare obținem o contradicție,​ deci nu putem avea cross-edge într-un graf neorientat.
  
-<spoiler De ce?> +Într-un graf **orientat** muchiile **(x, y)** și **(y, x)** sunt diferite, deci pot avea tipuri diferite! 
-De ce? Pentru ca stim ca exista ​**cel putin** o varianta in care adversarul obtine un castig mai bun decat daca am fi jucat miscarea M1. +</​note>​
  
-Nu conteaza exact cat de slaba poate fi miscarea M2 fata de M1. O analiza amanuntita ar putea releva ca poate fi si mai slaba decat am constatat initial, insa acest lucru este irelevant+<spoiler Analogie cu culori>​ 
 +Mai sus s-a catalogat o muchie **(x, y)** atunci când dintr-un nod în curs de vizitare x (culoare **GRAY**) se încearcă trecerea într-un nod y.
  
-De ce insa ignoram intregi subarbori si miscari potential bune numai pentru o miscare slaba gasita? Pentru cain conformitate cu **principiul de maximizare al castigului** folosit de fiecare jucatoradversarul va alege exact acea miscare ce ii va da un castig maximal. Daca exista o varianta si mai buna pentru el este irelevantdeoarece noi suntem interesati daca cea mai slaba miscare buna lui este mai buna decat miscarea noastra curent analizata.+În funcție de culoare lui yobservăm care este tipul muchiei: 
 +   
 +   * **(x, y)** -> **(GRAYWHITE)** => **tree-edge** 
 +   * **(x, y)** -> **(GRAY, GRAY)** => **back-edge** 
 +   * **(x, y)** -> **(GRAY, BLACK)** => **forward-edge** sau **cross-edge** 
 +</​spoiler>​ 
 + 
 +===== Aplicații parcurgeri ===== 
 + 
 +    * Componente Conexe 
 +    * Sortarea Topologică 
 +    * Componente Tare-Conexe 
 +    * Componente Biconexe 
 +  
 +În acest laborator vom studia doar problema sortare topologică. 
 + 
 +===== TopSort - Sortarea Topologică ===== 
 + 
 +==== Problemă ==== 
 + 
 +>> O **sortare topologică** într-un **graf orientat aciclic** reprezintă o aranjare/​permutare a nodurilor din graf care ține cont de arce. 
 + 
 +Orientarea muchiilor corespunde unei relatii de ordine de la nodul sursa catre cel destinație:​ dacă $(x,y$) este un arc, $x$ trebuie să apară înaintea lui $y$ în inșiruire 
 + 
 +<​note>​ 
 +Daca graful ar fi ciclic, nu ar putea exista o astfel de insiruire (nu se poate stabili o ordine intre nodurile care alcatuiesc un ciclu).  
 +</​note>​ 
 + 
 +<spoiler Exemplu TopSort>  
 +{{pa:​new_pa:​lab07-topsort-example1.png}} 
 + 
 +În figura anterioară avem un graf cu: 
 +    * $n = 5$   $m = 4$  
 +    * $arce: { (1,2); (1,3); (2,3); (2,4);} $ 
 + 
 + 
 +Toate sortările topologice valide sunt: 
 +  * cele date de ordinea relativa ​primelor 4 noduri: (1,2,3,4) 
 +    * $topsort = [1, 2, 3, 4, 5] $ 
 +    * $topsort = [1, 2, 3, 5, 4] $ 
 +    * $topsort = [1, 2, 5, 3, 4] $ 
 +    * $topsort = [1, 5, 2, 3, 4] $ 
 +    * $topsort = [5, 1, 2, 3, 4] $ 
 +  * cele date de ordinea relativa a primelor 4 noduri: (1,2,4,3) 
 +    * $topsort = [1, 2, 4, 3, 5] $ 
 +    * $topsort = [1, 2, 4, 5, 3] $ 
 +    * $topsort = [1, 2, 5, 4, 3] $ 
 +    * $topsort = [1, 5, 2, 4, 3] $ 
 +    * $topsort = [5, 1, 2, 4, 3] $
  
 +Explicație pentru $topsort = [1, 2, 3, 4, 5] $:
 +  * deoarece avem arcele $1 \rightarrow 3$ si $1 \rightarrow 2$, 1 trebuie să apara înainte lui 2 și 3
 +  * deoarece avem arcul $2 \rightarrow 3$ si $2 \rightarrow 4$, 2 trebuie să apara înainte lui 3 și 4
 +  * 5 nu depinde de nimeni, poate să apară oriunde
 </​spoiler>​ </​spoiler>​
 +==== Algoritmi ====
 +Sunt doi algoritmi cunoscuti pentru sortarea topologică.
  
 +=== TopSort - DFS: sortare descrescătoare după timpul de finalizare ===
 <​note>​ <​note>​
-O observatie foarte importanta ​se poate face analizand ​**modul de functionare** al acestui algoritm: ​este extrem de importanta **ordonarea miscarilor dupa valoarea castigului**. ​+Algoritm TopSort cu DFS: 
 +  * se face o parcurgere DFS pentru determinarea timpilor de finalizare 
 +  ​se sortează descrescător in functie de timpul de finalizare 
 +  ​permutarea ​de noduri obținută ​este o sortare topologică 
 +</​note>​
  
-In **cazul ideal** in care cea mai buna miscare a jucatorului curent ​este analizata prima, toate celelalte miscari, fiind mai slabe, vor fi eliminate din cautare timpuriu.+**Optimizare**: Pentru a evita sortarea nodurilor ​in functie de timpul de finalizare, se poate folosi o stiva ce retine aceste noduri in ordinea terminarii parcurgerii (sau un vector ​care la final este inversat).
  
-In **cazul cel mai defavorabil** insa, in care miscarile sunt ordonate crescator dupa castigul furnizat, Alpha-beta are aceeasi compelxitate cu Mini/​Nega-max,​ neobtinandu-se nicio imbunatatire.  ​+== Complexitate == 
 +    ​$T(n) = O(n+ m)$
  
-In **medie** se constata ​imbunatatire vizibila a algoritmului Alpha-beta fata de Mini/​Nega-max.+=== TopSort - BFS: algoritmul lui Kahn === 
 +<​note>​ 
 +Algoritm TopSort cu BFS: 
 +  ​se initializeaza coada de la BFS cu toate nodurile din graf care au grad inten **0** 
 +  ​* se porneste parcurgerea BFS 
 +    * la fiecare pas se vizitează un nod **node** 
 +    * se șterg toate muchiile care pleacă din **node**: $(node, neigh)$ 
 +      * $neigh$ este adaugat in coada doar daca devine un nod cu grad intern **0** 
 +  * se verifica la finalul parcugerii daca mai sunt muchii ramase in graf 
 +    * **daca** inca mai exista muchii neșterse, atunci graful conține cel puțin un ciclu - nu se poate determina ​sortare topologică  
 +    * **altfel**, ordinea in care s-au scos nodurile din coada reprezinta o sortare topologica
 </​note>​ </​note>​
  
-<spoiler Exemplu Grafic>+**Optimizare**:​ Pentru a evita ștergerea propriu-zisă a muchiilor din graf, se poate modifica gradul intern al fiecărui nod (care poate fi reținut într-un vector $in\_degree[node]$).
  
-Rolul miscarilor analizate la inceput presupune stabilirea unor **plafoane de minim si maxim** legate de cat de bune/slabe pot fi miscarile. ​+== Complexitate == 
 +    ​$T(n) = O(n+ m)$
  
-Astfel, plafonul de minim (Lower Bound), numit **alpha** stabileste ca o miscare nu poate fi mai slaba decat valoarea acestui plafon. ​ 
-Plafonul de maxim (Upper Bound), numit **beta**, este important doarece el foloseste la a stabili daca o miscare este prea buna pentru a fi luata in considerare. Depasirea plafonului de maxim inseamna ca o miscare este atat de buna incat adversarul nu ar fi permis-o, adica mai sus in arbore exista o miscare pe care ar fi putut s-o joace pentru a nu ajunge in situatia curent analizata. ​ 
  
-Astfel alpha si beta furnizeaza o fereastra ​ folosita pentru a filtra miscarile posibile pentru cei doi jucatoriEvident aceasta fereastra se poate actualiza ​pe masura ce se analizeaza mai multe miscariDe exemplul plafonul minim alpha se mareste pe masura ce gasim anumite tipuri de miscari ​mai bune (**better worst best moves**). Asadar, in implementare tinem seama si de aceste doua plafoaneIn conformitate cu principiul Minimax, plafonul de minim al unui jocator (alpha-ul) ​este plafonul de maxim al celuilalt (beta-ul) si invers. Prezentam ​in continuare ​descriere grafica a algoritmului Alpha-beta: +==== Concluzie ==== 
-  +Ambele variante au aceeasi complexitate 
-{{ :​pa:​laboratoare:​5.2_cr.png}}+    * Algoritmul bazat pe DFS nu verifica daca graful este ciclic: presupune corectitudinea inputuluiEste relativ ​mai simplu ​de implementat. 
 +    * Algoritmul bazat pe BFS se poate folosi pentru a detecta daca graful ​este aciclic; ​in caz afirmativ, gaseste ​sortare topologica valida. ​ 
 +===== TLDR =====
  
-</spoiler>+    * Cele mai uzuale moduri de reprezentare a unui graf sunt: liste de adiacentă și matrice de adiacentă. 
 + 
 +    * Cele doua moduri uzuale de parcurgere a unui graf sunt:  **BFS** și **DFS**. 
 + 
 +    * O aplicație importantă a parcurgerilor este **Sortarea topologică** -  o modalitate de aranjare a nodurilor în funcție de muchiile dintre ele. În functie de nodul de start al DFS, se pot obține sortări diferite, păstrând însă proprietatile generale ale sortarii topologice. 
 + 
 +===== Exerciții ===== 
 + 
 + 
 +<note warning>​ 
 +Înainte de a rezolva exercițiile,​ asigurați-vă ca ați citit și înțeles toate precizările din secțiunea 
 +[[https://​ocw.cs.pub.ro/​courses/​pa/​skel_graph | Precizări laboratoare 07-12]]. 
 + 
 +Prin citirea acestor precizari vă asigurați ca: 
 +   * cunoasteți **convențiile** folosite 
 +   * evitați **buguri** 
 +   * evitați **depunctări** la lab/​teme/​test 
 + 
 +</note>
  
 <​note>​ <​note>​
-Un video cu un exemplu detaliat si foarte bine explicat se gaseste in tutorialul recomandat ​de pe YouTube (de la minutul 21:30 la 30:30).+Scheletul ​de laborator se găsește ​pe pagina [[https://​github.com/​acs-pa/​pa-lab/​tree/​main/​skel/​lab07|pa-lab::skel/​lab07]]. 
 +</​note>​
  
 +<note warning>
 +Începând cu acest laborator, fiecare problemă are restricții concrete: dimensiuni pentru input și timp maxim de execuție. Pentru a vedea dacă o soluție (idee) intră în timp înainte de a o implementa, va trebui să îi calculați complexitatea și să aproximați timpul de execuție folosind tutorialul [[https://​github.com/​acs-pa/​pa-lab/​tree/​main/​docs/​complexity.md|pa-lab::​docs/​Complexity]].
 </​note>​ </​note>​
  
-=== Implementare === 
-In continuare prezentam o implementare conceptuala a Alpha-beta, atat pentru Minimax, cat si pentru Negamax: 
  
 +=== BFS ===
 +Se dă un graf **neorientat** cu **n** noduri și **m** muchii. Se mai dă un nod special **source**, pe care îl vom numi sursa. ​
  
-<spoiler Pseudocod Minimax with Alpha-beta>​ +Se cere să se găsească **numărul minim de muchii** ce trebuie parcurse ​de la **source** la **toate ** celelalte noduri. ​
-<code cpp> +
-int evaluate(stare); ​  // functia returneaza un scor asociat cu starea +
-                       // functia returneaza mereu scorul din perspectiva lui maxi +
-  +
-  +
-void apply_move(move);​ // functia modifica starea curenta: executa miscarea move +
-void undo_move(move); ​ // functia restaureaza starea anterioara (de dinainte ​de executa lui move)+
  
-// alege cea mai buna mutare pentru jucatorul maxi +<note warning> 
-// functia returneaza best_score pentru maxi, dar tine cont si de ce ii perminte mini +Restricții și precizări: 
-int aplhabeta_maxi(int aplhaint beta, int depth) { +  * $ nm <= 10^5 $ 
-    // daca jocul s-a terminat sau am atins nivelul maxim de recursivitate ales +  * timp de execuție 
-    ​if (gameOver() || depth == 0 ) { +    ​* C++: 1s 
-        ​return evaluate(); ​           ​// ne oprim si evaluam starea curenta +    * Java: 1s 
-    }+</note>
  
-    // incercam pe rand fiecare miscare posibila move    +<​note>​ 
-    for (move : all_moves) { +Rezultatul se va returna sub forma unui vector **d** cu **n** elemente.
- apply_move(move); ​        // executa move+
  
-        // incercam sa simulam jocul mai departe:  +Convenție
-        // daca maxi face move, ce ar face mini? +  * ** d[node] ** = numărul minim de muchii ​ce trebuie parcurse de la **source** la nodul **node** 
-        int score alphabeta_mini(alpha,​ beta, depth - 1);+  * ** d[source] = 0 ** 
 +  * ** d[node] ​= -1**, dacă nu se poate ajunge de la **source** la **node** 
 +</​note>​
  
-        if (score ​>= beta) { +<spoiler Exemplu 1
-            ​return beta;    // beta cut-off +$n = 5$   $m = 4$  $source = 3$ 
-        }+
  
-        // dintre toate variantele pe care mini le permite ​(!!!), +$muchii: { (1,2); (1,3); (2,3); (2,4);} $
-        // maxi o va alege pe cea cu scor maxim +
-        if (score > alpha+
-            alpha = score; +
-        ​}+
  
-        undo_move(move); ​        // restaureaza starea de dinainte de move 
-   } 
  
-    // cel mai bun scor pe care il poate obtine maxi este alpha +Răspuns: 
-    // (s-a tinut cont si de ce i-ar permite mini, avand in vedere ca si el joaca optim) +|node|1|2|3|4|5| 
-    return alpha; +|d|1|1|0|2|-1|
-}+
  
 +Explicație: ​
 +Graful dat este cel din figura urmatoare.
  
-// alege cea mai buna mutare pentru jucatorul mini +{{pa:​new_pa:​lab07-bfs-example1.png}}
-// functia returneaza best_score pentru mini, dar tine cont si de ce ii perminte maxi +
-int aplhabeta_mini(int aplha, int beta, int depth) ​{ +
-    // daca jocul s-a terminat sau am atins nivelul maxim de recursivitate ales +
-    if (gameOver() || depth == 0 ) { +
-        return ​-evaluate(); ​           // ne oprim si evaluam starea curenta +
-    ​}+
  
-    // incercam pe rand fiecare miscare posibila move    +  ​* ** d[3] = 0 ** pentru că 1 este sursa 
-    ​for ​(move : all_moves{ +  * ** d[1] = d[2] = 1 ** pentru că există muchie directă de la 2 la fiecare nod 
-        ​apply_move(move); ​        /executa move+  * ** d[4] = 2 ** pentru că trebuie să parcurgem 2 muchii ​($3-2-4$
 +  * ** d[5] = -1 ** pentru că nu se poate ajunge de la 3 la 5 
 +</spoiler>
  
-        // incercam sa simulam jocul mai departe: ​ 
-        // daca mini face move, ce ar face maxi? 
-        int score = alphabeta_maxi(alpha,​ beta, depth - 1); 
  
-        if (score ​<= alpha) { +<spoiler Exemplu 2> 
-            ​return alpha; ​   // alpha cut-off +$n = 7$   $m = 7$  $source = 1$ 
-        }+
  
-        // dintre toate variantele pe care maxi le permite ​(!!!), +$muchii: { (1,2); (1,4); (2,3); (4,5); (5,6); (3,7); (7,6) $
-        // mini o va alege pe cea cu scor minim +
-        if (score < beta+
-            beta = score; +
-        ​}+
  
-        undo_move(move); ​        // restaureaza starea de dinainte de move +Răspuns: 
-    }+|node|1|2|3|4|5|6|7| 
 +|d|0|1|2|1|2|3|3|
  
-    // cel mai bun scor pe care il poate obtine mini este beta +Explicație:​  
-    // (s-a tinut cont si de ce i-ar permite maxi, avand in vedere ca si el joaca optim) +Graful dat este cel din figura urmatoare. 
-    return beta; + 
-}+{{pa:​new_pa:​lab07-bfs-example2.png}}
  
-</​code>​+  * ** d[1] = 0 ** pentru că 1 este sursa 
 +  * ** d[2] = d[4] = 1 ** pentru că există muchie directă de la 2 la fiecare nod 
 +  * ** d[3] = d[5] = 2 ** pentru că trebuie să parcurgem 2 muchii ($1-2-3$, $1-4-5$) 
 +  * ** d[6] = d[7] = 3 ** pentru ca trebuie să parcurgem 3 muchii ($1-2-3-7$ sau $1-4-5-6$)
 </​spoiler>​ </​spoiler>​
  
  
 +<spoiler Exemplu 3>
 +$n = 7$   $m = 8$  $source = 1$ 
  
-<spoiler Pseudocod Negamax with Alpha-beta>​ +$muchii: { (1,2); (1,4); (2,3); (4,5)(5,6)(3,7)(7,6)(1, 6) } $
-<code cpp> +
-int evaluate(stare);   // functia returneaza un scor asociat cu starea +
-                       // functia returneaza mereu scorul din perspectiva jucatorului CURENT! +
-     +
-     +
-void apply_move(move); // functia modifica starea curenta: executa miscarea move +
-void undo_move(move);  // functia restaureaza starea anterioara ​(de dinainte de executa lui move) +
-     +
-// alege cea mai buna mutare pentru jucatorul CURENT ​(EU) +
-// functia returneaza best_score +
-int alphabeta_negamax(int aplhaint beta, int depth+
-    // daca jocul s-a terminat sau am atins nivelul maxim de recursivitate ales +
-    if (gameOver() || depth == 0 ) { +
-       ​return evaluate();            // ne oprim si evaluam starea curenta +
-    ​} +
-             +
-    // incercam pe rand fiecare care miscare posibila move    +
-    for (move : all_moves) { +
-        apply_move(move); ​        // executa move+
  
-        // incercam sa simulam jocul mai departe:  +Răspuns
-        // daca jucatorul CURENT face face move, ce ar face ADVERSARUL?​ +|node|1|2|3|4|5|6|7| 
-        int score = -alphabeta_negamax(-beta,​ -aplha depth - 1);  // cel mai bine pentru el,  +|d|0|1|2|1|2|1|2|
-                                                                  // este cel mai rau pentru mine +
-                                                                  // si invers+
  
-        // dintre toate variantele pe care ADVERSARUL le permite (!!!), +Explicație: ​ 
-        // EU (jucatorul CURENT) o voi alege pe cea cu scor maxim +Graful dat este cel din figura urmatoare.
-        if (score >= alpha) { +
-            alpha = score; +
-        }+
  
-        if (alpha >= beta) { +{{pa:​new_pa:​lab07-bfs-example3.png}}
-            break; ​              // cut-off +
-        ​} +
-                     +
-        undo_move(move); ​        // restaureaza starea de dinainte de move +
-    ​}+
  
-    // cel mai bun scor pe care il pot obtine EU este alpha +  * ** d[1] = 0 ** pentru că 1 este sursa 
-    // (s-a tinut cont si de ce mi-ar permite ADVERSARULavand in vedere ca si el joaca optim) +  * ** d[2] = d[4] = d[6] = 1 ** pentru că există muchie directă de la 2 la fiecare nod 
-    return alpha; +  * ** d[3] = d[5] = d[7] = 2 ** pentru că trebuie ​să parcurgem 2 muchii ($1-2-3$$1-4-5$, $1-6-7$)
-+
-</​code>​+
 </​spoiler>​ </​spoiler>​
 +
 +
 +=== Topological Sort ===
 +Se dă un graf **orientat** aciclic cu **n** noduri și **m** arce. Se cere să se găsească **o sortare topologica** validă.
 +
 +<note warning>
 +Restricții si precizari:
 +  * $ n, m <= 10^5 $
 +  * timp de executie
 +    * C++: 1s
 +    * Java: 1s
 +</​note>​
  
 <​note>​ <​note>​
-Din nou remarcam claritatea si coerenta sporita ​variantei negamax!+Rezultatul se va returna sub forma unui vector **topsort** cu ** n ** elemente. 
 + 
 +Vectorul **topsort** va reprezenta o permutare ​multimii ${1, 2, 3,..., n}$ reprezentand sortarea topologica gasita.
 </​note>​ </​note>​
-==== Complexitate ==== 
  
-In continuare prezentam complexitatile asociate algoritmilor prezentati anterior. ​ +<spoiler Exemplu 1> 
-Pentru aceasta vom introduce cateva notiuni:+$n = 5$   $m = 4$ 
  
-  * **branch factor** ​** b ** = **numarul mediu de ramificari** pe care le are un nod neterminal ​(care nu e frunzadin ** arborele de solutii ** +$arce(1,2); (1,3); (2,3); (2,4);} $
-  * **depth** : **d ** = **adancimea maxima ** pana la care se face cautarea in arborele de solutii +
-      * orice nod de adancime d va fi considerat terminal+
  
-<​note> ​ 
-Un arbore cu un branching factor **b**, care va fi examinat pana la un nivel **d** va furniza $b^d$ noduri frunze ce vor trebui procesate (ex. calculam scorul pentru acele noduri). ​ 
  
-<spoiler Explicatie>​ +Răspuns: ​$topsort = [1, 2, 3, 4, 5] $ 
-Nivelurile sunt notate cu $0, 1, 2, ...d+ 
-  * nivel 0: $1$   nod (radacina) +Explicație:​  
-  nivel 1$b  noduri +Graful dat este cel din figura următoare. 
-  nivel 2$b^2$ noduri + 
-  * nivel 3: $b^3$ noduri ​ +{{pa:​new_pa:​lab07-topsort-example1.png}} 
-  ... +  * deoarece avem arcele $1 \rightarrow 3$ si $1 \rightarrow 2$1 trebuie să apara înainte lui 2 și 3 
-  nivel d: $b^dnoduri+  * deoarece avem arcul $2 \rightarrow 3$ si $2 \rightarrow 4$, 2 trebuie să apara înainte lui 3 și 4 
 +  * 5 nu depinde de nimeni, poate să apară oriunde 
 + 
 +Toate sortările topologice valide sunt: 
 +  * cele date de ordinea relativa a primelor 4 noduri: 1,2,3,4 
 +    * $topsort = [1, 2, 3, 4, 5] 
 +    $topsort = [1, 2, 3, 5, 4] $ 
 +    * $topsort = [1, 2, 5, 3, 4] 
 +    $topsort = [1, 5, 2, 3, 4] $ 
 +    * $topsort = [5, 1, 2, 3, 4] 
 +  * cele date de ordinea relativa a primelor 4 noduri(1,2,4,3) 
 +    * $topsort = [1, 2, 4, 3, 5] 
 +    $topsort = [1, 2, 4, 5, 3] $ 
 +    * $topsort = [1, 2, 5, 4, 3] $ 
 +    * $topsort = [1, 5, 2, 4, 3] $ 
 +    * $topsort = [5, 1, 2, 4, 3] $ 
 </​spoiler>​ </​spoiler>​
-</​note>​ 
  
  
-  * **minimax/​negamax** +<spoiler Exemplu 2> 
-      * Un algoritm **mini/​negamax** clasic, care analizeaza toate starile posibile, va avea complexitatea ​$O(b ^ d)- deci exponentiala. ​+$n = 9$   $m = 8
  
-  * **alpha-beta** +$arce: {  
-      * Cat de bun este insa alpha-beta fata de un mini/​nega-max naiv? Dupa cum s-a mentionat anteriorin functie de ordonarea miscarilor ce vor fi evaluate putem avea un caz cel mai favorabil si un caz cel mai defavorabil. +(1,2); 
-      * ** best case **: miscarile sunt ordonate descrescator dupa castig ​(deci ordonate optim)rezulta o complexitate +(1,3); 
-          * $O(b*1*b*1*b*1...de\ ​ d \ ori...b*1)$ pentru d par +(3,4); 
-          * $O(b*1*b*1*b*1...de \ d \ ori...b)$ pentru d impar +(3,5); 
-          * restrangand ambele expresii rezulta o complexitate $O(b ^ {\frac{d}{2}}= O(\sqrt{b^d})$ +(5,9)
-          * prin urmareintr-un caz ideal, algoritmul alpha-beta poate explora de 2 ori mai putine nivele in arborele de solutii fata de un algoritm mini/​nega-max naiv. +(4,6); 
-      * ** worst case**: miscarile sunt ordonate crescator dupa castigul furnizat unui jucatorastfel fiind necesara o examinare a tuturor nodurilor pentru gasirea celei mai bune miscari. +(4,7); 
-          ​* ​ in consecinta complexitatea devine egala cu cea a unui algoritm mini/​negamax naiv. +(4,8); 
-  +} $
-    +
-===== Concluzii si observatii ===== +
-Alpha-beta NU ofera o alta solutie fata de Minimax! Este doar o optimizare pusa deasupra algoritmului Minimax care ne permite sa exploram mai multe stari in acelasi timp sau pentru acelasi numar de stari sa optinem un timp de doua ori mai mic.+
  
-===== Exemple ===== 
-Dintre cele mai importante jocuri in care putem aplica direct strategia minimax, mentionam: 
-  * [[https://​en.wikipedia.org/​wiki/​Tic-tac-toe | X și 0]] 
-    * joc foarte simplu/usor (spatiul starilor este mic).  
-    * Prin urmare tot arborele de solutii poate fi generat si explorat intr-un timp foarte scurt. 
-  * [[https://​en.wikipedia.org/​wiki/​Chess | sah ]] 
-    * joc foarte greu (spatiul starilor este foarte mare) 
-    * minimax/​negamax simplu poate merge pana la $ d = 7$ (nu reusea da bata campionul mondial la sah - campion uman) 
-    * alpha-beta poate merge pana la $d = 14$ 
-    * ** Deep Blue** a fost implementarea unui bot cu minimax si alpha-beta care a batut in 1997 campionul mondial la sah (Gary Kasparov). 
-  * [[https://​en.wikipedia.org/​wiki/​Ultimate_tic-tac-toe | Ultimate tic-tac-toe]] 
-    * varianta mult mai grea de X si 0 (spatiul starilor foarte mare) 
-    * s-a dat la proiect PA 2016 :D 
-    * implemantarile se pot testa [[http://​theaigames.com/​competitions/​ultimate-tic-tac-toe/​rules | aici]] 
-  * [[https://​en.wikipedia.org/​wiki/​Nim | Nim]] 
-  * [[https://​en.wikipedia.org/​wiki/​Reversi| Reversi]] ​ 
  
 +Răspuns: $topsort = [1, 2, 3, 4, 6, 7, 8, 5, 9] $
  
-Alte exempe de jocuri interesante+Explicație:  
-  * [[https://​en.wikipedia.org/​wiki/​Go_(game) | Go]] +Graful dat este cel din figura următoare.
-    * solutiile se bazeaza pe Monte Carlo Tree Search (nu pe minimax) +
-    * [[https://​en.wikipedia.org/​wiki/​AlphaGo|AlphaGo]] ​este botul cel mai bun pe tabla de 19x19 +
-  +
-===Nim===+
  
-Fiind date 3 multimi de bile, fiecare jucator trebuie sa extraga la fiecare mutare 1, 2 sau 3 bile din oricare multime+{{pa:​new_pa:​lab07-topsort-example2.png}}
  
-**Cel care este fortat sa aleaga ultima bila, pierde.**+Se observă din desen că soluția menționată ​este validă. 
 +</​spoiler>​
  
 +=== BONUS ===
 +**B1** Determinați componentele conexe ale unui graf neorientat. Puteți testa implementarea pe infoarena la problema [[https://​infoarena.ro/​problema/​dfs| dfs]].
  
-===Reversi game===+**B2** Rezolvați problema [[https://​infoarena.ro/​problema/​muzeu| muzeu]] pe infoarena.
  
-Tabla de joc consista dintr-un grid 6x6. Piesele pot fi reprezentate de monede, fiecarui jucator fiindu-i asociata o fata diferita a monezii. Jucatorii muta alternativ, dupa regula urmatoare:+=== Extra ===
  
-  * Pozitia (x, y) in care este plasata piesa trebuie sa fie libera +<spoiler arbore3> 
-  * Trebuie sa existe o alta pozitie (x', y'), pe aceeasi linie, coloana sau diagonala a jucatorului aflat la mutare si toate pozitiile dintre (x, y) si (x', y') trebuie sa fie ocupate de piese ale adversarului +Rezolvați problema [[https://​infoarena.ro/​problema/​arbore3 
-  * Piesele adversarului dintre (x, y) si (x', y') vor fi capturate, intorcandu-se monedele ​pe cealalta fata.+| arbore3]] ​pe infoarena 
 +</​spoiler>​
  
-Mai jos, de la stanga spre dreapta: pozitia initiala, posibilitatile de mutare ale primului jucator, tabla dupa prima mutare, posibilitatile de mutare ale celuilalt jucator, tabla dupa a doua mutare.+<spoiler Pokemon GO AWAY> 
 +Rezolvați problema [[https://​www.hackerrank.com/​contests/​test-practic-pa-2017-v2-meeseeks/​challenges/​test-2-pokemon-go-away-grea| Pokemon GO AWAY]] ​de la test PA 2017.
  
-{{:​pa:​laboratoare:​1.png?200|}} +Cu ce problemă seamana
-{{:​pa:​laboratoare:​2.png?​200|}} +</​spoiler>​
-{{:​pa:​laboratoare:​3.png?​200|}} +
-{{:​pa:​laboratoare:​4.png?​200|}} +
-{{:​pa:​laboratoare:​5.png?​200|}}+
  
 +<spoiler insule>
 +Rezolvați problema [[https://​infoarena.ro/​problema/​insule
 +| insule]] pe infoarena.
 +</​spoiler>​
  
-Observatii: +<spoiler tsunami> 
-  * Jucatorul poate acapara piese ale adversarului in mai multe directii simultan +Rezolvați problema [[https://​infoarena.ro/​problema/​tsunami 
-  * Daca un jucator nu are unde muta, acesta cedeaza randul, adversarul efectuand a doua mutare la rand +| tsunami]] pe infoarena. 
-  * Jocul se incheie cand nimeni nu mai poate muta, invingatorul fiind acela care detine cele mai multe piese proprii+</​spoiler>​
  
-===== Exercitii ===== 
-<​note>​ 
-In acest laborator vom folosi scheletul de laborator din arhiva {{pa:​new_pa:​skel-lab06.zip}}. 
-</​note>​ 
  
-Vom implementa algoritmi pentru jocurile **Nim** si **Reversi**.+<spoiler berarii2>​ 
 +Rezolvați problema [[https://​infoarena.ro/​problema/​berarii2 
 +| berarii2]] pe infoarena. 
 +</​spoiler>​
  
-=== Minimax Nim === 
-Se doreste implementarea algoritmulului minimax sau negamax pentru Nim. 
-<​note>​ 
-Recomandam implementarea variantei Negamax. 
-</​note>​ 
  
-=== Minimax Reversi === 
-Se doreste implementarea algoritmulului minimax sau negamax pentru Reversi. 
-<​note>​ 
-Recomandam implementarea variantei Negamax. 
-</​note>​ 
  
  
-=== Bonus === +===== Referințe ===== 
-Extindeti algoritmul implementat anterior pentru jocul **Reversi ​** intr-un algoritm de tip alpha-beta pruning+ 
-Cum puteti sa comparati cei doi algoritmi implementati pentru Reversi? +[0] Chapter ​**Elementary Graph Algorithms**, “Introduction to Algorithms”,​ Thomas HCormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein 
-===== Referinte ===== + 
-[1] [[http://​en.wikipedia.org/​wiki/​Minimax]]+[1] [[https://​en.wikipedia.org/​wiki/​Breadth-first_search]]
  
-[2] [[http://​en.wikipedia.org/​wiki/​Negamax]]+[2] [[https://​en.wikipedia.org/​wiki/​Depth-first_search]]
  
-[3] [[http://​en.wikipedia.org/​wiki/​Alpha-beta_pruning]]+[3] [[https://​en.wikipedia.org/​wiki/​Topological_sorting]]
  
-[4] http://​inst.eecs.berkeley.edu/​~cs61b/​fa14/​ta-materials/​apps/​ab_tree_practice/​ 
pa/laboratoare/laborator-06.1523300431.txt.gz · Last modified: 2018/04/09 22:00 by radu.iacob
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0