[[zalg:avl]]
 

Vyvážené stromy

Kapitola 6.1 ve skriptech
http://cs.wikipedia.org/wiki/AVL-strom
http://en.wikipedia.org/wiki/AVL_tree
Niklaus Wirth: Algoritmy + datové struktury = programy

Binární ohodnocené stromy.

Pro všechny uzly stromu musí platit, že
výšky podstromů se liší nejvýše o 1.

Animace: http://en.wikipedia.org/wiki/Tree_rotation

Obrázek z Wikipedie

Uložení dat

Jednotlivé uzly stromu budeme skladovat v datové struktuře Item.

struct Item
{
    int key;      // ulozena hodnota
    int height;   // vyska stromu zacinajiciho timto prvkem
    Item * left;  // levy podstrom s hodnotami < key (nebo nullptr, pokud levy podstrom neexistuje
    Item * right; // pravy podstrom s honotami > key
    Item ();      // konstruktor jednoho prvku
};

Konstruktor struktury / třídy Item.

Item::Item ()     // trida Item a v ni funkce, ktera se jmenuje stejne jako trida 
   :              // nezapomente na dvojtecku pred inicializaci promennych
   key (0), 
   height (1),     // vyska jedoprvkoveho stromu
   left (nullptr), 
   right (nullptr) // levy a pravy podstrom prozatim neexistuje 
{ }                // zde jiz neni co delat

Pomocná funkce h určí výšku podstromu.
Pro prázdý podstrom vrací 0
Jinak vrací uloženou hodnotu height

inline int h (Item * p)
{
    if (p == nullptr)
        return 0;
    else
        return p->height;
}

:!: Funkce h nesmí rekurzivně prohlížet celý podstrom, to by nám zkazilo složitost.
:?: Funkci nemohu umístit do třídy Item jako metodu, pro prázdné podstromy bude volána s nulovým parametrem.
Klíčové slovo inline doporučuje překladači v místě volání podprogramu rozepsat tělo funkce

:!: Značka pro poznámky o složitosti.
:?: Značka pro poznámky, které napoprvé asi vynecháte.

Funkce update nastaví hodnotu height na základě hodnot height v levém a pravém podstromu

void update (Item * p)
{
    int L = h (p->left);  
    int R = h (p->right);
    if (L > R)
        p->height = L + 1; // vyska leveho podstromu + 1 za nas prvek
    else
        p->height = R + 1;
}

:!: Opět nesmíme rekurzivně prohlížet celý podstrom.

Vkládání prvků do stromu

Nejprve vložíme nový prvek do stromu, tak jako v nevyvážených stromech funkce insert, vkládání prvku do stromu

void enter (Item * & root, int val)
{
    if (root == nullptr)
    {
        root = new Item;
        root->key = val;
    }
    else if (val < root->key) enter (root->left, val);
    else if (val > root->key) enter (root->right, val);
    else { } // hodnota je již ve stromu uložena
 
    /* v tomto místě se ocitneme po vložení nového prvku do stromu
       a později když se budeme "vynořovat" z předešlých funkcí enter */
}

Po vložení nového prvku a po každém volání funkce enter
musíme zkontrolovat výšky podstromů a případně přeuspořádat prvky.

    update (root);          // nastavíme root->height na základě hodnot v levém a prvém podstromu.
    int L = h(root->left);  // L = výška levého podstromu
    int R = h(root->right);
    Item * a = root;        // do proměnné a uložíme ukazatel na kořen našeho podstromu,
                            // referenční proměnná root se může změnit
 
    if (L > R + 1)
    {
       // levý podstrom je příliš vysoký
    }
    else if (R > L + 1)
    {
       // pravý podstrom je příliš vysoký
    }
    else
    {
       // výšku stromů se liší nejvýše o 1, nebudeme nic upravovat
    }
}

Levý podstrom je příliš vysoký

