红黑树的介绍

红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树。
红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。
除了具备该特性之外,红黑树还包括许多额外的信息。红黑树的每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。

红黑树一棵在每个结点上增加了一个存储位来表示结点颜色(红色RED或黑色BLACK)的二叉搜索树。

二叉搜索树简单的说就是:对树中任何结点x,其左子树中的关键字最大不超过x.key,即对左子树中任一结点y,有y.key<x.key;其右子树中的关键字最小不低于x.key,即对右子树中任一结点y,有y.key>x.key。

红黑树中每个结点包含5个属性:color、key、left、right和p。如果一个结点没有子结点或父结点,则该结点相应属性值为NIL。如图1所示:

图1 红黑树的叶结点

这些NIL被视为指向二叉搜索树的叶结点(外部结点)的指针,而把带关键字的结点视为树的内部结点。

为了便于处理红黑树代码中的边界条件,使用一个哨兵T.nil来代表所有的NIL:所有的叶结点和根结点的父结点。如图2所示:

图2 红黑树的T.nil属性

红黑树的特性:
  (1) 每个节点或者是黑色,或者是红色。
  (2) 根节点是黑色。
  (3) 每个叶子节点是黑色(为空的结点)。
  (4) 不能出现两个连续的红色结点(如果一个节点是红色的,那么它的两个子节点都是黑色的)。
  (5) 从一个节点开始所有路径上包含相同数目的黑节点。

关于它的特性,需要注意的是:
  第一,特性(3)中的叶子节点,是只为空(NIL或null)的节点。
  第二,特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

红黑树示意图如下:

  从红黑树中任一结点x出发(不包括结点x),到达一个外部结点的任一路径上的黑结点个数叫做结点x的黑高度,亦称为结点的阶(rank),记作bh(x)。红黑树的黑高度定义为其根结点的黑高度。下图,数字表示该结点的黑高度。

 

红黑树的搜索

  由于每一棵红黑树都是二叉搜索树,可以使用与搜索普通二又搜索树时所使用的完全相同的算法进行搜索。在搜索过程中不需使用颜色信息。
对普通二又搜索树进行搜索的时间复杂性为O(h),对于红黑树则为O(log2n)。因为在搜索普通二又搜索树、AVL树和红黑树时使用了相同的代码,并且在最差情况下AVL树的高度最小,因此,在那些以搜索操作为主的应用程序中,最差情况下AVL树能获得最优的时间复杂性。

红黑树的插入

  首先使用二又搜索树的插入算法将一个元素插入到红黑树中,该元素将作为新的叶结点插入到某一外部结点位置。在插入过程中需要为新元素染色。
如果插入前是空树,那么新元素将成为根结点,根结点必须染成黑色
如果插入前树非空,若新结点被染成黑色,将违反红黑树的特性,所有从根到外部结点的路径上的黑色结点个数不等。因此,新插入的结点将染成红色,但这又可能违反红黑树的特性,出现连续两个红色结点,因此需要重新平衡。
  
>>设新插入的结点为u,它的父结点和祖父结点分别是pu和gu。
  >若pu是黑色结点,再插入红色结点,则特性没有破坏,结束重新平衡的过程。
 
  >若pu是红色结点,则出现连续两个红色结点的情形,这时还要考查pu的兄弟结点。
 
        情况1:如果pu的兄弟结点gr是红色结点,此时结点pu的父结点gu是黑色结点,它有两个红色子女结点。交换结点gu和它的子女结点的颜色。

        情况2:如果pu的兄弟结点gr是黑色结点,此时又有两种情况。
              (1)u是pu的左子女,pu是gu的左子女。在这种情况下只要对pu做一次右单旋转交换pu和gu的颜色,就可恢复红黑树的特性,并结束重新平衡过程。

              (2)u是pu的右子女,pu是gu的左子女。在这种情况下对pu做先左后右的双旋转再交换u与gu的颜色,就可恢复红黑树的特性,结束重新平衡过程。

    >当结点u是pu的右子女的情形与u是pu的左子女的情形是镜像的,只要左右指针互换即可。

