Structuri de Date Liniare Implementate Dinamic – Liste Liniare Simplu Înlanțuite


1. Obiectivele lecției:

  • Să înțeleagă conceptul de listă liniară simplu înlănțuită.
  • Să învețe cum să implementeze o listă simplu înlănțuită dinamic.
  • Să utilizeze operații comune: adăugare, ștergere, parcurgere.

2. Ce este o listă liniară simplu înlănțuită?

  1. Definiție:
    O listă simplu înlănțuită este o structură de date compusă din noduri, unde fiecare nod conține:
    • O valoare (informație utilă).
    • Un pointer către următorul nod din listă.
  2. Caracteristici:
    • Alocarea memoriei este dinamică.
    • Dimensiunea listei poate varia în timpul execuției.
    • Primul nod este identificat de un pointer numit head.
    • Ultimul nod indică sfârșitul listei printr-un pointer nullptr.

3. Avantaje și dezavantaje

AvantajeDezavantaje
Dimensiunea listei este dinamică.Accesul la un element necesită parcurgere secvențială.
Inserarea și ștergerea elementelor sunt eficiente.Consumul de memorie suplimentară pentru pointere.
Nu este necesară alocarea unui bloc mare de memorie.Mai dificil de implementat comparativ cu vectorii.

4. Structura unui nod

  1. Structura nodului:

struct Nod {

    int valoare;

    Nod* urmator;

};

  1. Explicație:
    • valoare: Stochează informația utilă.
    • urmator: Pointer către următorul nod din listă.

5. Operații comune pe liste simplu înlănțuite


1. Crearea unui nod

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->urmator = nullptr;

    return nod;

}


2. Adăugarea unui element

  1. Adăugare la început:

void adaugaInceput(Nod*& head, int valoare) {

    Nod* nod = creeazaNod(valoare);

    nod->urmator = head;

    head = nod;

}

  1. Adăugare la sfârșit:

void adaugaSfarsit(Nod*& head, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (head == nullptr) {

        head = nod;

    } else {

        Nod* temp = head;

        while (temp->urmator != nullptr) {

            temp = temp->urmator;

        }

        temp->urmator = nod;

    }

}


3. Ștergerea unui element

  1. Ștergere de la început:

void stergeInceput(Nod*& head) {

    if (head == nullptr) return;

    Nod* temp = head;

    head = head->urmator;

    delete temp;

}

  1. Ștergere de la sfârșit:

void stergeSfarsit(Nod*& head) {

    if (head == nullptr) return;

    if (head->urmator == nullptr) {

        delete head;

        head = nullptr;

        return;

    }

    Nod* temp = head;

    while (temp->urmator->urmator != nullptr) {

        temp = temp->urmator;

    }

    delete temp->urmator;

    temp->urmator = nullptr;

}

  1. Ștergerea unui nod cu o anumită valoare:

void stergeValoare(Nod*& head, int valoare) {

    if (head == nullptr) return;

    if (head->valoare == valoare) {

        Nod* temp = head;

        head = head->urmator;

        delete temp;

        return;

    }

    Nod* temp = head;

    while (temp->urmator != nullptr && temp->urmator->valoare != valoare) {

        temp = temp->urmator;

    }

    if (temp->urmator != nullptr) {

        Nod* deSters = temp->urmator;

        temp->urmator = temp->urmator->urmator;

        delete deSters;

    }

}


4. Parcurgerea listei

void parcurgeLista(Nod* head) {

    Nod* temp = head;

    while (temp != nullptr) {

        cout << temp->valoare << ” „;

        temp = temp->urmator;

    }

    cout << endl;

}


6. Exemplu complet

#include <iostream>

using namespace std;

struct Nod {

    int valoare;

    Nod* urmator;

};

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->urmator = nullptr;

    return nod;

}

void adaugaInceput(Nod*& head, int valoare) {

    Nod* nod = creeazaNod(valoare);

    nod->urmator = head;

    head = nod;

}

void adaugaSfarsit(Nod*& head, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (head == nullptr) {

        head = nod;

    } else {

        Nod* temp = head;

        while (temp->urmator != nullptr) {

            temp = temp->urmator;

        }

        temp->urmator = nod;

    }

}

void stergeInceput(Nod*& head) {

    if (head == nullptr) return;

    Nod* temp = head;

    head = head->urmator;

    delete temp;

}

void stergeSfarsit(Nod*& head) {

    if (head == nullptr) return;

    if (head->urmator == nullptr) {

        delete head;

        head = nullptr;

        return;

    }

    Nod* temp = head;

    while (temp->urmator->urmator != nullptr) {

        temp = temp->urmator;

    }

    delete temp->urmator;

    temp->urmator = nullptr;

}

void parcurgeLista(Nod* head) {

    Nod* temp = head;

    while (temp != nullptr) {

        cout << temp->valoare << ” „;

        temp = temp->urmator;

    }

    cout << endl;

}

int main() {

    Nod* lista = nullptr;

    adaugaInceput(lista, 10);

    adaugaInceput(lista, 20);

    adaugaSfarsit(lista, 30);

    parcurgeLista(lista);

    stergeInceput(lista);

    parcurgeLista(lista);

    stergeSfarsit(lista);

    parcurgeLista(lista);

    return 0;

}


7. Activități practice pentru elevi

  1. Implementați o funcție care determină lungimea unei liste simplu înlănțuite.
  2. Scrieți o funcție care inserează un nod după un alt nod cu o valoare specificată.
  3. Realizați o funcție care inversează o listă simplu înlănțuită.

8. Scheme logice

  1. Adăugare:
    • Creează nod -> Leagă nodul la început/sfârșit -> Actualizează pointerul head.
  2. Ștergere:
    • Verifică dacă lista este goală -> Găsește nodul de șters -> Actualizează legăturile -> Eliberează memoria.

