AVL fák: elforgatások, beszúrás, törlés C++ Példa
⚡ Okos összefoglaló
Az AVL fák önkiegyensúlyozó bináris keresőfák, ahol az egyes csomópontok bal és jobb oldali részfái közötti magasságkülönbség -1, 0 vagy +1 értéken belül marad, garantálva az O(log n) keresési teljesítményt.
Mik azok az AVL fák?
AVL fák olyan bináris keresőfák, amelyekben minden csomópont bal és jobb részfája közötti magasságkülönbség -1, 0 vagy +1. Ezek önkiegyensúlyozó BST-k, amelyek logaritmikus keresési időt tartanak fenn, és Adelson-Velsky és Landis (AVL) feltalálókról kapták a nevüket.
Hogyan működik az AVL Tree?
Az AVL fák létezésének okának megértéséhez nézzük meg, mi a baj egy sima fával. Bináris keresési faTekintsük ezeket a kulcsokat a megadott sorrendben beillesztve:
AVL fa vizualizáció
A fa lineárisan növekszik, amikor a kulcsok növekvő sorrendben érkeznek, O(n)-re degenerálva a keresést. Ez ellentétes a BST céljával – csak egy kiegyensúlyozott fa tartja fenn a keresés logaritmikus jellegét. Most vizsgáljuk meg ugyanazokat a kulcsokat más sorrendben beillesztve.
Ugyanazok a kulcsok, eltérő beszúrási sorrend sekélyebb alakzatot eredményez, így minden keresés O(log n)-ben fut. Az AVL fák ezt az alakzatot úgy érvényesítik, hogy minden beszúráskor figyelik a magasságot, és korrigálják az egyensúlyhiányt a BST sorrend megsértése nélkül.
Balance Factor az AVL fákban
Az egyensúlyi tényező (BF) tracks minden csomópont magasságát, hogy a fa menet közben önmagát egyensúlyba hozhassa.
Az egyensúlytényező tulajdonságai
Egyensúlytényező AVL fa
- Az egyensúlyi tényező a bal oldali és a jobb oldali részfa magassága közötti különbség.
Balance factor(node) = height(node->left) − height(node->right)- Az egyetlen megengedett érték a −1, a 0 és a +1.
- Az −1 érték azt jelenti, hogy a jobb oldali részfa egy plusz szintet tartalmaz – a csomópont jobboldali nehézkességű.
- A +1 érték azt jelenti, hogy a bal oldali részfa egy extra szintet tartalmaz – a csomópont baloldali-nehézkességű.
- A 0 érték azt jelenti, hogy mindkét oldal azonos magasságú – a csomópont tökéletesen kiegyensúlyozott.
AVL forgások
A forgatások akkor futnak le, amikor egy beszúrás vagy törlés megsérti az egyensúlyi tényező szabályát. A négy eset a következő: LL, RR, LR és RL.
Balra – Balra forgatás
Ez a forgatás akkor történik meg, amikor egy új csomópontot szúrnak be a bal oldali részfa bal oldali gyermekéhez.
AVL fa balra – balra forgatás
Egyetlen jobbra forgatás történik. Ez az eset akkor aktiválódik, amikor egy csomópont BF +2-vel, a bal oldali gyermekének pedig BF +1-gyel rendelkezik.
Jobbra – Jobbra forgás
Ezt a forgatást akkor hajtják végre, ha egy új csomópontot szúrnak be a jobb oldali részfa jobb gyermekéhez.
Egyetlen balra forgatás történik. Ez az eset akkor aktiválódik, ha egy csomópont BF −2 értékkel, jobb oldali gyermekének pedig BF −1 értékkel rendelkezik.
Jobbra – Balra forgatás
Ezt a forgatást akkor hajtják végre, amikor egy új csomópontot szúrnak be a jobb oldali részfa bal oldali gyermekéhez.
Akkor aktiválódik, ha BF(csomópont) = −2 és BF(jobboldali gyermek) = +1. Jobbra forgatja a jobb oldali gyermeket, majd balra forgatja a csomópontot.
Balra – Jobbra Forgatás
Ez a forgatás akkor történik meg, amikor egy új csomópontot szúrnak be a bal oldali részfa jobb oldali gyermekéhez.
Akkor aktiválódik, ha BF(csomópont) = +2 és BF(bal oldali gyermek) = −1. Balra forgatja a bal gyermeket, majd jobbra forgatja a csomópontot.
Beillesztés az AVL fákba
A beszúrás majdnem megegyezik egy sima BST beszúrással. Minden beszúrás után a fa újra egyensúlyoz. A beszúrás a legrosszabb esetre vetítve O(log n) idő alatt fut le.
AVL fa beillesztési megvalósítás
Lépés 1: Szúrja be a csomópontot a standard BST algoritmussal. A fenti példában illessze be a 160-at.
Lépés 2: Frissítse az összes ős egyensúlyi tényezőjét a beszúrási útvonal mentén.
Lépés 3: Ha bármelyik ős megsérti az egyensúlyi tényező tartományát, akkor végezze el az egyeztető forgatást. A példában a 350-es csomópont egyensúlyi tényezője sérül, így az LL forgatás visszaállítja az egyensúlyt.
- If
BF(node) = +2és aBF(left-child) = +1, hajtson végre LL forgatást. - If
BF(node) = −2és aBF(right-child) = −1, végezzen RR forgatást. - If
BF(node) = −2és aBF(right-child) = +1, hajtsa végre az RL forgatást. - If
BF(node) = +2és aBF(left-child) = −1, hajtson végre LR forgatást.
Törlés az AVL-fákban
A törlés ugyanazt a logikát követi, mint egy sima BST, és utána újra kiegyensúlyozódik.
Lépés 1: Keresse meg az elemet a fában.
Lépés 2: Törölje a csomópontot a szabványos BST törléssel.
Lépés 3: Két eset lehetséges.
Case 1: Törlés a jobb oldali részfáról.
- 1A. If
BF(node) = +2és aBF(left-child) = +1, hajtson végre LL forgatást. - 1B. If
BF(node) = +2és aBF(left-child) = −1, hajtson végre LR forgatást. - 1C. If
BF(node) = +2és aBF(left-child) = 0, hajtson végre LL forgatást.
Case 2: Törlés a bal oldali részfából.
- 2A. If
BF(node) = −2és aBF(right-child) = −1, végezzen RR forgatást. - 2B. If
BF(node) = −2és aBF(right-child) = +1, hajtsa végre az RL forgatást. - 2C. If
BF(node) = −2és aBF(right-child) = 0, végezzen RR forgatást.
C++ Példa az AVL fákra
Az alábbiakban a C++ AVL fákat megvalósító program:
#include <iostream> #include <queue> #include <unordered_map> using namespace std; struct node { struct node *left; int data; int height; struct node *right; }; class AVL { public: struct node *root; AVL() { this->root = NULL; } int calheight(struct node *p) { if (p->left && p->right) { if (p->left->height < p->right->height) return p->right->height + 1; else return p->left->height + 1; } else if (p->left && p->right == NULL) { return p->left->height + 1; } else if (p->left == NULL && p->right) { return p->right->height + 1; } return 0; } int bf(struct node *n) { if (n->left && n->right) return n->left->height - n->right->height; else if (n->left && n->right == NULL) return n->left->height; else if (n->left == NULL && n->right) return -n->right->height; return 0; } struct node *llrotation(struct node *n) { struct node *p = n; struct node *tp = p->left; p->left = tp->right; tp->right = p; return tp; } struct node *rrrotation(struct node *n) { struct node *p = n; struct node *tp = p->right; p->right = tp->left; tp->left = p; return tp; } struct node *rlrotation(struct node *n) { struct node *p = n; struct node *tp = p->right; struct node *tp2 = p->right->left; p->right = tp2->left; tp->left = tp2->right; tp2->left = p; tp2->right = tp; return tp2; } struct node *lrrotation(struct node *n) { struct node *p = n; struct node *tp = p->left; struct node *tp2 = p->left->right; p->left = tp2->right; tp->right = tp2->left; tp2->right = p; tp2->left = tp; return tp2; } struct node *insert(struct node *r, int data) { if (r == NULL) { r = new struct node; r->data = data; r->left = r->right = NULL; r->height = 1; return r; } if (data < r->data) r->left = insert(r->left, data); else r->right = insert(r->right, data); r->height = calheight(r); if (bf(r) == 2 && bf(r->left) == 1) r = llrotation(r); else if (bf(r) == -2 && bf(r->right) == -1) r = rrrotation(r); else if (bf(r) == -2 && bf(r->right) == 1) r = rlrotation(r); else if (bf(r) == 2 && bf(r->left) == -1) r = lrrotation(r); return r; } void levelorder_newline() { if (this->root == NULL) { cout << "\nEmpty tree\n"; return; } levelorder_newline(this->root); } void levelorder_newline(struct node *v) { queue<struct node *> q; struct node *cur; q.push(v); q.push(NULL); while (!q.empty()) { cur = q.front(); q.pop(); if (cur == NULL && q.size() != 0) { cout << "\n"; q.push(NULL); continue; } if (cur != NULL) { cout << " " << cur->data; if (cur->left != NULL) q.push(cur->left); if (cur->right != NULL) q.push(cur->right); } } } struct node *deleteNode(struct node *p, int data) { if (p->left == NULL && p->right == NULL) { if (p == this->root) this->root = NULL; delete p; return NULL; } struct node *q; if (p->data < data) p->right = deleteNode(p->right, data); else if (p->data > data) p->left = deleteNode(p->left, data); else { if (p->left != NULL) { q = inpre(p->left); p->data = q->data; p->left = deleteNode(p->left, q->data); } else { q = insuc(p->right); p->data = q->data; p->right = deleteNode(p->right, q->data); } } if (bf(p) == 2 && bf(p->left) == 1) p = llrotation(p); else if (bf(p) == 2 && bf(p->left) == -1) p = lrrotation(p); else if (bf(p) == 2 && bf(p->left) == 0) p = llrotation(p); else if (bf(p) == -2 && bf(p->right) == -1) p = rrrotation(p); else if (bf(p) == -2 && bf(p->right) == 1) p = rlrotation(p); else if (bf(p) == -2 && bf(p->right) == 0) p = rrrotation(p); return p; } struct node *inpre(struct node *p) { while (p->right != NULL) p = p->right; return p; } struct node *insuc(struct node *p) { while (p->left != NULL) p = p->left; return p; } ~AVL() {} }; int main() { AVL b; int c, x; do { cout << "\n1.Display levelorder on newline"; cout << "\n2.Insert"; cout << "\n3.Delete\n"; cout << "\n0.Exit\n"; cout << "\nChoice: "; cin >> c; switch (c) { case 1: b.levelorder_newline(); break; case 2: cout << "\nEnter no. "; cin >> x; b.root = b.insert(b.root, x); break; case 3: cout << "\nWhat to delete? "; cin >> x; b.root = b.deleteNode(b.root, x); break; case 0: break; } } while (c != 0); }
A fenti kód futtatásának példája:
- Másold ki a fenti kódot, és mentsd el egy fájlba, melynek neve:
avl.cpp. - Állítsd össze a kódot:
g++ avl.cpp -o run
- Futtassa a kódot.
./run
Az AVL fák előnyei
- Az AVL fa magassága mindig kiegyensúlyozott, és soha nem nő a log N fölé.
- A keresés gyorsabb, mint egy sima bináris keresőfa, mivel a fa nem degenerálódik.
- Az önkiegyensúlyozás automatikus – nincs szükség újjáépítésre.
- A determinisztikus teljesítmény valós idejű rendszerekhez és memórián belüli indexekhez illik.