Variantu, kdy levý podstrom je příliš vysoký.
( Varianta s příliš vysokým pravým podstromem je symetrická, nebudeme ji popisovat. )

                  a
                  |
         ---------.
         |
         b
         |
   .----------.
   |          |
   C          D

:?: Prvky a, b určitě existují. Podstromy C a D mohou být prázdné.

Podívejme se výšky podstromů C a D v levém podstromu.
Zde posuzujeme zda výška C je větší než výška D, potom nastane varianta Left Left Case na obrázku z Wikipedie.
V případě, že výška C je menší nebo rovna výšce D, nastane varianta Left Right Case.

        Item * b = a->left;    // b = kořen levého podstromu
        int LL = h (b->left);  // LL = výška levého podstromu v levého podstromu  ( C ) 
        int RR = h (b->right); // RR = výška pravého podstromu v levého podstromu ( D )
        if (LL > RR)
        {
            /* Left Left Case */
        }
        else
        {
            /* Left Right Case */
        }

V levém podstromu je levý podstrom vyšší než pravý

Zde je můj neumělý náčrtek plánované akce nebo se podívejte na sloupec Left Left Case na obrázku z Wikipedie.

             a                                  b
        ------------                       -----------
        b          s          ---->        c          a
     --------                                      -------
     c      t                                      t     s

Tuto situaci zachycuje animace http://en.wikipedia.org/wiki/Tree_rotation

