【红黑树】的详细实现(C++)
红黑树的介绍
红黑树(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)。红黑树的黑高度定义为其根结点的黑高度。下图,数字表示该结点的黑高度。
红黑树的搜索
红黑树的插入
(2)u是pu的右子女,pu是gu的左子女。在这种情况下对pu做先左后右的双旋转,再交换u与gu的颜色,就可恢复红黑树的特性,结束重新平衡过程。
红黑树的删除
②若结点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++)的更多相关文章
- 红黑树之 原理和算法详细介绍(阿里面试-treemap使用了红黑树) 红黑树的时间复杂度是O(lgn) 高度<=2log(n+1)1、X节点左旋-将X右边的子节点变成 父节点 2、X节点右旋-将X左边的子节点变成父节点
红黑树插入删除 具体参考:红黑树原理以及插入.删除算法 附图例说明 (阿里的高德一直追着问) 或者插入的情况参考:红黑树原理以及插入.删除算法 附图例说明 红黑树与AVL树 红黑树 的时间复杂度 ...
- 红黑树(二)之 C语言的实现
概要 红黑树在日常的使用中比较常用,例如Java的TreeMap和TreeSet,C++的STL,以及Linux内核中都有用到.之前写过一篇文章专门介绍红黑树的理论知识,本文将给出红黑数的C语言的实现 ...
- 红黑树(四)之 C++的实现
概要 前面分别介绍红黑树的理论知识和红黑树的C语言实现.本章是红黑树的C++实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章. 目录1. 红黑树的介绍2. 红黑树的C++ ...
- 红黑树(五)之 Java的实现
概要 前面分别介绍红黑树的理论知识.红黑树的C语言和C++的实现.本章介绍红黑树的Java实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章.还是那句老话,红黑树的C/C+ ...
- 第十四章 红黑树——C++代码实现
红黑树的介绍 红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树.红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键 ...
- 【Java深入研究】10、红黑树
一.红黑树介绍 红黑树是二叉查找树,红黑树的时间复杂度为: O(lgn) 红黑树的特性:(1)每个节点或者是黑色,或者是红色.(2)根节点是黑色.(3)每个叶子节点(NIL)是黑色. [注意:这里叶子 ...
- 红黑树 Java实现
概要 前面分别介绍红黑树的理论知识.红黑树的C语言和C++的实现.本章介绍红黑树的Java实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章.还是那句老话,红黑树的C/C+ ...
- 红黑树 - C++代码实现
红黑树的介绍 红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树.红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键 ...
- Java集合(4)一 红黑树、TreeMap与TreeSet(下)
目录 Java集合(1)一 集合框架 Java集合(2)一 ArrayList 与 LinkList Java集合(3)一 红黑树.TreeMap与TreeSet(上) Java集合(4)一 红黑树. ...
- 红黑树(R-B Tree)
R-B Tree简介 R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树.红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black). ...
随机推荐
- Redis03——Redis之单线程+多路IO复用技术
Redis 是单线程+多路IO复用技术 多路复用:使用一个线程来检查多个文件描述符的就绪状态 如果有一个文件描述符就绪,则返回 否则阻塞直到超时 得到就绪状态后进行真正的操作可以在同一个线程里执行,也 ...
- var s=+newDate();
var s=+newDate(); 解释如下:=+是不存在的; +new Date()是一个东西; +相当于.valueOf(); 看到回复补充一下.getTime()这个也是得到毫秒数 //4个结果 ...
- 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 ...
- 【你不知道的javaScript 上卷 笔记5】javaScript中的this词法
function foo() { console.log( a ); } function bar() { var a = 3; foo(); } var a = 2; bar(); 上面这段代码为什 ...
- H3C IP路由基础
一.路由简介 在网络中路由器根据所收到的报文的目的地址选择一条合适的路径,并将报文转发到下一个路由器.路径中最后一个路由器负责将报文转发给目的主机. 路由就是报文在转发过程中的路径信息,用来指导报文转 ...
- 多线程启动selenium,报NameError: name '__file__' is not defined
将__file__加上单引号就解决了: # 获取当前文件名,用于创建模型及结果文件的目录 file_name = os.path.basename('__file__').split('.') ...
- createElement(九)
Vue.js 利用 createElement 方法创建 VNode,它定义在 src/core/vdom/create-elemenet.js 中: // wrapper function for ...
- javacv FFmpeg 视频压缩
package com.nmcc.demo.utils; import lombok.extern.slf4j.Slf4j; import org.bytedeco.javacpp.avcodec; ...
- Vue组件中的Data为什么是函数。
简单点说,组件是要复用的,在很多地方都会调用. 如果data不是函数,而是属性,就又可能会发生多个地方的相同组件操作同一个Data属性,导致数据混乱. 而如果是函数,因为组件data函数的返回值是 ...
- 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, ...