10011010... Algoritmi la nivel de bitMihai Oltean Există două motive esențiale pentru care se recomandă folosirea algoritmilor la nivel de bit și anume: reducerea cantității de memorie folosite și creșterea vitezei de lucru.
Numere miciSe dau două numere mici (cuprinse între 0 și 15). Se cere să se stocheze ambele numere, folosind un singur octet.
Soluție Este evident că fiecare număr cuprins între 0 și 15 are nevoie de 4 biți pentru a fi reprezentat. De aceea un octet (care are opt biți) este suficient pentru a reprezenta două astfel de numere. Vom stoca pe primii patru biți (cei mai puțini semnificativi) primul număr, iar pe următorii patru (cei mai semnificativi) următorul număr. Procedurile sunt următoarele:
function Codifica(a,b:Byte):Byte;
begin Codifica:=a+16*b end;
procedure Decodifica(x:Byte; var a,b:Byte);
begin a:=x mod 16; b:=x div 16 end;
O altă metodă de codificare se bazează pe operațiile la nivel de bit, și anume, dacă în procedura următoare dorim să îl scriem pe a în primii 4 biți (cei mai puțin semnificativi), iar pe b în următorii 4 biți, atunci este nevoie să îl deplasăm pe b cu 4 biți spre stânga:
function Codifica(a,b:Byte):Byte; begin Codifica:=a or b shl 4 end;
Decodificarea se face într-o manieră asemănătoare. Se știe că 15 = 00001111(2), iar 240 = 11110000 (2)
procedure Decodifica(x:Byte; var a,b:Byte); begin a:=15 and x; b:=(240 and x) shr 4 end;
Un scurt program care utilizează procedurile de mai sus este:
var a,b,x:Byte; { declaratiile procedurilor }
Begin x:=Codifica(3,4); Writeln(x); Decodifica(x,a,b); Writeln(a,' ',b) End.
Trasee comuneDoi prieteni A și B s-au hotărât să facă o excursie prinr-o țară cu N orașe. Fiecare și-a alcătuit singur intinerariul. Știind că un oraș este vizitat cel mult o dată de fiecare turist, se cere să se determine numărul legăturilor directe dintre orașele pe care ambii prieteni (A și B) le străbat.
Soluție Pentru a simplifica explicația presupunem că pot fi vizitate cel mult 32 de orașe; acestea sunt numerotate de la 0 la 31. Traseele celor doi sunt date în vectorii drum1 și drum2 (sub forma unei înșiruiri de orașe). Noi trebuie să determinăm numărul legăturilor directe dintre două orașe care sunt conținute în ambele drumuri. Un algoritm care calculează acest lucru are o complexitate medie pătratică. Putem reduce această complexitate folosindu-ne de o codificare mai specială a celor două drumuri. În loc să reținem un drum ca pe o succesiune de orașe, îl reținem ca pe o mulțime de legături directe dintre orașe. Aceste legături, la rândul lor, nu le reținem într-un șir, ci direct într-o matrice a drumurilor, adică o matrice NŽN unde pe poziția (i,j) avem valoarea 1 dacă între orașul i și orașul j există legătură directă și drumul turistului străbate această legătură. Deoarece avem două drumuri, vom construi două astfel de matrice. Dacă le reținem folosind efectiv două matrice de NŽN octeți, atunci nu obținem nici o optimizare. Dar dacă pentru fiecare legătură folosim un singur bit, adică o linie a matricei drumurilor să fie, nu un șir de octeți, ci un șir de biți, atunci o primă optimizare s-a făcut în sensul reducerii memoriei folosite. Am spus că orașele sunt numerotate cu numere de la 0 la 31, deoarece în acest fel este suficientă o variabilă de tip Longint pentru a reprezenta o linie a matricei drumurilor. Dacă numărul orașelor este mai mare decât 32, atunci vor trebui mai multe variabile de tip Longint (sau Byte, Word etc.) pentru a reprezenta drumul. A doua parte a optimizării constă în modul de a calcula perechile de legături directe care sunt conținute în cele două drumuri. Având la dispoziție cele două matrice de biți nu va trebui decât să aplicăm operația and între ele, rezultând astfel o nouă matrice care are biți 1 doar acolo unde ambele matrice inițiale au avut biți 1. Rezultatul se va obține numărând liniile care conțin o valoare diferită de 0.
Algoritmul Roy-WarshallFie G un graf neponderat și A matricea legăturilor directe între noduri (1 dacă două orașe sunt legate direct, 0 în caz contrar). Următorul algoritm calculează închiderea tranzitivă:
R:=A; for k:=1 to N do for i:=1 to N do for j:=1 to N do if ri,k=1 and rk,j=1 then ri,j=1 Reprezentând matricea legăturilor directe sub forma descrisă la problema anterioară rezultă un algoritm elegant și extrem de rapid. Notăm prin Ri linia i (văzută ca un șir de biți):
R:=A; for k:=1 to N do for i:=1 to N do if ri,k=1 then Ri:=Ri or Rk. Programul este următorul (o linie a matricei drumurilor este o înșiruire de variabile de tip Word):
Listing: ROY.PAS program WarshallBit; uses Crt; const MaxN=50; type linie=array[1..MaxN div 16+1] of Word; var a:array[1..MaxN] of linie; n,i,j,k,el,depl:Word; masca:Word; f:Text; procedure Citire; var c:Byte; begin Assign(f,'graf.in'); Reset(f); Readln(f,n); for i:=1 to n do begin for j:=1 to n do begin Read(f,c); if c=1
then begin el:=j div 16+1; depl:=j mod 16; depl:=16-depl; masca:=1 shl depl; a[i,el]:=a[i,el] or masca end end end; Close(f) end;
procedure LogicalOr(Q,W:linie; var E:linie); begin for j:=1 to (n-1) div 16+1 do e[j]:=q[j] or w[j] end;
procedure Warshall; begin for k:=1 to n do for i:=1 to n do begin el:=k div 16+1; depl:=k mod 16; masca:=1 shl (16-depl); if a[i,el] and masca<>0 then LogicalOr(a[i],a[k],a[i]) end end;
procedure Afisare; begin for i:=1 to n do begin for j:=1 to n do begin el:=j div 16+1; depl:=j mod 16; masca:=1 shl (16-depl); if a[i,el] and masca<>0 then Write(1,' ') else Write(0,' ') end; Writeln end end;
Begin Clrscr; Citire; Warshall; Afisare End.
Discuție: Comparați viteza de lucru a acestui algoritm cu cel clasic.
Transfer de lungimeLa problema determinării cuplajului într-un graf bipartit trebuie efectuată la un moment dat operația de transfer de lungime între un cuplaj deja construit și un lanț alternant crescător. Dacă cuplajul și lanțul alternant sunt reprezentate ca și la problema cu drumurile hamiltoniene, atunci operația de transfer de lungime (K-L)È(L-K), unde K este cuplajul, iar L este lanțul alternant crescător, se poate scrie astfel: (K and not (K and L)) or (L and not (K and L)) unde K și L sunt reprezentările cuplajului și lanțului sub formă de matrice de biți. Această expresie nu se poate simplifica deoarece operațiile se fac în raport cu graful G.
Jocul NimSe dau n grămezi de obiecte. Fiecare grămadă conține a[i] obiecte. Alternativ, doi jucători A și B extrag dintr-o singură grămadă un număr de obiecte (cel puțin unul). Cel care nu mai poate muta (adică toate obiectele au fost luate), pierde. Se cere să se determine dacă jucătorul A (care este primul la mutare) are o strategie sigură de câștig. În caz afirmativ să se programeze mutările lui A, cele ale lui B fiind citite de la intrare.
Soluție O prezentare teoretică completă poate fi găsită în [1]. Aici voi puncta doar lucrurile legate de lucrul la nivel de bit. Jucătorul A are strategie sigură de câștig doar dacă suma modulo 2 a reprezentărilor în baza 2 a numerelor a[i] este diferită de zero. Acest lucru îl calculăm folosind operatorul xor. În acest caz jucătorul A va extrage dintr-o grămadă un număr de obiecte, astfel încât suma calculată în modul descris anterior să devină nulă. Acest lucru se face determinând din suma modulo 2 cea mai semnificativă poziție pe care se află un 1 binar, iar apoi se complemetează toate cifrele din dreapta.
După efectuarea acestei mutări, suma modulo 2 va deveni 0, iar orice mutare efectuată de jucătorul B va trece jocul într-o poziție în care suma modulo 2 va fi diferită de zero.
Reprezentarea submulțimilorSe dă o submulțime a unei mulțimi de N elemente. Se cere să se reprezinte submulțimea dată cât mai eficient.
Soluție: Acest tip de reprezentare îl întâlnim și la implementarea tipului set din Pascal. O submulțime a unei mulțimi 0..MaxN este un șir de biți, unde pe poziția k (în șirul de biți) avem valoarea 1 dacă elementul k aparține submulțimii și 0 în caz contrar. Se poate demonstra că există o bijecție între submulțimile unei mulțimi de N elemente și mulțimea șirurilor binare de lungime N. Operațiile de intersecție, reuniune și diferență sunt realizate tot la nivel de bit. Se ține cont de faptul că în memorie octeții sunt numerotați crescător de la stânga la dreapta, iar biții în cadrul octetului sunt numerotați de la stânga la dreapta.
Submulțimi de sumă datăSe cere să se determine dacă o valoare dată k se poate scrie ca sumă de numere dintr-un vector dat. Fiecare număr din vector se poate folosi o singură dată.
Soluție Se aplică programarea dinamică. Trebuie construit un vector c de valori booleene, în care pe poziția i, avem valoarea true doar dacă numărul i se poate scrie ca sumă din vectorul cu valorile date. Deoarece lucrăm doar cu valori booleene (true și false) nu este nevoie de un vector de octeți (deoarece o valoare booleană se reprezintă pe un octet) ci de un șir de biți. Relațiile de recurență pentru programarea dinamică le puteți deduce din programul de mai jos. Remarcați valorile mari ale lui k pentru care se poate calcula soluția, spre deosebire de varianta clasică.
Bibliografie: 1. Oltean Mihai - Programarea jocurilor matematice, Editura Albastră, Grupul Microinformatica, Cluj, 1996
Mihai Oltean este doctorand la Universitatea "Babeș Bolyai", Cluj. Poate fi contactat prin e-mail la adresa: OM2519@scs.ubbcluj.ro. [cuprins] |