Problema satisfiabilității

Mihai Budiu -- mihaib+@cs.cmu.edu
http://www.cs.cmu.edu/~mihaib/

18 august 1999

Subiect:
problema satisfiabilității boolene, aplicațiile ei și soluții practice
Cunoștințe necesare:
cunoștințe elementare despre funcționarea calculatoarelor; funcții boolene
Cuvinte cheie:
boolean, satisfiabilitate, clasele P și NP, algoritm, verificare a soluției


Cuprins




Articolul de față atacă o problemă foarte importantă în informatică, din mai multe puncte de vedere (din mai multe puncte de vedere este atît importanța cît și atacul meu). Problema aceasta este extrem de importantă pentru practicieni, pentru că apare în extrem de multe contexte în viața ``reală'', sau foarte multe alte probleme reale se pot reduce la ea (definim precis noțiunea de ``reducere'' undeva mai jos).

Problema este însă și mai importantă pentru teoreticieni, din mai multe motive. În primul rînd, nu se cunoaște nici o soluție eficace pentru această problemă, în cazul ei cel mai general. Toate metodele de calcul sunt extrem de costisitoare, făcînd imposibilă rezolvarea unor instanțe de dimensiuni mari. Pe de altă parte, complexitatea algoritmilor cunoscuți îndepărtează speranța că vreodată vom avea suficiente resurse pentru a rezolva instanțe mari ale acestei probleme; chiar creșteri ale performanței calculatoarelor de milioane de ori ar permite creșterea numărului de variabile din cu doar cîteva zeci de unități (iar problemele practice pot avea zeci de mii de variabile).

În fine, mult mai interesant, problema aceasta este într-un anume sens ``cea mai grea problemă din clasa ei de probleme''. Sunt extrem de multe alte probleme practice care sunt echivalente, sau mai ușoare, decît această problemă. Teoreticienii au demonstrat că dacă, putem soluționa problema de față în mod eficient, atunci toate celelalte probleme echivalente sau mai ușoare se pot rezolva extrem de eficient, în mod imediat. O consecință surprinzătoare a acestui fapt ar fi că criptografia ar fi inexistentă, pentru că orice metodă de criptare ar putea fi spartă în mod eficient.

În fine, problema aceasta are implicații interesante pentru filozofie, pentru că, într-un anume sens, ne ajută să măsurăm diferența dintre geniu și normalitate, dintre creativitate și simpla înțelegere pasivă. Existența unei soluții eficace pentru această problemă ar avea de pildă consecința surprinzătoare că pentru fiecare teoremă din matematică există o metodă foarte eficace de a o demonstra cu calculatorul; asta ar transforma întreaga muncă de creație a matematicienilor într-o treabă de rutină, pe care o poate face o mașină de calcul.

Problema aceasta este piatra de temelie a unei ramuri a informaticii numită ``teoria complexității'', căreia intenționez să-i consacru în viitor un întreg articol. Existența unei soluții eficace pentru problema din acest articol este considerată aproape unanim cea mai importantă problemă nerezolvată din știința calculatoarelor.

Și acum, că v-am trezit curiozitatea, să purcedem să vedem cum se înlănțuie toate aceste fapte.

Probleme, instanțe și algoritmi

Trebuie dintru început să facem distincție dintre o clasă de probleme și instanțele problemei. O clasă de probleme este o colecție de instanțe înrudite.

În general, un program pentru calculator (un algoritm) rezolvă orice instanță dintr-o clasă. De exemplu, în procesorul Pentium se află o unitate aritmetică folosită pentru a efectua adunări. Această unitate aritmetică rezolvă următoarea clasă de probleme: ``care este rezultatul adunării a două numere mai scurte de 32 de biți?'' O instanță a acestei probleme este ``cît face 3+7?''. Observați că unitatea aritmetică poate rezolva orice instanță. Nu ar fi foarte folositor dacă ar putea rezolva numai întrebarea ``cît e 3+7?''.

În general nu avem nevoie să rezolvăm chiar toate instanțele, dar pentru că e prea complicat să construim mașini speciale pentru fiecare instanță (și nu prea practic), facem mașini care rezolvă clase întregi de probleme (adică pot rezolva orice instanță).

Observați că clasa de probleme: ``fiind dat un număr n, care este rezultatul adunării a oricare două numere de n biți'' este o clasă mai largă decît cea rezolvată de Pentium, pentru care n=32. Putem scrie un program care să calculeze în principiu suma oricăror două numere, oricît de lungi ar fi ele. Cum arată programul, am învățat cu toții în școala primară, folosind tabla adunării. Folosind această tablă putem aduna numere oricît de lungi (dacă nu obosim).

În general, teoreticienii studiază astfel de clase de probleme, în care ``dimensiunea'' datelor de intrare nu este limitată apriori de nici o valoare. Ei sunt interesați să afle dacă putem scrie programe care să funcționeze și atunci cînd nu se cunoaște această dimensiune dinainte.

Este de altfel o idee foarte bună ca algoritmii pe care îi implementați să fie proiectați în așa fel încît să poată trata probleme oricît de mari. Orice limitare arbitrară impusă datelor de intrare se poate dovedi extrem de neplăcută mai tîrziu; în definitiv din asta a și provenit celebrul bug Y2K: programatorii nu au permis programelor să manipuleze decît date într-un interval de 100 de ani, ceea ce are consecințe economice uriașe la ora actuală (companiile cheltuiesc acum zeci de miliarde de dolari să descopere și corecteze astfel de imperfecțiuni).

Definim un algoritm astfel: o procedură de calcul efectiv, care rezolvă orice instanță dintr-o anumită clasă de probleme. Nu uitați că întotdeauna algoritmul este legat de clasă; pentru clase diferite putem avea algoritmi diferiți, chiar dacă clasele sunt înrudite. De exemplu, pentru clasa de probleme: ``care este suma a două numere mai mici ca 100?'' putem face un algoritm foarte simplu, care pre-calculează toate sumele posibile, după care caută rezultatul într-o tabelă bidimensională de 100*100 de numere. Desigur, algoritmul risipește o mulțime de spațiu, dar răspunde practic instantaneu. Este clar că acest algoritm nu se poate generaliza pentru a face orice adunare, în care lungimea numerelor nu este limitată dinainte.

Problema satisfiabilității boolene

Voi introduce acum clasa de probleme care constituie subiectul principal al acestui articol. Pentru început însă voi trece în revistă rapid funcțiile boolene, pentru că problema noastră este legată de ele.

Funcții boolene

Georges Boole a trăit între 1815 și 1864. În istoria științei el este cunoscut mai ales prin rezultatele sale matematice, ca creator al algebrei boolene (numită, evident, în cinstea sa). Aceasta este o algebră extrem de simplă, care operează cu numai două valori, 0 și 1.

Dar Boole este extrem de important și în istoria filozofiei, pentru că algebra lui a fost creată în încercarea de a formaliza în limbajul matematicii logica aristotelică clasică. Georges Boole este considerat de unii părintele logicii matematice. Boole era interesat să reformuleze legile gîndirii umane în termeni matematici.

Algebra booleană operează cu valori de adevăr, din care avem numai două: adevărat și fals. Orice variabilă are la un moment dat exact una dintre aceste două valori. 0 este folosit pentru a denota falsul, iar 1 pentru adevărat.

Boole a introdus anumite operații pentru manipularea valorilor boolene: operațiile de ``și'' logic ($\land$), ``sau'' logic ($\lor$) și negație logică ($\lnot$). (Putem introduce și alte operații, dar acestea sunt cele mai importante.)

Putem combina valori boolene folosind aceste operații, și putem calcula valoarea de adevăr a unor propoziții complicate într-un mod foarte limpede.

De exemplu, dacă avem două propoziții logice oarecare, a și b, putem vorbi de propoziția $a \land b$ (a și b), care este adevărată numai cînd ambele propoziții sunt adevărate. Modul de operare al lui $\land$ poate astfel fi descris în același fel ca cel al operațiilor aritmetice obișnuite, cu o tabelă (cum e tabla adunării), așa cum arată figura 1.

De exemplu, dacă avem propozițiile ``Afară plouă'' și ``E ora 7'', propoziția formată din conjuncția acestora (adică legarea celor două cu ``și'') este ``E ora 7 și afară plouă''. În mod evident, această propoziție este adevărată doar dacă ambele enunțuri mai simple sunt adevărate, justificînd tabela operației ``și''.


Tabela 1: Tabelele operațiilor logice fundamentale.
Operația ``și''
$\land$ 0 1
0 0 0
1 0 1
 
Operația ``sau''
$\lor$ 0 1
0 0 1
1 1 1
 
Operația ``nu''
$\lnot$ 0 1
  1 0


O altă operație logică este cea numită ``sau'': rezultatul este adevărat dacă măcar una dintre valorile combinate este adevărată1.

În fine, avem operația de negare (``nu''), care inversează valoarea de adevăr a unei propoziții2.

Operațiile boolene sunt asociative și comutative. Putem astfel scrie prescurtat formule boolene complicate, de genul:


\begin{displaymath}(a \land b \land c) \lor (\lnot b \land c) \lor \lnot a.\end{displaymath}

Algebra booleană este enorm de importantă în calculatoare; componentele electronice de bază din care este construit un calculator sunt ``porțile logice'', care implementează exact aceste operații, manipulînd curenți electrici pentru a reprezenta valori boolene (anumite potențiale fiind alese în mod convențional pentru a reprezenta valorile 0 și 1). Altfel de dispozitive sunt foarte ușor de construit din tranzistoare, care apoi se pot construi la o scara minusculă, înghesuind milioane pe o pilulă de siliciu.

SAT

Dacă ni se dă o formulă booleană și o serie de valori pentru variabilele care o compun, putem scrie un algoritm foarte simplu și eficient care evaluează formula. Acesta este chiar algoritmul bine-cunoscut folosit pentru evaluarea expresiilor aritmetice, cu diferența că operează doar cu valori boolene.

Acum suntem în măsură să formulăm problema care ne interesează, numită problema satisfiabilității, (satisfiability), pe scurt SAT: ``fiind dată o formulă booleană, există o atribuire a variabilelor care o compun care face formula adevărată?''.

Reduceri între probleme

Îmi aduc aminte de un banc: ``Cică un inginer și un matematician dimineața își făceau ceaiul, aplicînd următorul algoritm: <<Ia ibricul, umple-l cu apa, pune-l pe foc, fierbe apa, pune plicul, toarnă în cană>>. Dar într-o zi, nevestele lor le lasă ibricul cu apă înauntru. Ce fac cei doi? Ei bine, inginerul aplică algoritmul de la al doilea pas: pune-l pe foc, fierbe apa, pune plicul, toarnă în cană. În schimb matematicianul varsă apa în chiuvetă și reduce problema la cea precedentă.''

Bancul este amuzant (găsesc eu), dar conceptul de reducere este cu adevărat util. Cel puțin în calculatoare, are extrem de multe aplicații. Aici ne vom uita la doar una dintre ele.

Adesea putem abstractiza o problemă și o putem enunța într-un alt fel; de fapt, chiar pentru a rezolva probleme practice, în general le abstractizăm în termeni matematici, după care aplicăm tehnici din domeniul matematicii.

Figura 1 ilustrează noțiunea de reducere pentru probleme în teoria complexității.

Ei bine, se întîmplă că problema SAT este o problemă extrem de expresivă; putem enunța o mulțime de alte probleme sub forma SAT.

Figura 1: O reducere între problema P1 și problema P2 transformă fiecare instanță a unei probleme din P1 într-o problemă din P2. Nu orice instanță din P2 este neapărat rezultatul transformării unei instanțe din P1 (partea nehașurată în figură). Reducerea este deci o funcție nu neapărat surjectivă. Reducerea păstrează proprietatea că fiecare instanță din P1 pentru care soluția este ``da'' este transformată într-o instanță din problema P2 pentru care soluția este tot ``da'' și invers (adică instanțele ``nu'' devin rămîn ``nu'' după transformare).
\begin{figure}\centerline{\epsfxsize=6cm\epsffile{reducere.eps}}\end{figure}

O reducere

Voi ilustra aici aici o reducere foarte simplă, între o problemă numită ``colorarea unei hărți cu două culori'' și SAT.

Să presupunem că avem o hartă pe care vrem să o colorăm în așa fel încît oricare două țări alăturate să aibă culori diferite.

Asociem fiecărei țări o variabilă booleană. Apoi pentru fiecare vecinătate dintre două țări creăm o clauză3: dacă țara A este vecină cu țara B, atunci creăm clauza $A \land
\lnot B$. Vom folosi valoarea 0 pentru a denota o culoare, și valoarea 1 pentru cealaltă. Formula de mai sus spune că țările A și B nu pot avea aceeași culoare simultan, pentru că dacă variabilele A și B au aceeași valoare, atunci clauza de mai sus va fi falsă.

Formula booleană pe care vrem s-o satisfacem este conjuncția tuturor acestor clauze.

Dacă formula este satisfiabilă, culorile țărilor corespund valorilor variabilelor boolene respective. Dacă formula nu este satisfiabilă, atunci nici problema originală nu are soluție.

Deci dacă rezolvăm problema satisfiabilității, atunci putem rezolva și problema colorării unei hărți cu două culori. Aceasta este deci o reducere de la una la cealaltă.

Probleme reductibile la SAT

Iată niște exemple de probleme cu însemnătate foarte mare pentru activitatea umană, care se pot reduce la SAT cu ușurință:

Și lista ar putea continua cu multe alte probleme.

Vedeți, faptul că aceste probleme sunt toate reductibile la SAT ne spune două lucruri:

  1. Dacă rezolvăm SAT4, am rezolvat imediat toate aceste probleme într-un mod foarte eficace;

  2. Dacă nu putem rezolva SAT, nu e neapărat ca aceste probleme să nu aibă o soluție eficace.

Din păcate, vom vedea puțin mai jos că speranțele noastre de a rezolva aceste probleme sunt foarte reduse.

Complexitatea algoritmilor și a problemelor; clasele P și NP

O soluție simplă

Aparent SAT este o problemă banală, și algoritmul pentru a o rezolva este foarte simplu:

  1. Parcurgem formula și facem o listă cu toate variabilele care apar;

  2. După aceea punem toate variabilele pe ``0'' și evaluăm formula. Am văzut că asta putem face destul de repede;

  3. Dacă formula dă ca rezultat ``1'' am terminat rezolvarea, și putem da răspunsul ``da'';

  4. Dacă nu, facem prima variabilă 1 și o luăm de la pasul 3;

  5. Mergem tot înainte, încercînd toate combinațiile posibile de valori; (în general, dacă ne uităm la un contor în baza 2 cu atîtea cifre cîte variabile avem, la fiecare pas o variabilă are valoarea bitului corespunzător din contor.)

