當有很多數據灌到我的樹中時,我肯定會希望最好是以“完全二叉樹”的形式展現,這樣我才能做到“查找”是嚴格的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
在此基礎上有所更改。