红黑树的删除

  红黑树的删除算法与二又搜索树的删除算法类似,不同之处在于,在红黑树中执行一次二叉搜索树的删除运算,可能会破坏红黑树的特性,需要重新平衡。
  在红黑树中真正删除的结点应是叶结点或只有一个子女的结点。若设被删除为p,其唯一的子女为s。结点p被删除后,结点s取代了它的位置。
如果被删结点p是红色的,删去它不存在问题。因为树中各结点的黑高度都没有改变,也不会出现连续两个红色结点,红黑树的特性仍然保持,不需执行重新平衡过程。
如果被删结点p是黑色的,一旦删去它,红黑树将不满足特性的要求,因为在这条路径上黑色结点少了一个,从根到外部结点的黑高度将会降低。因此必须通过旋转变换和改变结点的颜色,消除双重黑色结点,恢复红黑树的特性。
 
>>设u是被删结点p的唯一的子女结点。
 
  >如果u是红色结点,可以把结点u染成黑色,从而恢复红黑树的特性。
 
  >如果被删结点p是黑色结点,它的唯一的子女结点u也是黑色结点,就必须先将结点p摘下,将结点u链到其祖父结点g的下面。假设结点u成为结点g的右子女,v是u的左兄弟。根据v的颜色,分以下两种情况讨论:
 
            情况1:结点v是黑色结点,若设结点v的左子女结点为w。根据w的颜色又需分两种情况讨论:
 
                (1)结点w是红色结点,此时作一次右单旋转,将w、g染成黑色v染成红色,如图所示,就可消除结点u的双重黑色,恢复红黑树的性质。

                (2)结点w是黑色结点,还要看结点w的右兄弟结点r。根据结点r的颜色,又要分两种情况:
 
                  ①结点r是红色结点,可通过一次先左后右的双旋转,并将g染成黑色,就可消除结点u的双重黑色,恢复红黑树的特性。

                  ②结点r是黑色结点,这时还要看结点g的颜色。如果g是红色结点,只要交换结点g和其子女结点v的颜色就能恢复红黑树的特性,如图(a)。如果g是黑色结点,可做一次右单旋转,如图(b)。

        情况2:结点υ是红色结点。考查v的右子女结点r。根据红黑树的特性,r一定是黑色结点。再看结点r的左子女结点s。根据s的颜色,可以分为两种情况讨论。
                (1)结点s是红色结点。通过一次先左后右双旋转,让r上升,使包含u的路径的黑高度增1,从而消除结点u的双重黑色,恢复红黑树的特性。

                (2)结点s是黑色结点,再看结点s的右兄弟结点t。根据结点t的颜色又可分为两种情况进行讨论。
                  ①若结点t为红色结点,先以t为旋转轴,做左单旋转,以t替补r的位置;然后再以t为旋转轴,做一次先左后右的双旋转,可消除结点u的双重黑色,恢复红黑树的特性。

                  ②若结点t为黑色结点,以v为旋转轴,做一次右单旋转,并改变υ和r的颜色,即可消除结点u的双重黑色,恢复红黑树的特色。

  >当结点u是结点g的左子女的情况与上面讨论的情况是镜像的,只要左、右指针互换就可以了。

红黑树的实现代码

 typedef enum { RED = , BLACK } Color;
