Laborator 8 - Arbori Binari

Responsabili

Obiective

În urma parcurgerii articolului, studentul va fi capabil să:

  • înţeleagă noţiunea de arbore şi structura unui arbore binar
  • construiască, în limbajul C, un arbore binar
  • realizeze o parcurgere a structurii de date prin mai multe moduri

Noțiuni teoretice

Noțiunea de arbore. Arbori binari

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 (copii sau fii).

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:

  • nodul stâng
  • nodul drept.

Î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 articol ne vom concentra asupra unei utilizări comune a arborilor binari, şi anume pentru a reprezenta şi evalua expresii logice.

Reprezentarea arborilor binari

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 articol.

binary_tree.h
typedef struct b_node_t b_node_t;
struct b_node_t {
    /* left child */
    b_node_t *left;
    /* right child */
    b_node_t *right;
 
    /* data contained by the node */
    void *data;
};
 
typedef struct b_tree_t b_tree_t;
struct b_tree_t {
    /* root of the tree */
    b_node_t *root;
 
    /* size of the data contained by the nodes */
    size_t data_size;
};

Structura nodului de mai sus este clară:

  • pointer către fiul stâng
  • pointer către fiul drept
  • pointer către date

Pentru a ne reaminti cum alocăm/dealocăm memorie:

/**
 * Creates a new binary tree
 * @data_size: size of the data contained by the tree's nodes
 * @return: pointer to the newly created tree
 */
b_tree_t *b_tree_create(size_t data_size);
 
/**
 * Clear the whole memory used by the tree and its nodes
 * @b_tree: the binary tree to be freed
 * @free_data: function used to free the data contained by a node
 */
void b_tree_free(b_tree_t *b_tree, void (*free_data)(void *));

De exemplu, dacă dorim să creem un arbore binar ce conține elemente de tip *char*, codul arată astfel:

b_tree_t *char_tree = b_tree_create(sizeof(char));
 
b_tree_free(char_tree, free);

Parcurgerea arborilor

Se implementeaza foarte usor recursiv:

1. Preordine

  • Se parcurge rădăcina
  • Se parcurge subarborele stâng
  • Se parcurge subarborele drept

2. Inordine

  • Se parcurge subarborele stâng
  • Se parcurge rădăcina
  • Se parcurge subarborele drept

Exemplu:

static void __b_tree_print_inorder(b_node_t *b_node, void (*print_data)(void *))
{
	if (!b_node)
		return;
 
	/* TODO */
	__b_tree_print_inorder(b_node->left, print_data);
 
	print_data(b_node->data);
 
	__b_tree_print_inorder(b_node->right, print_data);
}
 
void b_tree_print_inorder(b_tree_t *b_tree, void (*print_data)(void *))
{
	__b_tree_print_inorder(b_tree->root, print_data);
	printf("\n");
}

3. Postordine

  • Se parcurge subarborele stâng
  • Se parcurge subarborele drept
  • Se parcurge rădăcina

4. Lățime

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ă.

Nodurile frunză nu au descendenţi → nodul stâng şi nodul drept pointează la NULL şi nu trebuie adăugate în coadă.

Arbori asociați expresiilor

O expresie matematică este un şir de caractere compus din:

  • variabile
  • constante
  • operatori
  • paranteze (eventual).

Fiecărei expresii i se poate asocia un arbore binar, în care:

  • nodurile interioare reprezintă operatorii
  • frunzele reprezintă constantele şi/sau variabilele.

Î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:

Cel mai mic strămoș comun

O problemă importantă în analiza arborilor este determinarea celui mai mic strămoș comun (LCA - Lowest Common Ancestor). LCA-ul a două noduri, u si v, este nodul cel mai depărtat de rădăcină care îi are pe u și v ca descendenți.

Spre exemplu, cel mai mic strămoș comun al nodurilor 1 și 12 este 0, în timp ce pentru nodurile 4 și 7, acesta este 1.

Schelet

Daca folositi Github Classroom, va rugam sa va actualizati scheletul cu cel de mai jos. Cel din repo-ul clonat initial nu este la cea mai recenta versiune.

Scheletul de laborator

Exerciții

Trebuie să vă creați cont de Lambda Checker, dacă nu v-ați creat deja, pe care îl veți folosi la SD pe toată durata semestrului. Aveti grija sa selectati contestul corect la submit, si anume SD-CA-LAB-08-BinaryTree

1) [4p] Implementarea arborelui binar. Problema SD-CA-LAB-08-Binary-Tree pe LambdaChecker.

2) [3p] O problema aleasa de catre asistent din cele ramase.

3) [Bonus] 1p bonus pe fiecare problema in plus pe care o rezolvati, cu un maxim de 12p per laborator.

Interviu

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.

  • Ce este un arbore?
  • Cum poate fi reprezentat un nod dintr-un arbore binar?
  • Daţi exemplu de un tip (mai multe tipuri) de parcurgere al arborilor binari. Descrieţi modul de funţionare al acestuia (acestora).
  • Daţi exemplu de un mod de utilizare al arborilor binari.
  • Ce complexitate medie / worst-case au funcţiile de inserare / ştergere / căutare pentru un arbore binar, BST, AVL, etc. (mai multe despre complexitatea algoritmilor şi structurilor de date veţi învăţa în anul 2: Analiza Algoritmilor şi Proiectarea Algoritmilor).

Bibliografie

sd-ca/2021/laboratoare/lab-08.txt · Last modified: 2022/10/03 15:10 (external edit)
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0