Într-adevăr, algoritmul de mai sus este corect; dacă există o distribuție de valori pentru care formula este adevărată, o va găsi. Dacă nu este nici una, atunci va da corect răspunsul ``nu'' odată ce a examinat toate posibilitățile. Acest algoritm este corect, dar nu este și eficient. Dacă nu credeți acest lucru, atunci încercați să executați algoritmul pentru o formulă cu 64 de variabile.

Din modul în care am expus algoritmul deficiența este vizibilă: dacă avem k variabile, atunci trebuie să incrementăm de la 0 la valoarea maximă un contor cu k cifre. Ori acest contor trebuie incrementat de 2k ori. Pentru 64 de pildă putem aproxima 210 = 103, deci 264 = 1018 * 16 = 1019. Dacă un calculator execută o instrucțiune la o nanosecundă (adică are un ceas de un gigahertz), îi vor trebui 1019/109 = 1010 secunde pentru a efectua doar incrementarea. Un an are cam 3 * 107 secunde, deci durata calculului ar fi de circa 30 de ani!

Dacă asta vi se pare o bagatelă, atunci încercați să rezolvați o problemă doar puțin mai mare, să zicem cu 70 de variabile (10% mai mare). Ei bine, asta o să dureze de 64 de ori mai mult, adică 1800 de ani!

Desigur, timpul acesta de rulare va fi petrecut doar dacă formula pe care o verificați nu este satisfiabilă, pentru că atunci contorul trebuie să treacă prin toate valorile.

Complexitatea unui algoritm

Teoria complexității măsoară timpul de execuție al unui algoritm ca o funcție de cantitatea de date oferite spre prelucrare. Dacă măsurăm formula SAT de la intrare după numărul de variabile n, atunci algoritmul de mai sus se poate executa pentru o durată proporțională cu 2n, pentru anumite formule.

Cea mai ades folosită metodă de măsurare a complexității unui algoritm consideră cel mai defavorabil caz posibil. De exemplu, printre formulele SAT de lungime 2 exista multe care se pot rezolva imediat, pentru că din prima încercare putem spune că formula este satisfiabilă (de exemplu formula $a \lor \lnot a$). Dar există cel puțin o formulă de lungime 2 pentru care algoritmul trebuie să facă toate cele 4 încercări posibile. Din cauza aceasta, spunem că complexitatea algoritmului de mai sus este de ordinul 2n, unde în mod implicit cu n se notează dimensiunea datelor de intrare5.

Vedeți, am spus că complexitatea este ``proporțională cu'', și nu ``exact'' 2n. Această caracterizare este destul de precisă, însă, pentru că, indiferent care este coeficientul de proporționalitate, (care de altfel depinde de viteza procesorului și de alți factori), modul în care acest algoritm se comportă pentru probleme foarte mari este același: este complet ne-practic.

Teoria complexității consideră că orice clasă de probleme pentru care complexitatea unui algoritm este proporțională cu un o funcție polinomială este eficace; prin contrast, atunci cînd complexitatea este proporțională cu o funcție exponențială (ca mai sus), problema este considerată nerezolvabilă.

Pentru ilustrație, algoritmii cei mai buni care sortează în ordine crescătoare un șir de n numere, au un timp de execuție proporțional cu n log n, care este o funcție mai mică decît polinomul n2 (pentru că funcția logaritmică crește mai încet decît orice polinom). Problema sortării are deci o soluție eficace.

Complexitatea unei probleme

Deci algoritmul de mai sus pentru SAT nu este prea eficace, cel puțin dacă avem intenția de a rezolva probleme foarte mari. Înseamnă asta că problema este practic nerezolvabilă?

Desigur, nu. Dacă am face această afirmație, ar fi ca și cum am zice că nu putem ajunge la Ploiești de la București decît după 10 zile, pentru că așa am ajuns la un moment dat mergînd prin Japonia.

Faptul că avem un algoritm lent pentru o problemă nu înseamnă că problema este grea. Poate există un alt algoritm, care rezolvă problema mult mai bine!

Algoritmul de mai sus nu exploatează în nici un fel înfățișarea formulei. De pildă, dacă formula conține următoarele două clauze: a și $\lnot a$, putem spune imediat că formula nu este satisfiabilă, pentru că, orice valoare ar avea a, și independent de valorile tuturor celorlalte variabile, rezultatul formulei va fi tot 0.

E adevărat că putem scrie tot felul de algoritmi mai inteligenți, dar pînă la ora actuală nimeni nu a reușit să găsească un algoritm eficace pentru SAT, care, pentru orice instanță a problemei să ofere răspunsul într-un timp mai scurt decît cel exponențial.

Mai mult decît atît, sunt dovezi foarte convingătoare (dar nu și o demonstrație exactă) că SAT nu admite o soluție eficientă. Poate să pară intuitiv clar că SAT are nevoie de foarte mult timp, dar nu există nici demonstrația inversă, cum că SAT nu poate fi rezolvată rapid.

NP

``Ei, și ce?'' o sa întrebați. Cine are nevoie de SAT? Din păcate, foarte multă lume.

Am văzut mai sus că foarte multe probleme se pot reduce la SAT. Asta înseamnă că dacă am găsi o soluție eficientă pentru SAT, am putea rezolva eficace și aceste probleme practice.

Toate problemele de mai sus au o trăsătură foarte interesantă: nu știe nimeni cum să le găsească o soluție, dar de îndată ce cineva ne-ar da un răspuns pentru o instanță, am putea verifica foarte repede dacă acela este răspunsul corect.

Teoria complexității definește astfel o mulțime de probleme numită NP (de la Nedeterminist-Polinomial, o denumire tradițională, care are o justificare despre care nu vom discuta acum): clasa NP este compusă din toate problemele pentru care putem verifica foarte eficient dacă un anumit răspuns este o soluție corectă (eficient înseamnă, din nou, că verificarea durează un timp polinomial în mărimea problemei pe care o avem de rezolvat). SAT și toate problemele de mai sus fac parte din NP.

Se definește de asemenea clasa tuturor problemelor pentru care știm să calculăm răspunsul exact într-un timp scurt, polinomial în lungimea datelor de la intrare. Această clasă este denumită simplu, P6.

De exemplu, problema circuitului hamiltonian este în clasa NP pentru că, dacă cineva ne dă o listă de orașe putem verifica foarte rapid dacă această listă formează sau nu un circuit hamiltonian. Pentru a face asta verificăm că fiecare oraș apare o singură dată în listă, că toate orașele de pe hartă apar, și că între fiecare două orașe consecutive din listă chiar există un drum direct. Prin definiție deci, această problemă este în clasa NP. Nimeni nu cunoaște însă un algoritm eficient pentru a decide dacă un ciclu hamiltonian există, deci nu știm dacă această problemă este în clasa P.

Ei bine, toate problemele din P fac parte și din clasa NP. Asta pentru că dacă ni se dă răspunsul la o problemă din P, pentru a verifica dacă este corect nu facem decît să executăm algoritmul eficace pentru a găsi soluția, și să comparăm soluția oferită cu cea calculată. Figura 2 rezumă starea cunoștințelor noastre la ora actuală.

