6天通喫樹結構

      

        一直很想寫一個關於樹結構的專題,再一個就是很多初級點的碼農會認爲樹結構無用論,其實歸根到底還是不清楚樹的實際用途。

 

一:場景:

1:現狀

    前幾天我的一個大學同學負責的網站出現了嚴重的性能瓶頸,由於業務是寫入和讀取都是密集型,如果做緩存,時間間隔也只能在30s左

右,否則就會引起客戶糾紛,所以同學也就沒有做緩存,通過測試發現慢就慢在數據讀取上面,總共需要10s,天啊...原來首頁的加載關聯

到了4張表,而且表數據中最多的在10w條以上,可以想象4張巨大表的關聯,然後就是排序+範圍查找等等相關的條件,讓同學抓狂。

 

2:我個人的提供解決方案

 ① 讀取問題

    既然不能做緩存,那沒辦法,我們需要自己維護一套”內存數據庫“,數據如何組織就靠我們的算法功底了,比如哈希適合等於性的查找,

樹結構適合”範圍查找“,lucene適合字符串的查找,我們在添加和更新的時候同時維護自己的內存數據庫,最終杜絕表關聯,老同學,還

是先應急,把常用的表灌倒內存,如果真想項目好的話,改架構吧...

② 添加問題

   或許你的Add操作還沒有達到瓶頸這一步,如果真的達到了那就看情況來進行”表切分“,”數據庫切分“吧,讓用戶的Add或者Update

操作分流,雖然做起來很複雜,但是沒辦法,總比用戶糾紛強吧,可對...

 

二:二叉查找樹

    正式切入主題,從上面的說明我們知道了二叉樹非常適合於範圍查找,關於樹的基本定義,這裏我就默認大家都知道,我就直接從

查找樹說起了。

1:定義

   查找樹的定義非常簡單,一句話就是左孩子比父節點小,右孩子比父節點大,還有一個特性就是”中序遍歷“可以讓結點有序。

2:樹節點

爲了具有通用性,我們定義成泛型模板,在每個結點中增加一個”數據附加域”。

複製代碼
 1     /// <summary>
 2     /// 二叉樹節點
 3     /// </summary>
 4     /// <typeparam name="K"></typeparam>
 5     /// <typeparam name="V"></typeparam>
 6     public class BinaryNode<K, V>
 7     {
 8         /// <summary>
 9         /// 節點元素
10         /// </summary>
11         public K key;
12 
13         /// <summary>
14         /// 節點中的附加值
15         /// </summary>
16         public HashSet<V> attach = new HashSet<V>();
17 
18         /// <summary>
19         /// 左節點
20         /// </summary>
21         public BinaryNode<K, V> left;
22 
23         /// <summary>
24         /// 右節點
25         /// </summary>
26         public BinaryNode<K, V> right;
27 
28         public BinaryNode() { }
29 
30         public BinaryNode(K key, V value, BinaryNode<K, V> left, BinaryNode<K, V> right)
31         {
32             //KV鍵值對
33             this.key = key;
34             this.attach.Add(value);
35 
36             this.left = left;
37             this.right = right;
38         }
39     }
複製代碼

 

3:添加

   根據查找樹的性質我們可以很簡單的寫出Add的代碼,一個一個的比唄,最終形成的效果圖如下

這裏存在一個“重複節點”的問題,比如說我在最後的樹中再插入一個元素爲15的結點,那麼此時該怎麼辦,一般情況下,我們最好

不要在樹中再追加一個重複結點,而是在“重複節點"的附加域中進行”+1“操作。

複製代碼
 1        #region 添加操作
 2         /// <summary>
 3         /// 添加操作
 4         /// </summary>
 5         /// <param name="key"></param>
 6         /// <param name="value"></param>
 7         public void Add(K key, V value)
 8         {
 9             node = Add(key, value, node);
10         }
11         #endregion
12 
13         #region 添加操作
14         /// <summary>
15         /// 添加操作
16         /// </summary>
17         /// <param name="key"></param>
18         /// <param name="value"></param>
19         /// <param name="tree"></param>
20         /// <returns></returns>
21         public BinaryNode<K, V> Add(K key, V value, BinaryNode<K, V> tree)
22         {
23             if (tree == null)
24                 tree = new BinaryNode<K, V>(key, value, null, null);
25 
26             //左子樹
27             if (key.CompareTo(tree.key) < 0)
28                 tree.left = Add(key, value, tree.left);
29 
30             //右子樹
31             if (key.CompareTo(tree.key) > 0)
32                 tree.right = Add(key, value, tree.right);
33 
34             //將value追加到附加值中(也可對應重複元素)
35             if (key.CompareTo(tree.key) == 0)
36                 tree.attach.Add(value);
37 
38             return tree;
39         }
40         #endregion
複製代碼

 

4:範圍查找

    這個纔是我們使用二叉樹的最終目的,既然是範圍查找,我們就知道了一個”min“和”max“,其實實現起來也很簡單,

第一步:我們要在樹中找到min元素,當然min元素可能不存在,但是我們可以找到min的上界,耗費時間爲O(logn)。

第二步:從min開始我們中序遍歷尋找max的下界。耗費時間爲m。m也就是匹配到的個數。

 

最後時間複雜度爲M+logN,要知道普通的查找需要O(N)的時間,比如在21億的數據規模下,匹配的元素可能有30個,那麼最後

的結果也就是秒殺和幾個小時甚至幾天的巨大差異,後面我會做實驗說明。

複製代碼
 1         #region 樹的指定範圍查找
 2         /// <summary>
 3         /// 樹的指定範圍查找
 4         /// </summary>
 5         /// <param name="min"></param>
 6         /// <param name="max"></param>
 7         /// <returns></returns>
 8         public HashSet<V> SearchRange(K min, K max)
 9         {
10             HashSet<V> hashSet = new HashSet<V>();
11 
12             hashSet = SearchRange(min, max, hashSet, node);
13 
14             return hashSet;
15         }
16         #endregion
17 
18         #region 樹的指定範圍查找
19         /// <summary>
20         /// 樹的指定範圍查找
21         /// </summary>
22         /// <param name="range1"></param>
23         /// <param name="range2"></param>
24         /// <param name="tree"></param>
25         /// <returns></returns>
26         public HashSet<V> SearchRange(K min, K max, HashSet<V> hashSet, BinaryNode<K, V> tree)
27         {
28             if (tree == null)
29                 return hashSet;
30 
31             //遍歷左子樹(尋找下界)
32             if (min.CompareTo(tree.key) < 0)
33                 SearchRange(min, max, hashSet, tree.left);
34 
35             //當前節點是否在選定範圍內
36             if (min.CompareTo(tree.key) <= 0 && max.CompareTo(tree.key) >= 0)
37             {
38                 //等於這種情況
39                 foreach (var item in tree.attach)
40                     hashSet.Add(item);
41             }
42 
43             //遍歷右子樹(兩種情況:①:找min的下限 ②:必須在Max範圍之內)
44             if (min.CompareTo(tree.key) > 0 || max.CompareTo(tree.key) > 0)
45                 SearchRange(min, max, hashSet, tree.right);
46 
47             return hashSet;
48         }
49         #endregion
複製代碼

 

5:刪除

   對於樹來說,刪除是最複雜的,主要考慮兩種情況。

<1>單孩子的情況

     這個比較簡單,如果刪除的節點有左孩子那就把左孩子頂上去,如果有右孩子就把右孩子頂上去,然後打完收工。

<2>左右都有孩子的情況。

     首先可以這麼想象,如果我們要刪除一個數組的元素,那麼我們在刪除後會將其後面的一個元素頂到被刪除的位置,如圖

       

那麼二叉樹操作同樣也是一樣,我們根據”中序遍歷“找到要刪除結點的後一個結點,然後頂上去就行了,原理跟"數組”一樣一樣的。

同樣這裏也有一個注意的地方,在Add操作時,我們將重複元素的值追加到了“附加域”,那麼在刪除的時候,就可以先判斷是

不是要“-1”操作而不是真正的刪除節點,其實這裏也就是“懶刪除”,很有意思。

複製代碼
 1         #region 刪除當前樹中的節點
 2         /// <summary>
 3         /// 刪除當前樹中的節點
 4         /// </summary>
 5         /// <param name="key"></param>
 6         /// <returns></returns>
 7         public void Remove(K key, V value)
 8         {
 9             node = Remove(key, value, node);
10         }
11         #endregion
12 
13         #region 刪除當前樹中的節點
14         /// <summary>
15         /// 刪除當前樹中的節點
16         /// </summary>
17         /// <param name="key"></param>
18         /// <param name="tree"></param>
19         /// <returns></returns>
20         public BinaryNode<K, V> Remove(K key, V value, BinaryNode<K, V> tree)
21         {
22             if (tree == null)
23                 return null;
24 
25             //左子樹
26             if (key.CompareTo(tree.key) < 0)
27                 tree.left = Remove(key, value, tree.left);
28 
29             //右子樹
30             if (key.CompareTo(tree.key) > 0)
31                 tree.right = Remove(key, value, tree.right);
32 
33             /*相等的情況*/
34             if (key.CompareTo(tree.key) == 0)
35             {
36                 //判斷裏面的HashSet是否有多值
37                 if (tree.attach.Count > 1)
38                 {
39                     //實現惰性刪除
40                     tree.attach.Remove(value);
41                 }
42                 else
43                 {
44                     //有兩個孩子的情況
45                     if (tree.left != null && tree.right != null)
46                     {
47                         //根據二叉樹的中順遍歷,需要找到”有子樹“的最小節點
48                         tree.key = FindMin(tree.right).key;
49 
50                         //刪除右子樹的指定元素
51                         tree.right = Remove(key, value, tree.right);
52                     }
53                     else
54                     {
55                         //單個孩子的情況
56                         tree = tree.left == null ? tree.right : tree.left;
57                     }
58                 }
59             }
60 
61             return tree;
62         }
63         #endregion


6天通喫樹結構—— 第二天 平衡二叉樹

       

      上一篇我們聊過,二叉查找樹不是嚴格的O(logN),導致了在真實場景中沒有用武之地,誰也不願意有O(N)的情況發生,

作爲一名碼農,肯定會希望能把“範圍查找”做到地球人都不能優化的地步。

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

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

     

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

 

一:平衡二叉樹(AVL)

1:定義

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

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

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

複製代碼
 1 #region 平衡二叉樹節點
 2     /// <summary>
 3     /// 平衡二叉樹節點
 4     /// </summary>
 5     /// <typeparam name="K"></typeparam>
 6     /// <typeparam name="V"></typeparam>
 7     public class AVLNode<K, V>
 8     {
 9         /// <summary>
10         /// 節點元素
11         /// </summary>
12         public K key;
13 
14         /// <summary>
15         /// 增加一個高度信息
16         /// </summary>
17         public int height;
18 
19         /// <summary>
20         /// 節點中的附加值
21         /// </summary>
22         public HashSet<V> attach = new HashSet<V>();
23 
24         /// <summary>
25         /// 左節點
26         /// </summary>
27         public AVLNode<K, V> left;
28 
29         /// <summary>
30         /// 右節點
31         /// </summary>
32         public AVLNode<K, V> right;
33 
34         public AVLNode() { }
35 
36         public AVLNode(K key, V value, AVLNode<K, V> left, AVLNode<K, V> right)
37         {
38             //KV鍵值對
39             this.key = key;
40             this.attach.Add(value);
41 
42             this.left = left;
43             this.right = right;
44         }
45     }
46     #endregion
複製代碼

 

2:旋轉

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

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

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

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

複製代碼
 1         #region 第一種:左左旋轉(單旋轉)
 2         /// <summary>
 3         /// 第一種:左左旋轉(單旋轉)
 4         /// </summary>
 5         /// <param name="node"></param>
 6         /// <returns></returns>
 7         public AVLNode<K, V> RotateLL(AVLNode<K, V> node)
 8         {
 9             //top:需要作爲頂級節點的元素
10             var top = node.left;
11 
12             //先截斷當前節點的左孩子
13             node.left = top.right;
14 
15             //將當前節點作爲temp的右孩子
16             top.right = node;
17 
18             //計算當前兩個節點的高度
19             node.height = Math.Max(Height(node.left), Height(node.right)) + 1;
20             top.height = Math.Max(Height(top.left), Height(top.right)) + 1;
21 
22             return top;
23         }
24         #endregion
複製代碼

 

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

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

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

複製代碼
 1         #region 第二種:右右旋轉(單旋轉)
 2         /// <summary>
 3         /// 第二種:右右旋轉(單旋轉)
 4         /// </summary>
 5         /// <param name="node"></param>
 6         /// <returns></returns>
 7         public AVLNode<K, V> RotateRR(AVLNode<K, V> node)
 8         {
 9             //top:需要作爲頂級節點的元素
10             var top = node.right;
11 
12             //先截斷當前節點的右孩子
13             node.right = top.left;
14 
15             //將當前節點作爲temp的右孩子
16             top.left = node;
17 
18             //計算當前兩個節點的高度
19             node.height = Math.Max(Height(node.left), Height(node.right)) + 1;
20             top.height = Math.Max(Height(top.left), Height(top.right)) + 1;
21 
22             return top;
23         }
24         #endregion
複製代碼

 

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

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

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

複製代碼
 1         #region 第三種:左右旋轉(雙旋轉)
 2         /// <summary>
 3         /// 第三種:左右旋轉(雙旋轉)
 4         /// </summary>
 5         /// <param name="node"></param>
 6         /// <returns></returns>
 7         public AVLNode<K, V> RotateLR(AVLNode<K, V> node)
 8         {
 9             //先進行RR旋轉
10             node.left = RotateRR(node.left);
11 
12             //再進行LL旋轉
13             return RotateLL(node);
14         }
15         #endregion
複製代碼

 

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

這種情況和“情景3”也是一種鏡像關係,很簡單,我們找到了”節點15“是失衡點,然後我們將”節點15“的右子樹進行”左左情況旋轉“,

然後進行”右右情況旋轉“,最終得到了我們滿意的平衡。

複製代碼
 1         #region 第四種:右左旋轉(雙旋轉)
 2         /// <summary>
 3         /// 第四種:右左旋轉(雙旋轉)
 4         /// </summary>
 5         /// <param name="node"></param>
 6         /// <returns></returns>
 7         public AVLNode<K, V> RotateRL(AVLNode<K, V> node)
 8         {
 9             //執行左左旋轉
10             node.right = RotateLL(node.right);
11 
12             //再執行右右旋轉
13             return RotateRR(node);
14 
15         }
16         #endregion
複製代碼

 

3:添加

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

複製代碼
 1  #region 添加操作
 2         /// <summary>
 3         /// 添加操作
 4         /// </summary>
 5         /// <param name="key"></param>
 6         /// <param name="value"></param>
 7         /// <param name="tree"></param>
 8         /// <returns></returns>
 9         public AVLNode<K, V> Add(K key, V value, AVLNode<K, V> tree)
10         {
11             if (tree == null)
12                 tree = new AVLNode<K, V>(key, value, null, null);
13 
14             //左子樹
15             if (key.CompareTo(tree.key) < 0)
16             {
17                 tree.left = Add(key, value, tree.left);
18 
19                 //如果說相差等於2就說明這棵樹需要旋轉了
20                 if (Height(tree.left) - Height(tree.right) == 2)
21                 {
22                     //說明此時是左左旋轉
23                     if (key.CompareTo(tree.left.key) < 0)
24                     {
25                         tree = RotateLL(tree);
26                     }
27                     else
28                     {
29                         //屬於左右旋轉
30                         tree = RotateLR(tree);
31                     }
32                 }
33             }
34 
35             //右子樹
36             if (key.CompareTo(tree.key) > 0)
37             {
38                 tree.right = Add(key, value, tree.right);
39 
40                 if ((Height(tree.right) - Height(tree.left) == 2))
41                 {
42                     //此時是右右旋轉
43                     if (key.CompareTo(tree.right.key) > 0)
44                     {
45                         tree = RotateRR(tree);
46                     }
47                     else
48                     {
49                         //屬於右左旋轉
50                         tree = RotateRL(tree);
51                     }
52                 }
53             }
54 
55             //將value追加到附加值中(也可對應重複元素)
56             if (key.CompareTo(tree.key) == 0)
57                 tree.attach.Add(value);
58 
59             //計算高度
60             tree.height = Math.Max(Height(tree.left), Height(tree.right)) + 1;
61 
62             return tree;
63         }
64         #endregion
複製代碼

4:刪除

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

複製代碼
 1 #region 刪除當前樹中的節點
 2         /// <summary>
 3         /// 刪除當前樹中的節點
 4         /// </summary>
 5         /// <param name="key"></param>
 6         /// <param name="tree"></param>
 7         /// <returns></returns>
 8         public AVLNode<K, V> Remove(K key, V value, AVLNode<K, V> tree)
 9         {
10             if (tree == null)
11                 return null;
12 
13             //左子樹
14             if (key.CompareTo(tree.key) < 0)
15             {
16                 tree.left = Remove(key, value, tree.left);
17 
18                 //如果說相差等於2就說明這棵樹需要旋轉了
19                 if (Height(tree.left) - Height(tree.right) == 2)
20                 {
21                     //說明此時是左左旋轉
22                     if (key.CompareTo(tree.left.key) < 0)
23                     {
24                         tree = RotateLL(tree);
25                     }
26                     else
27                     {
28                         //屬於左右旋轉
29                         tree = RotateLR(tree);
30                     }
31                 }
32             }
33             //右子樹
34             if (key.CompareTo(tree.key) > 0)
35             {
36                 tree.right = Remove(key, value, tree.right);
37 
38                 if ((Height(tree.right) - Height(tree.left) == 2))
39                 {
40                     //此時是右右旋轉
41                     if (key.CompareTo(tree.right.key) > 0)
42                     {
43                         tree = RotateRR(tree);
44                     }
45                     else
46                     {
47                         //屬於右左旋轉
48                         tree = RotateRL(tree);
49                     }
50                 }
51             }
52             /*相等的情況*/
53             if (key.CompareTo(tree.key) == 0)
54             {
55                 //判斷裏面的HashSet是否有多值
56                 if (tree.attach.Count > 1)
57                 {
58                     //實現惰性刪除
59                     tree.attach.Remove(value);
60                 }
61                 else
62                 {
63                     //有兩個孩子的情況
64                     if (tree.left != null && tree.right != null)
65                     {
66                         //根據平衡二叉樹的中順遍歷,需要找到”有子樹“的最小節點
67                         tree.key = FindMin(tree.right).key;
68 
69                         //刪除右子樹的指定元素
70                         tree.right = Remove(tree.key, value, tree.right);
71                     }
72                     else
73                     {
74                         //自減高度
75                         tree = tree.left == null ? tree.right : tree.left;
76 
77                         //如果刪除的是葉子節點直接返回
78                         if (tree == null)
79                             return null;
80                     }
81                 }
82             }
83 
84             //統計高度
85             tree.height = Math.Max(Height(tree.left), Height(tree.right)) + 1;
86 
87             return tree;
88         }
89         #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神器。

6天通喫樹結構—— 第三天 Treap樹

       

       我們知道,二叉查找樹相對來說比較容易形成最壞的鏈表情況,所以前輩們想盡了各種優化策略,包括AVL,紅黑,以及今天

要講的Treap樹。

       Treap樹算是一種簡單的優化策略,這名字大家也能猜到,樹和堆的合體,其實原理比較簡單,在樹中維護一個"優先級“,”優先級“

採用隨機數的方法,但是”優先級“必須滿足根堆的性質,當然是“大根堆”或者“小根堆”都無所謂,比如下面的一棵樹:

從樹中我們可以看到:

①:節點中的key滿足“二叉查找樹”。

②:節點中的“優先級”滿足小根堆。

 

一:基本操作

1:定義

複製代碼
 1     #region Treap樹節點
 2     /// <summary>
 3     /// Treap樹
 4     /// </summary>
 5     /// <typeparam name="K"></typeparam>
 6     /// <typeparam name="V"></typeparam>
 7     public class TreapNode<K, V>
 8     {
 9         /// <summary>
10         /// 節點元素
11         /// </summary>
12         public K key;
13 
14         /// <summary>
15         /// 優先級(採用隨機數)
16         /// </summary>
17         public int priority;
18 
19         /// <summary>
20         /// 節點中的附加值
21         /// </summary>
22         public HashSet<V> attach = new HashSet<V>();
23 
24         /// <summary>
25         /// 左節點
26         /// </summary>
27         public TreapNode<K, V> left;
28 
29         /// <summary>
30         /// 右節點
31         /// </summary>
32         public TreapNode<K, V> right;
33 
34         public TreapNode() { }
35 
36         public TreapNode(K key, V value, TreapNode<K, V> left, TreapNode<K, V> right)
37         {
38             //KV鍵值對
39             this.key = key;
40             this.priority = new Random(DateTime.Now.Millisecond).Next(0,int.MaxValue);
41             this.attach.Add(value);
42 
43             this.left = left;
44             this.right = right;
45         }
46     }
47     #endregion
複製代碼

節點裏面定義了一個priority作爲“堆定義”的旋轉因子,因子採用“隨機數“。

 

2:添加

    首先我們知道各個節點的“優先級”是採用隨機數的方法,那麼就存在一個問題,當我們插入一個節點後,優先級不滿足“堆定義"的

時候我們該怎麼辦,前輩說此時需要旋轉,直到滿足堆定義爲止。

旋轉有兩種方式,如果大家玩轉了AVL,那麼對Treap中的旋轉的理解輕而易舉。

①: 左左情況旋轉

從圖中可以看出,當我們插入“節點12”的時候,此時“堆性質”遭到破壞,必須進行旋轉,我們發現優先級是6<9,所以就要進行

左左情況旋轉,最終也就形成了我們需要的結果。

 

②: 右右情況旋轉

既然理解了”左左情況旋轉“,右右情況也是同樣的道理,優先級中發現“6<9",進行”右右旋轉“最終達到我們要的效果。

複製代碼
 1         #region 添加操作
 2         /// <summary>
 3         /// 添加操作
 4         /// </summary>
 5         /// <param name="key"></param>
 6         /// <param name="value"></param>
 7         public void Add(K key, V value)
 8         {
 9             node = Add(key, value, node);
10         }
11         #endregion
12 
13         #region 添加操作
14         /// <summary>
15         /// 添加操作
16         /// </summary>
17         /// <param name="key"></param>
18         /// <param name="value"></param>
19         /// <param name="tree"></param>
20         /// <returns></returns>
21         public TreapNode<K, V> Add(K key, V value, TreapNode<K, V> tree)
22         {
23             if (tree == null)
24                 tree = new TreapNode<K, V>(key, value, null, null);
25 
26             //左子樹
27             if (key.CompareTo(tree.key) < 0)
28             {
29                 tree.left = Add(key, value, tree.left);
30 
31                 //根據小根堆性質,需要”左左情況旋轉”
32                 if (tree.left.priority < tree.priority)
33                 {
34                     tree = RotateLL(tree);
35                 }
36             }
37 
38             //右子樹
39             if (key.CompareTo(tree.key) > 0)
40             {
41                 tree.right = Add(key, value, tree.right);
42 
43                 //根據小根堆性質,需要”右右情況旋轉”
44                 if (tree.right.priority < tree.priority)
45                 {
46                     tree = RotateRR(tree);
47                 }
48             }
49 
50             //將value追加到附加值中(也可對應重複元素)
51             if (key.CompareTo(tree.key) == 0)
52                 tree.attach.Add(value);
53 
54             return tree;
55         }
56         #endregion
複製代碼

 

3:刪除

  跟普通的二叉查找樹一樣,刪除結點存在三種情況。

①:葉子結點

      跟普通查找樹一樣,直接釋放本節點即可。

②:單孩子結點

     跟普通查找樹一樣操作。

③:滿孩子結點

    其實在treap中刪除滿孩子結點有兩種方式。

第一種:跟普通的二叉查找樹一樣,找到“右子樹”的最左結點(15),拷貝元素的值,但不拷貝元素的優先級,然後在右子樹中

           刪除“結點15”即可,最終效果如下圖。

第二種:將”結點下旋“,直到該節點不是”滿孩子的情況“,該賦null的賦null,該將孩子結點頂上的就頂上,如下圖:

當然從理論上來說,第二種刪除方法更合理,這裏我寫的就是第二種情況的代碼。

複製代碼
 1         #region 刪除當前樹中的節點
 2         /// <summary>
 3         /// 刪除當前樹中的節點
 4         /// </summary>
 5         /// <param name="key"></param>
 6         /// <returns></returns>
 7         public void Remove(K key, V value)
 8         {
 9             node = Remove(key, value, node);
10         }
11         #endregion
12 
13         #region 刪除當前樹中的節點
14         /// <summary>
15         /// 刪除當前樹中的節點
16         /// </summary>
17         /// <param name="key"></param>
18         /// <param name="tree"></param>
19         /// <returns></returns>
20         public TreapNode<K, V> Remove(K key, V value, TreapNode<K, V> tree)
21         {
22             if (tree == null)
23                 return null;
24 
25             //左子樹
26             if (key.CompareTo(tree.key) < 0)
27             {
28                 tree.left = Remove(key, value, tree.left);
29             }
30             //右子樹
31             if (key.CompareTo(tree.key) > 0)
32             {
33                 tree.right = Remove(key, value, tree.right);
34             }
35             /*相等的情況*/
36             if (key.CompareTo(tree.key) == 0)
37             {
38                 //判斷裏面的HashSet是否有多值
39                 if (tree.attach.Count > 1)
40                 {
41                     //實現惰性刪除
42                     tree.attach.Remove(value);
43                 }
44                 else
45                 {
46                     //有兩個孩子的情況
47                     if (tree.left != null && tree.right != null)
48                     {
49                         //如果左孩子的優先級低就需要“左旋”
50                         if (tree.left.priority < tree.right.priority)
51                         {
52                             tree = RotateLL(tree);
53                         }
54                         else
55                         {
56                             //否則“右旋”
57                             tree = RotateRR(tree);
58                         }
59 
60                         //繼續旋轉
61                         tree = Remove(key, value, tree);
62                     }
63                     else
64                     {
65                         //如果旋轉後已經變成了葉子節點則直接刪除
66                         if (tree == null)
67                             return null;
68 
69                         //最後就是單支樹
70                         tree = tree.left == null ? tree.right : tree.left;
71                     }
72                 }
73             }
74 
75             return tree;
76         }
77         #endregion
複製代碼

 

4:總結

treap樹在CURD中是期望的logN,由於我們加了”優先級“,所以會出現”鏈表“的情況幾乎不存在,但是他的Add和Remove相比嚴格的

平衡二叉樹有更少的旋轉操作,可以說性能是在”普通二叉樹“和”平衡二叉樹“之間。

最後是總運行代碼,不過這裏我就不做測試了。

複製代碼
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 
  6 namespace DataStruct
  7 {
  8     #region Treap樹節點
  9     /// <summary>
 10     /// Treap樹
 11     /// </summary>
 12     /// <typeparam name="K"></typeparam>
 13     /// <typeparam name="V"></typeparam>
 14     public class TreapNode<K, V>
 15     {
 16         /// <summary>
 17         /// 節點元素
 18         /// </summary>
 19         public K key;
 20 
 21         /// <summary>
 22         /// 優先級(採用隨機數)
 23         /// </summary>
 24         public int priority;
 25 
 26         /// <summary>
 27         /// 節點中的附加值
 28         /// </summary>
 29         public HashSet<V> attach = new HashSet<V>();
 30 
 31         /// <summary>
 32         /// 左節點
 33         /// </summary>
 34         public TreapNode<K, V> left;
 35 
 36         /// <summary>
 37         /// 右節點
 38         /// </summary>
 39         public TreapNode<K, V> right;
 40 
 41         public TreapNode() { }
 42 
 43         public TreapNode(K key, V value, TreapNode<K, V> left, TreapNode<K, V> right)
 44         {
 45             //KV鍵值對
 46             this.key = key;
 47             this.priority = new Random(DateTime.Now.Millisecond).Next(0,int.MaxValue);
 48             this.attach.Add(value);
 49 
 50             this.left = left;
 51             this.right = right;
 52         }
 53     }
 54     #endregion
 55 
 56     public class TreapTree<K, V> where K : IComparable
 57     {
 58         public TreapNode<K, V> node = null;
 59 
 60         #region 添加操作
 61         /// <summary>
 62         /// 添加操作
 63         /// </summary>
 64         /// <param name="key"></param>
 65         /// <param name="value"></param>
 66         public void Add(K key, V value)
 67         {
 68             node = Add(key, value, node);
 69         }
 70         #endregion
 71 
 72         #region 添加操作
 73         /// <summary>
 74         /// 添加操作
 75         /// </summary>
 76         /// <param name="key"></param>
 77         /// <param name="value"></param>
 78         /// <param name="tree"></param>
 79         /// <returns></returns>
 80         public TreapNode<K, V> Add(K key, V value, TreapNode<K, V> tree)
 81         {
 82             if (tree == null)
 83                 tree = new TreapNode<K, V>(key, value, null, null);
 84 
 85             //左子樹
 86             if (key.CompareTo(tree.key) < 0)
 87             {
 88                 tree.left = Add(key, value, tree.left);
 89 
 90                 //根據小根堆性質,需要”左左情況旋轉”
 91                 if (tree.left.priority < tree.priority)
 92                 {
 93                     tree = RotateLL(tree);
 94                 }
 95             }
 96 
 97             //右子樹
 98             if (key.CompareTo(tree.key) > 0)
 99             {
100                 tree.right = Add(key, value, tree.right);
101 
102                 //根據小根堆性質,需要”右右情況旋轉”
103                 if (tree.right.priority < tree.priority)
104                 {
105                     tree = RotateRR(tree);
106                 }
107             }
108 
109             //將value追加到附加值中(也可對應重複元素)
110             if (key.CompareTo(tree.key) == 0)
111                 tree.attach.Add(value);
112 
113             return tree;
114         }
115         #endregion
116 
117         #region 第一種:左左旋轉(單旋轉)
118         /// <summary>
119         /// 第一種:左左旋轉(單旋轉)
120         /// </summary>
121         /// <param name="node"></param>
122         /// <returns></returns>
123         public TreapNode<K, V> RotateLL(TreapNode<K, V> node)
124         {
125             //top:需要作爲頂級節點的元素
126             var top = node.left;
127 
128             //先截斷當前節點的左孩子
129             node.left = top.right;
130 
131             //將當前節點作爲temp的右孩子
132             top.right = node;
133 
134             return top;
135         }
136         #endregion
137 
138         #region 第二種:右右旋轉(單旋轉)
139         /// <summary>
140         /// 第二種:右右旋轉(單旋轉)
141         /// </summary>
142         /// <param name="node"></param>
143         /// <returns></returns>
144         public TreapNode<K, V> RotateRR(TreapNode<K, V> node)
145         {
146             //top:需要作爲頂級節點的元素
147             var top = node.right;
148 
149             //先截斷當前節點的右孩子
150             node.right = top.left;
151 
152             //將當前節點作爲temp的右孩子
153             top.left = node;
154 
155             return top;
156         }
157         #endregion
158 
159         #region 樹的指定範圍查找
160         /// <summary>
161         /// 樹的指定範圍查找
162         /// </summary>
163         /// <param name="min"></param>
164         /// <param name="max"></param>
165         /// <returns></returns>
166         public HashSet<V> SearchRange(K min, K max)
167         {
168             HashSet<V> hashSet = new HashSet<V>();
169 
170             hashSet = SearchRange(min, max, hashSet, node);
171 
172             return hashSet;
173         }
174         #endregion
175 
176         #region 樹的指定範圍查找
177         /// <summary>
178         /// 樹的指定範圍查找
179         /// </summary>
180         /// <param name="range1"></param>
181         /// <param name="range2"></param>
182         /// <param name="tree"></param>
183         /// <returns></returns>
184         public HashSet<V> SearchRange(K min, K max, HashSet<V> hashSet, TreapNode<K, V> tree)
185         {
186             if (tree == null)
187                 return hashSet;
188 
189             //遍歷左子樹(尋找下界)
190             if (min.CompareTo(tree.key) < 0)
191                 SearchRange(min, max, hashSet, tree.left);
192 
193             //當前節點是否在選定範圍內
194             if (min.CompareTo(tree.key) <= 0 && max.CompareTo(tree.key) >= 0)
195             {
196                 //等於這種情況
197                 foreach (var item in tree.attach)
198                     hashSet.Add(item);
199             }
200 
201             //遍歷右子樹(兩種情況:①:找min的下限 ②:必須在Max範圍之內)
202             if (min.CompareTo(tree.key) > 0 || max.CompareTo(tree.key) > 0)
203                 SearchRange(min, max, hashSet, tree.right);
204 
205             return hashSet;
206         }
207         #endregion
208 
209         #region 找到當前樹的最小節點
210         /// <summary>
211         /// 找到當前樹的最小節點
212         /// </summary>
213         /// <returns></returns>
214         public TreapNode<K, V> FindMin()
215         {
216             return FindMin(node);
217         }
218         #endregion
219 
220         #region 找到當前樹的最小節點
221         /// <summary>
222         /// 找到當前樹的最小節點
223         /// </summary>
224         /// <param name="tree"></param>
225         /// <returns></returns>
226         public TreapNode<K, V> FindMin(TreapNode<K, V> tree)
227         {
228             if (tree == null)
229                 return null;
230 
231             if (tree.left == null)
232                 return tree;
233 
234             return FindMin(tree.left);
235         }
236         #endregion
237 
238         #region 找到當前樹的最大節點
239         /// <summary>
240         /// 找到當前樹的最大節點
241         /// </summary>
242         /// <returns></returns>
243         public TreapNode<K, V> FindMax()
244         {
245             return FindMin(node);
246         }
247         #endregion
248 
249         #region 找到當前樹的最大節點
250         /// <summary>
251         /// 找到當前樹的最大節點
252         /// </summary>
253         /// <param name="tree"></param>
254         /// <returns></returns>
255         public TreapNode<K, V> FindMax(TreapNode<K, V> tree)
256         {
257             if (tree == null)
258                 return null;
259 
260             if (tree.right == null)
261                 return tree;
262 
263             return FindMax(tree.right);
264         }
265         #endregion
266 
267         #region 刪除當前樹中的節點
268         /// <summary>
269         /// 刪除當前樹中的節點
270         /// </summary>
271         /// <param name="key"></param>
272         /// <returns></returns>
273         public void Remove(K key, V value)
274         {
275             node = Remove(key, value, node);
276         }
277         #endregion
278 
279         #region 刪除當前樹中的節點
280         /// <summary>
281         /// 刪除當前樹中的節點
282         /// </summary>
283         /// <param name="key"></param>
284         /// <param name="tree"></param>
285         /// <returns></returns>
286         public TreapNode<K, V> Remove(K key, V value, TreapNode<K, V> tree)
287         {
288             if (tree == null)
289                 return null;
290 
291             //左子樹
292             if (key.CompareTo(tree.key) < 0)
293             {
294                 tree.left = Remove(key, value, tree.left);
295             }
296             //右子樹
297             if (key.CompareTo(tree.key) > 0)
298             {
299                 tree.right = Remove(key, value, tree.right);
300             }
301             /*相等的情況*/
302             if (key.CompareTo(tree.key) == 0)
303             {
304                 //判斷裏面的HashSet是否有多值
305                 if (tree.attach.Count > 1)
306                 {
307                     //實現惰性刪除
308                     tree.attach.Remove(value);
309                 }
310                 else
311                 {
312                     //有兩個孩子的情況
313                     if (tree.left != null && tree.right != null)
314                     {
315                         //如果左孩子的優先級低就需要“左旋”
316                         if (tree.left.priority < tree.right.priority)
317                         {
318                             tree = RotateLL(tree);
319                         }
320                         else
321                         {
322                             //否則“右旋”
323                             tree = RotateRR(tree);
324                         }
325 
326                         //繼續旋轉
327                         tree = Remove(key, value, tree);
328                     }
329                     else
330                     {
331                         //如果旋轉後已經變成了葉子節點則直接刪除
332                         if (tree == null)
333                             return null;
334 
335                         //最後就是單支樹
336                         tree = tree.left == null ? tree.right : tree.left;
337                     }
338                 }
339             }
340 
341             return tree;
342         }
343         #endregion
344     }
345 }



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