9. Concluzie

  • Listele simplu înlănțuite sunt structuri flexibile și eficiente pentru gestionarea datelor.
  • Implementarea necesită gestionarea atentă a pointerilor pentru a evita scurgerile de memorie.
  • Practica este esențială pentru a înțelege pe deplin cum funcționează aceste structuri.

Structuri de Date Liniare Implementate Dinamic – Liste Liniare Dublu Înlanțuite


1. Obiectivele lecției:

  • Să înțeleagă conceptul de listă liniară dublu înlănțuită.
  • Să învețe cum să implementeze o listă dublu înlănțuită dinamic.
  • Să implementeze operații comune: adăugare, ștergere, parcurgere în ambele sensuri.

2. Ce este o listă liniară dublu înlănțuită?

  1. Definiție:
    O listă dublu înlănțuită este o structură de date compusă din noduri, unde fiecare nod conține:
    • O valoare (informație utilă).
    • Un pointer către următorul nod.
    • Un pointer către nodul anterior.
  2. Caracteristici:
    • Permite parcurgerea în ambele direcții.
    • Primul nod al listei (denumit head) are pointerul către nodul anterior setat la nullptr.
    • Ultimul nod al listei (denumit tail) are pointerul către următorul nod setat la nullptr.
  3. Structura nodului:

struct Nod {

    int valoare;

    Nod* urmator;

    Nod* anterior;

};


3. Avantaje și dezavantaje

AvantajeDezavantaje
Permite parcurgerea bidirecțională.Necesită mai multă memorie pentru pointere suplimentare.
Operații de inserare și ștergere eficiente.Mai complicat de implementat decât listele simplu înlănțuite.
Utilă pentru structuri precum deque sau liste circulare.Mai greu de gestionat pentru noduri multiple.

4. Structura nodului

struct Nod {

    int valoare;      // Informația stocată în nod

    Nod* urmator;     // Pointer către următorul nod

    Nod* anterior;    // Pointer către nodul anterior

};


5. Operații comune pe liste dublu înlănțuite


1. Crearea unui nod

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->urmator = nullptr;

    nod->anterior = nullptr;

    return nod;

}


2. Adăugarea unui element

  1. Adăugare la început:

void adaugaInceput(Nod*& head, Nod*& tail, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (head == nullptr) {

        head = tail = nod;

    } else {

        nod->urmator = head;

        head->anterior = nod;

        head = nod;

    }

}

  1. Adăugare la sfârșit:

void adaugaSfarsit(Nod*& head, Nod*& tail, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (tail == nullptr) {

        head = tail = nod;

    } else {

        nod->anterior = tail;

        tail->urmator = nod;

        tail = nod;

    }

}


3. Ștergerea unui element

  1. Ștergere de la început:

void stergeInceput(Nod*& head, Nod*& tail) {

    if (head == nullptr) return;

    Nod* temp = head;

    if (head == tail) {

        head = tail = nullptr;

    } else {

        head = head->urmator;

        head->anterior = nullptr;

    }

    delete temp;

}

  1. Ștergere de la sfârșit:

void stergeSfarsit(Nod*& head, Nod*& tail) {

    if (tail == nullptr) return;

    Nod* temp = tail;

    if (head == tail) {

        head = tail = nullptr;

    } else {

        tail = tail->anterior;

        tail->urmator = nullptr;

    }

    delete temp;

}

  1. Ștergerea unui nod cu o valoare specificată:

void stergeValoare(Nod*& head, Nod*& tail, int valoare) {

    if (head == nullptr) return;

    Nod* temp = head;

    while (temp != nullptr && temp->valoare != valoare) {

        temp = temp->urmator;

    }

    if (temp == nullptr) return;

    if (temp == head) {

        stergeInceput(head, tail);

    } else if (temp == tail) {

        stergeSfarsit(head, tail);

    } else {

        temp->anterior->urmator = temp->urmator;

        temp->urmator->anterior = temp->anterior;

        delete temp;

    }

}


4. Parcurgerea listei

  1. Parcurgere de la început la sfârșit:

void parcurgeInainte(Nod* head) {

    Nod* temp = head;

    while (temp != nullptr) {

        cout << temp->valoare << ” „;

        temp = temp->urmator;

    }

    cout << endl;

}

  1. Parcurgere de la sfârșit la început:

void parcurgeInapoi(Nod* tail) {

    Nod* temp = tail;

    while (temp != nullptr) {

        cout << temp->valoare << ” „;

        temp = temp->anterior;

    }

    cout << endl;

}


6. Exemplu complet

#include <iostream>

using namespace std;

struct Nod {

    int valoare;

    Nod* urmator;

    Nod* anterior;

};

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->urmator = nullptr;

    nod->anterior = nullptr;

    return nod;

}

void adaugaInceput(Nod*& head, Nod*& tail, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (head == nullptr) {

        head = tail = nod;

    } else {

        nod->urmator = head;

        head->anterior = nod;

        head = nod;

    }

}

void adaugaSfarsit(Nod*& head, Nod*& tail, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (tail == nullptr) {

        head = tail = nod;

    } else {

        nod->anterior = tail;

        tail->urmator = nod;

        tail = nod;

    }

}

void parcurgeInainte(Nod* head) {

    Nod* temp = head;

    while (temp != nullptr) {

        cout << temp->valoare << ” „;

        temp = temp->urmator;

    }

    cout << endl;

}

void parcurgeInapoi(Nod* tail) {

    Nod* temp = tail;

    while (temp != nullptr) {

        cout << temp->valoare << ” „;

        temp = temp->anterior;

    }

    cout << endl;

}

void stergeInceput(Nod*& head, Nod*& tail) {

    if (head == nullptr) return;

    Nod* temp = head;

    if (head == tail) {

        head = tail = nullptr;

    } else {

        head = head->urmator;

        head->anterior = nullptr;

    }

    delete temp;

}

