Table of Contents

Hard Problems

În cadrul acestui laborator vom analiza suita de probleme NPC (nondeterministic polynomial-time complete) despre care se va discuta la cursurile și laboratoarele următoare. Lăsând pentru o secundă formalitățile deoparte, NP reprezintă, în linii mari, clasa de probleme cu „soluții ușor verificabile”. Toți algoritmii noștri vor urma același model: vor genera o listă a tuturor posibilelor soluții, apoi vor verifica dacă o anumită soluție îndeplinește criteriile cerute de problemă. Dacă oricare dintre candidați îndeplinește aceste criterii, algoritmul furnizează un răspuns pozitiv; dacă niciun candidat nu reușește, algoritmul furnizează un răspuns negativ. Așadar, putem împărți algoritmul în două secțiuni:

În cadrul laboratorului, ne vom focusa atenția pe cea de-a doua parte.

Probleme cu Grafuri

Înainte de a trece la examinarea unor probleme pe grafuri, este util să recapitulăm ce sunt grafurile.

Grafurile sunt considerate a fi perechi $ G = (V, E)$, unde $ V$ reprezintă un set de noduri, iar E un set de muchii. Fiecare nod este identificat printr-un număr de la 1 la $ |V|$. O muchie este o pereche de noduri. De obicei, ne referim la grafurile neorientate, unde o muchie este o pereche neordonată.

Vom utiliza matricele de adiacență pentru a reprezenta grafurile. O matrice de adiacență este o tabelă de dimensiune $ |V|$ pe $ |V|$, unde nodurile indexează atât liniile, cât și coloanele; intrarea $ i$ , $ j$ a matricei este o valoare booleană care arată dacă $ (i, j) ∈ E$. Pentru grafurile neorientate, matricea este simetrică în raport cu diagonala principală.

Matricea de adiacență în sine conține toate informațiile necesare pentru a descrie graful, astfel încât poate servi ca intrare pentru algoritmii noștri.

1. K Vertex Cover

Dându-se un graf neorientat $ G = (V, E)$ și un număr întreg pozitiv $ K$, se cere găsirea unui subset $ V' ⊆ V$, cu $ |V'| = K$, astfel încât fiecare muchie din $ E$ să aibă cel puțin unul dintre capete în $ V'$.

În termeni simpli, problema K Vertex Cover implică găsirea unui subset de $ K$ noduri astfel încât fiecare muchie a grafului să aibă cel puțin unul dintre capete în acel set.


2. K Clique

Dându-se un graf neorientat $ G = (V, E)$ și un număr întreg pozitiv $ K$, se cere găsirea unui set $ V' ⊆ V$, cu $ |V'| = K$, astfel încât toate nodurile din $ V'$ sunt conectate între ele prin muchii din $ E$. În termeni simpli, problema K Clique implică găsirea unui set de $ K$ noduri astfel încât oricare două noduri din acest set sunt conectate printr-o muchie (orice nod e conectat cu orice nod).


3. K Coloring

Dându-se un graf neorientat $ G = (V, E)$ și un număr întreg pozitiv $ K$, se cere să se determine dacă este posibil să se atribuie culori nodurilor astfel încât nicio pereche de două noduri adiacente să nu aibă aceeași culoare, iar numărul total de culori utilizate să fie cel mult $ K$.


4. Hamilton Path

Dându-se un graf orientat $ G = (V, E)$, se cere să se determine dacă există un drum Hamiltonian în graful respectiv. Un drum Hamiltonian este un drum care vizitează fiecare nod exact o dată.


5. K Cut

Dându-se un graf $ G = (V, E)$ și un număr $ K$, există o împărțire a nodurilor în două mulțimi astfel încât să existe $ K$ muchii cu câte un capăt în fiecare mulțime?

Formule Satisfiabile

O formulă booleană se referă la o expresie logică formată din variabile booleene (care pot avea valorile adevărat sau fals) și operații logice precum conjuncție (AND), disjuncție (OR), și negație (NOT).