//红黑树结点类型
template <typename Type>
struct RBTNode
{
Color color; //颜色
Type key; //关键字
RBTNode* left; //左孩子
RBTNode* right; //右孩子
RBTNode* parent; //父结点
}; //红黑树类型
template<typename Type>
class RBTree
{
public:
//构造函数
RBTree()
{
Nil = BuyNode();
root = Nil;
Nil->color = BLACK;
}
//析构函数
~RBTree()
{
destroy(root); //销毁创建的非Nil结点
delete Nil; //最后删除Nil结点
Nil = NULL;
} //中序遍历
void InOrder() { InOrder(root); } //插入
//1.BST方式插入
//2.调整平衡
bool Insert(const Type &value)
{
RBTNode<Type>* pr = Nil; //pr用来记住父节点
RBTNode<Type>* s = root; //定义变量s指向根
while (s != Nil)
{
if (value == s->key)
{
return false;
}
pr = s; //每次记住s的父节点
if (value < s->key)
{
s = s->left;
}
else
{
s = s->right;
}
}
//循环后s==Nil
s = BuyNode(value); //申请结点
if (pr == Nil) //如果父节点pr是根节点,第一次root指向Nil,所以pr==Nil
{
root = s;
root->parent = pr;
}
else //如果父节点不是根节点
{
if (value < pr->key)
{
pr->left = s;
}
else
{
pr->right = s;
}
s->parent = pr; //设置新结点s的父节点
}
//调整平衡
Insert_Fixup(s);
return true;
} //删除key结点(先查找,再调用内部删除)
void Remove(Type key)
{
RBTNode<Type>* t;
if ((t = Search(root, key)) != Nil)
{
Remove(t);
}
else
{
cout << "Key is not exist." << endl;
}
} //中序遍历打印结点详细的结点颜色
void InOrderPrint() { InOrderPrint(root); } protected:
//申请结点结点,将结点的颜色初始化为红色,初始化结点的关键字,其他的初始化为空
RBTNode<Type>* BuyNode(const Type &x = Type())
{
RBTNode<Type>* s = new RBTNode<Type>();
assert(s != NULL);
s->color = RED;
s->left = s->right = s->parent = Nil;
s->key = x;
return s;
} //中序遍历
void InOrder(RBTNode<Type>* root)
{
if (root != Nil)
{
InOrder(root->left);
cout << root->key << " ";
InOrder(root->right);
}
} //左转,对z结点左转
// zp zp
// / \
// z z
// / \ / \
// lz y lz y
// / \ / \
// ly ry ly ry
void LeftRotate(RBTNode<Type>* z)
{
RBTNode<Type>* y = z->right; //用y指向要转动的z结点
z->right = y->left;
if (y->left != Nil) //y所指结点的左结点不为空
{
y->left->parent = z;
}
y->parent = z->parent;
if (root == z) //z就是根节点
{
root = y;
}
else if (z == z->parent->left) //z在左结点
{
z->parent->left = y;
}
else //z在右结点
{
z->parent->right = y;
}
y->left = z;
z->parent = y;
} //右转,对z结点进行右转
// zp zp
// / \
// z z
// / \ / \
// y rz y rz
// / \ / \
// ly ry ly ry
void RightRotate(RBTNode<Type>* z)
{
RBTNode<Type>* y = z->left;
z->left = y->right;
if (y->right != Nil)
{
y->right->parent = z;
}
y->parent = z->parent;
if (root == z) //如果z是根结点
{
root = y;
}
else if (z == z->parent->left) //z在左结点
{
z->parent->left = y;
}
else //z在右结点
{
z->parent->right = y;
}
y->right = z;
z->parent = y;
} //插入后的调整函数
void Insert_Fixup(RBTNode<Type>* s)
{
RBTNode<Type>* uncle; //叔结点(父结点的兄弟结点)
while (s->parent->color == RED) //父节点的颜色也为红色
{
if (s->parent == s->parent->parent->left) //父节点是左结点
{
uncle = s->parent->parent->right; if (uncle->color == RED) //叔结点为红色
{
//父节点和叔结点都变为黑色
s->parent->color = BLACK;
uncle->color = BLACK;
//祖父结点变为红色
s->parent->parent->color = RED;
//将s指针指向祖父结点,下一次循环继续判断祖父的父节点是否为红色
s = s->parent->parent;
}
else //没有叔结点,或叔结点为黑色(经过多次循环转换,叔结点可能为黑)
{
if (s == s->parent->right) //如果调整的结点在右结点
{
s = s->parent; //先将s指向s的父结点
LeftRotate(s); //再左转
}
//如果调整的结点在左结点,将s的父节点变为黑色,将祖父的结点变为红色,将s的祖父结点右转
s->parent->color = BLACK;
s->parent->parent->color = RED;
RightRotate(s->parent->parent);
}
}
else
{
if (s->parent == s->parent->parent->right) //父节点是右结点
{
uncle = s->parent->parent->left;
if (uncle->color == RED) //叔结点为红色
{
//父节点和叔结点都变为黑色
s->parent->color = BLACK;
uncle->color = BLACK;
//祖父结点变为红色
s->parent->parent->color = RED;
//将s指针指向祖父结点,下一次循环继续判断祖父的父节点是否为红色
s = s->parent->parent;
}
else //没有叔结点,或叔结点为黑色(经过多次循环转换,叔结点可能为黑)
{
if (s == s->parent->left) //如果调整的结点在左结点
{
s = s->parent; //先将s指向s的父结点
RightRotate(s); //再右转
}
//如果调整的结点在右结点,将s的父节点变为黑色,将祖父的结点变为红色,将s的祖父结点右转
s->parent->color = BLACK;
s->parent->parent->color = RED;
LeftRotate(s->parent->parent);
}
}
}
}
root->color = BLACK; //最后始终将根节点置为黑色
} //查找key结点
RBTNode<Type>* Search(RBTNode<Type>* root, Type key) const
{
if (root == Nil) //root为空,或key和根的key相同
{
return Nil;
} if (root->key == key)
{
return root;
}
if (key<root->key)
{
return Search(root->left, key);
}
else
{
return Search(root->right, key);
}
} //将u的子节点指针指向u改变指向v,将v的父节点改变为指向u的父节点
// up
// \
// u
// / \
// ul ur
// / \
// v ulr
// \
// rv
void Transplant(RBTNode<Type>* u, RBTNode<Type>* v)
{
if (u->parent == Nil) //u的父节点为空
{
root = v; //直接令根root为v
}
else if (u == u->parent->left) //u父节点不为空,且u在左子树
{
u->parent->left = v;
}
else //u在左子树
{
u->parent->right = v;
}
v->parent = u->parent;
} //找到最左结点(最小)
// xp
// \
// x
// / \
// xl xr
// / \
// xll xlr RBTNode<Type>* Minimum(RBTNode<Type>* x)
{
if (x->left == Nil)
{
return x;
}
return Minimum(x->left);
} //删除红黑树结点z
void Remove(RBTNode<Type>* z)
{
RBTNode<Type>* x = Nil;
RBTNode<Type>* y = z; //y记住传进来的z结点
Color ycolor = y->color; //
if (z->left == Nil) //z只有右孩子
{
x = z->right;
Transplant(z, z->right);
}
else if (z->right == Nil) //z只有右孩子
{
x = z->left;
Transplant(z, z->left);
}
else //右左孩子和右孩子
{
y = Minimum(z->right); //y是z右子树的的最左子树
ycolor = y->color;
x = y->right;
if (y->parent == z) //z的右子结点没有左节点或为Nil
{
x->parent = y;
}
else //z的右子结点有左节点或为Nil
{
Transplant(y, y->right);
y->right = z->right;
y->right->parent = y;
}
Transplant(z, y);
//改变指向
y->left = z->left;
z->left->parent = y;
y->color = z->color;
}
if (ycolor == BLACK)
{
Remove_Fixup(x);
}
} //红黑树删除调整
void Remove_Fixup(RBTNode<Type>* x)
{
while (x != root&&x->color == BLACK) //当结点x不为根并且它的颜色不是黑色
{
if (x == x->parent->left) //x在左子树
{
RBTNode<Type>* w = x->parent->right; //w是x的兄结点 if (w->color == RED) //情况1
{
w->color = BLACK;
x->parent->color = RED;
LeftRotate(x->parent);
w = x->parent->right;
}
if (w->left->color == BLACK&&w->right->color == BLACK) //情况2
{
w->color = RED;
x = x->parent;
}
else
{
if (w->right->color == BLACK) //情况3
{
w->color = RED;
w->left->color = BLACK;
RightRotate(w);
w = x->parent->right;
}
//情况4
w->color = w->parent->color;
w->parent->color = BLACK;
w->right->color = BLACK;
LeftRotate(x->parent);
x = root; //结束循环 }
}
else //x在右子树
{
RBTNode<Type>* w = x->parent->left;
if (w->color == RED) //情况1
{
w->parent->color = RED;
w->color = BLACK;
RightRotate(x->parent);
w = x->parent->left;
}
if (w->right->color == BLACK&&w->right->color == BLACK) //情况2
{
w->color = RED;
x = x->parent;
}
else
{
if (w->left->color == BLACK) //情况3
{
w->right->color = BLACK;
w->color = RED;
LeftRotate(w);
w = x->parent->left;
}
//情况4
w->color = x->parent->color;
x->parent->color = BLACK;
w->left->color = BLACK;
RightRotate(x->parent);
x = root; //结束循环
}
}
}
x->color = BLACK;
} //销毁红黑树
void destroy(RBTNode<Type>* &root)
{
if (root == Nil)
{
return;
}
if (root->left != Nil)
{
destroy(root->left);
}
if (root->right != Nil)
{
destroy(root->right);
}
delete root;
root = NULL;
} //中序遍历打印结点详细的结点颜色
void InOrderPrint(RBTNode<Type>* node)
{
if (node == Nil)
{
return;
}
if (node->left != NULL)
{
InOrderPrint(node->left);
}
cout << node->key << "(" << ((node->color == BLACK) ? "BLACK" : "RED") << ")" << " ";
if (node->right != Nil)
{
InOrderPrint(node->right);
}
} private:
RBTNode<Type>* root; //根指针
RBTNode<Type>* Nil; //外部结点,表示空结点,黑色的
};