void stergeSfarsit(Nod*& head, Nod*& tail) {

    if (tail == nullptr) return;

    Nod* temp = tail;

    if (head == tail) {

        head = tail = nullptr;

    } else {

        tail = tail->anterior;

        tail->urmator = nullptr;

    }

    delete temp;

}

int main() {

    Nod* head = nullptr;

    Nod* tail = nullptr;

    adaugaInceput(head, tail, 10);

    adaugaInceput(head, tail, 20);

    adaugaSfarsit(head, tail, 30);

    cout << „Parcurgere înainte: „;

    parcurgeInainte(head);

    cout << „Parcurgere înapoi: „;

    parcurgeInapoi(tail);

    stergeInceput(head, tail);

    cout << „După ștergerea primului nod: „;

    parcurgeInainte(head);

    stergeSfarsit(head, tail);

    cout << „După ștergerea ultimului nod: „;

    parcurgeInainte(head);

    return 0;

}


7. Activități practice pentru elevi

  1. Implementați o funcție care determină lungimea unei liste dublu înlănțuite.
  2. Scrieți o funcție care inserează un nod înainte de un nod specificat.
  3. Realizați o funcție care inversează o listă dublu înlănțuită.

8. Scheme logice

  1. Adăugare:
    • Creează nod -> Actualizează legăturile -> Actualizează head sau tail.
  2. Ștergere:
    • Verifică dacă lista este goală -> Actualizează legăturile -> Eliberează memoria.

9. Concluzie

  • Listele dublu înlănțuite oferă flexibilitate și eficiență pentru operații bidirecționale.
  • Sunt utile în aplicații care necesită acces rapid în ambele sensuri.
  • Practica este esențială pentru a înțelege și a implementa corect această structură de date.

Structuri de Date Liniare Implementate Dinamic – Liste Circulare Simplu Înlanțuite


1. Obiectivele lecției:

  • Să înțeleagă conceptul de listă circulară simplu înlănțuită.
  • Să învețe cum să implementeze o astfel de listă dinamic.
  • Să implementeze operații comune: adăugare, ștergere, parcurgere.

2. Ce este o listă circulară simplu înlănțuită?

  1. Definiție:
    O listă circulară simplu înlănțuită este o structură de date în care:
    • Fiecare nod conține o valoare și un pointer către următorul nod.
    • Ultimul nod din listă indică înapoi către primul nod, creând astfel o structură circulară.
  2. Caracteristici:
    • Lista nu are început și sfârșit clar definite.
    • Se poate parcurge în mod continuu.
    • Primul nod este gestionat printr-un pointer numit head.

3. Avantaje și dezavantaje

AvantajeDezavantaje
Permite parcurgerea continuă a listei.Mai dificil de implementat decât listele simple.
Eficientă pentru aplicații circulare (ex.: bucle).Gestionarea pointerilor poate fi complicată.
Inserarea și ștergerea nodurilor sunt rapide.Este necesară atenție pentru a menține lista circulară.

4. Structura unui nod

struct Nod {

    int valoare;      // Informația stocată în nod

    Nod* urmator;     // Pointer către următorul nod

};


5. Operații comune pe liste circulare simplu înlănțuite


1. Crearea unui nod

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->urmator = nullptr;

    return nod;

}


2. Adăugarea unui element

  1. Adăugare la început:

void adaugaInceput(Nod*& head, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (head == nullptr) {

        nod->urmator = nod;

        head = nod;

    } else {

        Nod* temp = head;

        while (temp->urmator != head) {

            temp = temp->urmator;

        }

        nod->urmator = head;

        temp->urmator = nod;

        head = nod;

    }

}

  1. Adăugare la sfârșit:

void adaugaSfarsit(Nod*& head, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (head == nullptr) {

        nod->urmator = nod;

        head = nod;

    } else {

        Nod* temp = head;

        while (temp->urmator != head) {

            temp = temp->urmator;

        }

        temp->urmator = nod;

        nod->urmator = head;

    }

}


3. Ștergerea unui element

  1. Ștergere de la început:

void stergeInceput(Nod*& head) {

    if (head == nullptr) return;

    if (head->urmator == head) { // Lista are un singur nod

        delete head;

        head = nullptr;

        return;

    }

    Nod* temp = head;

    Nod* ultim = head;

    while (ultim->urmator != head) {

        ultim = ultim->urmator;

    }

    head = head->urmator;

    ultim->urmator = head;

    delete temp;

}

  1. Ștergere de la sfârșit:

void stergeSfarsit(Nod*& head) {

    if (head == nullptr) return;

    if (head->urmator == head) { // Lista are un singur nod

        delete head;

        head = nullptr;

        return;

    }

    Nod* temp = head;

    while (temp->urmator->urmator != head) {

        temp = temp->urmator;

    }

    delete temp->urmator;

    temp->urmator = head;

}

  1. Ștergerea unui nod cu o valoare specificată:

void stergeValoare(Nod*& head, int valoare) {

    if (head == nullptr) return;

    if (head->valoare == valoare) {

        stergeInceput(head);

        return;

    }

    Nod* temp = head;

    while (temp->urmator != head && temp->urmator->valoare != valoare) {

        temp = temp->urmator;

    }

    if (temp->urmator == head) return;

    Nod* deSters = temp->urmator;

    temp->urmator = deSters->urmator;

    delete deSters;

}


4. Parcurgerea listei

void parcurgeLista(Nod* head) {

    if (head == nullptr) return;

    Nod* temp = head;

    do {

        cout << temp->valoare << ” „;

        temp = temp->urmator;

    } while (temp != head);

    cout << endl;

}


6. Exemplu complet

#include <iostream>

using namespace std;

struct Nod {

    int valoare;