6. SAT

Primind ca intrare o formulă booleană, obiectivul problemei SAT este să se determine dacă există o atribuire a valorilor adevărat sau fals pentru variabilele booleane astfel încât întreaga formulă să fie evaluată la adevărat.


7. CNF SAT

Asemănător, verifică dacă formula booleană primită ca input este satisfiabilă. Diferența constă în faptul că formula booleană este dată în forma conjunctivă normală, adică este scrisă sub forma unei conjuncții de clauze, unde o clauză este o disjuncție de literali. Un literal este fie o variabilă booleană, fie negația unei variabile booleene.


8. 3SAT

Adăugăm încă o restricție și anume că numărul de literali dintr-o clauză este egal cu 3.


Probleme NPC cu mulțimi de numere

Pentru urmatoarele probleme, propuneți o variantă de pseudocod ce verifică validitatea unui candidat.

9. Subset Sum

Dându-se un set de numere întregi $ S$ și un număr întreg $ K$, problema Subset Sum solicită determinarea dacă există un subșir $ A$ al lui $ S$ astfel încât suma elementelor din $ A$ să fie egală cu $ K$. Cu alte cuvinte, este posibil să se găsească o submulțime a numerelor din $ S$ astfel încât suma acestora să fie exact $ K$?

10. Partitioning

Dându-se un set $ S$ de numere întregi, se cere determinarea dacă setul poate fi împărțit în două submulțimi $ S_1$ și $ S_2$ astfel încât suma elementelor din $ S_1$ să fie egală cu suma elementelor din $ S_2$. Cu alte cuvinte, se caută o împărțire a setului în două submulțimi cu sume egale.

11. Set Cover

Date de intrare: Un univers $ U$ și o colecție $ S$ de submulțimi ale lui $ U$.
Problemă: Există o subcolecție $ S'$ a lui $ S$ astfel încât fiecare element din $ U$ este acoperit de cel puțin un subset din $ S'$?
Verificare: Verificarea constă în a asigura că fiecare element din $ U$ este acoperit de cel puțin un subset din $ S'$.

BONUS: Programare Dinamică și Pseudopolinomiale

Programarea dinamică este o tehnică de proiectare a algoritmilor care constă în rezolvarea unei probleme prin împărțirea acesteia în subprobleme mai mici și rezolvarea fiecăreia dintre acestea doar o dată, stocând rezultatele pentru a evita recalcularea lor ulterioară. Această abordare este eficientă în rezolvarea problemelor ce pot fi descompuse în subprobleme overlapped sau care prezintă o structură de optimalitate.

Prin memorarea soluțiilor intermediare într-o tabelă sau altă structură de date, programarea dinamică ajută la reducerea complexității temporale și evită recalcularea anumitor subprobleme, contribuind la îmbunătățirea performanței algoritmilor.

12. Knapsack

Avem un rucsac cu o capacitate maximă dată (exprimată într-o anumită unitate, de exemplu, greutate), și un set de obiecte, fiecare având o valoare și o greutate specifică. Scopul este să determinăm cum să umplem rucsacul astfel încât să maximizăm valoarea totală a obiectelor, având în vedere restricția de capacitate a rucsacului. Matematic, dacă notăm:

$ n$ - numărul total de obiecte disponibile,
$ v_i$ - valoarea obiectului $ i$,
$ w_i$ - greutatea obiectului $ i$,
$ W$ - capacitatea maximă a rucsacului,

atunci problema Knapsack poate fi formulată astfel:

Maximizăm: $ ∑_{i=1}^n (v_i x_i)$

Sub restricția: $ ∑_{i=1}^n (w_i x_i)≤W$

unde $ x_i$ este o variabilă binară ce indică dacă obiectul $ i$ este sau nu inclus în rucsac. Adică, $ x_i = 1$ dacă obiectul i este inclus și $ x_i = 0$ în caz contrar.

Soluțiile acestui laborator se găsesc aici