Laborator 11 - AVL & Red-Black Trees

Obiective

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

  • înțeleagă conceptul unui arbore echilibrat de căutare
  • exemplifice acest concept pe structurile AVL & Red-Black
  • își sedimenteze informațiile despre arbori echilibrați și aplicațiile acestora

Noțiuni de bază despre AVL Trees

  • arbore binar de căutare echilibrat după înălțime
  • arborele se reechilibrează(rebalancing) după fiecare inserare sau ștergere

Diferență între 2 subarbori ai oricărui nod este maxim 1. Se definește:

  • factor_de_balans(nod) = înălțime(subarbore_drept(nod)) - înălțime(subarbore_stang(nod))
  • invariant = factor_de_balans(nod) este -1, 0 sau 1

Avantajul unui AVL Tree este faptul că produce cel mai echilibrat arbore în cazul cel mai defavorabil. Fiind perfect balansat, arborele AVL va scoate cel mai mic timp de căutare dintre toţi ceilalţi arbori. Dezavantajul său este numărul mai mare de rotaţii pe care îl efectuează.

Înălţimea unui AVL cu n noduri în cazul cel mai defavorabil este 1.44 * log(n).

La inserare se adaugă nodul astfel încât să aibă proprietatea de arbore binar de căutare, iar după se verifică factorul de balansare și se începe sau nu balansarea lui. Balansarea lui se face cu rotații duble.

Cele 4 tipuri de rotații (LL LR RL RR):

  • dacă factorul de balans este pozitiv
    • dacă factorul de balans al nodului stâng este pozitiv
      • Rotaţie LL (Left): rotire spre stânga

  • dacă factorul de balans al nodului stâng este negativ
    • Rotaţie LR (Left-Right): rotire spre stânga + rotire spre dreapta

  • dacă factorul de balans este negativ
    • dacă factorul de balans al nodului drept este pozitiv
      • Rotire RL (Right-Left): rotire spre dreapta + rotire spre stânga

  • dacă factorul de balans al nodului drept este negativ
    • Rotire RR (Right) : rotire spre dreapta

Puteți urmări încă un exemplu aici.

Reprezentarea unui AVL

Structura de mai jos este cea folosită de noi pentru reprezentarea AVL-ului din scheletul de laborator.

avl.h
/**
 * The AVL node struct definition
 */
typedef struct avl_node_t avl_node_t;
struct avl_node_t {
	/* left child - smaller key */
	avl_node_t   *left;
	/* right child - bigger key */
	avl_node_t   *right;
 
	/* the key of the node which will also be used for sorting */
	char		*key;
 
	/* height of the node */
	int		height;
};
 
/**
 * The AVL tree struct definition
 */
typedef struct avl_tree_t avl_tree_t;
struct avl_tree_t {
	/* root of the tree */
	avl_node_t	*root;
 
	/* function used for sorting the keys */
	int	(*cmp)(const void *key1, const void *key2);
};

Observăm absenţa pointer-ului către nodul părinte, fapt ce ne va cere să lucrăm cu (**node) pentru a putea efectua rotaţiile corespunzător.

Inserare în AVL

Inserarea unui nod într-un AVL se realizează în 2 etape şi este asemănătoare inserării unui nod pentru un Treap:

  • Se adaugă nodul conform inserării într-un arbore binar de cautare: se caută poziţia de inserare pe baza comparaţiei dintre nod şi cheia nodului stâng / drept.
  • Din locul unde am efectuat adăugarea până în rădăcină, se actualizează înălţimea fiecărui nod şi in cazul în care este necesar, se aplică operaţii de rotire pentru a păstra proprietatea AVL-ului (diferenţa în modul dintre înalţimea nodului stâng şi întălţimea nodului drept este mai mică sau egală cu 1).

Mai jos avem pseudocodul pentru operaţia de inserare:

void insert(nod, cheie) {

	if (nod == NULL) {
		node = create_node(cheie)
	} else if (nod->cheie > cheie) {
		insert(nod->left, cheie)
	} else {
		insert(nod->right, cheie)
	}

	nod->inaltime = 1 + max(intaltime(nod->left), inaltime(nod->right)
	aplicare_rotiri()
}

Ștergere

Operaţia de ştergere pentru o cheie dintr-un arbore AVL se aseamănă cu ştergerea unei chei dintr-un BST. Vom elimina nodul pe care dorim să îl ştergem în momentul în care acesta ajunge la baza arborelui. Următorul pas este să reactualizăm înălţimile fiecărui nod şi să efectuăm rotiri atunci când este necesar. Astfel, putem rezuma stergerea unui nod dintr-un AVL în 3 etape:

1. Căutarea cheii pe care dorim să o ştergem.

2. Ştergerea nodului cu cheia respectivă. Dacă nodul are 2 succesori, îl vom înlocui cu cel mai mare nod din subarborele stâng (sau cel mai mic nod din subarborele drept). Altfel, nodul va fi înlocuit cu unul dintre succesorii săi nenuli (sau NULL, în cazul în care nodul este frunză).

3. Reactualizarea înălţimilor şi realizarea rotaţiilor necesare.

Mai jos avem pseudocodul pentru operaţia de ştergere:

void avl_delete(nod, cheie) {
	if (nod == NULL) {
		return
	}
	if (nod->cheie > cheie) {
		avl_delete(node->left, cheie)
	} else if (nod->cheie < cheie) {
		avl_delete(node->right, cheie)
	} else {
		if (node are 0 succesori) {
			stergere(node)
			return;
		} else if (node are 1 succesor) {
			node = succesor(node)
		} else {
			max_node = max_element(node->left);
			copy(node, max_node)
			avl_delete(node->left, max_node->cheie)
    		}
	}
	
	nod->inaltime = 1 + max(intaltime(nod->left), inaltime(nod->right))
	aplicare_rotiri()
}

Noțiuni de bază despre Red-Black Trees

Un arbore roșu-negru este un arbore binar de căutare care are un bit suplimentar pentru memorarea fiecărui nod: culoarea acestuia, care poate fi roșu sau negru. Prin restrângerea modului în care se colorează nodurile pe orice drum de la rădăcină la o frunză, arborii roșu-negru garantează că nici un astfel de drum nu este mai lung decât dublul lungimii oricărui alt drum, deci că arborele este aproximativ echilibrat.

Un arbore binar de căutare este arbore roșu-negru dacă el îndeplineste următoarele proprietăți:

  • Fiecare nod este fie roșu, fie negru.
  • Fiecare frunză (nil) este neagră.
  • Dacă un nod este roșu, atunci ambii fii ai săi sunt negri.
  • Fiecare drum simplu de la un nod la un descendent care este frunza conține același număr de noduri negre.

Mai multe detalii, aici:

AVL vs Red-Black

  • AVL permite căutări mai rapide - sunt echilibrați mai “strict”
  • Red-Black este mai potrivit pentru multe inserări și ștergeri - mai puține rotiri necesare

Cu toate acestea, ambele tipuri de arbori discutate in acest articol sunt foarte populare si folosite.

Arborii Red-Black sunt foarte folosiți în cazuri generale, unde se descurcă binișor la toate capitolele, în timp ce arborii AVL sunt folosiți în domeniul bazelor de date, unde timpul pentru o căutare reprezintă un factor foarte important.

Cazuri concrete unde este utilizat Red-Black Tree:

AVL Trees vs. Red-Black Trees

Reprezentarea unui Red-Black

rb_tree.h
enum COLOR {
	RED,
	BLACK
};
 
/**
 * The Red-Black node struct definition
 */
typedef struct rb_node_t rb_node_t;
struct rb_node_t {
	/* parent - RB_NODE_NULL for root */
	rb_node_t   *parent;
	/* left child - smaller key */
	rb_node_t   *left;
	/* right child - bigger key */
	rb_node_t   *right;
 
	/* the sorting is based on key */
	void		*key;
	/* data contained by the node */
	void		*data;
 
	/* color of the node */
	enum COLOR	color;
};
 
/**
 * The Red-Black tree struct definition
 */
typedef struct rb_tree_t rb_tree_t;
struct rb_tree_t {
	/* root of the tree */
	rb_node_t	*root;
 
	/* key size */
	size_t 		key_size;
 
    /* data size */
    size_t 		data_size;
 
	/* function used for sorting the keys */
	int	(*cmp)(const void *key1, const void *key2);
};

Schelet

Exerciții

1) [45p] Completați funcția de reechilibrare și implementați funcțiile de inserare, căutare și ștergere pentru AVL.

2) [45p] Completați părțile de cod care lipsesc din funcțiile din rb_tree.c.

sd-ca/laboratoare/lab-11.txt · Last modified: 2020/05/14 02:01 by teodor_stefan.dutu
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