數據結構之二叉搜索樹、AVL自平衡樹

前言

最近在幫公司校招~~ 因此來整理一些數據結構方面的知識,這些知識呢,光看一遍理解仍是很淺的,看過跟動手作過一遍的同窗仍是很容易分辨的喲~html

一直以爲數據結構跟算法,就比如金庸小說裏的《九陽神功》,學會九陽神功後,有了內功基礎,再去學習其餘武功,速度就有質的提高node

內容大概包含這些,會分多篇文章來整理:算法

  1. 二叉搜索樹
  2. 平衡二叉樹(AVL)
  3. 二叉堆
  4. 堆排序
  5. 四叉樹
  6. 八叉樹
  7. 圖,深度優先DFS、廣度優先BFS
  8. 最短路徑

二叉樹

二叉樹,也就是每一個節點最多有兩個孩子的樹。多用於搜索,查找,還有能夠用來求最短編碼的哈弗曼樹,也稱爲最優二叉樹。數據結構

二叉排序/搜索樹

如圖,樹的每一個有孩子的節點都知足:左節點的值<根節點的值<右節點的值條件的樹,稱爲二叉排序樹,也叫二叉搜索樹。
二叉排序樹ide

若是對這個樹進行中序遍歷,就能獲得一個排序的數列,很是簡單,下面貼出插入操做跟遍歷的代碼
插入操做學習

public void Add(BinaryTree node)
        {
            if (node.Value < Value)
            {
                if (this.Left != null)
                {
                    this.Left.Add(node);
                }
                else
                {
                    this.Left = node;
                }
            }
            else
            {
                if (this.Right != null)
                {
                    this.Right.Add(node);
                }
                else
                {
                    this.Right = node;
                }
            }
        }

中序遍歷輸出排序列表this

public void InOrder(List<int> list)
        {
            if (Left != null)
            {
                Left.InOrder(list);
            }
            
            list.Add(this.Value);
            
            if (Right != null)
            {
                Right.InOrder(list);
            }
        }

可是二叉排序樹極端的狀況,效率會變成鏈表線性結構,這樣查找起來時間複雜度會變成O(n),就失去了樹形結構的意義,如圖:
編碼

這時就要引出咱們的另一種二叉樹樹結構了3d

平衡二叉樹

平衡二叉樹(AVL)簡單來講就是插入的時候,要保證子節點的平衡,別老往一邊一直插入下去,那樣又成了鏈表效率了code

首先來搞懂這個幾個定義
平衡因子:即左子樹的高度減去右子樹的高度
平衡二叉樹上全部節點的平衡因子都必須爲:-一、0和1。不然該二叉樹就不是平衡二叉樹
以下圖,圖左邊是一顆平衡二叉樹,圖右根節點平衡因子爲-2,則不是平衡二叉樹

如何保持樹的平衡
每當插入一個節點的時候,都檢查此次插入是否會破壞平衡性,如果,則找出最小不平衡子樹,在保持二叉排序樹的前提下,進行相應旋轉,使之成爲新的平衡子樹。
一般會有四種旋轉狀況:

單向右旋平衡處理

也有地方稱爲Left Left旋轉,是否是以爲很奇怪,一下左,一下右邊的,它估計是想把你轉暈,好套出你的花唄密碼。

那麼究竟是什麼意思呢,請看下圖
右旋轉
這棵樹有三個節點:6,4,2

咱們把節點2當成是最新插入進來的節點,因爲這個節點2的插入,致使節點6的平衡因子變成了2,不符合-一、0、1的規定,破壞了平衡性,因此咱們須要對節點6進行右旋轉,而節點2又是節點6的Left節點的Left節點,因此也稱爲LL旋轉。

右旋操做

也就是若是結點6的左孩子節點4有右孩子,則將節點4的右孩子變成節點6的左孩子,最後將節點6變成節點4的右孩子

單向左旋平衡處理
左旋平衡處理也叫RR旋轉,是LL的鏡像操做
左旋轉

雙向旋轉(先右後左)平衡處理 (Right Left)
爲何會有這種狀況出現呢,由於咱們的平衡樹,首先也是一顆二叉排序樹,必須知足左節點<根節點<右節點的插入規則。

因此以下圖,節點4插入致使樹失去平衡,單向旋轉已經不能知足要求了,須要先讓節點6右旋,而後再把節點2左旋
右左旋轉

雙向旋轉(先左後右)平衡處理 (Left Right)
同理,是RL的鏡像操做
左右旋轉

代碼實現

//右旋轉
        public BinaryTree RightRotate(BinaryTree root)
        {
            BinaryTree lchild = root.Left;
            root.Left = lchild.Right;
            lchild.Right = root;
            return lchild;
        }

        //左旋轉
        public BinaryTree LeftRotate(BinaryTree root)
        {
            BinaryTree rchild = root.Right;
            root.Right = rchild.Left;
            rchild.Left = root;
            return rchild;
        }

        //先左後右旋轉
        public BinaryTree LeftRightRotate(BinaryTree root)
        {
            root.Left = root.Left.LeftRotate(root);
            return RightRotate(root);
        }

        //先右後左旋轉
        public BinaryTree RightLeftRotate(BinaryTree root)
        {
            root.Right = root.Right.RightRotate(root);
            return LeftRotate(root);
        }
        
        //計算平衡因子,取絕對值
        public int Balance(BinaryTree root)
        {
            int val = 0;
            if (root.Left != null) val += Height(root.Left);
            if (root.Right != null) val -= Height(root.Right);
            return Math.Abs(val);
        }

        //計算樹的高度
        public int Height(BinaryTree root)
        {
            int leftHeight = 0;
            int rightHeight = 0;
            if (root != null && root.Left != null)
            {
                leftHeight += Height(root.Left);
            }
            if (root != null && root.Right != null)
            {
                rightHeight += Height(root.Right);
            }
            return rightHeight > leftHeight ? ++rightHeight : ++leftHeight;
        }

插入操做

public BinaryTree Inster(BinaryTree root, int key)
        {
            if (root == null)
            {
                root = new BinaryTree(key);
            }
            else if (key < root.Value)//插入到左邊
            {
                root.Left = Inster(root.Left, key);

                if (Balance(root) > 1)//插入左節點致使樹失衡了
                {
                    if (key < root.Left.Value)//LL處理,右旋
                    {
                        root = RightRotate(root);
                    }
                    else
                    {
                        root = LeftRightRotate(root);//LR處理,先左後右
                    }
                }
            }
            else
            {
                root.Right = Inster(root.Right, key);

                if (Balance(root) > 1)//插入右節點致使失衡
                {
                    if (key > root.Right.Value)//RR處理, 左旋
                    {
                        root = LeftRotate(root);
                    }
                    else
                    {
                        root = RightLeftRotate(root);//RL處理,先右後左
                    }
                }
            }
            return root;
        }

使用平衡二叉樹後,查詢起來時間複雜度就從O(n)變爲了O( log n)。

總結

平衡二叉樹的優勢在於由於樹結構維護的較好,因此搜索查詢速度很快,但在插入,刪除的時候,爲了保持樹的平衡會作一次或屢次旋轉。
適合用於插入刪除操做少,而搜索操做不少的狀況。

爲了減小插入,刪除在旋轉方面的消耗,另外一種自平衡樹結構出現了

它就是:紅黑樹
紅黑樹不追求"徹底平衡",即不像AVL那樣要求節點的 |平衡因子| <= 1,它只要求部分達到平衡,可是提出了爲節點增長顏色,紅黑是用非嚴格的平衡來換取增刪節點時候旋轉次數的下降,任何不平衡都會在三次旋轉以內解決,而AVL是嚴格平衡樹,所以在增長或者刪除節點的時候,根據不一樣狀況,旋轉的次數比紅黑樹要多。

學會了AVL在去看紅黑樹也就很簡單了~~

參考

http://www.javashuo.com/article/p-rzzewkvd-ks.html
https://baijiahao.baidu.com/s?id=1577200621749785094&wfr=spider&for=pc

相關文章
相關標籤/搜索