    Nod* urmator;

};

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->urmator = nullptr;

    return nod;

}

void adaugaInceput(Nod*& head, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (head == nullptr) {

        nod->urmator = nod;

        head = nod;

    } else {

        Nod* temp = head;

        while (temp->urmator != head) {

            temp = temp->urmator;

        }

        nod->urmator = head;

        temp->urmator = nod;

        head = nod;

    }

}

void adaugaSfarsit(Nod*& head, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (head == nullptr) {

        nod->urmator = nod;

        head = nod;

    } else {

        Nod* temp = head;

        while (temp->urmator != head) {

            temp = temp->urmator;

        }

        temp->urmator = nod;

        nod->urmator = head;

    }

}

void parcurgeLista(Nod* head) {

    if (head == nullptr) return;

    Nod* temp = head;

    do {

        cout << temp->valoare << ” „;

        temp = temp->urmator;

    } while (temp != head);

    cout << endl;

}

void stergeInceput(Nod*& head) {

    if (head == nullptr) return;

    if (head->urmator == head) {

        delete head;

        head = nullptr;

        return;

    }

    Nod* temp = head;

    Nod* ultim = head;

    while (ultim->urmator != head) {

        ultim = ultim->urmator;

    }

    head = head->urmator;

    ultim->urmator = head;

    delete temp;

}

int main() {

    Nod* lista = nullptr;

    adaugaInceput(lista, 10);

    adaugaInceput(lista, 20);

    adaugaSfarsit(lista, 30);

    cout << „Lista după adăugări: „;

    parcurgeLista(lista);

    stergeInceput(lista);

    cout << „Lista după ștergerea primului element: „;

    parcurgeLista(lista);

    return 0;

}


7. Activități practice pentru elevi

  1. Implementați o funcție care determină lungimea unei liste circulare.
  2. Scrieți o funcție care inserează un nod după un alt nod specificat.
  3. Realizați o funcție care verifică dacă un element există în listă.

8. Scheme logice

  1. Adăugare:
    • Creează nod -> Actualizează pointerii -> Asigură circularitatea listei.
  2. Ștergere:
    • Verifică dacă lista este goală -> Găsește nodul de șters -> Actualizează pointerii -> Eliberează memoria.

9. Concluzie

  • Listele circulare simplu înlănțuite sunt utile în aplicații care necesită parcurgere continuă, precum sistemele circulare de priorități.
  • Implementarea necesită gestionarea atentă a pointerilor pentru a menține circularitatea listei.
  • Practica este esențială pentru a stăpâni implementarea și utilizarea acestor liste.

Structuri de Date Liniare Implementate Dinamic – Liste Circulare Dublu Înlanțuite


1. Obiectivele lecției:

  • Să înțeleagă conceptul de listă circulară dublu înlănțuită.
  • Să învețe cum să implementeze această structură dinamic.
  • Să implementeze operații comune: adăugare, ștergere, parcurgere în ambele sensuri.

2. Ce este o listă circulară dublu înlănțuită?

  1. Definiție:
    O listă circulară dublu înlănțuită este o structură de date în care:
    • Fiecare nod conține o valoare, un pointer către nodul următor și un pointer către nodul anterior.
    • Primul nod este legat de ultimul nod, iar ultimul nod este legat de primul, creând o structură circulară.
  2. Caracteristici:
    • Permite parcurgerea listei în ambele sensuri.
    • Este circulară, astfel încât lista poate fi parcursă continuu fără punct final.
  3. Structura nodului:

struct Nod {

    int valoare;

    Nod* urmator;  // Pointer către următorul nod

    Nod* anterior; // Pointer către nodul anterior

};


3. Avantaje și dezavantaje

AvantajeDezavantaje
Parcurgere bidirecțională continuă.Gestionarea pointerilor este mai complexă.
Eficientă pentru aplicații circulare (ex.: cozi).Necesită mai multă memorie (doi pointeri/nod).
Inserare și ștergere eficiente în orice poziție.Poate apărea riscul de erori dacă pointerii nu sunt corect gestionați.

4. Operații comune pe liste circulare dublu înlănțuite


1. Crearea unui nod

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->urmator = nullptr;

    nod->anterior = nullptr;

    return nod;

}


2. Adăugarea unui element

  1. Adăugare la început:

void adaugaInceput(Nod*& head, Nod*& tail, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (head == nullptr) {

        head = tail = nod;

        nod->urmator = nod->anterior = nod;

    } else {

        nod->urmator = head;

        nod->anterior = tail;

        head->anterior = nod;

        tail->urmator = nod;

        head = nod;

    }

}

  1. Adăugare la sfârșit:

void adaugaSfarsit(Nod*& head, Nod*& tail, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (head == nullptr) {

        head = tail = nod;

        nod->urmator = nod->anterior = nod;

    } else {

        nod->anterior = tail;

        nod->urmator = head;

        tail->urmator = nod;

        head->anterior = nod;

        tail = nod;

    }

}


3. Ștergerea unui element

  1. Ștergere de la început:

void stergeInceput(Nod*& head, Nod*& tail) {

    if (head == nullptr) return;

    if (head == tail) { // Lista are un singur nod

        delete head;

        head = tail = nullptr;

    } else {

        Nod* temp = head;

        head = head->urmator;

        head->anterior = tail;

        tail->urmator = head;

        delete temp;

    }

}

  1. Ștergere de la sfârșit:

void stergeSfarsit(Nod*& head, Nod*& tail) {

    if (tail == nullptr) return;

    if (head == tail) { // Lista are un singur nod

        delete tail;

        head = tail = nullptr;

    } else {

        Nod* temp = tail;

        tail = tail->anterior;

        tail->urmator = head;

        head->anterior = tail;

        delete temp;

    }

}

  1. Ștergerea unui nod cu o valoare specificată:

void stergeValoare(Nod*& head, Nod*& tail, int valoare) {

    if (head == nullptr) return;

    Nod* temp = head;

    do {

        if (temp->valoare == valoare) {

            if (temp == head) {

                stergeInceput(head, tail);

                return;

            } else if (temp == tail) {

                stergeSfarsit(head, tail);

                return;

            } else {

                temp->anterior->urmator = temp->urmator;

                temp->urmator->anterior = temp->anterior;

                delete temp;

                return;

            }

        }

        temp = temp->urmator;

    } while (temp != head);

}


4. Parcurgerea listei

  1. Parcurgere de la început la sfârșit:

void parcurgeInainte(Nod* head) {

    if (head == nullptr) return;

    Nod* temp = head;

    do {

        cout << temp->valoare << ” „;

        temp = temp->urmator;

    } while (temp != head);

    cout << endl;

}

  1. Parcurgere de la sfârșit la început:

void parcurgeInapoi(Nod* tail) {

    if (tail == nullptr) return;

    Nod* temp = tail;

    do {

        cout << temp->valoare << ” „;

        temp = temp->anterior;

    } while (temp != tail);

    cout << endl;

}


5. Exemplu complet

#include <iostream>

using namespace std;

struct Nod {

    int valoare;

    Nod* urmator;

    Nod* anterior;

};

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->urmator = nullptr;

    nod->anterior = nullptr;

    return nod;

}

void adaugaInceput(Nod*& head, Nod*& tail, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (head == nullptr) {

        head = tail = nod;

        nod->urmator = nod->anterior = nod;

    } else {

        nod->urmator = head;

        nod->anterior = tail;

        head->anterior = nod;

        tail->urmator = nod;

        head = nod;

    }

}

void adaugaSfarsit(Nod*& head, Nod*& tail, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (head == nullptr) {

        head = tail = nod;

        nod->urmator = nod->anterior = nod;

    } else {

        nod->anterior = tail;

        nod->urmator = head;

        tail->urmator = nod;

        head->anterior = nod;

        tail = nod;

    }

}

void parcurgeInainte(Nod* head) {

    if (head == nullptr) return;

    Nod* temp = head;

    do {

        cout << temp->valoare << ” „;

        temp = temp->urmator;

    } while (temp != head);

    cout << endl;

}

void parcurgeInapoi(Nod* tail) {

    if (tail == nullptr) return;

    Nod* temp = tail;

    do {

        cout << temp->valoare << ” „;

        temp = temp->anterior;

    } while (temp != tail);

    cout << endl;

}

int main() {

    Nod* head = nullptr;

    Nod* tail = nullptr;

    adaugaInceput(head, tail, 10);

    adaugaInceput(head, tail, 20);

    adaugaSfarsit(head, tail, 30);

    cout << „Parcurgere înainte: „;

    parcurgeInainte(head);

    cout << „Parcurgere înapoi: „;

    parcurgeInapoi(tail);

    return 0;

}


6. Activități practice pentru elevi

  1. Implementați o funcție care determină lungimea unei liste circulare dublu înlănțuite.
  2. Scrieți o funcție care inserează un nod după un alt nod specificat.
  3. Realizați o funcție care verifică dacă un element există în listă.

7. Scheme logice

  1. Adăugare:
    • Creează nod -> Actualizează pointerii -> Asigură circularitatea listei.
  2. Ștergere:
    • Verifică dacă lista este goală -> Găsește nodul de șters -> Actualizează pointerii -> Eliberează memoria.

8. Concluzie

  • Listele circulare dublu înlănțuite sunt utile pentru aplicații care necesită parcurgere bidirecțională și acces circular.
  • Implementarea lor necesită gestionarea atentă a pointerilor pentru a asigura integritatea listei.
  • Practica este esențială pentru a stăpâni implementarea și utilizarea acestor liste.

Structuri de Date Liniare Implementate Dinamic – Stiva


1. Obiectivele lecției:

  • Să înțeleagă conceptul de stivă și utilizările sale.
  • Să învețe cum să implementeze o stivă utilizând alocarea dinamică a memoriei.
  • Să implementeze operații comune pe stive: push, pop, peek și isEmpty.

2. Ce este o stivă?

  1. Definiție:
    O stivă este o structură de date liniară care urmează principiul LIFO (Last In, First Out), ceea ce înseamnă că ultimul element introdus este primul care se scoate.
  2. Operații comune:
    • Push: Adăugarea unui element în stivă.
    • Pop: Eliminarea elementului de la vârful stivei.
    • Peek: Accesarea elementului de la vârful stivei fără a-l elimina.
    • isEmpty: Verificarea dacă stiva este goală.

3. Avantaje și dezavantaje

AvantajeDezavantaje
Structură simplă și ușor de implementat.Accesibilitate limitată (doar vârful stivei).
Eficientă pentru gestionarea problemelor LIFO.Necesită mai multe operații pentru acces aleator.
Utilă în aplicații precum recursivitatea sau backtracking.

4. Implementarea unei stive folosind liste simplu înlănțuite


Structura unui nod

  1. Structura nodului pentru stivă:

struct Nod {

    int valoare;  // Informația stocată

    Nod* urmator; // Pointer către următorul nod

};

  1. Pointerul către vârful stivei:

Nod* top = nullptr;


1. Crearea unui nod

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->urmator = nullptr;

    return nod;

}


2. Operații comune pe stivă

  1. Push (Adăugarea unui element):

void push(Nod*& top, int valoare) {

    Nod* nod = creeazaNod(valoare);

    nod->urmator = top;

    top = nod;

}

  1. Pop (Eliminarea elementului de la vârf):

void pop(Nod*& top) {

    if (top == nullptr) {

        cout << „Stiva este goală. Nu se poate elimina elementul.” << endl;

        return;

    }

    Nod* temp = top;

    top = top->urmator;

    delete temp;

}

  1. Peek (Accesarea elementului de la vârf):