 测试代码

 int main(int argc, char* argv[])
{
RBTree<int> rb;
// rb.InitTree();
int arr[] = { ,,,,,,,, };
int n = sizeof(arr) / sizeof(int);
for (int i = ; i < n; i++)
{
rb.Insert(arr[i]);
} rb.InOrder();
cout << endl;
rb.InOrderPrint();
cout << endl;
rb.Remove();
rb.InOrder();
cout << endl;
rb.Remove();
return ;
}

主函数

【红黑树】的详细实现(C++)的更多相关文章

  1. 红黑树之 原理和算法详细介绍(阿里面试-treemap使用了红黑树) 红黑树的时间复杂度是O(lgn) 高度<=2log(n+1)1、X节点左旋-将X右边的子节点变成 父节点 2、X节点右旋-将X左边的子节点变成父节点

    红黑树插入删除 具体参考:红黑树原理以及插入.删除算法 附图例说明   (阿里的高德一直追着问) 或者插入的情况参考:红黑树原理以及插入.删除算法 附图例说明 红黑树与AVL树 红黑树 的时间复杂度 ...

  2. 红黑树(二)之 C语言的实现

    概要 红黑树在日常的使用中比较常用,例如Java的TreeMap和TreeSet,C++的STL,以及Linux内核中都有用到.之前写过一篇文章专门介绍红黑树的理论知识,本文将给出红黑数的C语言的实现 ...

