O structura de date este un set de operatii, care poate fi caracterizate ca un lucru, o entitate noua.
O structură de date este o metodă sistematică de stocare a informațiilor șidatelor într-un calculator, în așa
fel încât ele să poată fi folosite în mod eficient.
O structura de date este un set de operatii, care poate fi caracterizate ca un lucru, o entitate noua.
SD-1. SD liniare 2. SD neliniare
SD liniare- *tablou(uni-dimensional; multidimensional); *structura, record, inregistrare; *liste lantuite
liniare[unidimensionale(stiva- stack, coada-quene) multidimensionale]
SD neliniare-*liste lantuite neliniare multi-directionale; *graf[arbore(arbori de gradul m>2; arbori binari)]
O lista liniara este un set finit de elemente ordonate liniar, adica exista unelement considerat primul in lista,
un element considerat ultimul si pentru orice altelement din lista exista un element precedent si un element
urmator. Principalele operatii cu liste sunt:
−accesarea sau modificarea unui element din lista
−inserarea unui element in lista
−eliminarea unui element din lista
Modul in care aceste operatii sunt efectuate depinde de modul in care suntreprezentate listele. Exista doua
feluri de reprezentari: secventiala si inlantuita
O lista circulara este o lista in reprezentarea inlantuita care are proprietatea ca ultimul nod pointeaza la
primul nod al listei in loc sa aiba drept componenta pointerul NULL.
Info adresa info adresa
...
Listele inlantuite se clasifica in: Liste liniare dublu inlantuite si Liste circulare.
O lista liniara dublu inlantuita este caracterizata prin faptul ca pe multimea elementelor sunt definite doua
relatii de ordine totala inverse una alteia (inainte si inapoi). Rezulta doua secventializari ale listei Ordinea
elementelor pentru o astfel de lista este specificata prin doua cimpuri de informatie care sunt parte
componenta a fiecarui element si indica elementul urmator si respectiv elementul precedent, conform cu
relatiile de ordine definite pe multimea elementelor listei.
O lista circulara simplu/dublu inlantuita este o lista liniara simplu/dublu inlantuita modificata astfel
incat ultimul element pointeaza spre primul element din lista.
6. Lista simplu înlanțuită. Implementarea tipului de date abstract „Lista simplu înlanțuită” în limbajul C
Lista simpla inlantuita este o SD in care elementele (nodurile) sunt legate printr-un singur cimp ce reprezinta
adresa elementului urmator. Elementele sunt definite in diferite locatii in memorie. Primul element al listei
este numit corpul listei-head. Spre deosebire de masive, listele nu sunt alocate ca blocuri omogene de
memorie, ci ca elemente separate de memorie. Pentru accesarea listei trebuie cunoscuta adresa primului
elemen; elementele urmatoare sunt accesate parcurgand lista.
Typedef struct lelement {
Int info;
Struct element *next;
}element;
Element *head=NULL;
7. Lista dublu înlanțuită. Implementarea tipului de date abstract „Lista dublu înlanțuită” în
limbajul C
Lista este dublu inlantuita daca intre nodurile ei sunt definite doua relatii de ordine. In general, o lista este
n-inlantuita daca intre nodurile ei sunt definite n relatii de ordine. Listele dublu inlantuita sunt alcatuite din
date la care se adauga atit legaturi cu urmatorul articol, cit si cu precedentul .
Aceasta figura indica modul de dispunere al acestor legaturi.
Existent a doua legaturi in locul uneia singure prezinta mai multe avantaje, din care poate cel mai important
rezida in faptul ca lista poate fi citita in ambele directii. Construirea unei liste dublu inlantuite este un
process asemanator celui de alcatuire a unei liste simplu inlantuite, cu deosebirea ca sunt necesare doua
legaturi. Structura trebuie sa asigure spatiu pentru ambele.
Ca modalitate de implementare, putem defini un tip de date inregistrare in care partile dinamice vor fi
pointeri catre acelasi tip, pentru a retine adresa nodului din stanga, respectiv adresa nodului din dreapta si
vor fi numite in continuare "st", respectiv "dr" iar partea statica va fi numita "info":
struct nod
{
< tip> info;
nod *st, *dr;
};
typedef struct nod *NOD;
Coada C; //coada
12. Arbore binar. Implementarea tipului de date abstract "Arbore binar" în limbajul C
Un arbore binar este un arbore in care orice nod are cel mult doi descendenti facandu-se distincatie clara
intre descendentul drept si descendentul stang. Radacina unui arbore binar are doi subarbori, subarborele
stang, cel care are drept radacina fiul stang si subarborele drept, cel care are ca radacina fiul drept. Orice
aubarbore al unui arbore binar este el insusi arbore binar.
Reprezentarea secventiala Pentru fiecare nod al arborelui se precizeaza informatia si descendentii directi ca
elemente a trei vector diferiti, INFO[i], ST[i] si DR[i], unde i este indicele asociat unui nod. Cei trei vectori au
dimensiunea egala cu numarul de noduri din arbore.
Reprezentarea inlantuita
Pentru fiecare nod al arborelui se precizeaza informatia si descendentii directi ca elemente ale unei structuri
definita astfel:
struct nodarbore
{
T info;
struct nodarbore *stang;
struct nodarbore *drept;
};
typedef struct nodarbore NODARB;
unde T este presupus definit anterior (eventual printr-o definitie typedef), stang este pointer la subarborele
stang al nodului, iar drept este pointer la subarborele drept al nodului. Pentru identificarea radacinii
arborelui vom defini NODARB rad; drept un pointer la radacina arborelui. Daca unul din subarbori este vid,
atunci pointerul la acel subarbore este NULL
15. Crearea arborelui binar de căutare. Algoritm pentru inserarea unui nod
Prin crearea unui arbore de cautare intelegem de fapt crearea radacinii, restul nodurilor fiind adaugate prin
operatia de inserare.
16. Arbore binar de căutare. Algoritmi pentru căutarea unui nod și pentru determinarea nodului
max/min
Cautarea unei valori date intr-un arbore binar de cautare este un proces care poate firealizat recursiv in
ordinea valorilor care sunt stocate.Se incepe prin axaminarea noduluiradacina.Daca valoarea care este
cautata este egala cu valoare nodului radacina, atunciinseamna ca valoarea exista in arbore.Daca valoarea
ce trebuie cautata este mai micadecat valoarea nodului radacina, atunci aceasta trebuie sa fie in sub-
arborele stang, astfel printr-o cautare recursiva se va cauta valoarea in structura acestui sub-
arborestang.Asemenator,daca valoarea care trebuie cautata este mai mare decat valoareadetinuta de nodul
radacina, atunci aceasta trebuie sa fie in structura sub-arboreluidrept.Daca procedura de cautare a ajuns la
un nod curent si valoarea cautata nu s-a gasit,atunci valoarea nu se afla in arbore.O compararatie ar putea fi
executata cu o cautare binara, care efectueaza o cautarea a valorii printr-o metoda foarte asemanatoare,
insaabordeaza un acces aleator in tabloul valorilor prin urmarirea legaturilor.
Quicksort este un celebru algoritm de sortare, dezvoltat de C. A. R. Hoare și care, în medie, efectuează comparații
pentru a sorta n elemente. În cazul cel mai rău, efectuează comparații. De obicei, în practică, quicksort este mai rapid decât
ceilalți algoritmi de sortare de complexitate deoarece bucla sa interioară are implementări eficiente pe majoritatea
arhitecturilor și, în plus, în majoritatea implementărilor practice se pot lua, la proiectare, decizii ce ajută la evitarea cazului când
#include <stdlib.h>
Functia qsort sorteaza un masiv de nel elemente, fiecare de marime size. Argumentul base indica spre
începutul masivului.
Elementele masivului sunt sortate în ordine crescatoare în concordanta cu functia de comparare referita
de comp, apelata cu doua argumente care indica spre obiectele ce se compara. Functia de comparare
trebuie sa returneze un întreg mai mic decât, egal cu, sau mai mare decât zero daca primul argument
este considerat a fi mai mic decât, egal cu, respectiv mai mare decât al doilea. Daca cele doua
elemente comparate sunt egale, ordinea în masivul sortat este nedefinita.
Functia bsearch cauta într-un masiv de nel elemente, fiecare de marime size, un membru care coincide
cu obiectul indicat de key. Argumentul base indica spre începutul masivului.
Continutul masivului trebuie sa fie sortat crescator în concordanta cu functia de comparare referita de
comp, apelata cu doua argumente care indica spre obiectele ce se compara. Functia de comparare se
defineste ca în cazul qsort. Primul argument este adresa cheii, al doilea este adresa unui element din
masiv.
Valoare returnata
Functia bsearch returneaza un pointer la un membru al masivului care coincide cu obiectul
indicat de key, sau NULL daca nu se gaseste nici un membru. Daca exista mai multe elemente care
coincid cu key, poate fi returnat oricare element cu aceasta proprietate.
29. Tehnica de programare „Forța brută ” și tehnica de programare „ Divide et empera”. Exemple
Divide et impera este o clasă de algoritmi care funcționează pe baza tacticii divide et impera. Divide et impera se bazează pe principiul
descompunerii problemei în două sau mai multe subprobleme (mai ușoare), care se rezolvă, iar soluția pentru problema inițială se obține
combinând soluțiile subproblemelor. De multe ori, subproblemele sunt de același tip și pentru fiecare din ele se poate aplica aceeași
tactică a descompunerii în (alte) subprobleme, până când (în urma descompunerilor repetate) se ajunge la probleme care admit
rezolvare imediată.
Nu toate problemele pot fi rezolvate prin utilizarea acestei tehnici. Se poate afirma că numărul celor rezolvabile prin "divide et impera"
este relativ mic, tocmai datorită cerinței ca problema să admită o descompunere repetată.
Divide et impera este o tehnică ce admite o implementare recursivă. Principiul general prin care se elaborează algoritmi recursivi este:
"ce se întâmplă la un nivel, se întâmplă la orice nivel" (având grijă să asigurăm condițiile de terminare). Așadar, un algoritm prin divide et
impera se elaborează astfel: la un anumit nivel avem două posibilități:
1. s-a ajuns la o problemă care admite o rezolvare imediată (condiția de terminare), caz în care se rezolvă și se revine din apel;
2. nu s-a ajuns în situația de la punctul 1, caz în care problema curentă este descompusă în (două sau mai multe) subprobleme,
pentru fiecare din ele urmează un apel recursiv al funcției, după care combinarea rezultatelor are loc fie pentru fiecare
subproblemă, fie la final, înaintea revenirii din apel.
Deseori în practica trebuie să rezolvăm probleme care au un numar foarte mare de soluţii posibile.
De cele mai multe ori însa, nu ne intereseaza toate soluţiile, ci numai o parte dintre ele, care
îndeplinesc anumite condiţii specifice problemei.
Pentru astfel de probleme este indicată folosirea metodei backtracking care evită generarea soluţiilor
inutile. Backtracking este numele unui algoritm general de descoperire a tuturor soluțiilor unei probleme de
calcul, algoritm ce se bazează pe construirea incrementală de soluții-candidat, abandonând fiecare candidat
parțial imediat ce devine clar că acesta nu are șanse să devină o soluție validă. [1][2][3]
Exemplul de bază folosit în numeroase manuale de liceu și de nivel universitar este problema reginelor, care
cere să se găsească toate modurile în care pot fi așezate pe o tablă de șah opt regine astfel încât să nu se
atace. În abordarea backtracking, candidatele parțiale sunt aranjamente de câte k regine pe primele k rânduri
ale tablei, toate pe rânduri și coloane diferite. Orice soluție parțială ce conține două regine care se atacă
poate fi abandonată, deoarece în mod clar restul de regine nu pot fi așezate într-o soluție validă.
Tehnica backtracking se poate aplica doar pentru probleme ce admit conceptul de „candidat parțial de
soluție” și oferă un test relativ rapid asupra posibilității ca un astfel de candidat să fie completat către o soluție
validă. Când se poate aplica, însă, backtrackingul este adesea mult mai rapid decât căutarea prin metoda
forței brute prin toți candidații, întrucât este capabilă să elimine dintr-un singur test un mare număr de
candidați.
Backtrackingul este util la rezolvarea unor probleme de satisfacere a constrângerilor, cum ar fi cuvintele
încrucișate, jocuri de sudoku și alte probleme similare. Ea stă la baza unei serii de limbaje de programare
logică, cum ar fi Icon, Planner și Prolog.
Programarea dinamică rezolvă problemele prin descompunerea lor în subproblemeşi prin combinarea
rezolvărilor acestora. Programarea dinamică se aplică în general problemelor de optimizare, atunci când
dorim să determinăm rapid soluţia optimă pentru o problemă. De fapt, aplicând această tehnică
determinăm una din soluţiile optime, problema putând avea mai multe soluţii optime.
Aplicarea acestei tehnici de programare poate fi descompusă în următoarea secvenţă de paşi:
1. Descoperirea structurii şi "măsurii" pe care o are o soluţie optimă.
2. Definirea recursivă a valorii care caracterizează o soluţie optimă.
3. Calcularea "de jos în sus" a acestei valori.
4. Construirea soluţiei optime pornind de la calculele efectuate anterior