Responsabili:
În urma parcurgerii laboratorului, studentul va fi capabil să:
Matematic, un arbore este un graf neorientat conex aciclic.
În ştiinţa calculatoarelor, termenul de arbore este folosit pentru a desemna o structură de date care respectă definiţia de mai sus, însă are asociate un nod rădăcină şi o orientare înspre sau opusă rădăcinii.
Arborii sunt folosiţi în general pentru a modela o ierarhie de elemente.
Astfel, fiecare element (nod) poate deţine un număr de unul sau mai mulţi descendenţi, iar în acest caz nodul este numit părinte al nodurilor descendente.
Fiecare nod poate avea un singur nod părinte. Un nod fără descendenţi este un nod terminal, sau nod frunză.
În schimb, există un singur nod fără părinte, iar acesta este întotdeauna rădăcina arborelui (root).
Un arbore binar este un caz special de arbore, în care fiecare nod poate avea maxim doi descendenţi:
În funcţie de elementele ce pot fi reprezentate în noduri şi de restricţiile aplicate arborelui, se pot crea structuri de date cu proprietăţi deosebite: heap-uri, arbori AVL, arbori roşu-negru, arbori Splay şi multe altele. O parte din aceste structuri vor fi studiate la curs şi în laboratoarele viitoare.
În acest laborator ne vom concentra asupra unei utilizări comune a arborilor binari, şi anume pentru a reprezenta şi evalua expresii logice.
Arborii binari pot fi reprezentați în mai multe moduri. Structura din spatele acestora poate fi un simplu vector, alocat dinamic sau nu, sau o structură ce folosește pointeri, așa cum îi vom reprezenta în acest laborator.
#ifndef __BINARY_TREE_H #define __BINARY_TREE_H #include <cstdio> #include <cstdlib> template <typename T> class BinaryTree { public: BinaryTree(); ~BinaryTree(); private: BinaryTree<T> *leftNode; BinaryTree<T> *rightNode; T *pData; }; #endif // __BINARY_TREE_H
Structura nodului de mai sus este clară:
De asemenea, dezalocarea memoriei se va face recursiv, dar numai atunci cand este necesar.
Pentru a ne reaminti cum alocăm memorie:
BinaryTree<T> *node = new BinaryTree<T>(); delete node; T *pData = new T; delete pData;
Exemplu:
PreorderTraverse(BinaryTree<T> *node) { Process(node->pData); PreorderTraverse(node->leftNode); PreorderTraverse(node->rightNode); }
Se folosește o coadă, iar la fiecare pas se extrage din această coadă câte un nod și se adăugă înapoi în coadă nodul stâng, respectiv drept al nodului scos. Acest algoritm continuă până când coada devine goală.
O expresie matematică este un şir de caractere compus din:
Fiecărei expresii i se poate asocia un arbore binar, în care:
În terminologia limbajelor formale şi a compilatoarelor, acest arbore se mai numeşte şi Abstract Syntax Tree (AST).
Pentru expresia (a+1)*(b+10)+25/c , arborele asociat este prezentat mai jos:
Următorul pseudo-cod reprezintă în linii mari algoritmului de evaluare a expresiilor reprezentate sub formă de arbori binari:
Evalueaza(Node nod) { // Daca nu este nod terminal... if (nod->left || nod->right) { // Evaluam expresiile subarborilor... res1 = Evalueaza(nod->left); res2 = Evalueaza(nod->right); // ... si combinam rezultatele aplicand operatorul return AplicaOperator(nod->op, res1, res2); } else { // Daca nodul terminal contine o variabila, atunci intoarcem valoarea variabilei if (nod->var) return Valoare(nod->var); else // Avem o constanta return nod->val; } }
Acest laborator se va realiza pornind de la scheletul de cod.
În cadrul arhivei, aveți la dispoziție un parser pentru expresii logice sub forma normal disjunctivă - DNF:
(Expresie) := (Termen1) | (Termen2) | (Termen3) | ... | (TermenN), N >= 1 (Termen) := (Literal1) & (Literal2) & ... & (LiteralM), M >= 1
Câteva exemple de expresii logice valide: E1 = a & b & !c E2 = a & b | c & !a
În cazul expresiilor logice considerate în forma de mai sus şi ţinând cont de precedenţa convenabilă a operatorilor, arborele expresiilor se generează destul de uşor, de exemplu dupa regulile următoare (folosită în implementarea din laborator - de remarcat ca sunt mai multe posibilităţi de generare a acestui arbore):
Se genereaza un nod pentru prima disjuncţie (|) întâlnită:
Pentru fiecare subarbore asociat unui termen se generează un nod pentru prima conjuncţie întâlnită (&):
1. [4p] Implementați (și compilați!) următoarele funcții pentru un arbore binar:
2. [3p] Implementaţi (şi compilaţi!) următoarele funcţii pentru un arbore binar:
inordine
în displayTree. Puteți să folosiți sau nu variabila de indentare care este dată ca argument. (TODO 2.1)
Această secțiune nu este punctată și încearcă să vă facă o oarecare idee a tipurilor de întrebări pe care le puteți întâlni la un job interview (internship, part-time, full-time, etc.) din materia prezentată în cadrul laboratorului.