Figura 2: Știm că toate problemele din clasa P, care se pot rezolva în timp polinomial, sunt de asemenea în clasa NP, adică există metode pentru a verifica corectitudinea unei ipotetice soluții în timp polinomial. Pe de alta parte nu se știe dacă există probleme în NP care nu sunt în P (adică dacă partea hașurată este vidă sau nu).
\begin{figure}\centerline{\epsfxsize=6cm\epsffile{np.eps}}\end{figure}

Teorema lui Cook

Ar fi minunat dacă am putea găsi soluții eficiente pentru toate problemele care sunt în NP, mai ales că multe dintre ele sunt probleme de o mare importanță economică și practică.

În 1971 Stephen Cook a demonstrat7 o teoremă celebră, care arată că orice problemă din NP se reduce la SAT. Am văzut mai sus că anumite probleme sunt reductibile la SAT; Cook a arătat printr-un argument ingenios că orice altă problemă am avea (chiar cele ne-formulate încă), dacă putem verifica soluțiile acelei probleme într-un mod eficace (deci dacă problema este în clasa NP), atunci problema este reductibilă la SAT.

Asta înseamnă că, dacă ni se dă o instanță a unei probleme oarecare din NP, putem construi în mod automat o instanță a unei probleme SAT. Construcția aceasta însăși se poate face într-un mod foarte eficace (adică putem face construcția în timp polinomial). Construcția garantează faptul că orice soluție a problemei SAT construite corespunde unei soluții a problemei reale. Astfel, putem acum rezolva problema SAT în locul problemei originale, și apoi putem ``decodifica'' răspunsul.

Ce ne spune teorema lui Cook?

Ne spune în primul rînd că SAT este problema ``cea mai importantă'' din clasa NP, pentru că, dacă o putem rezolva eficient, putem rezolva toate celelalte probleme din NP. De fiecare dată cînd găsim o problemă în NP, nu avem decît să facem o reducere a problemei la SAT, să rezolvăm problema SAT, și apoi să convertim înapoi răspunsul în termenii problemei originare. Acest lucru este întotdeauna posibil.

Pe de altă parte, dificultatea găsirii răspunsului la problema originară este clar mai mică decît dificultatea rezolvării problemei SAT (avînd un răspuns la SAT obținem un răspuns la problema originară, dar nu neapărat și invers).

Putem deci considera în acest sens SAT ca fiind cea mai grea problemă din NP. Din aceste motive, SAT este numită o problemă NP-completă. Acesta este un rezultat deosebit de important, pentru că ne spune că mai curînd vom găsi algoritmi eficienți pentru oricare altă problemă din NP decît pentru SAT. Pentru că foarte multe din celelalte probleme sunt încă nerezolvate, nu sunt prea multe șanse să rezolvăm SAT.

Deci SAT este cea mai grea problemă. Partea proastă este că în decursul vremii oamenii au reușit să arate că există o sumedenie de alte probleme din NP care... sunt mai grele ca SAT!

Pentru a demonstra că o problemă este ``mai grea'' ca SAT nu trebuie decît să facem lucrul invers: să reducem pe SAT la acea problemă. Asta ne permite să codificăm orice instanță SAT ca pe o instanță a acelei probleme, și ne permite să rezolvăm SAT punînd întrebări despre instanțe ale celeilalte probleme.

Dacă o problemă este mai grea decît SAT, din moment ce SAT este deja cea mai grea problemă, ajungem la concluzia că acea problemă este la fel de grea ca SAT; dificultatea ambelor probleme este aceeași. Cele două probleme sunt practic echivalente.

Demonstrarea că o problemă este mai grea decît SAT nu este în general o treabă ușoară. Cu toate acestea, toate problemele enunțate în lista de mai sus (circuitul hamiltonian, problema planificării, problema rucsacului, etc.), repet, probleme de o deosebită importanță practică, sunt demonstrate a fi mai grele decît SAT.

Toate aceste probleme sunt deci la rîndul lor NP-complete!

Va urma

Închei aici prima parte a prezentării mele despre SAT; acesta este partea cea mai tehnică, deși evită orice demonstrație. În realitate teorema lui Cook nu este prea complicată, și în afară de noțiunile expuse în acest text nu mai este nevoie de mare lucru pentru a o înțelege. Cititorul interesat este trimis de pildă la cartea lui Papadimitriou ``Computational Complexity'', sau la cartea lui Weinker & Davis ``Computability, Complexity and Languages''.

În partea a doua a acestui text (în numărul viitor) voi discuta despre alte implicații ale problemei SAT, despre interpretarea filozofică a clasei NP, despre soluțiile practice euristice care s-au găsit pentru a rezolva unele dintre instanțele acestei probleme, și despre niște proprietăți statistice interesante ale densității soluțiilor problemei.

Mesajul cel mai important al acestui text se află însă în partea de față. Am văzut în acest text că între anumite probleme computaționale putem face reduceri, astfel încît să transformăm soluțiile uneia în soluțiile celeilalte. Am văzut de asemenea că există o clasă foarte interesantă de probleme (numită NP) pentru care știm să verificăm eficient dacă ceva este o soluție, dar nu avem nici o idee despre cum să găsim o soluție. Am văzut și că SAT este o astfel de problemă, și că orice altă problemă din NP se poate reduce la SAT, făcînd din SAT o problemă NP-completă.

Dacă cel puțin un cititor găsește aceste fapte demne de interes, sunt satisfăcut; mai satisfăcut decît probabil SAT va fi vreodată.

Recapitulare

În numărul anterior din PC Report am consacrat un articol unei probleme pe care am numit-o cu emfază ``cea mai importantă problemă nerezolvată din informatică''. Textul de față este o continuare; cititorii care nu posedă primul episod sunt invitați să obțină o copie a textului din pagina mea de web.

Satisfiabilitate

Problema de care vorbim se numește problema ``satisfiabilității'', notată pe scurt cu SAT. Un enunț foarte intuitiv, pentru cei cu oarecare noțiuni despre arhitectura calculatoarelor, este următorul: ``Fie un circuit digital combinațional (adică fără cicluri), construit din porți logice ``sau'', ``și'', ``nu'', care are o singură valoare la ieșire. Există o combinație de valori la intrarea circuitului care cauzează ieșirea circuitului să fie 1?''.

În textul anterior am formulat această întrebare pentru o formulă booleană (o formulă care operează cu operatorii algebrei boolene, introdusă de George Boole); cele două formulări sunt echivalente, deoarece porțile logice ale unui circuit combinațional corespund operațiilor boolene, în așa fel încît fiecare formulă corespunde unui circuit și invers.

De fapt găsirea unui algoritm care să ofere răspunsul corect la această problemă nu este un lucru prea greu; neplăcerea constă în faptul că nu se cunoaște nici un algoritm eficace, care să fie capabil să rezolve probleme mari într-un timp scurt. Toți algoritmii cunoscuți, inclusiv cei prezentați ceva mai jos, au trăsătura indezirabilă că există instanțe ale problemei pentru care timpul de execuție este exponențial în dimensiunea problemei. Asta înseamnă că, dacă adăugăm o singură variabilă în plus, timpul necesar rezolvării se dublează. Este ușor de văzut că acest ritm de creștere este covîrșitor, și că întreaga capacitate de calcul a omenirii nu este suficientă pentru a trata probleme de dimensiuni nu prea mari (zeci de mii de variabile).

Importanța practică; NP

