AVL Træer

 

Video: Find ubalance i AVL træet

 

Simulation:AVL træer

 

Vi kender allerede til binær søgetræer, som har den egenskab, at man let kan finde det man søger efter, ved hele tiden at kikke om det man leder efter er højere eller lavere end den man kikker på lige nu, og så bevæger man sig igennem træet indtil man finder det man søger.

AVL træer er også et binært søgetræ, men med en extra egenskab, nemlig at den altid er balanseret. Det betyder at den er så hurtig som et binært træ kan være at søge i. Mens et almindeligt binært søgetræ, sagtens kan være skæv, således at det mest kommer til at ligne en linked-list, og dermed ikke er ret effektivt at søge i.

MEN det er ret kompliceret at holde træet balanceret, men hvis du kikker på koden som er her i dokumentet, kan du se hvordan det fungerer.  Teknikken som bruges til at balancere træet er ved at rotere en del af træet. Men først skal vi undersøge hvordan vi finder frem til at der overhoved skal roteres.

Hvornår skal der roteres

For at bestemme om der skal laves en rotation, bruger man højden på grenene. Hvis forskellen på grene højden er større end 2, skal der roteres. Rotationen vil medføre at træet igen er balancerest, dvs. alle højder har en mindre forskel end 2.

 

Som du ser på billedet ovenover her, så er de første 4 grafer i orden, fordi ingen af noderne har en værdi som er 2 eller -2, som betyder at træet er balanceret så godt som det kan.

De ander 3 træer er IKKE balanceret, fordi du ser at de har -2 og/eller 2 i sig. En lille bemærking her. Den graf helt til højre som har både 2 og -2 kan forvirre lidt, fordi hvilke skal man så roter om. Men man roterer altid om den FØRST man støder på, og man vil lede fra bunden/bladet man lige har indsat og op. Så i dette tilfælde vi har her, vil rotatione være om -2, og når den så er blevet roteret, vil 2 også blive fikset automatisk fordi den vil ende på 1.

Hvordan udregnes behov for rotation

Eftersom AVL som udgangspunkt er et almindeligt binært søgetræ, så vil det højst sandsynligt blive u-balanceret på et eller andet tidspunkt og så mister det sin effektivitet som et søgetræ. Men det første problem er at finde ud af om træet er u-balanceret eller ikke. Algoritmen som vi bruger her er forsøgt

illustreret her.

 

 

image003

I billede 5. Opstår der en u-balance, fordi værden er blevet 2, og vi ryger ind i en else if sætning. Hvis du kikker på koden for denne else if, ser du at der her kaldes videre på RotationRight eller RotationRightLeft, som vi skal kikke på efterfølgende. Men det vigtigte her er at algoritmen fanger u-balancen og så sender problemet videre til en Rotation algortme.

De 4 rotationer

Der findes 2 slags rotationer, single og double, og efter som de kan bruges på højre og venstre får vi 4 forskellige rotationer.

Single rotation

 

Som du ser her så skal vi kun rotere én gang for at få et balanceret træ.

 

Men hvordan finder man frem til at det er en single rotation? Jo, det gør du ved at du travereser ned fra den node som var i ubalancen (større end 2), og ned mod den som vi lige har indsat, og hvis den første node er f.eks. Left, så ved vi at det er en Left rotation. Og om vi så igen går fra den node og den igen er left (altså det er en lige linje fra i dette tilfælde 5 til 3 til 2, så har vi en Single Left.

 

Dvs. en lige linje kræver en single rotation.

Double rotation.

 

Hvordan finder vi denne? Jo først finder vi om det er en right eller Left, og så om den knækker til det modsatte (altså IKKE en lige linje) så er det en Double rotation vi har.

 

Man løser denne ved førsta at lave en ”udligning”, så at vi nu har en almindelig lige linje, og så bruger vi bare single rotation på den.

 

Så det betyder at alle fire rotationer har singel rotation i sig, men double har en extra rotation.

 


 

Nu når du har set taktikken, lad os se hvordan koden virkelig finder frem til hvilken rotation der skal laves. Vi bruger eksempler fra før, som fanger en +2 ubalance. Men hvilken rotation skal der laves?

Kode

Billede

else if (balance == 2)

{

   if (n.Left.Balance == 1)

   {

       RotateRight(n);

   }

   else

   {

       RotateLeftRight(n);

   }

   return;

}

 

Som du ser, så kommer vi ind i else if (balance == 2) fordi node 70 har balance +2. Men så har vi endnu en if else sætning indeni.  Og den undersøger først om n.Left.Balance == 1 (dvs. node 60), men 60 er -1, og vi ryger derfor ind i RotateLeftRight, det betyder at når der er -1 så er der et knæk på listen fra 70 og op til 65. Mens hvis resultatet var 1 så var der ikke knæk på og vi vil få en enkel rotation til højre.

 

Nu kender du til de 2 fundamentale dele i AVL, hvordan man finder en ubalance og hvordan man finder den rigtige rotation.


 

Koden til AVL Træet

Her kommer nu koden til et AVL træ. Jeg har ikke medtaget Delete, fordi det er ret kompliceret, og det er rigeligt at forstå hvordan man balancerer træet. Men hvis du vil vide mere om Delete, så kan du hente koden fra linket nedenfor her, men Delete er ikke med i pensum.

Følgend kode er taget fra: http://www.superstarcoders.com/blogs/posts/efficient-avl-tree-in-c-sharp.aspx

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace AVL_tree

{

    class Program

    {

        static void Main(string[] args)

        {

            AvlTree<int> tree = new AvlTree<int>();

 

            tree.InsertNode(10);

            tree.Print();

            tree.InsertNode(15);

            tree.Print();

            tree.InsertNode(13);   

            tree.Print();

        }

    }

    class AvlTree<T> where T : IComparable<T>

    {

        AvlNode<T> root;

 

        public void InsertNode(T data)

        {

            if (root == null)

            {

                root = new AvlNode<T> { Data = data };

                Console.WriteLine("Insert at root: " + data);

            }

            else

            {

                AvlNode<T> node = root;

 

                while (node != null)

                {

                    if (data.CompareTo(node.Data) < 0)

                    {

                        AvlNode<T> left = node.Left;

 

                        if (left == null)

                        {

                            node.Left = new AvlNode<T> { Data = data, Parent = node };

                            Console.WriteLine("Insert after "+ node.Data + " to Left, value: " + data);

                            BalanceNode(node, 1);

                            return;

                        }

                        else

                        {

                            node = left;

                        }

                    }

                    else if (data.CompareTo(node.Data) > 0)

                    {

                        AvlNode<T> right = node.Right;

 

                        if (right == null)

                        {

                            node.Right = new AvlNode<T> { Data = data, Parent = node };

                            Console.WriteLine("Insert after " + node.Data + " to right, value: " + data);

                            BalanceNode(node, -1);

                            return;

                        }

                        else

                        {

                            node = right;

                        }

                    }

                    else

                    {

                        node.Data = data;

                        return;

                    }

                }

            }

        }

        private void BalanceNode(AvlNode<T> n, int balance)

        {

            while (n != null)

            {

                balance = (n.Balance += balance);

 

                if (balance == 0)

                {

                    return;

                }

                else if (balance == 2)

                {

                    if (n.Left.Balance == 1)

                    {

                        Console.WriteLine("--------------------------");

                        Console.WriteLine("  Print before rotation");

                        Console.WriteLine("--------------------------");

                        Print();

                        Console.WriteLine("--------------------------");

                        Console.WriteLine("Rotate Right at " + n.Data);

                        RotateRight(n);

                    }

                    else

                    {

                        Console.WriteLine("--------------------------");

                        Console.WriteLine("  Print before rotation");

                        Console.WriteLine("--------------------------");

                        Print();

                        Console.WriteLine("--------------------------");

                        Console.WriteLine("Rotate LeftRight at " + n.Data);

                        RotateLeftRight(n);

                    }

                    return;

                }

                else if (balance == -2)

                {

                    if (n.Right.Balance == -1)

                    {

                        Console.WriteLine("--------------------------");

                        Console.WriteLine("  Print before rotation");

                        Console.WriteLine("--------------------------");

                        Print();

                        Console.WriteLine("--------------------------");

                        Console.WriteLine("Rotate Left at " + n.Data);

                        RotateLeft(n);

                    }

                    else

                    {

                        Console.WriteLine("--------------------------");

                        Console.WriteLine("  Print before rotation");

                        Console.WriteLine("--------------------------");

                        Print();

                        Console.WriteLine("--------------------------");

                        Console.WriteLine("Rotate RightLeft at " + n.Data);

                        RotateRightLeft(n);

                    }

                    return;

                }

                AvlNode<T> parent = n.Parent;

 

                if (parent != null)

                {

 

                    if (parent.Left == n)

                    {

                        balance = 1;

                    }

                    else

                    {

                        balance = -1;

                    }

                }

                n = parent;

            }

        }

 

        #region rotation left, right, leftright og rightleft

 

        #region left

        /*

 

                            ROTATION VENSTRE

         

                       (5)                   (4)

                      /   \                 /   \

                     (4)   S               /     \

                    /   \        -->     (3)     (5)

                   (3)   R               / \     / \

                  /   \                 /   \   /   \

                 P     Q               P     Q R     S

 

 

        */

 

//Den Grå markerede kode er ikke del af Pensum.

 

        private AvlNode<T> RotateLeft(AvlNode<T> node)

        {

            AvlNode<T> right = node.Right;

            AvlNode<T> rightLeft = right.Left;

            AvlNode<T> parent = node.Parent;

 

            right.Parent = parent;

            right.Left = node;

            node.Right = rightLeft;

            node.Parent = right;

 

            if (rightLeft != null)

            {

                rightLeft.Parent = node;

            }

 

            if (node == root)

            {

                root = right;

            }

            else if (parent.Right == node)

            {

                parent.Right = right;

            }

            else

            {

                parent.Left = right;

            }

 

            right.Balance++;

            node.Balance = -right.Balance;

 

            return right;

        }

#endregion

 

        #region right

        /*

 

                            ROTATION HØJRE

        

                     (3)                     (4)

                    /   \                   /   \

                   P   (4)                 /     \

                      /   \      -->     (3)     (5)

                     Q   (5)             / \     / \

                        /   \           /   \   /   \

                       R     S         P     Q R     S

 

 

        */

        private AvlNode<T> RotateRight(AvlNode<T> node)

        {

            AvlNode<T> left = node.Left;

            AvlNode<T> leftRight = left.Right;

            AvlNode<T> parent = node.Parent;

 

            left.Parent = parent;

            left.Right = node;

            node.Left = leftRight;

            node.Parent = left;

 

            if (leftRight != null)

            {

                leftRight.Parent = node;

            }

 

            if (node == root)

            {

                root = left;

            }

            else if (parent.Left == node)

            {

                parent.Left = left;

            }

            else

            {

                parent.Right = left;

            }

 

            left.Balance--;

            node.Balance = -left.Balance;

 

            return left;

        }

#endregion

 

        #region left>right

        /*

                     ROTATION VENSTRE OG SÅ HØJRE

        

               (5)                  (5)                 (4)

              /   \                /   \               /   \

             (3)   S              (4)   S             /     \

            /   \        -->     /   \       -->    (3)     (5)

           P    (4)             (3)   R             / \     / \

               /   \           /   \               /   \   /   \

              Q     R         P     Q             P     Q R     S

        

        */

 

        private AvlNode<T> RotateLeftRight(AvlNode<T> node)

        {

            AvlNode<T> left = node.Left;

            AvlNode<T> leftRight = left.Right;

            AvlNode<T> parent = node.Parent;

            AvlNode<T> leftRightRight = leftRight.Right;

            AvlNode<T> leftRightLeft = leftRight.Left;

 

            leftRight.Parent = parent;

            node.Left = leftRightRight;

            left.Right = leftRightLeft;

            leftRight.Left = left;

            leftRight.Right = node;

            left.Parent = leftRight;

            node.Parent = leftRight;

 

            if (leftRightRight != null)

            {

                leftRightRight.Parent = node;

            }

 

            if (leftRightLeft != null)

            {

                leftRightLeft.Parent = left;

            }

 

            if (node == root)

            {

                root = leftRight;

            }

            else if (parent.Left == node)

            {

                parent.Left = leftRight;

            }

            else

            {

                parent.Right = leftRight;

            }

 

            if (leftRight.Balance == -1)

            {

                node.Balance = 0;

                left.Balance = 1;

            }

            else if (leftRight.Balance == 0)

            {

                node.Balance = 0;

                left.Balance = 0;

            }

            else

            {

                node.Balance = -1;

                left.Balance = 0;

            }

 

            leftRight.Balance = 0;

 

            return leftRight;

        }

        #endregion

 

        #region right>left

        /*

                      ROTATION HØJRE OG SÅ VENSTRE

        

             (3)                 (3)                     (4)

            /   \               /   \                   /   \

           P   (5)             P   (4)                 /     \

              /   \      -->      /   \      -->     (3)     (5)

            (4)    D             Q   (5)             / \     / \

           /   \                    /   \           /   \   /   \

          B     C                  R     S         P     Q R     S

        

        */

 

 

        private AvlNode<T> RotateRightLeft(AvlNode<T> node)

        {

            AvlNode<T> right = node.Right;

            AvlNode<T> rightLeft = right.Left;

            AvlNode<T> parent = node.Parent;

            AvlNode<T> rightLeftLeft = rightLeft.Left;

            AvlNode<T> rightLeftRight = rightLeft.Right;

 

            rightLeft.Parent = parent;

            node.Right = rightLeftLeft;

            right.Left = rightLeftRight;

            rightLeft.Right = right;

            rightLeft.Left = node;

            right.Parent = rightLeft;

            node.Parent = rightLeft;

 

            if (rightLeftLeft != null)

            {

                rightLeftLeft.Parent = node;

            }

 

            if (rightLeftRight != null)

            {

                rightLeftRight.Parent = right;

            }

 

            if (node == root)

            {

                root = rightLeft;

            }

            else if (parent.Right == node)

            {

                parent.Right = rightLeft;

            }

            else

            {

                parent.Left = rightLeft;

            }

 

            if (rightLeft.Balance == 1)

            {

                node.Balance = 0;

                right.Balance = -1;

            }

            else if (rightLeft.Balance == 0)

            {

                node.Balance = 0;

                right.Balance = 0;

            }

            else

            {

                node.Balance = 1;

                right.Balance = 0;

            }

 

            rightLeft.Balance = 0;

 

            return rightLeft;

        }

        #endregion

 

        #endregion

 

        public void Print()

        {

            Console.WriteLine("--------Print AVL Træ-----------");

            String t = "";

 

            if (root != null)

                root.Print(root, t);

            Console.WriteLine("--------------------------------");

            Console.WriteLine();

        }

    }

    class AvlNode<T>

    {

        private T data;

        private int balance;

        private AvlNode<T> left;

        private AvlNode<T> right;

        private AvlNode<T> parent;

 

        #region Properties

        public AvlNode<T> Left

        {

            get { return left; }

            set { left = value; }

        }

        public AvlNode<T> Right

        {

            get { return right; }

            set { right = value; }

        }

        public AvlNode<T> Parent

        {

            get { return parent; }

            set { parent = value; }

        }

        public T Data

        {

            get { return data; }

            set { data = value; }

        }

        public int Balance

        {

            get { return balance; }

            set { balance = value; }

        }

        #endregion

 

        public void Print(AvlNode<T> n, string t)

        {

            AvlNode<T>parentData;

 

            if (n.parent != null)

            {

                parentData = parent;

            }

            else

            {

                parentData = new AvlNode<T>();

            }

            t += "-";

            if (right != null)

                right.Print(n.right, t);

            Console.WriteLine(t + " node: " + n.data + " parent: " + parentData.data + " Balance: " + n.Balance);

            if (left != null)

                left.Print(n.left, t);

        }

    }

}

 

Fagudtryk(AVL-Træ)

AVL træet er et normalt Binært-Søgetræ, med den ene forskel, at det hele tiden er balanceret.

 

Formålet med AVL træet er at det holdes balanceret hele tiden, mens nye elementer sættes ind. Det opnås ved at rotere rundt på grenene i træet.

 

For at bestemme om der skal laves en rotation, bruger man højden på grenene. Hvis forskellen på grene højden er større end 2, skal er roteres. Rotationen vil medføre at træet igen er balancerest, dvs. alle højder har en mindre forskel end 2.

 

Hvornår skal man rotere?