Differences

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

Link to this comparison view

pa:laboratoare:laborator-06 [2021/04/13 02:08]
radu.nichita [Referințe]
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:​ +===== Obiective laborator =====
-  * [[radunichita99@gmail.com | Radu Nichita (2021)]] +
-  * [[neatudarius@gmail.com|Darius-Florentin Neațu (2017-2021)]] +
-  * [[cristianolaru99@gmail.com | Cristian Olaru (2021)]] +
-  * [[mirunaelena.banu@gmail.com ​ | Miruna-Elena Banu (2021)]] +
-  * [[maraioana9967@gmail.com | Mara-Ioana Nicolae (2021)]] +
-  * [[stefanpopa2209@gmail.com | Ștefan Popa (2018-2020)]]+
  
-<note warning> +  * Înțelegerea conceptele de graf, reprezentare și parcugere 
-The content of this page may change! +  * Studierea unor aplicații pentru parcurgeri
-</​note>​+
  
-===== Obiective laborator ===== 
-  * Însușirea unor cunoștințe de bază despre **teoria jocurilor** precum și despre **jocurile de tip joc de sumă zero (suma nulă, zero-sum games)** 
-  * Însușirea unor cunostințe elementare despre **algoritmii necesari** rezolvării unor probleme de joc de sumă zero (zero-sum game). 
-  * Găsirea și compararea diverselor euristici pentru jocurile cunoscute, precum **șah**. 
  
