Aviz Biroului Electoral Central!
ALEGERI prezidențialeMihai ScorțaruAlegerile electorale și informaticaProbabil că majoritatea cititorilor (cel puțin cei cu drept de vot) cunosc modul în care se desfășoară primul tur de scrutin al unor alegeri în care dintr-un număr de candidați unul singur va fi ales într-o anumită funcție. Pentru cei care nu au încă drept de vot sau din diferite alte motive nu cunosc acest sistem de votare vom face o scurtă și simplă descriere: fiecare alegător optează pentru unul dintre candidați, iar dacă un candidat obține mai mult de jumătate din voturi atunci el va ocupa funcția respectivă. În caz contrar va avea loc un al doilea tur de scrutin la care se vor califica primii doi candidați în ordinea numărului de voturi obținute la primul tur de scrutin. "Cunoscătorii" știu că procedura este de fapt mai complicată, în realitate ținându-se seama și de alți factori, cum ar fi voturile anulate, prezența la vot etc. La un moment dat cuiva i-a venit ideea să folosească un calculator pentru a putea anunța mai rapid rezultatele primului tur, adică numele candidatului ales sau "extraordinara" veste că va mai avea loc un tur de scrutin. Se pare că tot mai mulți "oameni importanți" sunt interesați de aceasta și ca urmare o problemă asemănătoare a fost propusă spre rezolvare la etapa regională a concursului ACM din acest an. În zilele noastre rezolvarea practică a acestei probleme ar fi simplă, deoarece nu vom întâlni un număr foarte mare de candidați pentru un post. Dificultatea va apărea peste puțin timp când, să zicem în jurul anului 2457, sistemele de comunicație vor fi atât de dezvoltate încât oricine va putea candida cu șanse relativ egale la un anumit post. Fiecare alegător va avea atunci voie să voteze pentru oricine și anunțarea rezultatului final va fi o problemă mult mai dificilă. Enunțul problemeiDându-se un șir de n numere care pot lua valori între 1 și n, precizați dacă există un număr care apare în șir de mai mult de n/2 ori și în caz că acesta există, precizați care este acest număr. Am numerotat locuitorii cu drept de vot cu numere între 1 și n, elementul cu numărul i din șir conține votul locuitorului cu numărul i și alegătorul poate opta pentru oricine (valorile elementelor sunt între 1 și n). Situația este diferită în funcție de numărul alegătorilor alegerile pentru funcția de primar al unui oraș cu 25.000 de locuitori diferind de alegerile pentru funcția de președinte al României la care vor participa peste 10 milioane de alegători. Diferența esențială va fi în păstrarea datelor în memorie, deoarece, deși există posibilitatea memorării unui șir cu 25.000 de elemente, nu același lucru se poate spune despre unul cu peste 10 milioane de elemente (poate în 2457, dar până atunci va crește și numărul alegătorilor). Vom prezenta câțiva algoritmi pentru cele două cazuri amintite anterior cu scopul de a ilustra diferite tehnici de memorare a datelor, dar și pentru a arăta că deși implementarea anumitor algoritmi este mai dificilă, efortul depus nu este în zadar, deoarece timpul necesar executării programului va fi redus substanțial. Alegeri localeSă considerăm pentru început cazul orașului cu cel mult 25.000 de locuitori cu drept de vot. Cel mai simplu algoritm ar fi ca pentru fiecare element al șirului să contorizăm numărul de apariții în șir și în cazul în care acest număr este mai mare decât n/2 să afișăm fericitul câștigător. Program Alegeri_25000_1; var id_cand:array[1..25000] of Integer; i,j,n,nr:Integer; f:Text; Begin Assign(f,'ALEGERI.IN'); Reset(f); Readln(f,n); for i:=1 to n do Read(f,id_cand[i]); Close(f); for i:=1 to n div 2 + 1 do begin nr:=1; for j:=i+1 to n do if id_cand[i]=id_cand[j] then Inc(nr); if nr>n div 2 then begin Writeln('Primar: ',id_cand[i]); Halt end end; Writeln('Ne vedem duminica viitoare!!!') End. Am numărat voturile începând doar cu poziția i+1 pentru că dacă posibilul primar ar fi obținut voturi înainte, atunci programul s-ar fi oprit în cazul în care acesta ar fi fost primarul ales în pașii anteriori. Motivul pentru care ciclul controlat de i merge până la valoarea n div 2 + 1 este că pentru valori mai mari vom putea aduna mai puțin de jumătate din voturi. Este evident că ordinul de complexitate al acestui algoritm este O(n2), iar pentru valoarea n=25.000 timpul de execuție ar fi destul de mare. Un algoritm mai bun ar fi să păstrăm pentru fiecare candidat numărul voturilor obținute și să verificăm la sfârșit dacă vreun candidat a obținut mai mult de n/2 voturi. Program Alegeri_25000_2; var frecv_id_cand:array[1..25000] of Integer; i,n,id_cand:Integer; f:Text; Begin Assign(f,'ALEGERI.IN'); Reset(f); Readln(f,n); for i:=1 to n do begin Read(f,id_cand); Inc(frecv_id_cand[id_cand]) end; Close(f); for i:=1 to n do if frecv_id_cand[i]>n div 2 then begin Writeln('Primar: ',i); Halt end; Writeln('Ne vedem duminica viitoare!!!') End. Acest algoritm este performant în cazul unui număr mic de alegători, complexitatea lui fiind de ordinul O(n). Implementarea ar putea fi optimizată reducând căutarea câștigătorului doar printre candidații care au primit voturi. O altă posibilitate care va fi cu siguranță agreată de fanii algoritmilor probabiliști este alegerea la întâmplare a unui candidat votat și verificarea numărului de voturi obținute. În cazul în care primarul a fost ales încă din primul tur de scrutin, la fiecare alegere avem cel puțin 50% șanse să nimerim adevăratul primar. Dacă vom repeta operația de mai multe ori, șansele cresc considerabil. Un calcul foarte ușor arată că pentru n verificări, probabilitatea de a nu găsi primarul, dacă acesta a fost ales din primul tur, este de sub 1/2n. Pentru valori ale lui n suficient de mari această valoare poate fi ignorată, de exemplu pentru n=50 șansa de a greși este una din peste un milion de miliarde, adică practic 0. Dacă după 50 de încercări nu am găsit primarul, putem trage concluzia că acesta nu a fost ales din primul tur. Am putea să ne mulțumim și cu numai 20 de încercări sau chiar numai cu 10 deoarece probabilitățile de eroare ar fi mai mici decât 1/1.000.000, respectiv 1/1.000. Program Alegeri_25000_3; const incercari=50; var id_cand:array[1..25000] of Integer; i,j,n,nr,vot:Integer; f:Text; Begin Randomize; Assign(f,'ALEGERI.IN'); Reset(f); Readln(f,n); for i:=1 to n do Read(f,id_cand[i]); Close(f); for i:=1 to incercari do begin vot:=id_cand[Random(n)+1]; nr:=0; for j:=1 to n do if id_cand[j]=vot then Inc(nr); if nr>n div 2 then begin Writeln('Primar: ',vot); Halt end end; Writeln('Ne vedem duminica viitoare!!!') End. Alegeri generaleSă trecem acum la lucruri mai importante cum ar fi alegerea președintelui României. Vom încerca să adaptăm algoritmii prezentați anterior la noua situație (număr mult mai mare de alegători). Este evident că nu avem suficientă memorie convențională pentru a memora niște vectori corespunzători celor folosiți în programele descrise anterior. De aceea, pentru a putea păstra scheletul algoritmilor, va trebui să memorăm datele pe hard-disk. Trebuie să precizăm că de data aceasta numărul de voturi obținute de un candidat nu va mai putea fi reprezentat pe 2 bytes deoarece valorile ar putea depăși 216 și ca urmare suntem nevoiți să folosim o reprezentare pe 4 bytes. Cum pentru fiecare locuitor cu drept de vot vom avea nevoie de 4 bytes, necesarul total de memorie liberă pe disk va fi de 4n bytes, unde n este numărul alegătorilor, deci peste 40 MB pentru cei 10 milioane de alegători care vor participa la alegerea președintelui. Pentru a rula în bune condiții programele care vor urma, veți avea nevoie, pe lângă cei 40 MB auxiliari, de încă aproximativ 80 - 90 MB pentru a putea păstra fișierul de intrare care constă din peste 10 milioane de numere a câte 7 cifre separate de cel puțin un spațiu. În concluzie vă recomandăm să aveți aproximativ 125 MB spațiu liber pe disc înainte să rulați următoarele programe, cu date de test reprezentative. Primul algoritm prezentat poate fi adaptat astfel: Program Alegeri_10000000_1; var i,j,n,nr,vot,aux:Longint; f:Text; Begin Assign(f,'ALEGERI.IN'); Reset(f); Readln(f,n); Close(f); for i:=1 to n div 2+1 do begin Reset(f); Readln(f); for j:=1 to i-1 do Read(f,aux); Read(f,vot); nr:=1; for j:=i+1 to n do begin Read(f,aux); if aux=vot then Inc(nr) end; if nr>n div 2 then begin Writeln('Presedinte: ',vot); Close(f); Halt end; Close(f) end; Writeln('Ne vedem duminica viitoare!!!') End. Se observă că nu a fost nevoie de folosirea celor 40 MB de memorie pentru fișierul auxiliar deoarece nu a fost nevoie de memorarea altor date în afara celor inițiale. Pentru a adapta al doilea algoritm la această nouă situație va trebui să folosim fișierul auxiliar deoarece va trebui să numărăm voturile obținute de fiecare candidat. Vom inițializa cu 0 numărul de voturi pentru fiecare candidat. Program Alegeri_10000000_2; var i,n,v,vot:Longint; f:Text; g:file of Longint; Begin Assign(f,'ALEGERI.IN'); Reset(f); Readln(f,n); Assign(g,'ALEGERI.VOT'); Rewrite(g); v:= 0; for i:=1 to n do Write(g,v); for i:=1 to n do begin Read(f,vot); Seek(g,vot-1); Read(g,v); Seek(g,vot-1); Inc(v); Write(g,v) end; Close(f); Close(g); Reset(g); for i:=1 to n do begin Read(g,v); if v>n div 2 then begin Writeln('Presedinte: ', i); Close(g); Halt end end; Close(g); Writeln('Ne vedem duminica viitoare!!!') End. Pentru implementarea algoritmului probabilist pentru cazul alegerilor prezidențiale vom folosi din nou un fișier auxiliar în care vom păstra voturile fiecărui alegător pentru a nu fi nevoie să parcurgem secvențial fișierul de intrare atunci când vom vedea cu cine a votat alegătorul care va fi ales la întâmplare. Deoarece pentru fiecare încercare vom citi de pe disc 10 milioane de numere, vom reduce numărul de încercări de la 50 la 10 deoarece șansele de apariție a erorilor sunt oricum foarte mici. Program Alegeri_10000000_3; const incercari=10; var i,j,n,nr,v,vot:Longint; g:file of Longint; f:Text; Begin Randomize; Assign(f,'ALEGERI.IN'); Reset(f); Assign(g,'ALEGERI.VOT'); Rewrite(g); Readln(f,n); for i:=1 to n do begin Read(f,vot); Write(g,vot) end; Close(f); for i:=1 to incercari do begin Seek(g,Trunc(n*Random)); Read(g,vot); Seek(g,0); nr:=0; for j:=1 to n do begin Read(g,v); if v=vot then Inc(nr) end; if nr>n div 2 then begin Writeln('Presedinte: ',vot); Close(g); Halt end end; Close(g); Writeln(?Ne vedem duminica viitoare!!!') end. Pentru a alege aleator un alegător nu am mai putut folosi funcția Random cu parametru un număr întreg deoarece parametrul ar fi trebuit să fi o variabilă de tip Word ceea ce nu ar fi fost posibil în cazul în care valorile ar depăși 216. Timpul de execuție al primului algoritm este prea mare pentru ca acest algoritm să poată fi folosit, o apreciere aproximativă demonstrând că ar fi fost nevoie de peste 1000 de zile până când ar fi putut fi anunțate rezultatele, adică aproximativ la următoarele alegeri. Următorul tabel ilustrează timpii de execuție pentru ceilalți doi algoritmi în diferite cazuri. (Programele au fost rulate pe un calculator 486DX4-100MHz.) Alegători % Algoritm 2 Algoritm 3 (minute) (minute) 1.000.000 <50 11 24 2.000.000 <50 22 48 5.000.000 <50 56 120 10.000.000 90 49-75 30-90%;45-9%;60-0.9%;75-0,09% 10.000.000 75 49-75 30-75%;45-18.75%;60-4,6875% 10.000.000 >50 49-75 30-50%;45-25%;60-12,5%;75-6,25% 10.000.000 <50 75 165 Se observă că în cazul algoritmului probabilist există o anumită probabilitate pentru un timp de execuție în funcție de procentajul celor care au votat pentru președintele care a fost ales. Timpii de execuție sunt destul de mari, nefiind acceptabili în cazul în care publicul ar dori să afle rezultatele alegerilor imediat după închiderea urnelor. De aceea trebuie căutat un algoritm care să aibă performanțe superioare. Algoritmul propus este următorul:
Probabil că puțini dintre dumneavoastră ar accepta un astfel de algoritm fără o demonstrație. Putem să vă spunem că există o demonstrație matematică riguroasă, dar diferitele semne matematice care ar apărea în cazul în care vom prezenta o astfel de demonstrație v-ar determina să nu mai citiți ceea ce va urma, adică cea mai importantă parte a articolului. Ca urmare vă vom prezenta o demonstrație intuitivă. Dacă posibilul președinte la un moment dat nu este cel care a obținut numărul cel mai mare de voturi atunci, de fiecare dată când va apărea un vot pentru adevăratul președinte contorul va fi scăzut cu o unitate. Datorită faptului că am face mai mult de n/2 scăderi contorul va ajunge în mod inevitabil o dată la 0 și adevăratul președinte va ajunge să fie considerat ca posibil președinte. Este posibil ca această postură să fie pierdută, dar ea va fi recâștigată din aceleași motive. Implementarea acestui algoritm presupune parcurgerea de două ori a fișierului de intrare deoarece nu există altă modalitate de a verifica dacă cel care este posibil președinte în final a obținut mai mult de jumătate din voturi. Program Alegeri_10000000_4; var i,n,nr,vot,contor,presedinte:Longint; f:Text; Begin Assign(f,'ALEGERI.IN'); Reset(f); Readln(f,n); Read(f,presedinte); contor:=1; for i:=2 to n do begin Read(f,vot); if vot=presedinte then Inc(contor) else Dec(contor); if contor=0 then begin presedinte:=vot; contor:=1 end end; Close(f); nr:= 0; Reset(f); Readln(f); for i:=1 to n do begin Read(f,vot); if vot=presedinte then Inc(nr) end; Close(f); if nr>n div 2 then Writeln('Presedinte: ',presedinte) else Writeln('Ne vedem duminica viitoare!') end. Acest algoritm nu depinde de diferite caracteristici particulare ale datelor de intrare, de aceea timpii de execuție vor depinde numai de valoarea lui n. Alegători Timp de execuție Alegători Timp de execuție (secunde) (secunde) 1.000.000 15 6.000.000 101 2.000.000 30 7.000.000 118 3.000.000 45 8.000.000 135 4.000.000 60 9.000.000 152 5.000.000 79 10.000.000 168 Din tabel se observă că timpii de execuție variază aproape liniar cu numărul de alegători. Acest lucru era de așteptat deoarece algoritmul prezentat are ordinul de complexitate O(n). Se observă că timpul de execuție s-a redus de cel puțin 50 de ori, iar intervalul de timp care ar trece de la închiderea urnelor până la anunțarea rezultatelor este foarte mic. Se mai observă că nu este nevoie nici de folosirea unui fișier auxiliar deoarece nu mai avem nevoie de spațiu de memorie suplimentar pentru a memora diferite date. Acest algoritm poate fi folosit și în cazul alegerilor locale, atunci când voturile sunt memorate într-un șir, dar performanțele sale nu ar fi cu mult mai bune decât cele ale celorlalți doi algoritmi liniari prezentați. [cuprins] |