Existența unei soluții eficace pentru problema satisfiabilității nu este considerată cea mai importantă din întreaga informatică doar de către niște teoreticieni amatori de curiozități. Importanța practică a acestei probleme este enormă: foarte foarte multe probleme practice sunt echivalente ca dificultate cu problema satisfiabilității. Michael Garey și David Johnson au alcătuit în 1979 o culegere de probleme, toate echivalente cu SAT: ``Computers and Intractability: A Guide to the Theory of NP-Completeness'', publicată de editura W. H. Freeman. Între timp lista aceasta a crescut mult; la ora aceasta cuprinde cîteva mii de exemple.

Toate aceste probleme au o trăsătură foarte interesantă în comun: pentru nici una nu se cunoaște o soluție eficientă (adică un algoritm eficient care să rezolve toate instanțele acestor probleme), dar nici nu există argumente că astfel de soluții nu ar exista. Dacă măcar am ști că nu există soluții eficiente, poate am fi mai împăcați, chit că aflarea soluției ar putea avea importanță economică de miliarde de dolari. Dar nu știm că nu putem, așa că suntem nervoși.

Mai mult decît atît: dacă noi căutăm soluția și cineva vine și zice: ``știu răspunsul: formula ta nu este satisfiabilă''. Noi îl putem provoca: ``dovedește''. Ei bine, atunci acel cineva poate produce o dovadă simplă că are dreptate și că știe într-adevăr soluția. Cu alte cuvinte, pentru aceste probleme există dovezi simple și ușor de verificat că ceva este o soluție8.

Există o denumire specială pentru problemele pentru care putem verifica imediat dacă o pretinsă soluție este corectă: toate aceste probleme formează așa numita clasă NP, de la nedeterminist-polinomial. SAT este o astfel de problemă, pentru că dacă cineva ne arată intrările unui circuit putem imediat vedea dacă circuitul va obține un ``1'' la ieșire.

În prima parte a acestui articol menționam că în 1971 Cook a demonstrat că SAT este cea mai grea problemă din clasa NP, în sensul că orice altă problemă din NP se reduce la SAT. Din cauza asta SAT este numită o problemă NP-completă, la fel ca toate celelalte probleme echivalente prezentate în culegerea lui Garey și Johnson.

Prin contrast, clasa tuturor problemelor pe care le putem rezolva eficient (care sunt definite ca probleme pentru care cunoaștem algoritmi al căror timp de rulare este proporțional cu o funcție polinomială în dimensiunea problemei) se numește P.

Alte clase de complexitate

NP nu este nici pe departe clasa problemelor celor mai dificile pe care le cunoaștem; există clase de complexitate mult mai grele, ca de pildă EXP, clasa problemelor pentru care cu siguranță nu există soluții ne-exponențiale ca timp. Există chiar clasa problemelor nedecidabile, pentru care nu există... nici un fel de soluție9!

De fapt teoria complexității demonstrează existența a tot felul de ierarhii de clase de complexitate și studiază relațiile dintre feluritele clase. Adesea relațiile sunt de incluziune, așa cum avem între P și NP: orice problemă P este și una NP; uneori însă relațiile sunt mai bizare, sau sunt condiționate de alte relații (ceva de genul: dacă clasa A e diferită de clasa B, atunci clasa D e tot una cu clasa C). Teoria complexității clasifică problemele în funcție nu neapărat de timpul necesar soluționării, ci și în funcție de alte resurse necesare, cum ar fi: cantitate de memorie necesară (numită și spațiu), număr de biți aleatori, porți logice, ``adîncimea'' unui circuit care rezolvă problema, etc.

Lumea face atît de mult tam-tam vis-a-vis de NP nu numai pentru că există în NP multe probleme practice importante (căci și celelalte clase de complexitate conțin probleme practice importante), ci și pentru că pentru problemele din NP există speranța că ar putea fi rezolvate rapid, dar nimeni nu știe cum. Dacă am ști că nu se poate, am fi probabil mai relaxați, dar așa e ca și cum soluția ne scapă printre degete.


Auto-reducere

În acest paragraf vom discuta o trăsătură foarte interesantă a lui SAT. SAT, așa cum este ea formulată, este o ``problemă de decizie''; acest nume tehnic înseamnă doar că răspunsul trebuie să fie doar ``DA'' sau ``NU'': dacă ni se dă un circuit, trebuie să spunem doar dacă circuitul este satisfiabil sau nu, dar nu și cum este satisfiabil.

Aparent capacitatea de a rezolva o problemă de decizie nu e prea utilă: dacă un profesor la școală are voie să pună numai întrebări la care studentul să răspundă numai cu ``DA'' sau ``NU'', nu pare să poată verifica prea bine cunoștințele studenților în acest fel (în definitiv dacă aceștia dau cu banul au șansa de a răspunde 50% corect, deci s'a ia not'a de trecere fără nici un efort).

În realitate însă această limitare la probleme de decizie nu este o constrîngere prea severă. Folosind un procedeu numit auto-reducere putem transforma mai multe răspunsuri DA-NU într-o soluție completă (adică putem afla și cum circuitul poate fi făcut satisfiabil, pentru care intrări). Iată cum:

Să presupunem că avem la dispoziție un algoritm care rezolvă rapid și corect SAT, în sensul că răspunde întotdeauna ``DA'' cînd i se dă o formulă SAT care poate genera ``1'' și ``NU'' cînd pentru toate intrările posibile ale circuitului rezultatul este ``0''.

Vrem să găsim o serie de intrări pentru care rezultatul este ``1'', dacă acestea există.

Putem atunci proceda în acest fel :

  1. Întrebăm algoritmul ``este formula satisfiabilă''? Dacă răspunsul este ``NU'' am terminat.

  2. Altfel știm că există o valoare a primei variabile, fie 0, fie 1, pentru care circuitul generează un ``1''. Atunci punem prima variabilă pe ``0'', după care simplificăm formula, folosind regulile algebrei boolene. Obținem o nouă formulă de tip SAT, mai simplă.

  3. Pentru noua formulă întrebăm din nou algoritmul.

    1. Dacă răspunsul este ``DA'', lăsăm prima variabilă pe ``0''.

    2. Dacă răspunsul este ``NU'', atunci știm că prima variabilă nu poate fi ``0'', deci trebuie să fie ``1''. Punem prima variabilă pe ``1''.

  4. Repetăm algoritmul de la pasul 2, cu formula rezultată, care este mai mică.

După atîția pași cîte variabile avem, obținem pentru fiecare variabilă o valoare.

(Putem apoi verifica dacă algoritmul nu a mințit, testînd valoarea formulei cu valorile obținute. Același truc îl poate face și profesorul pentru a testa un student numai cu răspunsuri ``DA'' și ``NU'': întreabă în felul următor: ``este prima literă din capitala Braziliei A'', apoi, dacă răspunsul studentului este ``da'', întreabă de a doua literă, altfel încearcă B pentru prima, etc. Deși metoda este incomodă, este pe deplin în concordanță cu regulile jocului, și aproape infailibilă: profesorul obține cîte un bit de la student prin fiecare întrebare. Dacă studentul știe răspunsul, profesorul trebuie să obțină toți biții corecți, și nu numai unul.)

Metoda auto-reducerii este extrem de puternică: ne permite să folosim un potențial algoritm pentru a rezolva SAT pentru a rezolva o sumedenie de alte probleme, aparent mult mai complicate (de exemplu, chiar problema de a găsi valorile care fac o formulă satisfiabilă).

Despre creativitate