-===== Precizări inițiale ​===== +===== Importanţă – aplicaţii practice ​=====
-<note warning>​ +
-Un curs foarte bine explicat este pe canalul de YouTube de la MIT. Vă sfătuim să vizionați integral [[https://​youtu.be/​STjW3eH0Cik| Search: Games, Minimax, and Alpha-Beta]] înainte să parcurgeți materialul de pe ocw. +
-</​note>​+
  
-===== Introducere =====+Grafurile sunt utile pentru a modela diverse probleme și au numeroase aplicații practice:
  
-**Jocuri ​de sumă 0** ([[https://​en.wikipedia.org/​wiki/​Zero-sum_game |wikipedia.org/​zero-sum-game]]sunt jocurile care de obicei se joacă în doi jucători șîn care o mutare bună a unui jucător este în dezavantajul celuilalt jucător în mod direct. Aplicațiile din cadrul acestui laborator sunt în orice joc de sumă 0: șah, table, X ș0, dame, go etcOrice joc în care se poate acorda un scor / punctaj pentru anumite evenimente - de exemplu la șah pentru capturarea unor piese sau la X și 0 pentru plasarea unui X pe o anumite poziție).+  ​Rețele ​de calculatoare ​(exstabilirea 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ăcu drumuri (ex. drum minim între două localități
 +  * Modelare grafică (exarbori ​de parționare) 
 +  * Rețele de transport (exflux)
  
-Algoritmul Minimax reprezintă una din cele mai cunoscute strategii pentru a juca jocurile de sumă 0. Minimax este un algoritm ce permite minimizarea pierderii de puncte într-o mutare următoare a jucătorului advers. Concret, pentru o alegere într-un joc oarecare se preferă cea care aduce un risc minim dintre viitoarele mutări bune ale adversarului. 
  
-De asemenea, algoritmul Minimax este folosit în diverse domenii precum teoria jocurilor (Game Theory), teoria jocurilor combinatorice (Combinatorial Game Theory - CGT), teoria deciziei (Decision Theory) șstatistică.+===== Grafuri ===== 
 +Puteț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.
  
 + ==== Definiții ====
  
-===== Descrierea algoritmului =====+>> Un **graf** G se definește ca fiind o pereche (V, E), unde **V {node / un nod oarecare din graf}**, iar **E {(x, y) / (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**.
-Ideea pe care se bazează algoritmul ​este că jucătorii adoptă următoarele strategii:+
  
-  ​*Jucătorul 1 (**maxi**) va încerca mereu să-și **maximizeze** propriul câștig prin mutarea pe care o are de făcut; +>> 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**.
-  *Jucătorul 2 (**mini**va încerca mereu să **minimizeze** câștigul jucătorului 1 la fiecare mutare.+
  
-De ce merge o astfel de abordare? După cum se preciza la început, discuția se axează pe jocuri de sumă zero (zero-sum game). Acest lucru garantează, printre altele, ​că orice câștig al Jucătorului 1 este egal cu modulul sumei pierdute ​de Jucătorul 2Cu alte cuvintecât pierde Jucătorul 2, atât câștigă Jucător 1. Invers, cât pierde Jucător 1, atât câștigă Jucator 2Sau:+>> O **componentă conexă (CC)** este o submulțime maximală de noduricu proprietatea ​că oricare ar fi două noduri x și y din aceasta, există drum de la x la yPentru grafuri orientateo componentă conexă se numește ** componentă tare conexă (CTC)**.
  
-$$ Câștig_{Jucător_1} ​| Pierdere_{Jucător_2} |$$ +>> Un graf **aciclic** este un graf (orientat/​neorientat) care nu conține cicluri. 
-$$ Câștig_{Jucător_2} = | Pierdere_{Jucător_1} | $$+==== 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 spațiului soluțiilor ===+Un graf poate fi modelat în mai multe moduri (folosind mai multe notații):
  
 +    * 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=(x, y)$ este o muchie în graf între nodurile x și y} = mulțimea muchiile/​arcelor (EN: edges), fiecare muchie stabilind o relație de vecinătate între doua noduri. ​
  
-În general spațiul soluțiilor pentru un joc în doi de tip zero-sum se reprezintă ca un **arbore**, fiecărui nod fiindu-i asociată o stare a jocului în desfășurare ​(game state) +    * printr-o pereche $G = (nodes, a)$ 
-De exemplul, putem considera jocul de X și O ce are următorul arbore ​(parțialde soluții. Acesta corespunde primelor mutări ale lui X, respectiv O: +      * $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
  
-{{ :​pa:​laboratoare:​5.1_cr.png |}}+    * 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)
  
-**Metodele de reprezentare a arborelui** variază în funcție de paradigma de programare aleasă, de limbaj, precum și de gradul de optimizare avut în vedere. 
  
-==== Implementare ==== 
  
-Având noțiunile ​de bază asupra strategiei celor doi jucătoriprecum și a reprezentării spațiului soluțiilor problemei, o primă implementare a algoritmului Minimax ar folosi ​două funcții maxi() ​și mini(), care ambele calculează cel mai bun scor pe care îl poate obține jucătorul menționatIntuitiv, cele 2 funcții au implementare aproape identică, astfel că dorim o organizam a codului fără porțiuni duplicate. De aceea singura variantă de implementare pe care o recomandăm,​ este varianta Negamax: ​+Reprezentarea în memorie a grafurilor se face, de obiceicu **liste de adiacență**. Se pot folosi ​însă și alte structuri de date, care vor fi introduse ​pe parcurs.
  
-<spoiler Pseudocod Negamax>​ +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).
-<code cpp> +
-// compute the state score for current player +
-int evaluate(stateplayer);+
  
-// apply the move on the current state: old_state -> new_state 
-void apply_move(state,​ move); 
-// undo the move and restore previous state: new_state -> old_state 
-void undo_move(state,​ move); 
  
-// check if any player won the game +==== Colorare ==== 
-bool game_over(state);​+Algoritmii de parcugere se pot folosi de o colorare a nodurilor:
  
-// return the opponent for current player +    * **white** ​(alb) = nod care nu a fost încă vizitat (nu este în coadă)
-Player get_opponent(player);+
  
-// compute ​the best score that player can get, +    * **gray** (gri)  = nod care este în curs de vizitare (a fost adăugat în coadă) 
-// considering that the opponent also has an optimal strategy + 
-int negamax(State& stateint depthPlayer player) { +    * **black** (negru) = nod care a fost complet vizitat (node scos din coadă și pentru care s-a vizitat tot subarborele) 
-    // STEP 1game over or maximum recursion depth was reached + 
-    ​if (game_over() || depth == 0) { + 
-       return evaluate(state, player);+==== 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 cpp| BFS> 
 +// 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 (nodeneigh) 
 +BFS(sourceG=(nodes, adj)) { 
 +    // STEP 0initialize 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;
     }     }
  
-    // STEP 2generate all possible moves for player +    // STEP 1initialize a queue 
-    ​all_moves ​get_all_moves(state,​ player);+    ​{}
  
 +    // STEP 2: add the source(s) into q
 +    d[source] = 0;                              // distance from source to source
 +    p[source] = null;                           // the source never has a parent (because it's the root of the traversal)
 +    q.push(source);​
 +    // [optional] color[source] = gray;
  
-    // STEP 3: try to apply each move - compute best score +    // STEP 3: start traversal using the node(s) from q 
-    ​int best_score = -oo; +    ​while (!q.empty()) {                        // while still have nodes to explore 
-    for (move : all_moves) { +        // STEP 3.1: extract the next node from queue 
-        // STEP 3.1: do move +        ​node = q.pop();
-        ​apply_move(state, move);+
  
-        // STEP 3.2: play for the opponent +        // [optional] ​STEP 3.2: print/​use ​the node
-        int score = -negamax(state,​ depth - 1, get_opponent(player));​ +
-        // opponent allows player to obtain this score if player will do current move. +
-        // player chooses this move only if it has a better score. +
-        if (score > best_score) { +
-            best_score = score;+
  
-            ​// [optional]the best move can be saved +        ​// STEP 3.3: expand/​visit the node 
-            // best_move ​move;+        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; 
 +            }
         }         }
 +        ​
 +       // [optional] color[node] = black;
 +    }
 +}
 +
 +</​code>​
 +
 +<​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>​
 +
 +==== Complexitate ==== 
 +  *cu liste de adiacență:​ $O(n + m)$ sau $O(|V| + |E|)$
 +  *cu matrice de adiacență:​ $O(n^2)$ sau  $ O(|V|^2)$
 +
 +===== DFS - Parcurgerea în adâncime =====
 +
 +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.
 +
 +Î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.
 +
 +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
 + 
 +
 +Spre deosebire de BFS, pentru 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.
 +
  
-        ​// STEP 3.3undo move +====  Algoritm ==== 
-        ​undo_move(statemove);+<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 (node, neigh) 
 +// 
 +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;
     }     }
  
-    // STEP 4: return best allowed score +    ​timestamp = 0;                                  ​// the first timestamp before the DFS traversal 
-    // [optional] ​also return the best move + 
-    ​return best_score;+    ​foreach (node in nodes) { 
 +        if (p[node] == null) {                      ​// or [optional] ​color[node] == white 
 +            DFS_RECURSIVE(node,​ G, p, timestamp) 
 +        } 
 +    ​}
 } }
  
 +DFS_RECURSIVE(node,​ G=(node, adj), p, ref timestamp) {
 +    start[node] = ++timestamp; ​                     // start visiting its subtree
 +    // [optional] color[node] = gray;
 +
 +    for (neigh in adj[node]) {                      // for each neighbour
 +        if (p[neigh] == null) {                     // or [optional] color[neigh] = white;
 +            p[neigh] = node;                        // save parent
 +            DFS_RECURSIVE(neigh,​ G, p, timestamp); ​ // continue traversal
 +        }
 +    }
 +
 +    finish[node] = ++timestamp; ​                    // finish visiting its subtree
 +    // [optional] color[node] = black;
 +}
 </​code>​ </​code>​
-</​spoiler>​ 
  
-=== De ce este nevoie ​de utilizarea unei adâncimi 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>​
  
-Datorită ​**spațiului ​de soluții mare**, de multe ori copleșitor ca volum de date de analizat, o inspectare completă a acestuia nu este fezabilă și devine impracticabilă din punctul de vedere al timpului consumat ​sau chiar a memoriei alocate ​(se vor discuta aceste aspecte în 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 preferată o abordare care parcurge arborele numai până la o anumită **adâncime maximă („depth”)**. Aceasta abordare permite examinarea arborelui destul de mult pentru a putea lua decizii minimalist coerente in deșfăsurarea jocului. ​ 
  
-Totuși, **dezavantajul major** este că pe termen lung se poate dovedi ca decizia luată la adâncimea depth nu este global favorabilă jucătorului în cauză (s-a ales o valoare maxim local, iar dacă s-ar fi continuat în arborele de explorare s-ar fi constatat că este o decizie ce avantajează celălălt jucător). ​ 
  
-De asemenea, se observă recursivitatea indirectă. Prin convenție acceptăm ca **începutul algoritmului** să fie cu jucătorul maxAstfel, se analizează succesiv diferite stări ale jocului din punctul ​de vedere al celor doi jucatori până la adâncimea depthRezultatul întors ​este scorul final al mișcării celei mai bune pentru un jucător din perspectiva următoarelor depth mutări ​în joc.+==== Tipuri de muchii/arce în parcurgerea DFS ==== 
 +>> ​**Arborele de parcurgere DFS** cuprinde toate nodurile din graf împreună cu muchiile/​arcele pe vizitate de DFSDacă 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 ​grafic ===+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.
  
-{{  :​pa:​laboratoare:​minimax.png?​750 |}}+</​note>​
  
-==== Optimizări====+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. ​
  
-=== Alpha-beta pruning ===+>> **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). ​
  
-Până acum s-a discutat despre algoritmii Minimax / Negamax. Aceștia sunt algoritmi exhaustivi (**exhausting search algorithms**). Cu alte cuvinte, ei găsesc soluția optima examinând întreg spațiul de soluții al problemei. Acest mod de abordare este extrem de ineficient în ceea ce privește efortul de calcul necesar, mai ales considerând că extrem de multe stări de joc inutile sunt explorate ​(este vorba de acele stări ​care nu pot fi atinse datorită încălcării principului că fiecare jucător joacă optim la fiecare rundă).+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** 
  
-O îmbunățațire substanțială a Minimax/​Negamax ​este **Alpha-beta pruning** (**tăiere alfa-beta**). Acest algoritm încearcă ​să optimizeze Minimax/​Negamax profitând de o observație importantă**pe parcursul examinării arborelui de soluții ​se pot elimina întregi subarbori, corespunzători unei mișri mdacă pe parcursul analizei găsim că mișcarea m este mai slabă calitativ decât cea mai bună mișcare curentă.**+<​note>​ 
 +În graful neorientat aceasta nu are sens. Într-un graf neorientat, **(x, y)** și **(y, x)** reprezintă același lucru. Dacă x ar fi strămoș ​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 **(xy)** 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**.
  
-Astfelconsiderăm că pornim cu o primă mișcare M1. După ce analizăm această mișcare în totalitate ​și îi atribuim un scorcontinuăm să analizăm mișcarea M2. Dacă în analiza ulterioară găsim că adversarul are cel puțin o mișcare care transformă M2 într-o mișcare mai slabă decât M1 atunci orice alte variante ce corespund mișcării M2 (subarborinu mai trebuie analizate.+Într-un graf **orientat** muchiile **(xy)** și **(yx)** sunt diferite, deci pot avea tipuri diferite! 
 +</​note>​
  
-<spoiler De ce?+>> **cross-edge** (**C**) / muchie de traversare = muchie **(x, y)** care conectează un nod x de un nod y din alt subarbore.  
-De ce? Pentru că știm că există **cel puțin** o variantă în care adversarul ​obține un câștig mai bun decât dacă am fi jucat mișcarea M1+<note
 +În graful neorientat aceasta nu are sens. Într-un graf neorientat, dacă **(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.
  
-Nu conteaza exact cât de slabă poate fi mișcarea M2 față de M1. O analiză amănunțită ar putea releva că poate fi și mai slabă decât am constatat inițialînsă acest lucru este irelevant. ​+Într-un graf **orientat** muchiile **(x, y)** și **(y, x)** sunt diferitedeci pot avea tipuri diferite! 
 +</​note>​
  
-De ce însă ignorăm întregi subarbori și mișcări potențial bune numai pentru ​mișcare slabă găsită? Pentru căîn conformitate cu **principiul ​de maximizare al câștigului** folosit de fiecare jucator, adversarul va alege exact acea mișcare ce îi va da un câștig maximalDacă există o variantă și mai bună pentru el este irelevantdeoarece noi suntem interesați dacă cea mai slabă mișcare bună a lui este mai bună decât mișcarea noastră curent analizată.+<spoiler Analogie cu culori>​ 
 +Mai sus s-a catalogat ​muchie **(xy)** atunci când dintr-un nod în curs de vizitare x (culoare ​**GRAY**) se încearcă trecerea într-un nod y. 
 + 
 +În funcție de culoare lui yobservăcare este tipul muchiei: 
 +   
 +   * **(x, y)** -> **(GRAY, WHITE)** => **tree-edge** 
 +   * **(x, y)** -> **(GRAY, GRAY)** => **back-edge** 
 +   * **(x, y)** -> **(GRAY, BLACK)** => **forward-edge** sau **cross-edge**
 </​spoiler>​ </​spoiler>​
  
 +===== Aplicații parcurgeri =====
  
-<​note>​ +    * Componente Conexe 
-Un video cu un exemplu detaliat și foarte bine explicat se găsește în tutorialul recomandat de pe YouTube (de la minutul 21:30 la 30:30).+    * 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>​ </​note>​
  
-=== Implementare === +<spoiler Exemplu TopSort> ​ 
-În continuare prezentăm o implementare conceptuală a Alpha-beta pentru varianta Negamax:+{{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);} $
  
-<spoiler Pseudocod Negamax with Alpha-beta>​ 
-<code cpp> 
-// compute the state score for current player 
-int evaluate(state,​ player); 
  
-// apply the move on the current stateold_state -> new_state +Toate sortările topologice valide sunt
-void apply_move(statemove); +  * cele date de ordinea relativa a primelor 4 noduri: ​(1,2,3,4
-// undo the move and restore previous state: new_state -> old_state +    * $topsort = [1, 2, 3, 4, 5] $ 
-void undo_move(statemove);+    * $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] $
  
-// check if any player won the game +Explicație pentru $topsort = [1, 2, 3, 4, 5] $: 
-bool game_over(state);​+  * 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>​ 
 +==== Algoritmi ==== 
 +Sunt doi algoritmi cunoscuti pentru sortarea topologică.
  
-// return the opponent for current player +=== TopSort - DFS: sortare descrescătoare după timpul de finalizare === 
-Player get_opponent(player);​+<​note>​ 
 +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>​
  
-// compute the best score that player can get, +**Optimizare**:​ Pentru a evita sortarea nodurilor in functie de timpul de finalizarese poate folosi o stiva ce retine aceste noduri in ordinea terminarii parcurgerii ​(sau un vector care la final este inversat).
-// considering that the opponent also has an optimal strategy +
-int alphabeta_negamax(State& state, int depth, Player player, int aplha, int beta) { +
-    // STEP 1: game over or maximum recursion depth was reached +
-    if (game_over() || depth == 0) { +
-       ​return evaluate(state,​ player)+
-    }+
  
-    // STEP 2: generate all possible moves for player +== Complexitate == 
-    ​// [optiona]: sort moves descending by score (if possible) +    ​* $T(n) = O(n+ m)$
-    all_moves ​get_all_moves(state, player);+
  
 +=== 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 o sortare topologică ​
 +    * **altfel**, ordinea in care s-au scos nodurile din coada reprezinta o sortare topologica
 +</​note>​
  
-    // STEP 3try to apply each move compute best score +**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]$).
-    int best_score = -oo; +
-    for (move : all_moves+
-        // STEP 3.1: do move +
-        apply_move(state,​ move);+
  
-        // STEP 3.2: play for the opponent +== Complexitate == 
-        int score = -alphabeta_negamax(state,​ depth - 1, get_opponent(player), -beta, -alpha); +    * $T(n= O(n+ m)$
-        // opponent allows player to obtain this score if player will do current move. +
-        // player chooses this move only if it has a better score. +
-        if (score > best_score+
-            best_score = score;+
  
-            // [optional]: the best move can be saved 
-            // best_move = move; 
-        } 
  
-        // STEP 3.3update alpha (found a better move?) +==== Concluzie ==== 
-        if (best_score > alpha) { +Ambele variante au aceeasi complexitate 
-            alpha = best_score+    * Algoritmul bazat pe DFS nu verifica daca graful este ciclicpresupune corectitudinea inputului. Este relativ mai simplu de implementat. 
-        }+    * Algoritmul bazat pe BFS se poate folosi pentru a detecta daca graful este aciclicin caz afirmativ, gaseste o sortare topologica valida. ​ 
 +===== TLDR =====
  
-        // STEP 3.4: cut-off +    ​Cele mai uzuale moduri de reprezentare ​unui graf sunt: liste de adiacentă și matrice de adiacentă.
-        // already found the best possible score (alpha == beta) +
-        // OR +
-        // * on this branch we can obtain ​score (alpha) better than the +
-        // maximum allowed score by the opponent => drop the branch because +
-        // opponent also plays optimal +
-        if (alpha >= beta) { +
-            break +
-        }+
  
-        // STEP 3.4undo move +    * Cele doua moduri uzuale de parcurgere a unui graf sunt ​**BFS** și **DFS**.
-        undo_move(state,​ move); +
-    }+
  
-    ​// STEP 4: return best allowed score +    ​* 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.
-    // [optional] also return the best move +
-    return best_score;​ +
-}+
  
-</​code>​ +===== Exerciții =====
-</​spoiler>​+
  
 +
 +<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>​
-O observație foarte importantă se poate face analizând **modul de funcționare** al acestui algoritmeste extrem de importantă **ordonarea mișcărilor după valoarea câștigului**+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>​
  
-În **cazul ideal** în care cea mai bună mișcare jucătorului curent este analizată primatoate celelalte mișcări, fiind mai slabe, vor fi eliminate din căutare timpuriu.+<note warning>​ 
 +Începând cu acest laborator, fiecare problemă are restricții concrete: dimensiuni pentru input și timp maxim de execuție. Pentru ​vedea dacă o soluție (idee) intră în timp înainte de a o implementava 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>​
  
-În **cazul cel mai defavorabil** însă, în care mișcările sunt ordonate crescător după câștigul furnizat, Alpha-beta are aceeași complexitate cu Minimax/ Negamax, îmbunătățirea fiind nulă.  ​ 
  
-În **medie** se constată o eficiență sporită in practică pentru Alpha-beta.+=== 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.  
 + 
 +Se cere să se găsească **numărul minim de muchii** ce trebuie parcurse de la **source** la **toate ** celelalte noduri 
 + 
 +<note warning>​ 
 +Restricții și precizări:​ 
 +  * $ n, m <= 10^5 $ 
 +  * timp de execuție 
 +    * C++: 1s 
 +    * Java: 1s
 </​note>​ </​note>​
  
-=== Exemplu grafic ===+<​note>​ 
 +Rezultatul se va returna sub forma unui vector **d** cu **n** elemente.
  
-{{ :pa:​laboratoare:​alphabeta.png?​751}}+Convenție: 
 +  * ** d[node] ** = numărul minim de muchii ce trebuie parcurse de la **source** la nodul **node** 
 +  * ** d[source] = 0 ** 
 +  * ** d[node] = -1**, dacă nu se poate ajunge de la **source** la **node** 
 +</​note>​
  
-=== Iterative deepening ===+<spoiler Exemplu 1> 
 +$n 5$   ​$m ​4$  $source ​3$ 
  
-Pe scurt, iterative deepening reprezintă o strategie de optimizare a timpului prin căutarea progresivă în adâncime. Uneoriexistă posibilitatea pentru anumite stări să nu fie explorate complet, iar în acest caz rezultatul este aproximat printr-o euristică. Tehnica presupune salvarea în memorie a rezultatelor neexplorate complet ​(toate sau cele mai "​promițătoare"​ stări), iar la următoarea rundă se vor refolosi o parte din aceste rezultate ​(cele care mai sunt posibile din noul moment al jocului). Un video cu un exemplu explicat se găsește pe link-ul [[https://​www.youtube.com/​watch?​v=Y85ECk_H3h4&​ab_channel=JohnLevine | youtube.com/​iterative_deepening]]+$muchii: { (1,2); (1,3); (2,3); (2,4);} $
  
  
-==== Complexitate ====+Răspuns: 
 +|node|1|2|3|4|5| 
 +|d|1|1|0|2|-1|
  
-Pentru a vedea complexitatea algoritmilor prezentați anterior, se vor introduce câteva noțiuni:+Explicație 
 +Graful dat este cel din figura urmatoare.
  
-  * **branch factor** ​** b ** = **numărul mediu de ramificări** pe care le are un nod neterminal (care nu e frunză) din ** arborele de soluții ** +{{pa:new_pa:lab07-bfs-example1.png}}
-  * **depth** ​**d ** = **adâncimea maximă ** pană la care se face căutarea în arborele de soluții +
-      * orice nod de adâncime d va fi considerat terminal+
  
-<​note> ​ +  * ** d[3] = 0 ** pentru că 1 este sursa 
-Un arbore cu un branching factor ​**b**, care va fi examinat până la un nivel **d** va furniza ​$b^dnoduri frunze ce vor trebui procesate (ex. calculăm scorul pentru acele noduri)+  * ** d[1] = d[2] = 1 ** pentru că există muchie directă de la 2 la fiecare nod 
 +  * ** 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>​
  
-<​spoiler ​Explicație+ 
-Nivelurile sunt notate cu $0, 1, 2, ...d+<​spoiler ​Exemplu 2
-  * nivel 0$1$   nod (radacină) +$n = 7$   $m = 7$  $source = 1$  
-  * nivel 1: $b$   ​noduri + 
-  * nivel 2: $b^2$ noduri +$muchii: { (1,2); (1,4); (2,3); (4,5); (5,6); (3,7); (7,6) } 
-  * nivel 3$b^3$ noduri ​ + 
-  * ... +Răspuns: 
-  ​nivel d: $b^d$ noduri+|node|1|2|3|4|5|6|7| 
 +|d|0|1|2|1|2|3|3| 
 + 
 +Explicație:​  
 +Graful dat este cel din figura urmatoare. 
 + 
 +{{pa:​new_pa:​lab07-bfs-example2.png}} 
 + 
 +  * ** d[1] = 0 ** pentru că 1 este sursa 
 +  * ** d[2] = d[4] = 1 ** pentru că există muchie directă de la 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$ 
 +
 +$muchii: { (1,2); (1,4); (2,3); (4,5); (5,6); (3,7); (7,6); (1, 6) } $
 +
 +Răspuns:
 +|node|1|2|3|4|5|6|7|
 +|d|0|1|2|1|2|1|2|
 +
 +Explicație: ​
 +Graful dat este cel din figura urmatoare.
 +
 +{{pa:​new_pa:​lab07-bfs-example3.png}}
 +
 +  * ** d[1] = 0 ** pentru că 1 este sursa
 +  * ** d[2] = d[4] = d[6] = 1 ** pentru că există muchie directă de la 2 la fiecare nod
 +  * ** d[3] = d[5] = d[7] = 2 ** pentru că trebuie să parcurgem 2 muchii ($1-2-3$, $1-4-5$, $1-6-7$)
 +</​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>​
 +Rezultatul se va returna sub forma unui vector **topsort** cu ** n ** elemente.
  
-  ​* **minimax/​negamax*+Vectorul ​**topsort** va reprezenta o permutare a multimii ${123,..., n}reprezentand sortarea topologica gasita. 
-      * Un algoritm **Minimax / Negamax** clasiccare analizează toate stările posibileva avea complexitatea ​$O(b ^ d)$ - deci exponențială+</​note>​
  
-  * **alpha-beta** +<spoiler Exemplu ​1> 
-      * Cât de bun este însa alpha-beta fața de un Minimax / Negamax naiv? Dupa cum s-a menționat anterior, în funcție de ordonarea mișcărilor ce vor fi evaluate putem avea un caz cel mai favorabil și un caz cel mai defavorabil. +$n = 5  ​$4
-      * ** best case **: mișcările sunt ordonate descrescător după câștig (deci ordonate optim), rezultă o complexitate +
-          * $O(b*1*b*1*b*1...de\ ​ d \ ori...b*1)$ pentru d par +
-          ​* ​$O(b*1*b*1*b*1...de \ d \ ori...b)pentru d impar +
-          * restrângând ambele expresii rezultă o complexitate ​$O(b ^ {\frac{d}{2}}) ​O(\sqrt{b^d})$ +
-          * prin urmare, într-un caz ideal, algoritmul Alpha-beta poate explora de 2 ori mai multe nivele în arborele de soluții în același timp față de un algoritm Minimax/​Negamax naiv. +
-      * ** worst case**: mișcările sunt ordonate crescător după câștigul furnizat unui jucător, astfel fiind necesară o examinare a tuturor nodurilor pentru găsirea celei mai bune mișcări. +
-          *  în acest caz complexitatea devine egală cu cea a unui algoritm Minimax / Negamax naiv. +
-  +
-   +
  
-===== Exemple ===== +$arce(1,2)(1,3)(2,3)(2,4);} $
-Dintre cele mai importante jocuri în care putem aplica direct strategia minimax, menționăm:​ +
-  * [[https://​en.wikipedia.org/​wiki/​Tic-tac-toe | X și 0]] +
-    * joc foarte simplu/​ușor (spațiul stărilor este mic).  +
-    * Prin urmare tot arborele de soluții poate fi generat și explorat într-un timp foarte scurt. +
-  * [[https://​en.wikipedia.org/​wiki/​Chess | sah ]] +
-    * joc foarte greu (spațiul stărilor este foarte mare) +
-    * minimax/​negamax simplu poate merge până la $ d = 7$ (nu reușea să bată campionul mondial la șah - campion uman) +
-    * alpha-beta poate merge până la $d = 14$ +
-    * ** [[https://​en.wikipedia.org/​wiki/​Deep_Blue_(chess_computer)| Deep Blue]]** a fost implementarea unui bot cu minimax și alpha-beta care a bătut în 1997 campionul mondial la șah (Gary Kasparov)+
-  * [[https://​en.wikipedia.org/​wiki/​Ultimate_tic-tac-toe | Ultimate tic-tac-toe]] +
-    * varianta mult mai grea de X și 0 (spațiul stărilor foarte mare) +
-    * s-a dat la proiect PA 2016 :D +
-  * [[https://​en.wikipedia.org/​wiki/​Go_(game| Go]] +
-    * soluțiile se bazează pe Monte Carlo Tree Search (nu pe minimax) +
-    * [[https://​en.wikipedia.org/​wiki/​AlphaGo|AlphaGo]] este botul cel mai bun pe tabla de 19x19+
  
  
-===== Exerciții =====+Răspuns: $topsort ​[1, 2, 3, 4, 5] $
  
-În lab06 nu există exerciții propriu-zise.+Explicație:  
 +Graful dat este cel din figura următoare.
  
-Task-uri:+{{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 
 +  * 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
  
-* Parcugeți teoria din acest laborator împreună cu asistentul. +Toate sortările topologice valide sunt: 
-  * Trebuie să întelegeți și să comparați ​**Negamax** vs **Negamax cu Alpha-beta pruning**. +  * cele date de ordinea relativa a primelor 4 noduri: 1,2,3,4 
-  * Indicație: Desenați exemplul din videoclipul de pe YouTube din sectiunea ​de **Precizări inițiale**.  ​ +    ​$topsort = [1, 2, 3, 4, 5] $ 
-Discutați și comparați euristici pentru: +    ​$topsort = [1, 2, 3, 5, 4] $ 
-  șah +    ​$topsort = [1, 2, 5, 3, 4] $ 
-  table+    ​$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] $
  
-===== Referințe ===== +</spoiler>
-[0] [[http://​en.wikipedia.org/​wiki/​Minimax | wikipedia.org/​Minimax]]+
  
-[1] [[http://​en.wikipedia.org/​wiki/​Negamax | wikipedia.org/​Negamax]] 
  
-[2] [[http://​en.wikipedia.org/​wiki/​Alpha-beta_pruning | wikipedia.org/​Alpa-beta_pruning]]+<spoiler Exemplu ​2
 +$n = 9$   $m = 8$ 
  
-[4] [[https://​en.wikipedia.org/​wiki/​Monte_Carlo_tree_search | wikipedia.org/​Monte_Carlo_tree_search]]+$arce{  
 +(1,2); 
 +(1,3); 
 +(3,4); 
 +(3,5); 
 +(5,9); 
 +(4,6); 
 +(4,7); 
 +(4,8); 
 +} $
  
-[5] [[https://​en.wikipedia.org/​wiki/​MTD-f] | wikipedia.org/​MTD-f]] 
  
-[6] [[https://​www.chessprogramming.org/​Null_Window | chessprogramming.org/​Null_Window]]+Răspuns: $topsort = [1, 2, 3, 4, 6, 7, 8, 5, 9$
  
-[7] [[https://www.chessprogramming.org/​Principal_Variation | chessprogramming.org/​Principal_Variation]]+Explicație 
 +Graful dat este cel din figura următoare.
  
-[8] [[https://​www.chessprogramming.org/Iterative_Deepeningchessprogramming.org/Iterative_deepening]]+{{pa:​new_pa:​lab07-topsort-example2.png}} 
 + 
 +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]]. 
 + 
 +**B2** Rezolvați problema [[https://​infoarena.ro/​problema/​muzeu| muzeu]] pe infoarena. 
 + 
 +=== Extra === 
 + 
 +<spoiler arbore3>​ 
 +Rezolvați problema [[https://​infoarena.ro/​problema/​arbore3 
 +| arbore3]] pe infoarena.  
 +</​spoiler>​ 
 + 
 +<spoiler Pokemon GO AWAY> 
 +Rezolvați problema ​[[https://​www.hackerrank.com/contests/​test-practic-pa-2017-v2-meeseeks/​challenges/​test-2-pokemon-go-away-greaPokemon GO AWAY]] de la test PA 2017. 
 + 
 +Cu ce problemă seamana? 
 +</spoiler>​ 
 + 
 +<spoiler insule>​ 
 +Rezolvați problema [[https://​infoarena.ro/​problema/​insule 
 +| insule]] pe infoarena. 
 +</​spoiler>​ 
 + 
 +<spoiler tsunami>​ 
 +Rezolvați problema [[https://​infoarena.ro/​problema/​tsunami 
 +| tsunami]] pe infoarena. 
 +</​spoiler>​ 
 + 
 + 
 +<spoiler berarii2>​ 
 +Rezolvați problema [[https://​infoarena.ro/​problema/​berarii2 
 +| berarii2]] pe infoarena. 
 +</​spoiler>​ 
 + 
 + 
 + 
 + 
 +===== Referințe =====
  
-[9[[https://​www.aaai.org/​Papers/​AIIDE/​2008/​AIIDE08-036.pdf | Monte Carlo Tree Search]]+[0Chapter **Elementary Graph Algorithms**,​ “Introduction to Algorithms”,​ Thomas HCormen, Charles ELeiserson, Ronald LRivest and Clifford Stein
  
 +[1] [[https://​en.wikipedia.org/​wiki/​Breadth-first_search]]
  
 +[2] [[https://​en.wikipedia.org/​wiki/​Depth-first_search]]
  
 +[3] [[https://​en.wikipedia.org/​wiki/​Topological_sorting]]
  
pa/laboratoare/laborator-06.1618268906.txt.gz · Last modified: 2021/04/13 02:08 by radu.nichita
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