int peek(Nod* top) {

    if (top == nullptr) {

        cout << „Stiva este goală.” << endl;

        return -1; // Valoare simbolică pentru eroare

    }

    return top->valoare;

}

  1. isEmpty (Verificarea dacă stiva este goală):

bool isEmpty(Nod* top) {

    return top == nullptr;

}


3. Parcurgerea stivei

void parcurgeStiva(Nod* top) {

    if (top == nullptr) {

        cout << „Stiva este goală.” << endl;

        return;

    }

    Nod* temp = top;

    while (temp != nullptr) {

        cout << temp->valoare << ” „;

        temp = temp->urmator;

    }

    cout << endl;

}


4. Exemplu complet

#include <iostream>

using namespace std;

struct Nod {

    int valoare;

    Nod* urmator;

};

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->urmator = nullptr;

    return nod;

}

void push(Nod*& top, int valoare) {

    Nod* nod = creeazaNod(valoare);

    nod->urmator = top;

    top = nod;

}

void pop(Nod*& top) {

    if (top == nullptr) {

        cout << „Stiva este goală. Nu se poate elimina elementul.” << endl;

        return;

    }

    Nod* temp = top;

    top = top->urmator;

    delete temp;

}

int peek(Nod* top) {

    if (top == nullptr) {

        cout << „Stiva este goală.” << endl;

        return -1; // Valoare simbolică pentru eroare

    }

    return top->valoare;

}

bool isEmpty(Nod* top) {

    return top == nullptr;

}

void parcurgeStiva(Nod* top) {

    if (top == nullptr) {

        cout << „Stiva este goală.” << endl;

        return;

    }

    Nod* temp = top;

    while (temp != nullptr) {

        cout << temp->valoare << ” „;

        temp = temp->urmator;

    }

    cout << endl;

}

int main() {

    Nod* top = nullptr;

    push(top, 10);

    push(top, 20);

    push(top, 30);

    cout << „Stiva după adăugări: „;

    parcurgeStiva(top);

    cout << „Elementul de la vârf: ” << peek(top) << endl;

    pop(top);

    cout << „Stiva după eliminarea elementului de la vârf: „;

    parcurgeStiva(top);

    cout << „Stiva este goală? ” << (isEmpty(top) ? „Da” : „Nu”) << endl;

    return 0;

}


5. Utilizări practice ale stivei

  1. Evarea expresiilor aritmetice:
    Utilizată în evarea expresiilor postfixate sau conversia expresiilor infixate.
  2. Controlul recursivității:
    Stiva este utilizată intern pentru a gestiona apelurile recursive.
  3. Operații de backtracking:
    Găsirea soluțiilor prin explorarea căilor posibile și revenirea (ex.: labirinturi).
  4. Parantezare corectă:
    Verificarea dacă o expresie conține paranteze deschise și închise în mod corect.

6. Activități practice pentru elevi

  1. Implementați o funcție care verifică dacă o expresie aritmetică are paranteze corecte utilizând o stivă.
  2. Realizați o funcție care calculează factorialul unui număr utilizând o stivă în loc de recursivitate.
  3. Implementați un algoritm care convertește o expresie infixată într-o expresie postfixată utilizând o stivă.

7. Scheme logice

  1. Push:
    • Creează un nod -> Conectează nodul la vârful stivei -> Actualizează top.
  2. Pop:
    • Verifică dacă stiva este goală -> Elimină nodul de la vârf -> Actualizează top.
  3. Peek:
    • Returnează valoarea din nodul de la vârf fără a-l elimina.

8. Concluzie

  • Stiva este o structură de date esențială în informatică, cu multiple aplicații practice.
  • Implementarea sa folosind alocarea dinamică oferă flexibilitate în gestionarea datelor.
  • Practica ajută la înțelegerea conceptelor și la utilizarea eficientă a acestei structuri.

Structuri de Date Liniare Implementate Dinamic – Coada


1. Obiectivele lecției:

  • Să înțeleagă conceptul de coadă și principiul său de funcționare.
  • Să învețe cum să implementeze o coadă utilizând alocarea dinamică a memoriei.
  • Să implementeze operații comune pe cozi: enqueue, dequeue, peek, isEmpty.

2. Ce este o coadă?

  1. Definiție:
    O coadă este o structură de date liniară care urmează principiul FIFO (First In, First Out), ceea ce înseamnă că primul element introdus este primul care se scoate.
  2. Operații comune:
    • Enqueue: Adăugarea unui element la sfârșitul cozii.
    • Dequeue: Eliminarea elementului din fața cozii.
    • Peek: Accesarea elementului din fața cozii fără a-l elimina.
    • isEmpty: Verificarea dacă coada este goală.
  3. Aplicații practice:
    • Gestionarea cozii de imprimare.
    • Planificarea proceselor în sistemele de operare.
    • Simulări (ex.: linii de așteptare, sisteme bancare).

3. Avantaje și dezavantaje

AvantajeDezavantaje
Structură simplă și ușor de implementat.Inserarea și ștergerea sunt limitate la capete.
Eficientă pentru gestionarea operațiilor FIFO.Acces aleator nu este posibil.
Poate fi implementată dinamic.Necesită gestionarea pointerilor pentru dinamism.

4. Implementarea unei cozi folosind liste simplu înlănțuite


Structura unui nod

  1. Structura nodului pentru coadă:

struct Nod {

    int valoare;  // Informația stocată

    Nod* urmator; // Pointer către următorul nod

};

  1. Pointeri pentru gestionarea cozii:
    • Front: Indică primul element al cozii.
    • Rear: Indică ultimul element al cozii.

1. Crearea unui nod

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->urmator = nullptr;

    return nod;

}