  3. 红黑树(四)之 C++的实现

    概要 前面分别介绍红黑树的理论知识和红黑树的C语言实现.本章是红黑树的C++实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章. 目录1. 红黑树的介绍2. 红黑树的C++ ...

  4. 红黑树(五)之 Java的实现

    概要 前面分别介绍红黑树的理论知识.红黑树的C语言和C++的实现.本章介绍红黑树的Java实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章.还是那句老话,红黑树的C/C+ ...

  5. 第十四章 红黑树——C++代码实现

    红黑树的介绍 红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树.红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键 ...

  6. 【Java深入研究】10、红黑树

    一.红黑树介绍 红黑树是二叉查找树,红黑树的时间复杂度为: O(lgn) 红黑树的特性:(1)每个节点或者是黑色,或者是红色.(2)根节点是黑色.(3)每个叶子节点(NIL)是黑色. [注意:这里叶子 ...

  7. 红黑树 Java实现

    概要 前面分别介绍红黑树的理论知识.红黑树的C语言和C++的实现.本章介绍红黑树的Java实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章.还是那句老话,红黑树的C/C+ ...

  8. 红黑树 - C++代码实现

    红黑树的介绍 红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树.红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键 ...

  9. Java集合(4)一 红黑树、TreeMap与TreeSet(下)

    目录 Java集合(1)一 集合框架 Java集合(2)一 ArrayList 与 LinkList Java集合(3)一 红黑树.TreeMap与TreeSet(上) Java集合(4)一 红黑树. ...

  10. 红黑树(R-B Tree)

    R-B Tree简介 R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树.红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black). ...

随机推荐

