Sunteți pe pagina 1din 40

C4: Liste dublu inlantuite.

Gasirea si eliminarea de bucle dintr-o lista.


Stive si Cozi.
Lista dublu inlantuita
 o structura de date abstracta, liniara construita dintr-o succesiune de elemente in care
fiecare element/nod contine informatii si legatura catre nodul urmator si, respectiv,
nodul anterior
 setul de operatii e acelasi cu cel de la lista simplu inlantuita: creeaza/sterge lista;
adauga/ modifica / sterge element

typedef int Data;

typedef struct Elem Node;

struct Elem
{
Data val; // datele efective memorate
struct Elem* next, *prev; // legatura catre nodul urmator ,
// legatura catre nodul anterior
};
Lista dublu inlantuita
Avantaje:
 Dat fiind un nod in lista – se poate naviga in ambele directii
 Un nod dintr-o lista simplu inlantuita nu se poate sterge daca nu avem pointer la nodul
predecesor (aux), in cazul listei dublu inlantuite stergerea se face mai usor – deoarece
adresa acestuia e cunoscuta.

Dezavantaje:
 E nevoie de spatiu in plus pentru fiecare nod ca sa se stocheze si legatura catre nodul
predecesor.
 Inserarile si stergerile de elemente sunt ceva mai laborioase
Lista dublu inlantuita
1. Inserare element la inceputul listei

void addAtBeginning(Node**head, Data v)


{
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->val = v;
newNode->next=*head;
newNode->prev=NULL;
*head=newNode;
}
//complexitate O(1)

//main
Node* head = NULL; //creare lista
addAtBeginning(&head,0);
addAtBeginning(&head,1);
addAtBeginning(&head,2);
Lista dublu inlantuita
2. Inserare element oriunde in lista (include cazul – adaugare la inceput /final)
void addAtPos(Node**head, Data v, int pos) //adaug pe pe pozitia pos
{
Node*aux, *headcopy=*head;
int p=0;

while (headcopy!=NULL && p<pos) {


aux=headcopy;
headcopy=headcopy->next;
p++;
} //aux se foloseste pentru cazul in care se adauga dupa ultimul nod (headcopy e NULL );
//altfel - ne puteam referi la aux prin headcopy->prev si tratam adaugarea la final separat

if (pos==0 || *head==NULL) addAtBeginning(&*head,v);


else{ Node* newNode=(Node*)malloc(sizeof(Node));
newNode->val=v;
aux->next=newNode;
newNode->prev=aux;
newNode->next=headcopy; //headcopy poate sa fie inclusiv NULL (dupa ultimul el.)
if (headcopy!=NULL) headcopy->prev=newNode; //se poate trata separat adaugarea
//la finalul listei
} // complexitate O(n);
}//Daca se stia adresa nodului dupa/inainte de care sa se adauge unul nou => O(1); TEMA
Lista dublu inlantuita
2. sau: Inserare element oriunde in lista (include cazul – adaugare la inceput /final) – fara aux
void addAtPos(Node**head, Data v, int pos)
{ Node*aux, *headcopy=*head;
int p=0;
while (headcopy!=NULL && p<pos-1) {
headcopy=headcopy->next;
p++;
} //merg pana inainte de elementul de pe pos

if (pos==0 || *head==NULL) addAtBeginning(&*head,v);


else{ Node* newNode=(Node*)malloc(sizeof(Node));
newNode->val=v;
if ( p+1 == pos && headcopy!=NULL && headcopy->next!=NULL ) {//daca nu e ultimul el
(headcopy->next)->prev=newNode;
newNode->next=headcopy->next;
headcopy->next=newNode;
newNode->prev=headcopy;
} else if ( p+1 == pos && headcopy!=NULL && headcopy->next==NULL){//adaug la final
newNode->next=NULL;
headcopy->next=newNode;
newNode->prev=headcopy;
}
}
Lista dublu inlantuita
3. Parcurgerea si afisarea listei

void print(Node*head){
Node *headcopy=head;
while (headcopy!=NULL) {
printf("%d",headcopy->val);
headcopy=headcopy->next;
}
} // daca as cunoaste/stoca adresa ultimului element - as putea sa parcurg si in sens invers

//main
int i;
Node* head = NULL;
for (i=0;i<4;i++)
addAtPos(&head,i,i);
print(head);
// 0 1 2 3

addAtPos(&head,7,2);
print(head);
// 0 1 7 2 3
Lista dublu inlantuita
4. Modificarea informatiei stocate intr-un nod (include si cautarea)

void modify(Node*head, Data newdata, int pos) // modific elementul de pe pozitia pos
{
if (head == NULL) return;

Node*aux=head, *headcopy=head;
int p=0;

while (headcopy!=NULL&&p<pos) {
aux=headcopy;
//ma folosesc de auxiliar ca sa tratez cazul in care trebuie sa modific ultimul element, pentru
//ca, atunci nu as putea sa ma refer la aux prin headcopy->prev, pentru ca headcopy e NULL
headcopy=headcopy->next;
p++;
}
aux->val=newdata;
}//complexitate O(n)

OBS: Adaugarea dupa ultimul element, respectiv modificarea acestuia trebuiesc tratate separat
ca sa ma pot folosi de legatura la nodul anterior. Ce as putea face?
-> santinele ->Tema
Lista dublu inlantuita
5. Stergerea unui element (include si cautarea)

void deleteV(Node**head, Data val) //se cauta pentru stergere elementul cu valoarea val
{
if (*head==NULL) return;
Node *headcopy=*head;

if (headcopy->val==val) {
*head=(*head)->next;
if((*head)!=NULL) (*head)->prev=NULL;
free(headcopy);
return;
} // stergere cap lista – tratat separat
Lista dublu inlantuita
// stergere alt tip de elemente
// Node *headcopy=*head;

while (headcopy!=NULL) {
if (headcopy->val!=val){
headcopy=headcopy->next;
}else{
(headcopy->prev)->next=headcopy->next;
if((headcopy->prev)->next!=NULL)
((headcopy->prev)->next)->prev=headcopy->prev;
free(headcopy);
return;
}
}
}
Lista dublu inlantuita

6. Stergerea listei

void deleteList(Node **head)


{
Node* headcopy=*head;
while (*head!=NULL){
headcopy=(*head)->next;
free(*head);
*head=headcopy;
}

*head=NULL;
}

Tema: Implementati recursiv stergerea listei.


Tema

1. Implementati o lista dublu inlantuita cu santinela la final.

2. Transformati-o intr-o lista dublu inlantuita cu santinela si circulara.


Algoritmul Floyd pentru detectia buclelor intr-o lista

Problema: Se da o lista inlantuita prin primul ei element. Se cere un algoritm cat mai
eficient care sa determine daca lista are sau nu bucle/cicluri.

Solutie: Algoritmul lui Floyd de detectie a buclelor intr-o lista


- se folosesc doi pointeri unul rapid (care parcurge lista din 2 in 2) si unul lent
(care parcurge lista din 1 in 1)
- eventual, daca exista bucla, cei doi pointeri vor ajunge in acelasi punct.
//consideram tipul Node definit in C3 – pentru lista simplu inlantuita

void detectAndRemoveLoop(Node *head)


{ // daca lista e goala sau contine doar un nod fara bucla - ies
if (head == NULL || head->next == NULL) return;
Node *slow = head, *fast = head;

//pornind de la inceputul listei ma deplasez inainte cu 2 viteze diferite


slow = slow->next;
fast = fast->next->next;

//cautam bucla folosind doi pointeri unul lent si unul rapid


while (fast != NULL && fast->next != NULL)
{
if (slow == fast)
break;
slow = slow->next;
fast = fast->next->next;
}
Cum aflu unde incepe/se termina
bucla ca sa pot sa o elimin?

Fie:
m – nr noduri pana la inceputul buclei
n – nr de noduri din bucla
k – nr de noduri dupa care cei 2 pointeri se intalnesc.
Distanta parcursa de fast e de doua ori mai mare decat cea parcursa de slow:

m+n*i+k = 2*(m+n*j+k) , unde i si j reprezinta numarul de parcurgeri al buclei de catre


pointerul fast respectiv pointerul slow

n*(i-2*j) = m+k => m+k este multiplu de n: m+k = x*n

Ducem pointerul slow din nou la inceput si setam viteza pentru amandoi pointerii la
un pas.

Cat timp slow face m pasi - > tot atatia face si fast, dar incepand de la m+k -> ajunge la
m+m+k= m+x*n => amandoi pointerii o sa indice la inceputul buclei
// daca exista bucla vreau sa o elimin
if (slow == fast)
{
slow = head;
while (slow->next != fast->next)
{
slow = slow->next;
fast = fast->next;
}

// deoarece fast->next e punctul de “intersectie”


fast->next = NULL;
}
}

void print(Node*head) Se mai poate rezolva problema


{ eliminarii de bucle si altfel?
Node *headcopy=head; As putea sa numar cate elemente n am in
while (headcopy!=NULL) { bucla, apoi sa pozitionez pointerii unul
printf("%d ",headcopy->val); pointand la head celalalt la elementul de
headcopy=headcopy->next; pe pozitia n; sa ii misc pe amandoi cu
} aceeasi viteza cat timp sunt diferiti, altfel
am ajuns la inceputul buclei.
} Tema: Implementati!
Node *newNode(int v)
{
Node *temp = (Node*)malloc(sizeof(Node));
temp->val = v;
temp->next = NULL;
return temp;
}

int main()
{
Node *head = newNode(0);
head->next = newNode(1);
head->next->next = newNode(2);
head->next->next->next = newNode(3);
head->next->next->next->next = newNode(4);
// Creez bucla
head->next->next->next->next->next = head->next->next;
detectAndRemoveLoop(head);
print(head);
return 0;
Stiva

 o structura de date abstracta, liniara pentru care atat operatia de inserare a unui
element cat si cea de extragere se realizeaza la un singur capat numit varful stivei.
 ultimul element adaugat e primul scos – LIFO (Last In First Out).

Operatii caracteristice
 crearea/distrugerea unei stive
 inserarea unui element in stiva (push)
 scoaterea unui element din stiva (pop)
 accesarea elementului din varf (top)
 aflarea capacitatii
 verificare daca stiva e goala - isEmpty
 verificare daca stiva e plina - isFull

Ex. - vraf de farfurii - nu o sa scot/pun farfurii la mijloc, iau/pun in varf


- prioritizare de taskuri: cineva la munca lucreaza la un proiect, suna telefonul –
raspunde; mai lucreaza un pic; iar suna telefonul; iar raspunde…

Cine mai functioneaza asa?


Stiva
Utilitate
- concept fundamental pentru recursivitate si executia programelor in general
- utila cand trebuie memorate informatii si regasite in aceeasi ordine:
 butoane de inapoi - browsere
 secventa de undo - editoarele de text
- utilizata atunci cand o aplicatie trebuie sa amane realizarea unor operatii pentru a le
executa in ordinea inversa a aparitiei. Operatia curenta este cea corespunzatoare
varfului stivei, in stiva fiind retinute toate informatiile necesare programului pentru a
executa operatiile respective.

Implementare
 cu vector
Avantaje - implementarea e simpla, consumul de memorie e redus, viteza mare pentru
operatii (mereu accesez ultimul element prin index)
Dezavantaje - numarul de elemente ar trebui cunoscut (vector static) sau risipa de
memorie daca dimensiunea alocata e mult mai mare decat cea necesara (vector
dinamic), respectiv, timp daca trebuie realocat spatiu
 cu liste
Avantaje – numar oarecare de elemente; stocate in locatii neconsecutive
Dezavantaje - consum de memorie suplimentar pentru memorarea legaturilor
Stiva – Implementarea cu lista

typedef int Data;


typedef struct Elem Node;

struct Elem
{
Data val; // datele efective memorate
struct Elem* next; // legatura catre nodul urmator
};

//main
//o stiva goala se declara - creaza astfel:

Node* stackTop = NULL;

//mereu se va memora varful stivei – deoarece toate operatiile se fac aici


Stiva – Implementarea cu lista

1. Adaugarea unui element (se face in varful stivei )

void push(Node**top, Data v) //varful trebuie intors modificat din functie


{
Node* newNode=(Node*)malloc(sizeof(Node));
newNode->val=v;
newNode->next=*top;
*top=newNode;
}

//main
int i;
Node* stackTop = NULL;
for (i=0;i<4;i++)
push(&stackTop,i);
Stiva – Implementarea cu lista

2. Verificare daca stiva e goala / plina

int isEmpty(Node*top){
return top==NULL;
}

Daca exista o limita maxima pentru capacitatea stivei se putea testa depasirea
spatiului alocat – inainte de adaugarea un element.
Stiva – Implementarea cu lista
3. Scoaterea unui element (se face din varful stivei )

Data pop(Node**top) // se returneaza informatia stocata in varf si se stege acest nod


{
if (isEmpty(*top)) return INT_MIN; //daca stiva e goala returnez o valoare standard
// definita de mine
Node *temp=(*top); //stochez adresa varful in temp
Data aux=temp->val; //stochez valaorea din varfului in aux

*top=(*top)->next; //sterg elementul din varf


free(temp);
return aux;
}
//main – ce face secventa:
Node* stackTop = NULL;
for (i=0;i<4;i++)
push(&stackTop,i);
while (!isEmpty(stackTop))
printf("%d",pop(&stackTop));
Stiva – Implementarea cu lista

4. Investigarea valorii stocate in varf

Data top(Node *top){


if (isEmpty(top)) return INT_MIN;
return top->val;
}

//main
int i;
Node* stackTop = NULL; //creare stiva
for (i=0;i<4;i++)
push(&stackTop, i);

printf("%d", top(stackTop));
printf("%d", pop(stackTop));
Stiva – Implementarea cu lista
5. Stergerea stivei

void deleteStack(Node**top)
{
Node* topCopy=*top, *temp;
while (topCopy!=NULL){
temp=topCopy;
topCopy=topCopy->next;
free(temp);
}
}

//main
int i;
Node* stackTop = NULL;
for (i=0;i<4;i++)
push(&stackTop,i);

deleteStack(&stackTop);
Stiva – Implementarea cu lista
• Fie n numarul de elemente din stiva

Operatie Complexitate
creare stiva O(1)
push O(1)
pop O(1)
top O(1)
isEmpy O(1)
deleteStack O(n)

• Cum as putea sa declar o stiva careia ii stiu si capacitatea si numarul de elemente?

struct stack{
Node *top;
int capacity, size;
};

Tema: Ce modificari ar trebui facute? Implementati!


Stiva – Implementarea cu vector

Tipul de date folosit este declarat astfel:

typedef int Data;

struct DynArrayStack{
int top;
int capacity;
Data*array;
};

typedef struct DynArrayStack Stack;

unde:
top – pozitia elementului cel mai din varf (final in vector)
capacity – dimensiunea vectorului
array –vectorul in care sunt stocate elementele
Stiva – Implementarea cu vector
1. Crearea stivei

Stack* createStack()
{
Stack *s=(Stack*)malloc(sizeof(Stack));
if (!s) return NULL; // verific ca am alocat spatiu
s->capacity=5; // as fi putut sa transmit capacitatea ca parametru
s->top=-1; //nu am niciun element
s->array= (Data*) malloc(sizeof(Data)*s->capacity);
if (!s->array) return NULL; // verific daca s-a alocat spatiu pentru bufferul stivei
return s;
}

//main
Stack *stack=createStack();

//Cum arata stack in memorie? Desenati!


Stiva – Implementarea cu vector
2. Verificari daca stiva e plina sau goala si realocare de spatiu de memorie in
caz de depasire a dimensiunii (la incercarea de adaugare a unui element)

int isFull(Stack*s){
return (s->top==s->capacity-1);
}

int isEmpty(Stack*s){
return (s->top==-1);
}

void increaseCapacity(Stack*s){
s->capacity*=2;
s->array = (Data*)realloc(s->array,s->capacity);
}
//Capacitatea se poate dubla; creste din 2 in 2; etc
//Realocarea de spatiu afecteaza complexitatea pentru push -> O(n) deci ar trebui sa se
//intample cat mai rar => dublarea spatiul e o varianta buna
Stiva – Implementarea cu vector
3. Adaugarea si scoaterea de elemente; aflarea valorii varfului

void push(Stack*s, Data x){


if (isFull(s)) increaseCapacity(s);
s->array[++s->top]=x; //cresc valoarea pentru numarul de elemente si adaug x
}

Data pop(Stack*s){
if (!isEmpty(s)) return s->array[s->top--]; //returnez valoarea varfului si decrementez top
return INT_MIN; //daca stiva e goala returnez o valoare de minim presetata
}

Data top(Stack*s){
if (!isEmpty(s)) return s->array[s->top]; //returnez valoarea varfului
return INT_MIN;
}
Stiva – Implementarea cu vector
4. Stergerea stivei

void deleteStack(Stack**s){
if (*s) {
if ((*s)->array) free((*s)->array); //eliberez spatiul ocupat de buffer
free(*s); //si apoi spatiul ocupat de stiva si o fac NULL
(*s)=NULL;
}
}

//main()
Stack*stack=createStack();
push(stack,1);
push(stack,2);
top(stack);
pop(stack);

deleteStack(&stack);
Stiva – Implementarea cu vector
• Fie n numarul de elemente din stiva
Operatie Complexitate
creare stiva O(1)
push O(1) – in medie*
pop O(1)
top O(1)
isEmpy O(1)
deleteStack O(1)

*Cresterea incrementala (cu 1) a capacitatii vs dublarea capacitatii la realocare


• Fie T(n) timpul necesar pentru n operatii push.
• Consideram ca stiva e goala. Fie timpul amortizat pentru o operatie de push media
duratelor pentru toate cele n operatii: T(n)/n.
• Crestere incrementala a dimensiunii bufferului -> timpul amortizat e O(n) = [O(n^2)/n]
• Crestere prin dublare a dimensiunii bufferului - timpul amortizat e O(1) = [O(n)/n]

Concluzii: Implementarea cu vectori e facila si la fel de buna ca cea cu liste, dar – vectorul
necesita locatii succesive de memorie, lista nu => acesta e un dezavantaj daca numarul de
inregistrari e mare.
Tema stiva
1. Concatenati 2 stive. Ce varianta de implementare e mai buna (cu vector sau lista)?
2. Inversati cuvintele dintr-o propozitie folosind o stiva.
3. Se considera un sir de numere intregi. Sa se scrie functia care construieste doua stive (una
cu numere negative si cealalta cu numere pozitive ) ce contin numerele in ordinea initiala –
folosind doar structuri de tip stiva. Indicatie: Se adauga toate elementele intr-o stiva
temporara dupa care se extrag elementele din aceasta si se introduc in stiva
corespunzatoare.
4. Creati o structura de date twoStacks care reprezinta doua stive. Implementarea
pentru twoStacks trebuie sa foloseasca un singur vector pentru stocarea elementelor.
Implementati functiile
push1 –> adauga elementul x in prima stiva
push2 –> adauga elementul x in a doua stiva
pop1 –> scoate element cu returnarea valorii sale din prima stiva
pop2 –> scoate element cu returnarea valorii sale din a doua stiva
Cum implementez twoStack cat mai eficient dpdv al spatiului?
Incep stivele de la 0 si n-1.
Coada
 o structura de date abstracta, liniara pentru care operatia de inserare se face la un
capat si operatia de extragere se face la celalalt capat (first in first out sau last in
last out).
 avem acces direct la elementul de la inceput, respectiv, sfarsitul cozii.

Operatii caracteristice
 Creare/stergere coada
 Adaugare/scoatere elemente - enQueue/deQueue
 Testarea capacitatii - overflow/underflow
 Aflarea valorilor de la inceputul si respectiv finalul cozii - front/rear
 Test daca e goala - isEmpty
 Aflarea dimensiunii - queueSize

Aplicatii directe: operatii de tip programari cu prioritati egale ( transfer de date


asincron; timp de asteptare la call center, cereri facute spre un anumit serviciu etc)

Implementarea se poate face cu vectori (Tema) sau liste.


Coada – implementarea cu liste
Tipul de date pentru coada presupune definirea tipului nod si a unei structuri
ce contine primul si, respectiv, ultimul element.

typedef int Data;

struct Elem
{
Data val; // datele efective memorate
struct Elem* next; // legatura catre nodul urmator
};

typedef struct Elem Node;

struct Q{
Node *front,*rear;
};

typedef struct Q Queue;


Coada – implementarea cu liste
1. Creare coada

Queue* createQueue()
{
Queue *q;
q=(Queue *)malloc(sizeof(Queue));
if (q==NULL) return NULL;

q->front=q->rear=NULL;

return q;
}

//main
Queue* q;
q=createQueue();
Coada – implementarea cu liste
2. Adaugarea unui element
void enQueue(Queue*q,Data v)
{
Node* newNode=(Node*)malloc(sizeof(Node));
newNode->val=v;
newNode->next=NULL;

if (q->rear==NULL) q->rear=newNode;
else{
(q->rear)->next=newNode;
(q->rear)=newNode;
}

if (q->front==NULL) q->front=q->rear; // daca e singurul element din coada


}
//complexitate O(1)

//main
Queue* q=createQueue();
for (i=0;i<4;i++)
enQueue(q,i);
Coada – implementarea cu liste
3. Test coada goala si 4. Scoaterea unui element

int isEmpty(Queue*q){
return q->front==NULL;
}

Data deQueue(Queue*q)
{
Data d=0;
Node* newNode;
if (isEmpty(q)) return d;
newNode=q->front;
d=newNode->val;
q->front=(q->front)->next;
free(newNode);
return d;
}
//complexitate O(1)

//main
while (q->front!=NULL)
printf("%d",deQueue(q));
Coada – implementarea cu liste

5. Stergere coada
void deleteQueue(Queue*q){
Node* temp;
while (!isEmpty(q)){
temp=q->front;
q->front=q->front->next;
printf("%d",temp->val);
free(temp);
}
free(q);
}

//complexitate O(n)
Tema coada
• Implementati o coada folosind o lista dublu inlantuita circulara cu santinela.

• Implementati o coada cu prioritati ( fiecare element din structura are o prioritate) –


primul element extras din coda e cel cu prioritatea maxima.
Implementati cat mai eficient.

_______________________________________________________________
• Implementati merge sort folosind lista in loc de vector.

S-ar putea să vă placă și