Suntem înclinați să afirmăm că există o diferență substanțială între (1) a înțelege ceva și (2) a crea ceva nou. Aceste două feluri de activitate sunt net diferite: spectatorul unei opere de artă apreciază rezultatul, dar efortul artistului pentru a crea acea operă pare mult mai impresionant. Putem spune că aprecierea este un algoritm de clasă P, dar creația este unul de clasă NP: putem aprecia rezultatul, dar nu știm cum să-l producem.

Dacă vă deranjează excursul într-un domeniu care este rezervat subiectivității totale (arta), atunci putem face exact aceeași analogie în matematică: între (1) a demonstra o teoremă și (2) a citi o demonstrație făcută de altcineva și a ne convinge de corectitudinea ei, pare să fie din nou o diferență de natură. Actul (1), al demonstratorului este un act creativ, de căutare într-o enormă masă de posibilități, pe cînd actul (2), al discipolului, care recitește demonstrația, este relativ simplu, constînd în a verifica faptul că fiecare pas logic făcut în demonstrație respectă regulile de inferență ale logicii.

Din nou avem aceeași antiteză, între actul de a genera (care poate fi asimilat cu clasa NP) și actul de a verifica (care poate fi asimilat cu clasa P)10.

Cum spuneam, nu există o demonstrație riguroasă că P $\not=$ NP, dar nici una că P=NP. Suntem supuși unei tensiuni dureroase:

Dacă P=NP, am avea și alte consecințe extrem de importante, în oarecare măsură devastatoare:

Matematica trivială

Pentru orice teoremă din matematică am putea scrie un program care să găsească o demonstrație într-un timp nu mult mai mare decît lungimea celei mai scurte demonstrație posibile (ar exista un polinom P(x) astfel încît, dacă cea mai scurtă demonstrație posibilă este de lungime x, algoritmul am găsi una în timp cel mult P(x).).

Un astfel de algoritm ar folosi tehnica auto-reducerii pentru a deriva o demonstrație corectă (ceva de genul: există o demonstrație care începe cu litera ``A''? etc.)

Importanța acestei tehnici pentru demonstrarea automată a teoremelor din matematică a fost observată de către logicianul Kurt Gödel înca din 1956, într-o scrisoare adresată marelui matematician John von Neumann, cu mult înainte ca teoria NP-complexității să existe (și cu mult înainte să existe calculatoare de uz comun).

Criptografia inexistentă

Decriptarea este la rîndul ei o problemă NP, dacă o formulăm în felul următor: ``este acest șir de caractere criptarea unui text cu sens''? Decriptarea este în NP, pentru că dacă cineva ne oferă cheia de decriptare, putem obține textul criptat cu un algoritm rapid; putem astfel verifica în timp polinomial că mesajul criptat este într-adevăr rezultat din sursă.

Dar dacă P=NP, înseamnă că există un algoritm de timp polinomial pentru SAT; îl putem folosi împreună cu metoda auto-reducerii pentru a ``sparge'' orice mesaj! Deci, dacă P=NP, nu există criptografie! Dintr-o dată, lumea arată cu totul altfel.

Densitatea soluțiilor și dificultatea problemelor

Criptografia și NP-completitudinea

Pentru că SAT este o problema atît de dificilă multă lume s-a străduit să creeze algoritmi de criptare bazați pe probleme NP-complete. Din nefericire pînă la urmă toate metodele de criptare bazate pe probleme NP-complete s-au dovedit a fi foarte ușor de spart!

Asta e chiar o surpriză: creăm probleme dintr-o clasă reputat dificilă, dar în practică reușim foarte des să le rezolvăm! Nu e ceva putred la mijloc?

De fapt nimic nu ne garantează că NP este o clasă potrivită pentru criptografie, pentru că ``tăria'' oferită de NP nu este aceeași cu ``tăria'' necesară criptografilor. Iată de ce: dacă ne ducem la prima parte a acestui articol, vedem că complexitatea unei clase de probleme este dată de cea mai grea problemă din clasă. Astfel, dacă toate problemele din NP ar fi foarte ușoare, dar una singură ar fi extrem de grea, noi tot am declara clasa NP ca fiind grea. Asta se numește ``complexitatea în cazul cel mai rău'' (worst-case complexity).

Criptografii pe de altă parte nu vor ca unele probleme să fie grele și altele ușoare; ei vor ca o problemă aleasă la întîmplare să fie dificilă: nu ai nici un avantaj dacă există o singură cheie care nu poate fi ``spartă'', pentru că este nevoie de multe chei. Complexitatea ``medie'' a unei probleme trebuie să fie ridicată pentru ca acea problemă să fie potrivită pentru criptografie.

Clasa problemelor NP-complete nu pare să fie densă în probleme grele; ea constă în probleme expresive: vă reamintiți că o problemă este NP-completă dacă putem reduce orice altă problemă din NP la ea; asta înseamnă că ``limbajul'' problemelor NP-complete este foarte expresiv: ne permite să codificăm orice altă problemă.

La ora asta cei mai rezilienți algoritmi criptografici se bazează pe probleme care nu sunt demonstrate a fi NP-complete, și care foarte probabil nici nu sunt (dar, din nou, nu există încă nici o demonstrație în acest sens). Cele mai bune candidate s-au dovedit unele probleme de teoria numerelor. De fapt niște probleme absolut banale (în sensul că pot fi explicate și unui școlar): criptosistemul cu cheie publică RSA se bazează pe problema factorizării: dacă am un număr, cum să-l descompun într-un produs de factori (primi).

Dacă vă grăbiți să sugerați algoritmul învățat la școală pentru descompunere în factori primi, vă invit să-i analizați complexitatea. Acel algoritm trebuie să încerce toate numerele prime mai mici decît numărul pe care vreți să-l descompuneți. Ori, pentru numărul n, există aproximativ 2n/log(2n) astfel de numere, adică un număr exponențial. Asta înseamnă că algoritmul, deși funcționează binișor pentru numerele oferite în clasă ca exercițiu, este absolut inaplicabil, chiar cu cele mai performante calculatoare, atunci cînd avem de-a face cu numere de sute de cifre.

CNF-SAT și k-CNF-SAT

O întrebare legitimă pe care ne-o putem pune este ``cînd este o instanță a lui SAT dificilă''? Pentru a putea răspunde la această întrebare, vom introduce niște noi elemente de terminologie.

După cum am spus, o problemă din SAT constă într-o formulă booleană (sau un circuit logic) care aplică operațiile ``sau'', ``și'' și ``nu'' logic unor variabile pentru a obține o singură valoare, de un bit. Ce vrem să știm este dacă există valori ale variabilelor manipulate care să facă formula ``1''.

Algebra booleană ne învață că fiecare formulă logică se poate rescrie într-o formă mai simplă, în care:

O astfel de formă se numește ``forma normală conjunctivă'', sau CNF (Conjunctive Normal Form), pentru că formula este o conjuncție (și) de disjuncții (sau). De exemplu $(a \lor b \lor \lnot c) \land
(\lnot a \lor \lnot b)$ este în forma CNF, pe cînd $\lnot (a \land
b)$ nu este, pentru că avem o negație aplicată unei formule.

Se poate arăta că, chiar dacă formula este exprimată în forma CNF, asta nu simplifică cu nimic sarcina lui SAT, problema rămînînd NP-completă. Poate să vi se pară ciudat că s-ar putea întîmpla altfel: în definitiv orice formulă poate fi scrisă sub forma CNF; aparent noi rezolvăm aceeași problemă, doar scrisă diferit: de ce ne-am aștepta ca complexitatea rezolvării să fie alta dacă exprimăm formula altfel?