Prvek b se stane novým kořenem našeho podstromu.
Prvek c ponecháme vlevo pod b.
Prvek a spolu s jeho pravým podstromem s umístíme vpravo pod b.
Podstrom t který byl vpravo od b umístíme vlevo pod a (Ukazatel na t si schováme dříve, než na předchozím řádku připojíme a.

            Item * c = b->left; 
            //     a
            //   b
            // c
            Item * t = b->right; // podstrom
            root = b; // novy koren
            //     b
            //  c     a
            b->right = a;
            a->left = t;
            update (a); // vyska stromu se zmenila
            update (b); // az nakonec

Výška nového stromu

:?: V literatuře můžete najít zdůvodnění, proč strom s novým kořenem bude vyhovovat našemu požadavky na výšky podstromů.
Nebo si napište relace, které známe (včetně předpokladů, že podstromy na nižších úrovních náš požaddavek splňují) a
můžete se pokusit se dokázat, že výsledný strom také požadavek splňuje.

označíme si výšky v původním stromu: va = h(a), vb = h(b), atd.
v nově vzniklém stromu označíme na výšku podstromu s kořenem a, nb = h(b)

levý podstrom porušuje pravidlo: vb > vs+1
ale o patro níže bylo vše v pořádku: |vc - vt| < = 1

v levém podstromu je levý podstrom vyšší než pravý: vc > vt
tedy vb určeno hodnotou vc: vb = vc+1
z toho také plyne: vc > vs

v novém stromu uzel a má podstromy výšky vs a vt, vs < vc, vt < vc,
uzel a přidává sám jedno patro: na < = vc

celý nový strom b má podstromy výšky vc, na
a platí na ⇐ vc

V levém podstromu je pravý podstrom vyšší nebo stejně vysoký

Sloupec Left Right Case na obrázku z Wikipedie.

             a                                  c
        ------------                       -----------
        b          s          ---->        b          a
     --------                            -----      -----
     r      c                            r   t      u   s 
          -----
          t   u                                      

Tuto situaci zachycuje animace http://en.wikipedia.org/wiki/Tree_rotation

Prvek c se stane novým kořenem našeho podstromu.
Pod prvek c umístíme b a a.
Vpravo pod b připojíme t. Vlevo pod a připojíme u. Podstromy r a s zůstávají připojené stejně.

            Item * c = b->right; 
            //         a
            //   b
            //      c
            Item * t = c->left; // podstrom
            Item * u = c->right; // podstrom
            root = c; // novy koren
            //     c
            //  b     a
            c->left = b;
            c->right = a;
            b->right = t;
            a->left = u;
            update(a);
            update(b);
            update(c); // jako posledni

Obě pravé varianty jsou symentrické s levými.
Zbývá funkce print pro zobrazení stromu a funkce check pro kontrolu rostoucí posloupnosti hodnot.

Celý program

#include <string>
#include <iostream>
using namespace std;
 
struct Item
{
    int key;
    int height;
    Item * left;
    Item * right;
    Item () : key (0), height(1), left (nullptr), right (nullptr) { }
};
 
inline int h (Item * p)
{
    if (p == nullptr)
        return 0;
    else
        return p->height;
}
 
void update (Item * p)
{
    int L = h (p->left);
    int R = h (p->right);
    if (L > R)
        p->height = L + 1;
    else
        p->height = R + 1;
}
 
void enter (Item * & root, int val)
{
    if (root == nullptr)
    {
        root = new Item;
        root->key = val;
    }
    else if (val < root->key) enter (root->left, val);
    else if (val > root->key) enter (root->right, val);
    else { }
 
    update (root);
    int L = h(root->left);
    int R = h(root->right);
    Item * a = root;
    if (L > R + 1)
    {
        Item * b = a->left;
        int LL = h(b->left);
        int RR = h(b->right);
        if (LL > RR)
        {
            Item * c = b->left;
            //     a
            //   b
            // c
            Item * t = b->right; // podstrom
            root = b; // novy koren
            //     b
            //  c     a
            b->right = a;
            a->left = t;
            update (a);
            update (b); // jako posledni
        }
        else
        {
            Item * c = b->right;
            //         a
            //   b
            //      c
            Item * t = c->left; // podstrom
            Item * u = c->right; // podstrom
            root = c; // novy koren
            //     c
            //  b     a
            c->left = b;
            c->right = a;
            b->right = t;
            a->left = u;
            update(a);
            update(b);
            update(c); // jako posledni
        }
    }
    else if (R > L + 1)
    {
        Item * b = a->right;
        int LL = h(b->right);
        int RR = h(b->left);
        if (LL > RR)
        {
            Item * c = b->right;
            //  a
            //     b
            //        c
            Item * t = b->left; // podstrom
            root = b; // novy koren
            //     b
            //  a     c
            b->left = a;
            a->right = t;
            update(a);
            update(b); // jako posledni
        }
        else
        {
            Item * c = b->left;
            //  a
            //        b
            //     c      
            Item * t = c->right; // podstrom
            Item * u = c->left; // podstrom
            root = c; // novy koren
            //     c
            //  a     b
            c->right = b;
            c->left = a;
            b->left = t;
            a->right = u;
            update(a);
            update(b);
            update(c); // jako posledni
        }
    }
}
 
void print (Item * branch, int level = 0)
{
    if (branch != nullptr) 
    {
        for (int i = 0; i < level; i++) 
            cout << "-";
 
        cout << branch->key << " (" << branch->height << ")" << endl;
 
        print (branch->left, level+1);
        print (branch->right, level+1);
    }
}
 
bool ok;
bool first;
int  last_value;
 
void checkBranch (Item * branch)
{
    if (branch != nullptr)
    {
        checkBranch (branch->left);
 
        if (! first)
           if (last_value >= branch->key)
           {
               cout << "CHYBA" << endl;
               ok = false;
           }
 
        last_value = branch->key;
        first = false;
        checkBranch (branch->right);
    }
}
 
void check (Item * root)
{
   ok = true;
   first = true;
   checkBranch (root);
   if (ok) cout << "O.K." << endl;
}
 
int main()
{
    Item * root = nullptr;
 
    const int N = 32;
    // const int N = 16*1024;
 
    for (int k = 1; k <= N; k++)
       enter (root, k);
 
    if (N <= 1000)
       print (root);
 
    cout << "Vyska stromu " << h(root) << endl;
    check (root);
}

Případně varianta pro Qt http://gitlab.fjfi.cvut.cz/culikzde/zalg/-/tree/master/avltree-qt

 
zalg/avl.txt · Last modified: 2020/09/30 11:06 by 88.103.111.44
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki