平衡二叉樹

 當有很多數據灌到我的樹中時,我肯定會希望最好是以“完全二叉樹”的形式展現,這樣我才能做到“查找”是嚴格的O(logN),

比如把這種”樹“調正到如下結構。

這裏就涉及到了“樹節點”的旋轉,也是我們今天要聊到的內容。

一:平衡二叉樹(AVL)

1:定義

       父節點的左子樹和右子樹的高度之差不能大於1,也就是說不能高過1層,否則該樹就失衡了,此時就要旋轉節點,在

編碼時,我們可以記錄當前節點的高度,比如空節點是-1,葉子節點是0,非葉子節點的height往根節點遞增,比如在下圖

中我們認爲樹的高度爲h=2。



#region 平衡二叉樹節點
    /// <summary>
    /// 平衡二叉樹節點
    /// </summary>
    /// <typeparam name="K"></typeparam>
    /// <typeparam name="V"></typeparam>
    public class AVLNode<K, V>
    {
        /// <summary>
        /// 節點元素
        /// </summary>
        public K key;

        /// <summary>
        /// 增加一個高度信息
        /// </summary>
        public int height;

        /// <summary>
        /// 節點中的附加值
        /// </summary>
        public HashSet<V> attach = new HashSet<V>();

        /// <summary>
        /// 左節點
        /// </summary>
        public AVLNode<K, V> left;

        /// <summary>
        /// 右節點
        /// </summary>
        public AVLNode<K, V> right;

        public AVLNode() { }

        public AVLNode(K key, V value, AVLNode<K, V> left, AVLNode<K, V> right)
        {
            //KV鍵值對
            this.key = key;
            this.attach.Add(value);

            this.left = left;
            this.right = right;
        }
    }
    #endregion

2:旋轉

    節點再怎麼失衡都逃不過4種情況,下面我們一一來看一下。

① 左左情況(左子樹的左邊節點)



我們看到,在向樹中追加“節點1”的時候,根據定義我們知道這樣會導致了“節點3"失衡,滿足“左左情況“,可以這樣想,把這

棵樹比作齒輪,我們在“節點5”處把齒輪往下拉一個位置,也就變成了後面這樣“平衡”的形式,如果用動畫解釋就最好理解了。


#region 第一種:左左旋轉(單旋轉)
        /// <summary>
        /// 第一種:左左旋轉(單旋轉)
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public AVLNode<K, V> RotateLL(AVLNode<K, V> node)
        {
            //top:需要作爲頂級節點的元素
            var top = node.left;

            //先截斷當前節點的左孩子
            node.left = top.right;

            //將當前節點作爲temp的右孩子
            top.right = node;

            //計算當前兩個節點的高度
            node.height = Math.Max(Height(node.left), Height(node.right)) + 1;
            top.height = Math.Max(Height(top.left), Height(top.right)) + 1;

            return top;
        }
        #endregion

② 右右情況(右子樹的右邊節點)



同樣,”節點5“滿足”右右情況“,其實我們也看到,這兩種情況是一種鏡像,當然操作方式也大同小異,我們在”節點1“的地方

將樹往下拉一位,最後也就形成了我們希望的平衡效果。

#region 第二種:右右旋轉(單旋轉)
        /// <summary>
        /// 第二種:右右旋轉(單旋轉)
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public AVLNode<K, V> RotateRR(AVLNode<K, V> node)
        {
            //top:需要作爲頂級節點的元素
            var top = node.right;

            //先截斷當前節點的右孩子
            node.right = top.left;

            //將當前節點作爲temp的右孩子
            top.left = node;

            //計算當前兩個節點的高度
            node.height = Math.Max(Height(node.left), Height(node.right)) + 1;
            top.height = Math.Max(Height(top.left), Height(top.right)) + 1;

            return top;
        }
        #endregion

③左右情況(左子樹的右邊節點)



從圖中我們可以看到,當我們插入”節點3“時,“節點5”處失衡,注意,找到”失衡點“是非常重要的,當面對”左右情況“時,我們將

失衡點的左子樹進行"右右情況旋轉",然後進行”左左情況旋轉“,經過這樣兩次的旋轉就OK了,很有意思,對吧。

#region 第三種:左右旋轉(雙旋轉)
        /// <summary>
        /// 第三種:左右旋轉(雙旋轉)
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public AVLNode<K, V> RotateLR(AVLNode<K, V> node)
        {
            //先進行RR旋轉
            node.left = RotateRR(node.left);

            //再進行LL旋轉
            return RotateLL(node);
        }
        #endregion

④右左情況(右子樹的左邊節點)


這種情況和“情景3”也是一種鏡像關係,很簡單,就不上圖了。

#region 第四種:右左旋轉(雙旋轉)
        /// <summary>
        /// 第四種:右左旋轉(雙旋轉)
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public AVLNode<K, V> RotateRL(AVLNode<K, V> node)
        {
            //執行左左旋轉
            node.right = RotateLL(node.right);

            //再執行右右旋轉
            return RotateRR(node);

        }
        #endregion

3:添加

    如果我們理解了上面的這幾種旋轉,那麼添加方法簡直是輕而易舉,出現了哪一種情況調用哪一種方法而已。

#region 添加操作
        /// <summary>
        /// 添加操作
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="tree"></param>
        /// <returns></returns>
        public AVLNode<K, V> Add(K key, V value, AVLNode<K, V> tree)
        {
            if (tree == null)
                tree = new AVLNode<K, V>(key, value, null, null);

            //左子樹
            if (key.CompareTo(tree.key) < 0)
            {
                tree.left = Add(key, value, tree.left);

                //如果說相差等於2就說明這棵樹需要旋轉了
                if (Height(tree.left) - Height(tree.right) == 2)
                {
                    //說明此時是左左旋轉
                    if (key.CompareTo(tree.left.key) < 0)
                    {
                        tree = RotateLL(tree);
                    }
                    else
                    {
                        //屬於左右旋轉
                        tree = RotateLR(tree);
                    }
                }
            }

            //右子樹
            if (key.CompareTo(tree.key) > 0)
            {
                tree.right = Add(key, value, tree.right);

                if ((Height(tree.right) - Height(tree.left) == 2))
                {
                    //此時是右右旋轉
                    if (key.CompareTo(tree.right.key) > 0)
                    {
                        tree = RotateRR(tree);
                    }
                    else
                    {
                        //屬於右左旋轉
                        tree = RotateRL(tree);
                    }
                }
            }

            //將value追加到附加值中(也可對應重複元素)
            if (key.CompareTo(tree.key) == 0)
                tree.attach.Add(value);

            //計算高度
            tree.height = Math.Max(Height(tree.left), Height(tree.right)) + 1;

            return tree;
        }
        #endregion

4:刪除

刪除方法跟添加方法也類似,當刪除一個結點的時候,可能會引起祖先結點的失衡,所以在每次”結點“回退的時候計算結點高度。

#region 刪除當前樹中的節點
        /// <summary>
        /// 刪除當前樹中的節點
        /// </summary>
        /// <param name="key"></param>
        /// <param name="tree"></param>
        /// <returns></returns>
        public AVLNode<K, V> Remove(K key, V value, AVLNode<K, V> tree)
        {
            if (tree == null)
                return null;

            //左子樹
            if (key.CompareTo(tree.key) < 0)
            {
                tree.left = Remove(key, value, tree.left);

                //如果說相差等於2就說明這棵樹需要旋轉了
                if (Height(tree.left) - Height(tree.right) == 2)
                {
                    //說明此時是左左旋轉
                    if (key.CompareTo(tree.left.key) < 0)
                    {
                        tree = RotateLL(tree);
                    }
                    else
                    {
                        //屬於左右旋轉
                        tree = RotateLR(tree);
                    }
                }
            }
            //右子樹
            if (key.CompareTo(tree.key) > 0)
            {
                tree.right = Remove(key, value, tree.right);

                if ((Height(tree.right) - Height(tree.left) == 2))
                {
                    //此時是右右旋轉
                    if (key.CompareTo(tree.right.key) > 0)
                    {
                        tree = RotateRR(tree);
                    }
                    else
                    {
                        //屬於右左旋轉
                        tree = RotateRL(tree);
                    }
                }
            }
            /*相等的情況*/
            if (key.CompareTo(tree.key) == 0)
            {
                //判斷裏面的HashSet是否有多值
                if (tree.attach.Count > 1)
                {
                    //實現惰性刪除
                    tree.attach.Remove(value);
                }
                else
                {
                    //有兩個孩子的情況
                    if (tree.left != null && tree.right != null)
                    {
                        //根據平衡二叉樹的中順遍歷,需要找到”有子樹“的最小節點
                        tree.key = FindMin(tree.right).key;

                        //刪除右子樹的指定元素
                        tree.right = Remove(tree.key, value, tree.right);
                    }
                    else
                    {
                        //自減高度
                        tree = tree.left == null ? tree.right : tree.left;

                        //如果刪除的是葉子節點直接返回
                        if (tree == null)
                            return null;
                    }
                }
            }

            //統計高度
            tree.height = Math.Max(Height(tree.left), Height(tree.right)) + 1;

            return tree;
        }
        #endregion

5: 測試

不像上一篇不能在二叉樹中灌有序數據,平衡二叉樹就沒關係了,我們的需求是檢索2012-7-30 4:00:00 到 2012-7-30 5:00:00

的登陸用戶的ID,數據量在500w,看看平衡二叉樹是如何秒殺對手。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.Diagnostics;

namespace DataStruct
{
    class Program
    {
        static void Main(string[] args)
        {
            AVLTree<int, int> avl = new AVLTree<int, int>();

            Dictionary<DateTime, int> dic = new Dictionary<DateTime, int>();

            AVLTree<DateTime, int> tree = new AVLTree<DateTime, int>();

            //500w
            for (int i = 1; i < 5000000; i++)
            {
                dic.Add(DateTime.Now.AddMinutes(i), i);

                tree.Add(DateTime.Now.AddMinutes(i), i);
            }

            //檢索2012-7-30 4:00:00 到 2012-7-30 5:00:00的登陸人數
            var min = Convert.ToDateTime("2012/7/30 4:00:00");

            var max = Convert.ToDateTime("2012/7/30 5:00:00");

            var watch = Stopwatch.StartNew();

            var result1 = dic.Keys.Where(i => i >= min && i <= max).Select(i => dic[i]).ToList();

            watch.Stop();

            Console.WriteLine("字典查找耗費時間:{0}ms", watch.ElapsedMilliseconds);

            watch = Stopwatch.StartNew();

            var result2 = tree.SearchRange(min, max);

            watch.Stop();

            Console.WriteLine("平衡二叉樹查找耗費時間:{0}ms", watch.ElapsedMilliseconds);
        }
    }

    #region 平衡二叉樹節點
    /// <summary>
    /// 平衡二叉樹節點
    /// </summary>
    /// <typeparam name="K"></typeparam>
    /// <typeparam name="V"></typeparam>
    public class AVLNode<K, V>
    {
        /// <summary>
        /// 節點元素
        /// </summary>
        public K key;

        /// <summary>
        /// 增加一個高度信息
        /// </summary>
        public int height;

        /// <summary>
        /// 節點中的附加值
        /// </summary>
        public HashSet<V> attach = new HashSet<V>();

        /// <summary>
        /// 左節點
        /// </summary>
        public AVLNode<K, V> left;

        /// <summary>
        /// 右節點
        /// </summary>
        public AVLNode<K, V> right;

        public AVLNode() { }

        public AVLNode(K key, V value, AVLNode<K, V> left, AVLNode<K, V> right)
        {
            //KV鍵值對
            this.key = key;
            this.attach.Add(value);

            this.left = left;
            this.right = right;
        }
    }
    #endregion

    public class AVLTree<K, V> where K : IComparable
    {
        public AVLNode<K, V> node = null;

        #region 添加操作
        /// <summary>
        /// 添加操作
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public void Add(K key, V value)
        {
            node = Add(key, value, node);
        }
        #endregion

        #region 添加操作
        /// <summary>
        /// 添加操作
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="tree"></param>
        /// <returns></returns>
        public AVLNode<K, V> Add(K key, V value, AVLNode<K, V> tree)
        {
            if (tree == null)
                tree = new AVLNode<K, V>(key, value, null, null);

            //左子樹
            if (key.CompareTo(tree.key) < 0)
            {
                tree.left = Add(key, value, tree.left);

                //如果說相差等於2就說明這棵樹需要旋轉了
                if (Height(tree.left) - Height(tree.right) == 2)
                {
                    //說明此時是左左旋轉
                    if (key.CompareTo(tree.left.key) < 0)
                    {
                        tree = RotateLL(tree);
                    }
                    else
                    {
                        //屬於左右旋轉
                        tree = RotateLR(tree);
                    }
                }
            }

            //右子樹
            if (key.CompareTo(tree.key) > 0)
            {
                tree.right = Add(key, value, tree.right);

                if ((Height(tree.right) - Height(tree.left) == 2))
                {
                    //此時是右右旋轉
                    if (key.CompareTo(tree.right.key) > 0)
                    {
                        tree = RotateRR(tree);
                    }
                    else
                    {
                        //屬於右左旋轉
                        tree = RotateRL(tree);
                    }
                }
            }

            //將value追加到附加值中(也可對應重複元素)
            if (key.CompareTo(tree.key) == 0)
                tree.attach.Add(value);

            //計算高度
            tree.height = Math.Max(Height(tree.left), Height(tree.right)) + 1;

            return tree;
        }
        #endregion

        #region 計算當前節點的高度
        /// <summary>
        /// 計算當前節點的高度
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public int Height(AVLNode<K, V> node)
        {
            return node == null ? -1 : node.height;
        }
        #endregion

        #region 第一種:左左旋轉(單旋轉)
        /// <summary>
        /// 第一種:左左旋轉(單旋轉)
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public AVLNode<K, V> RotateLL(AVLNode<K, V> node)
        {
            //top:需要作爲頂級節點的元素
            var top = node.left;

            //先截斷當前節點的左孩子
            node.left = top.right;

            //將當前節點作爲temp的右孩子
            top.right = node;

            //計算當前兩個節點的高度
            node.height = Math.Max(Height(node.left), Height(node.right)) + 1;
            top.height = Math.Max(Height(top.left), Height(top.right)) + 1;

            return top;
        }
        #endregion

        #region 第二種:右右旋轉(單旋轉)
        /// <summary>
        /// 第二種:右右旋轉(單旋轉)
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public AVLNode<K, V> RotateRR(AVLNode<K, V> node)
        {
            //top:需要作爲頂級節點的元素
            var top = node.right;

            //先截斷當前節點的右孩子
            node.right = top.left;

            //將當前節點作爲temp的右孩子
            top.left = node;

            //計算當前兩個節點的高度
            node.height = Math.Max(Height(node.left), Height(node.right)) + 1;
            top.height = Math.Max(Height(top.left), Height(top.right)) + 1;

            return top;
        }
        #endregion

        #region 第三種:左右旋轉(雙旋轉)
        /// <summary>
        /// 第三種:左右旋轉(雙旋轉)
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public AVLNode<K, V> RotateLR(AVLNode<K, V> node)
        {
            //先進行RR旋轉
            node.left = RotateRR(node.left);

            //再進行LL旋轉
            return RotateLL(node);
        }
        #endregion

        #region 第四種:右左旋轉(雙旋轉)
        /// <summary>
        /// 第四種:右左旋轉(雙旋轉)
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public AVLNode<K, V> RotateRL(AVLNode<K, V> node)
        {
            //執行左左旋轉
            node.right = RotateLL(node.right);

            //再執行右右旋轉
            return RotateRR(node);

        }
        #endregion

        #region 是否包含指定元素
        /// <summary>
        /// 是否包含指定元素
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Contain(K key)
        {
            return Contain(key, node);
        }
        #endregion

        #region 是否包含指定元素
        /// <summary>
        /// 是否包含指定元素
        /// </summary>
        /// <param name="key"></param>
        /// <param name="tree"></param>
        /// <returns></returns>
        public bool Contain(K key, AVLNode<K, V> tree)
        {
            if (tree == null)
                return false;
            //左子樹
            if (key.CompareTo(tree.key) < 0)
                return Contain(key, tree.left);

            //右子樹
            if (key.CompareTo(tree.key) > 0)
                return Contain(key, tree.right);

            return true;
        }
        #endregion

        #region 樹的指定範圍查找
        /// <summary>
        /// 樹的指定範圍查找
        /// </summary>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <returns></returns>
        public HashSet<V> SearchRange(K min, K max)
        {
            HashSet<V> hashSet = new HashSet<V>();

            hashSet = SearchRange(min, max, hashSet, node);

            return hashSet;
        }
        #endregion

        #region 樹的指定範圍查找
        /// <summary>
        /// 樹的指定範圍查找
        /// </summary>
        /// <param name="range1"></param>
        /// <param name="range2"></param>
        /// <param name="tree"></param>
        /// <returns></returns>
        public HashSet<V> SearchRange(K min, K max, HashSet<V> hashSet, AVLNode<K, V> tree)
        {
            if (tree == null)
                return hashSet;

            //遍歷左子樹(尋找下界)
            if (min.CompareTo(tree.key) < 0)
                SearchRange(min, max, hashSet, tree.left);

            //當前節點是否在選定範圍內
            if (min.CompareTo(tree.key) <= 0 && max.CompareTo(tree.key) >= 0)
            {
                //等於這種情況
                foreach (var item in tree.attach)
                    hashSet.Add(item);
            }

            //遍歷右子樹(兩種情況:①:找min的下限 ②:必須在Max範圍之內)
            if (min.CompareTo(tree.key) > 0 || max.CompareTo(tree.key) > 0)
                SearchRange(min, max, hashSet, tree.right);

            return hashSet;
        }
        #endregion

        #region 找到當前樹的最小節點
        /// <summary>
        /// 找到當前樹的最小節點
        /// </summary>
        /// <returns></returns>
        public AVLNode<K, V> FindMin()
        {
            return FindMin(node);
        }
        #endregion

        #region 找到當前樹的最小節點
        /// <summary>
        /// 找到當前樹的最小節點
        /// </summary>
        /// <param name="tree"></param>
        /// <returns></returns>
        public AVLNode<K, V> FindMin(AVLNode<K, V> tree)
        {
            if (tree == null)
                return null;

            if (tree.left == null)
                return tree;

            return FindMin(tree.left);
        }
        #endregion

        #region 找到當前樹的最大節點
        /// <summary>
        /// 找到當前樹的最大節點
        /// </summary>
        /// <returns></returns>
        public AVLNode<K, V> FindMax()
        {
            return FindMin(node);
        }
        #endregion

        #region 找到當前樹的最大節點
        /// <summary>
        /// 找到當前樹的最大節點
        /// </summary>
        /// <param name="tree"></param>
        /// <returns></returns>
        public AVLNode<K, V> FindMax(AVLNode<K, V> tree)
        {
            if (tree == null)
                return null;

            if (tree.right == null)
                return tree;

            return FindMax(tree.right);
        }
        #endregion

        #region 刪除當前樹中的節點
        /// <summary>
        /// 刪除當前樹中的節點
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public void Remove(K key, V value)
        {
            node = Remove(key, value, node);
        }
        #endregion

        #region 刪除當前樹中的節點
        /// <summary>
        /// 刪除當前樹中的節點
        /// </summary>
        /// <param name="key"></param>
        /// <param name="tree"></param>
        /// <returns></returns>
        public AVLNode<K, V> Remove(K key, V value, AVLNode<K, V> tree)
        {
            if (tree == null)
                return null;

            //左子樹
            if (key.CompareTo(tree.key) < 0)
            {
                tree.left = Remove(key, value, tree.left);

                //如果說相差等於2就說明這棵樹需要旋轉了
                if (Height(tree.left) - Height(tree.right) == 2)
                {
                    //說明此時是左左旋轉
                    if (key.CompareTo(tree.left.key) < 0)
                    {
                        tree = RotateLL(tree);
                    }
                    else
                    {
                        //屬於左右旋轉
                        tree = RotateLR(tree);
                    }
                }
            }
            //右子樹
            if (key.CompareTo(tree.key) > 0)
            {
                tree.right = Remove(key, value, tree.right);

                if ((Height(tree.right) - Height(tree.left) == 2))
                {
                    //此時是右右旋轉
                    if (key.CompareTo(tree.right.key) > 0)
                    {
                        tree = RotateRR(tree);
                    }
                    else
                    {
                        //屬於右左旋轉
                        tree = RotateRL(tree);
                    }
                }
            }
            /*相等的情況*/
            if (key.CompareTo(tree.key) == 0)
            {
                //判斷裏面的HashSet是否有多值
                if (tree.attach.Count > 1)
                {
                    //實現惰性刪除
                    tree.attach.Remove(value);
                }
                else
                {
                    //有兩個孩子的情況
                    if (tree.left != null && tree.right != null)
                    {
                        //根據平衡二叉樹的中順遍歷,需要找到”有子樹“的最小節點
                        tree.key = FindMin(tree.right).key;

                        //刪除右子樹的指定元素
                        tree.right = Remove(tree.key, value, tree.right);
                    }
                    else
                    {
                        //自減高度
                        tree = tree.left == null ? tree.right : tree.left;

                        //如果刪除的是葉子節點直接返回
                        if (tree == null)
                            return null;
                    }
                }
            }

            //統計高度
            tree.height = Math.Max(Height(tree.left), Height(tree.right)) + 1;

            return tree;
        }
        #endregion
    }
}



wow,相差98倍,這個可不是一個級別啊...AVL神器。

轉自 http://www.cnblogs.com/huangxincheng/archive/2012/07/22/2603956.html

在此基礎上有所更改。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章