左倾红黑树
红黑树也分为左倾红黑树,右倾红黑树,AA树 。
下面都是按照左倾红黑树来进行讲解,其实红色节点是左连接还是右连接,还是左右孩子节点都是红色节点都属于红黑树,下面那些定义只是针对于左倾红黑树来说的。
一般的,红黑树,满足以下性质,即只有满足以下全部性质的树,我们才称之为红黑树:
1)每个结点要么是红的,要么是黑的。
2)根结点是黑的。
3)每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的。
4)如果一个结点是红的,那么它的俩个儿子都是黑的。
5)对于任一结点而言,其到叶结点树尾端NULL指针到根节点的每一条路径都包含相同数目的黑结点。
额外插一句平衡二叉树的定义: https://www.cnblogs.com/easyidea/p/13625616.html 例子
1、可以是空树;
2、它的左子树和右子树的高度之差绝对值不超过1;
3、它的左子树和右子树都满足条件2;
看下图:红节点20在2-3查找树中和右边的2-3查找树中是等价的表示,在红黑树中只不过是用红色节点来表示。
任何一个即将插入的新结点的初始颜色都为红色。这一点很容易理解,因为插入黑点会增加某条路径上黑结点的数目,从而导致整棵树黑高度的不平衡,原因是红黑树的定义之一就是空节点到根节点路径上经过的黑色节点的数量是一样的。但如果新结点父结点为红色时 ,将会违返红黑树性质:一条路径上不能出现相邻的两个红色结点。这时就需要通过一系列操作来使红黑树保持平衡。
左旋转
之所做左旋转是因为要符合左倾红黑树。
颜色反转和右旋转
如下图:新节点15(颜色肯定是红色),按照二叉树的规则,15为10节点的右孩子。
但是,当前讨论的是左倾红黑树,不符合左倾红黑树中 红连接都为左连接上这个定义,所以需要做颜色翻转。首先将5,15都变为黑色,10变为红色,因为根节点不能是红色,所以10变为黑色。
右旋转
添加新节点3,按照二叉树规则,最终成为5节点的左孩子节点。
图1 图2
图2明显不符合左倾红黑树中一条路径上不能出现相邻的两个红色结点的定义,所以需要右旋转。
红黑树的生长轨迹
下面可以按照左倾红黑树的规则来理解,也就是不允许存在右连接的红节点。
插入2的时候,按照二叉树规则,2为1节点的右孩子节点,此时2节点为红色,所以需要做左旋转操作,变成插入2下面的结构;插入红色节点3之后,3节点是2节点的右孩子节点,此时存在左右子节点都是红节点的情况,所以需要颜色翻转,2变成红色,1与3都变成黑色,但是根节点不能是红色,所以插入3之后这3个节点全是黑色了;插入4节点,4节点成为3节点的右孩子节点,因为4节点是红色且是右连接,所以进行左旋转;插入5节点之后,4节点的左右子节点都是红色,所以需要颜色翻转,3,5变成黑色,4节点变成红色,此时对于节点2来说,有节点4变成红色,所以需要做左旋转处理,4节点此时成为根节点,所以也要变成黑色;下面都是这个规则。
/// <summary> /// 红黑树 基于2-3查找树(平衡二叉树) /// </summary> /// <typeparam name="E"></typeparam> class RBST<E> where E : IComparable<E> { /// <summary> /// 红用true表示 /// 黑用false表示 /// </summary> private const bool Red = true; private const bool Black = false; /// <summary> /// 节点类 /// </summary> private class Node { public E e; public Node left; public Node right; /// <summary> /// /// </summary> public bool color; public Node(E e) { this.e = e; left = null; right = null; color = Red; } } private Node root; private int N; public RBST() { root = null; N = 0; } private bool IsRed(Node node) { if (node == null) { //根节点为黑节点 return Black; } return node.color; } /// <summary> /// 左旋转 /// </summary> /// <param name="node"></param> /// <returns></returns> private Node LeftRotate(Node node) { Node x = node.right; node.right = x.left; x.color = node.color; node.color = Red; return x; } /// <summary> /// 右旋转 /// </summary> /// <param name="node"></param> /// <returns></returns> private Node RightRotate(Node node) { Node x = node.left; node.left = x.right; x.right = node; x.color = node.color; node.color = Red; return x; } /// <summary> /// 颜色反转 /// </summary> /// <param name="node"></param> private void ColorRotate(Node node) { node.color = Red; node.left.color = Black; node.right.color = Black; } public int Count { get { return N; } } public bool IsEmpty { get { return N == 0; } } /// <summary> /// 红黑树添加 /// </summary> /// <param name="e"></param> public void Add(E e) { root = Add(root, e); root.color = Black;//根节点为黑色 } /// <summary> /// 添加新节点 /// 以node为根的树中添加元素e,添加后返回根节点node /// </summary> /// <param name="node"></param> /// <param name="e"></param> /// <returns></returns> private Node Add(Node node, E e) { if (node == null) { N++; //新节点默认为红节点 return new Node(e); } if (e.CompareTo(node.e) < 0) { //添加的节点比当前要比较的节点小 node.left = Add(node.left, e); } else if (e.CompareTo(node.e) > 0) { node.right = Add(node.right, e); } //如果出现右节点是红色,左子节点是黑色,进行左旋转 if (IsRed(node.right) && !IsRed(node.left)) { node = LeftRotate(node); } //如果连续的左子节点都是红色,进行右旋转 if (IsRed(node.left) && IsRed(node.left.left)) { node = RightRotate(node); } //如果左右子节点都是红色,进行颜色反转 if (IsRed(node.left) && IsRed(node.right)) { ColorRotate(node); } return node; } /// <summary> /// 是否包含元素 /// </summary> /// <param name="e"></param> /// <returns></returns> public bool Contains(E e) { //从根节点开始查找 return Contains(root, e); } /// <summary> /// 从根节点开始查找是否包含此节点 /// 1:根据查找的节点从根节点开始比较大小 /// </summary> /// <param name="node">根节点或者二叉树中被比较的节点</param> /// <param name="e">目标节点</param> /// <returns></returns> private bool Contains(Node node, E e) { if (node == null) { return false; } if (e.CompareTo(node.e) == 0) { //说明找到了 return true; } else if (e.CompareTo(node.e) < 0) { //目标节点比被比较节点小,所以在被比较节点的左子树中找 Contains(node.left, e); } else if (e.CompareTo(node.e) > 0) { //目标节点比被比较节点大,所以在被比较节点的右子树中找 Contains(node.right, e); } return false; } public void PreOrder() { PreOrder(root); } /// <summary> /// 前序遍历 /// </summary> /// <param name="node"></param> private void PreOrder(Node node) { if (node == null) { return; } Console.WriteLine(node.e); PreOrder(node.left); PreOrder(node.right); } public void InOrder() { InOrder(root); } /// <summary> /// 中序遍历 /// </summary> /// <param name="node"></param> private void InOrder(Node node) { if (node == null) { return; } //1:遍历左子树 InOrder(node.left); //2:访问根节点 Console.WriteLine(node.e); //3:遍历右子树 InOrder(node.right); } public void PostOrder() { PostOrder(root); } /// <summary> /// 后序遍历 /// </summary> /// <param name="node"></param> private void PostOrder(Node node) { if (node == null) { return; } //1:遍历左子树 PostOrder(node.left); //2:遍历右子树 PostOrder(node.right); //3:访问根节点 Console.WriteLine(node.e); } public void LevelOrder() { Queue<Node> q = new Queue<Node>(); //在队列的末尾添加一个元素 q.Enqueue(root); while (q.Count != 0) { //移除队头的元素 Node cur = q.Dequeue(); Console.Write(cur.e); if (cur.left != null) { q.Enqueue(cur.left); } if (cur.right != null) { q.Enqueue(cur.right); } } } public E Min() { if (root == null) { throw new Exception("空树"); } return Min(root).e; } /// <summary> /// 查找最小值 /// </summary> /// <param name="node"></param> /// <returns></returns> private Node Min(Node node) { if (node.left == null) { return node; } return Min(node.left); } public E Max() { if (root == null) { throw new Exception("空树"); } return Max(root).e; } /// <summary> /// 查找最大值 /// </summary> /// <param name="node"></param> /// <returns></returns> private Node Max(Node node) { if (node.left == null) { return node; } return Max(node.right); } #region 删除最大最小值 public E RemoveMin() { E ret = Min(); root = RemoveMin(root); return ret; } private Node RemoveMin(Node node) { if (node.left == null) { N--; return node.right; } node.left = RemoveMin(node.left); return node; } public E RemoveMax() { E ret = Min(); root = RemoveMax(root); return ret; } private Node RemoveMax(Node node) { //如果node右子树为空,说明当前节点最大,所以需要删除当前 //节点,直接返回当前节点的右子树 if (node.right == null) { N--; //此时还需要保留被删除节点的左子树,因为它的左子树肯定比其小,不需要删除, //所以直接保留下来。就算是此时左子树为空也没问题,因为存在空树。 return node.left; } //如果找到了最大节点,会返回最大节点右子树(此时为空),赋给上一个节点的右孩子节点 //所以此时当前节点被删除了。 node.right = RemoveMax(node.right); return node; } #endregion #region 删除任意元素 /// <summary> /// 删除以node为根节点的二叉查找树中值为e的节点。 /// 返回删除节点后新的二叉查找树的根。 /// </summary> /// <param name="node"></param> /// <param name="e"></param> /// <returns></returns> private Node Remove(Node node, E e) { if (node == null) return null; if (e.CompareTo(node.e) < 0) { //如果要查找的值小于被删除的节点,到左子树中找 node.left = Remove(node.left, e); return node; } else if (e.CompareTo(node.e) > 0) { //如果要查找的值大于被删除的节点,到右子树中找 node.right = Remove(node.right, e); return node; } else { //被删除节点就是要查找的节点 if (node.right == null) { N--; return node.left; } if (node.left == null) { N--; return node.right; } //左右子树都存在的情况 Node s = Min(node.right); s.right = RemoveMin(node.right); s.left = node.left; return s; } } #endregion #region 计算高度 public int MaxHeight() { return MaxHeight(root); } /// <summary> /// 以node为根节点的二叉树的最大高度 /// </summary> /// <param name="node"></param> /// <returns></returns> private int MaxHeight(Node node) { if (node == null) { return 0; } int l = MaxHeight(node.left); int r = MaxHeight(node.right); //加1是为了加上根节点本身高度 return Math.Max(l, r) + 1; } #endregion }
基于红黑树的集合和映射
其实基于红黑树的集合和映射总体来说性能比基于二叉查找树实现的要高,因为它能保持树的高度,维持树的平衡。
其中SortedDictionary就是基于红黑树实现的映射。