  1. Redis03——Redis之单线程+多路IO复用技术

    Redis 是单线程+多路IO复用技术 多路复用:使用一个线程来检查多个文件描述符的就绪状态 如果有一个文件描述符就绪,则返回 否则阻塞直到超时 得到就绪状态后进行真正的操作可以在同一个线程里执行,也 ...

  2. var s=+newDate();

    var s=+newDate(); 解释如下:=+是不存在的; +new Date()是一个东西; +相当于.valueOf(); 看到回复补充一下.getTime()这个也是得到毫秒数 //4个结果 ...

  3. Cannot resolve collation conflict between "Chinese_Taiwan_Stroke_CI_AS" and "Chinese_PRC_CI_AS" in UNION ALL operator occurring in SELECT statement column 1.

    Cannot resolve collation conflict between . 解决方案: COLLATE Chinese_PRC_CI_AS 例子: SELECT A.Name FROM A ...

  4. 【你不知道的javaScript 上卷 笔记5】javaScript中的this词法

    function foo() { console.log( a ); } function bar() { var a = 3; foo(); } var a = 2; bar(); 上面这段代码为什 ...

  5. H3C IP路由基础

    一.路由简介 在网络中路由器根据所收到的报文的目的地址选择一条合适的路径,并将报文转发到下一个路由器.路径中最后一个路由器负责将报文转发给目的主机. 路由就是报文在转发过程中的路径信息,用来指导报文转 ...

  6. 多线程启动selenium,报NameError: name '__file__' is not defined

    将__file__加上单引号就解决了:   # 获取当前文件名,用于创建模型及结果文件的目录   file_name = os.path.basename('__file__').split('.') ...

  7. createElement(九)

    Vue.js 利用 createElement 方法创建 VNode,它定义在 src/core/vdom/create-elemenet.js 中: // wrapper function for ...

  8. javacv FFmpeg 视频压缩

    package com.nmcc.demo.utils; import lombok.extern.slf4j.Slf4j; import org.bytedeco.javacpp.avcodec; ...

  9. Vue组件中的Data为什么是函数。

    简单点说,组件是要复用的,在很多地方都会调用.   如果data不是函数,而是属性,就又可能会发生多个地方的相同组件操作同一个Data属性,导致数据混乱. 而如果是函数,因为组件data函数的返回值是 ...

  10. python面试的100题(12)

    25.求出列表所有奇数并构造新列表 a=[1,2,3,4,5,6,7,8,9,10] res=[i for i in a if i%2==1] print(res) 结果为:[1, 3, 5, 7, ...