Ei bine, clasa de probleme depinde foarte mult și de felul în care enunțăm problema; clasa SAT este diferită de clasa CNF-SAT: prima clasă conține orice formulă logică, a doua numai formulele logice care sunt scrise în forma normală conjunctivă. E adevărat că orice formulă din prima clasă este absolut echivalentă cu una din a doua clasă (în sensul că calculează aceeași funcție booleană), dar cele două formule nu sunt identice.

Pentru a ne convinge că este important modul de exprimare, să notăm că există și o formă de scriere a formulelor boolene, numită ``normală disjunctivă'', notată DNF, care e la fel cu CNF, dar la care se schimbă semnele ``sau'' și ``și'' între ele. Se poate de asemenea arăta că de asemenea orice formulă booleană se poate pune în forma normală disjunctivă. Dar orice întrebare din DNF-SAT se poate rezolva în timp polinomial! DNF-SAT este o clasă de probleme mult mai ușoare decît cele din SAT sau CNF-SAT.

De ce stau lucrurile așa? Pentru că atunci cînd transcriem o formulă din SAT în formă DNF-SAT, lungimea ei poate crește foarte mult; dacă lungimea inițială a formulei era x și complexitatea algoritmului era 2x, și dacă după traducere avem lungimea n=2x și complexitatea n2 = 2x2, care este exponențială în x dar polinomială în n.

Iată încă un exemplu: problema de a găsi divizorii unui număr este foarte grea dacă numărul este scris în baza 2 (sau orice altă bază mai mare ca 2), dar este foarte simplă dacă numărul este scris în baza 1. Dacă scriem numărul 100 folosind 100 de bețișoare, atunci putem exhiba un algoritm de complexitate n2 care găsește toți divizorii lui 100 (de pildă algoritmul învățat în școala primară). Dar, scriind un număr în baza 2, folosim mult mai puține cifre (mai exact, pentru n folosim log n cifre), deci chiar dacă aplicăm același algoritm, într-un caz măsurăm timpul algoritmului relativ la n, iar în altul relativ la log n$.

Din motive tehnice adesea cercetătorii lucrează cu o versiune a problemei CNF-SAT, descrisă printr-un număr k: k-CNF-SAT. Avem astfel 2-CNF-SAT, 3-CNF-SAT, etc. Clasa k-CNF-SAT constă din formulele boolene scrise în formă CNF dar pentru care orice clauză are cel mult k literali. De exemplu formula $(a \lor b \lor \lnot c) \land
(\lnot a \lor \lnot b)$ este în 3-CNF-SAT, și în 4-CNF-SAT, dar nu în 2-CNF-SAT.

Se arată de asemenea, fără prea multă dificultate, că, pentru orice k întreg, orice formulă booleană se poate aduce în forma k-CNF-SAT.

Avem însă de a face cu un fenomen interesant: 1- și 2-CNF-SAT sunt clase de probleme foarte ușoare: se cunoaște chiar un algoritm de timp linear care rezolvă aceste probleme. Pe de altă parte toate clasele CNF-SAT cu mai mult de doi literali pe clauză sunt NP-complete!

Ce straniu! O diferență atît de mare de complexitate, și o diferență atît de mică de formă. Acest fenomen se întîlnește foarte adesea în teoria NP-complexității; sunt foarte multe probleme pentru care o anumită variantă este simplă, dar de îndată ce schimbi un pic enunțul o transformi într-o variantă foarte dificilă. (De exemplu, întrebarea dacă putem colora o hartă cu două culori, în așa fel încît țari vecine să nu fie colorate la fel, este banală, dar aceeași întrebare cu numărul trei este NP-completă. Știm că putem oricînd colora o hartă cu 4 culori, dintr-o teoremă extrem de celebră.)

Tranziții de fază

După cum am spus mai sus, nu toate instanțele unor probleme NP-complete sunt la fel de grele: numai cele mai dificile sunt foarte grele; unele pot fi foarte ușoare. Un articol extrem de interesant, care a făcut vîlvă, a asimilat comportarea formulelor boolene cu fenomenele fizice care se petrec cînd un material face o tranziție de fază, de exemplu de la lichid spre solid. Acest articol este o colaborare între fizicieni și informaticieni, și a fost publicat în revista Nature. Articolul este scris de Rémi Monasson, de la laboratorul CNRS de fizică teoretică de la Paris, Riccardo Zecchina, de la Centrul internațional pentru fizică teoretică de la Trieste, Scott Kirkpatrick, de la laboratoarele T. J. Watson ale lui IBM, Bart Selman, profesor la departamentul de calculatoare de la Universitatea Cornell și Lidror Troyansky, de la departamentul de calculatoare al universității evreiești din Ierusalim.

Articolul a făcut atîta vîlvă încît a apărut și în ziare; o versiune de popularizare despre această temă puteți găsi de exemplu în New York Times la
http://www.nytimes.com/library/national/science/071399sci-satisfiability-problems.html.

Articolul studiază care dintre problemele SAT sunt grele și care sunt ușoare. Cantitatea aflată sub studiu este constrîngerea: cît de multe condiții sunt puse asupra unei formule. Dacă o formulă este prea puțin constrînsă, are o multitudine de soluții. Dacă formula este foarte constrînsă, atunci nu are nici o soluție. Astfel de formule sunt foarte ușor de rezolvat: pentru cele puțin constrînse putem găsi soluții aproape la întîmplare, pe cele supra-constrînse le putem ușor demonstra ca ne-avînd nici o soluție. Noțiunea de constrîngere poate fi asimilată cu rangul unui sistem de ecuații lineare din algebră: un sistem de ecuații care are mai multe variabile decît ecuații11 are o infinitate de soluții, un sistem care are mai multe ecuații decît variabile nu are nici o soluție. Articolul asimilează constrîngerea cu gradul de libertate al moleculelor dintr-un lichid, respectiv solid: solidul este mult mai constrîns.

Constrîngerea poate fi măsurată pentru probleme k-CNF-SAT prin raportul dintre numărul de clauze din formulă și numărul de variabile. Iată intuitiv de ce lucrurile stau așa: să luăm o clauză izolată și să o facem adevărată selectînd un literal anume ca adevărat (să zicem ca acesta este literalul a). Dacă ne uităm acum la toate celelalte clauze care conțin $\lnot a$, ele vor fi forțate să aleagă un alt literal să fie adevărat. Aceste clauze sunt constrînse de prima, pentru că au mai puține alegeri de făcut. Cu cît avem mai multe clauze, cu atît avem mai multe constrîngeri reciproce.

Articolul citat face un studiu experimental asupra raportului $\alpha$ dintre numărul de clauze și numărul de variabile, folosind formule generate aleator pe care le rezolvă prin forță brută. Graficul din figura 3 arată dependența dificultății unei probleme de acest număr. Cînd $\alpha$ este un număr mic, problemele au multe soluții, și sunt ușor de rezolvat. Cînd $\alpha$ este mare problemele nu au nici o soluție, și acest lucru este iarăși foarte ușor de descoperit. Cînd $\alpha$ atinge o valoare critică, (care depinde de k), nu e clar dacă problemele au sau nu soluții, iar determinarea acestui lucru este extrem de dificil. Pentru aceste instanțe, algoritmi cunoscuți calculează foarte mult.