2. Operații comune pe coadă

  1. Enqueue (Adăugarea unui element):

void enqueue(Nod*& front, Nod*& rear, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (rear == nullptr) { // Coada este goală

        front = rear = nod;

    } else {

        rear->urmator = nod;

        rear = nod;

    }

}

  1. Dequeue (Eliminarea elementului din față):

void dequeue(Nod*& front, Nod*& rear) {

    if (front == nullptr) { // Coada este goală

        cout << „Coada este goală. Nu se poate elimina elementul.” << endl;

        return;

    }

    Nod* temp = front;

    front = front->urmator;

    if (front == nullptr) { // Dacă coada devine goală

        rear = nullptr;

    }

    delete temp;

}

  1. Peek (Accesarea elementului din față):

int peek(Nod* front) {

    if (front == nullptr) {

        cout << „Coada este goală.” << endl;

        return -1; // Valoare simbolică pentru eroare

    }

    return front->valoare;

}

  1. isEmpty (Verificarea dacă coada este goală):

bool isEmpty(Nod* front) {

    return front == nullptr;

}


3. Parcurgerea cozii

void parcurgeCoada(Nod* front) {

    if (front == nullptr) {

        cout << „Coada este goală.” << endl;

        return;

    }

    Nod* temp = front;

    while (temp != nullptr) {

        cout << temp->valoare << ” „;

        temp = temp->urmator;

    }

    cout << endl;

}


4. Exemplu complet

#include <iostream>

using namespace std;

struct Nod {

    int valoare;

    Nod* urmator;

};

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->urmator = nullptr;

    return nod;

}

void enqueue(Nod*& front, Nod*& rear, int valoare) {

    Nod* nod = creeazaNod(valoare);

    if (rear == nullptr) { // Coada este goală

        front = rear = nod;

    } else {

        rear->urmator = nod;

        rear = nod;

    }

}

void dequeue(Nod*& front, Nod*& rear) {

    if (front == nullptr) { // Coada este goală

        cout << „Coada este goală. Nu se poate elimina elementul.” << endl;

        return;

    }

    Nod* temp = front;

    front = front->urmator;

    if (front == nullptr) { // Dacă coada devine goală

        rear = nullptr;

    }

    delete temp;

}

int peek(Nod* front) {

    if (front == nullptr) {

        cout << „Coada este goală.” << endl;

        return -1; // Valoare simbolică pentru eroare

    }

    return front->valoare;

}

bool isEmpty(Nod* front) {

    return front == nullptr;

}

void parcurgeCoada(Nod* front) {

    if (front == nullptr) {

        cout << „Coada este goală.” << endl;

        return;

    }

    Nod* temp = front;

    while (temp != nullptr) {

        cout << temp->valoare << ” „;

        temp = temp->urmator;

    }

    cout << endl;

}

int main() {

    Nod* front = nullptr;

    Nod* rear = nullptr;

    enqueue(front, rear, 10);

    enqueue(front, rear, 20);

    enqueue(front, rear, 30);

    cout << „Coada după adăugări: „;

    parcurgeCoada(front);

    cout << „Elementul din fața cozii: ” << peek(front) << endl;

    dequeue(front, rear);

    cout << „Coada după eliminarea elementului din față: „;

    parcurgeCoada(front);

    cout << „Coada este goală? ” << (isEmpty(front) ? „Da” : „Nu”) << endl;

    return 0;

}


5. Utilizări practice ale cozii

  1. Planificarea proceselor:
    Cozile sunt utilizate pentru gestionarea proceselor în ordine de sosire.
  2. Aplicații grafice:
    Cozile sunt utilizate pentru parcurgerea pe niveluri (Breadth-First Search – BFS).
  3. Aplicații în rețele:
    Gestionarea pachetelor de date în comunicațiile de rețea.

6. Activități practice pentru elevi

  1. Implementați o funcție care verifică dacă două cozi sunt identice.
  2. Scrieți un program care inversează elementele unei cozi utilizând o stivă.
  3. Realizați o funcție care simulează un sistem de coadă de așteptare pentru un ATM.

7. Scheme logice

  1. Enqueue:
    • Creează nod -> Conectează nodul la sfârșitul cozii -> Actualizează rear.
  2. Dequeue:
    • Verifică dacă coada este goală -> Elimină nodul din față -> Actualizează front și, eventual, rear.

8. Concluzie

  • Cozile sunt structuri de date esențiale pentru gestionarea operațiilor FIFO.
  • Implementarea lor dinamică oferă flexibilitate în gestionarea dimensiunii.
  • Practica ajută la înțelegerea utilizării și implementării eficiente a acestei structuri.

Structuri de Date Arborescente Implementate Dinamic – Arbori


1. Obiectivele lecției:

  • Să înțeleagă conceptul de arbore și proprietățile acestuia.
  • Să învețe cum să implementeze un arbore binar folosind alocarea dinamică a memoriei.
  • Să implementeze operații comune pe arbori: inserare, traversare (în ordine, preordine, postordine) și căutare.

2. Ce este un arbore?

  1. Definiție:
    Un arbore este o structură de date non-liniară care conține noduri organizate ierarhic. Fiecare nod poate avea copii, iar primul nod este numit rădăcină (root).
  2. Proprietăți ale arborilor:
    • Un arbore are un singur nod rădăcină.
    • Fiecare nod are un părinte, cu excepția rădăcinii.
    • Nodurile fără copii se numesc frunze (leaves).
    • Un arbore poate avea subarbori.
  3. Tipuri de arbori:
    • Arbore binar: Fiecare nod poate avea cel mult doi copii.
    • Arbore binar de căutare (BST): Valorile din subarborele stâng sunt mai mici decât rădăcina, iar cele din subarborele drept sunt mai mari.

3. Avantaje și dezavantaje