Figura 3: Efortul computațional necesar pentru a rezolva o instanță aleatoare a 3-CNF-SAT în funcție de constrîngerea formulei $\alpha =$ (numărul de clauze)/(numărul de variabile). Această figură este extrasă din pagina de web de la Cornell a lui Bart Selman.
\begin{figure}\centerline{\epsfxsize=5cm\epsffile{alfa.eps}}\end{figure}

Soluții practice pentru SAT

Voi încheia excursia în domeniul SAT prin expunerea soluțiilor folosite pentru a rezolva aceste probleme în practică. Faptul că nu putem să rezolvăm problemele în general nu înseamnă că nu avem nevoie să o facem, ba chiar, așa cum am arătat, probleme NP-complete apar adesea în practică. Așa că tot ce putem face este să facem tot felul de trucuri care măcar rezolvă rapid cazurile ușoare.

În mod paradoxal, cei mai eficace algoritmi din ziua de azi sunt doar variante ale unor algoritmi deosebit de simpli. Trimitem cititorul interesat la articolul lui Stephen Cook12 și David Mitchell ``Finding Hard Instances of the Satisfiability Problem: A Survey'', publicată în DIMACS Series in Discrete Mathematics and Computer Science în 1997.

Algoritmul Putnam-Davis

Cel mai eficace algoritm general pentru SAT rămîne un algoritm propus de M. Davis și H. Putnam în 1960! Algoritmul lor alege succesiv un literal pe care îl face adevărat, după care șterge toate clauzele care conțin acel literal, pentru că aceste clauze sunt deja satisfăcute. În plus, literalul negat este eliminat de peste tot din celelalte clauze, pentru că este deja fals.

Crucială este alegerea literalului, iar algoritmul lor sugerează niște euristici care duc la rezultate foarte bune în practică. Euristicile sunt reguli care fac un algoritm mai eficient în general, dar despre care nu se poate demonstra că sunt întotdeauna alegerea corectă; sunt deci ``ghiciri înțelepte''.

Metoda lor începe prin a elimina întotdeauna cele mai constrînse clauze, adică cele care conțin un singur literal: este clar că acesta trebuie să fie adevărat pentru a face formula adevărată. Datorită eliminărilor, astfel de clauze apar și în cursul evoluției algoritmului. Dacă nu sunt astfel de clauze, atunci un literal este ales la întîmplare și se încearcă pe rînd ambele valori posibile, 0 și 1, făcînd backtracking dacă o încercare eșuează.

Algoritmul Walk-SAT

Acest algoritm face parte din ceea ce se numesc ``metode incomplete de rezolvare'', pentru că nu funcționează pe probleme care nu sunt satisfiabile, dar funcționează excelent pe cele care au soluții, găsind foarte repede soluții. Acest algoritm a fost propus de Bart Selman, acum la Universitatea Cornell (și unul dintre autorii articolului de mai sus despre tranzițiile de fază).

Acest algoritm este foarte surprinzător pentru că este un algoritm aleator: dă cu banul pentru a decide ce valoare trebuie să aibă fiecare variabilă. De fapt acest algoritm este reprezentativ pentru cercetarea curentă în teoria algoritmilor aleatori (pe care i-am menționat pe larg într-un articol în PC Report din august 1997).

Algoritmul este următorul:

  1. Generăm la întîmplare atribuiri 0 și 1 pentru fiecare variabilă.

  2. Dacă nu sunt clauze nesatisfăcute, am terminat și am găsit o soluție.

  3. Alegem o clauză nesatisfăcută la întîmplare.

  4. Dăm cu banul.

    1. Dacă iese cap, atunci schimbăm valoarea unei variabile alese la întîmplare (în opusul ei).

    2. Dacă a ieșit pajură, alegem variabila din clauză a cărei schimbare ar maximiza numărul de alte clauze care devin satisfăcute. Alegem la întîmplare dacă avem mai multe variabile la fel de bune.

  5. Dacă nu ne-am ``plictisit'' reluăm de la pasul (2).

Încheiere

Am văzut în acest articol că există probleme al căror enunț este banal, dar care sunt de fapt foarte greu de rezolvat în practică în mod eficient. Această stare de fapt simultan ne bucură și ne întristează: ne bucură pentru că dacă am putea rezolva aceste probleme ușor, ar rezulta că unele din îndeletnicirile umane pe care le prețuim cel mai mult, de exemplu creativitatea matematicienilor, pot fi reduse la simple manipulări mecaniciste. Ne-am întrista, pentru că de fapt ne dorim foarte mult să rezolvăm aceste probleme, fiindcă importanță lor practică este enormă.

Dacă vă doriți un drum rapid spre faimă mondială, atunci una dintre soluții este să rezolvați această întrebare: se pot sau nu rezolva aceste probleme în mod eficient, sau, tehnic vorbind, este P tot una cu NP? Dar, fiți preveniți, multe capete luminate s-au luptat cu această enigmă și au fost toate înfrînte!



Note

... a1
Operația aceasta nu coincide neapărat cu sensul cuvîntului ``sau'' din limba vorbită, care este cîteodată folosit în sensul exclusiv: în limba vorbită folosim uneori ``sau'' pentru a zice că ``una sau alta se întîmplă, dar nu amîndouă''.
... tii2
Iarăși avem o oarecare discrepanță cu sensul negației în limba vorbită, deoarece în româna o dublă negație (``n-am mîncat nimic'') nu este o afirmație, pe cînd în logică $\lnot \lnot a = a$.
... a3
O clauză este definită ca o conjuncție de variabile sau variabile negate.
... SAT4
Desigur, e vorba de o rezolvare eficientă; vom vedea că SAT poate fi rezolvată, dar într-un mod foarte costisitor.
... intrare5
În general cea mai bună măsurătoare pentru dimensiunea datelor de intrare este numărul de biți de care avem nevoie pentru a le descrie, dar în acest caz folosim o altă măsură, și anume numărul de variabile. De fapt ambele măsuri ar oferi același rezultat.
... P6
Strict vorbind, clasele P și NP, riguros definite, conțin numai probleme de decizie, deci probleme la care răspunsul poate fi numai ``da'' sau ``nu''. Așa cum arătăm pe scurt în secținea 9, între probleme de optimizare, de genul ``care e cel mai mic X'' și probleme de decizie de tipul ``există un X'' este o legătură strînsă, care le face la fel de grele (în sensul că atunci cînd putem rezolva una o putem rezolva și pe cealaltă). În acest text ignorăm deci distincția dintre problemele de decizie și cele de optimizare.
... demonstrat7
Stephen Cook: The Complexity of Theorem-Proving Procedures, în 3rd ACM Symposium on the Theory of Computation.
... tie8
Prin contrast, există probleme la care nu există dovezi scurte că ceva este o soluție corectă; de pildă nu există o dovadă scurtă care să demonstreze că o anumită strategie de a juca un joc gen șah este optimală.
... tie9
De exemplu, nu există un algoritm care să răspundă la întrebarea ``dat fiind programul P și datele de intrare x, se va termina vreodată P?''; această problemă este indecidabilă
... P)10
În realitate aceste asociații nu sunt prea precise, pentru că clasele P și NP sunt definite relativ la comportarea asimptotică a algoritmilor; un algoritm este în P dacă complexitatea lui crește ca un polinom cînd problema pe care o rezolvă devine din ce în ce mai mare.
... tii11
De fapt decît ecuații independente linear.
... Cook12
Același care a demonstrat teorema lui Cook menționată în prima parte a acestui articol