AvantajeDezavantaje
Eficient pentru căutări rapide în structuri mari (BST).Implementarea și gestionarea sunt mai complexe.
Structură flexibilă pentru date ierarhice.Consumul de memorie crește cu dimensiunea arborelui.

4. Structura unui nod

  1. Structura unui nod pentru un arbore binar:

struct Nod {

    int valoare;       // Informația stocată

    Nod* stanga;       // Pointer către subarborele stâng

    Nod* dreapta;      // Pointer către subarborele drept

};

  1. Pointerul către rădăcina arborelui:

Nod* radacina = nullptr;


5. Operații comune pe arbori binari


1. Crearea unui nod

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->stanga = nullptr;

    nod->dreapta = nullptr;

    return nod;

}


2. Inserarea unui nod într-un arbore binar de căutare (BST)

  1. Funcția de inserare:

Nod* insereazaNod(Nod* radacina, int valoare) {

    if (radacina == nullptr) {

        return creeazaNod(valoare);

    }

    if (valoare < radacina->valoare) {

        radacina->stanga = insereazaNod(radacina->stanga, valoare);

    } else if (valoare > radacina->valoare) {

        radacina->dreapta = insereazaNod(radacina->dreapta, valoare);

    }

    return radacina;

}


3. Traversări ale arborelui

  1. Traversare în ordine (in-order):
    Vizitează nodurile în ordinea: stânga → rădăcină → dreapta.

void traversareInOrdine(Nod* radacina) {

    if (radacina == nullptr) return;

    traversareInOrdine(radacina->stanga);

    cout << radacina->valoare << ” „;

    traversareInOrdine(radacina->dreapta);

}

  1. Traversare preordine (pre-order):
    Vizitează nodurile în ordinea: rădăcină → stânga → dreapta.

void traversarePreOrdine(Nod* radacina) {

    if (radacina == nullptr) return;

    cout << radacina->valoare << ” „;

    traversarePreOrdine(radacina->stanga);

    traversarePreOrdine(radacina->dreapta);

}

  1. Traversare postordine (post-order):
    Vizitează nodurile în ordinea: stânga → dreapta → rădăcină.

void traversarePostOrdine(Nod* radacina) {

    if (radacina == nullptr) return;

    traversarePostOrdine(radacina->stanga);

    traversarePostOrdine(radacina->dreapta);

    cout << radacina->valoare << ” „;

}


4. Căutarea unui element

  1. Funcția de căutare:

bool cautaValoare(Nod* radacina, int valoare) {

    if (radacina == nullptr) {

        return false;

    }

    if (valoare == radacina->valoare) {

        return true;

    } else if (valoare < radacina->valoare) {

        return cautaValoare(radacina->stanga, valoare);

    } else {

        return cautaValoare(radacina->dreapta, valoare);

    }

}


5. Exemplu complet

#include <iostream>

using namespace std;

struct Nod {

    int valoare;

    Nod* stanga;

    Nod* dreapta;

};

Nod* creeazaNod(int valoare) {

    Nod* nod = new Nod();

    nod->valoare = valoare;

    nod->stanga = nullptr;

    nod->dreapta = nullptr;

    return nod;

}

Nod* insereazaNod(Nod* radacina, int valoare) {

    if (radacina == nullptr) {

        return creeazaNod(valoare);

    }

    if (valoare < radacina->valoare) {

        radacina->stanga = insereazaNod(radacina->stanga, valoare);

    } else if (valoare > radacina->valoare) {

        radacina->dreapta = insereazaNod(radacina->dreapta, valoare);

    }

    return radacina;

}

void traversareInOrdine(Nod* radacina) {

    if (radacina == nullptr) return;

    traversareInOrdine(radacina->stanga);

    cout << radacina->valoare << ” „;

    traversareInOrdine(radacina->dreapta);

}

bool cautaValoare(Nod* radacina, int valoare) {

    if (radacina == nullptr) {

        return false;

    }

    if (valoare == radacina->valoare) {

        return true;

    } else if (valoare < radacina->valoare) {

        return cautaValoare(radacina->stanga, valoare);

    } else {

        return cautaValoare(radacina->dreapta, valoare);

    }

}

int main() {

    Nod* radacina = nullptr;

    radacina = insereazaNod(radacina, 50);

    insereazaNod(radacina, 30);

    insereazaNod(radacina, 70);

    insereazaNod(radacina, 20);

    insereazaNod(radacina, 40);

    insereazaNod(radacina, 60);

    insereazaNod(radacina, 80);

    cout << „Traversare în ordine: „;

    traversareInOrdine(radacina);

    cout << endl;

    cout << „Căutare valoare 40: ” << (cautaValoare(radacina, 40) ? „Găsită” : „Negăsită”) << endl;

    cout << „Căutare valoare 25: ” << (cautaValoare(radacina, 25) ? „Găsită” : „Negăsită”) << endl;

    return 0;

}


6. Activități practice pentru elevi

  1. Implementați o funcție care determină numărul de noduri dintr-un arbore.
  2. Realizați o funcție care calculează înălțimea unui arbore.
  3. Scrieți un program care determină dacă un arbore este arbore binar de căutare.

7. Scheme logice

  1. Inserare:
    • Verifică dacă nodul curent este gol -> Creează un nou nod -> Compară valoarea și inserează în stânga sau dreapta.
  2. Traversare:
    • Utilizează recursivitate pentru a parcurge nodurile în ordinea specificată.
  3. Căutare:
    • Compară valoarea căutată cu nodul curent -> Explorează subarborele corespunzător.

8. Concluzie

  • Arborii sunt structuri de date esențiale pentru gestionarea informațiilor ierarhice și pentru căutări rapide.
  • Implementarea arborilor binari necesită înțelegerea recursivității.
  • Practica ajută la stăpânirea conceptelor și implementării eficiente.

Similar Posts

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *