前言:
红黑树的学习需要大家对二叉搜索树与AVL树有深刻的理解,如果话没有看过我对二叉搜索树与AVL树的讲解的铁子们可以先看看上一篇文章:二叉搜索树与AVL树(java数据结构)-CSDN博客
红黑树:
什么是红黑树?
当然要满足红黑树没有想象当中的简单:
接下来就来看看红黑树的性质:
红黑树的性质:
尤其是性质4和性质5尤为重要!!是满足一棵树是红黑树的核心!
红黑树结点的定义:
public class RBTreeNode {
private int val;
private RBTreeNode left = null;
private RBTreeNode rigth = null;
private RBTreeNode parent;
private COLOR color;
public RBTreeNode(int val) {
this.val = val;
}
}
红黑树的插入:
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
接下来在 基础上逐步插入:
一共分为两大种情况:
1、parent = grandfather.left
2、parent = grandfather.right
每种情况中又可以分为两种情况:
1、uncle == null || uncle == black
2、uncle != null && uncle == red
接下来我以第一种parent == greandfather.left为例,剩下的一种情况与之相反!!
大家最好自己画一遍图分析得更加清楚!!
代码如下:
public boolean insert(int val) {
RBTreeNode node = new RBTreeNode(val);
if (root == null) {
root = node;
root.color = COLOR.BLACK;
return true;
}
RBTreeNode cur = root;
RBTreeNode parent = null;
while (cur != null) {
if (val > cur.val) {
parent = cur;
cur = cur.right;
} else if (val == cur.val) {
return false;
} else {
parent = cur;
cur = cur.left;
}
}
cur = node;
cur.parent = parent;
if (cur.val < parent.val) {
parent.left = cur;
} else {
parent.right = cur;
}
while(parent != null && parent.color == COLOR.RED) {
//因为插入的节点颜色是红色,只有两个红色相邻才需要进行调整
RBTreeNode grandfather = parent.parent;
//需要记录g节点
if (parent == grandfather.left) {
RBTreeNode uncle = grandfather.right;
//只有确定了father节点,才可以确定uncle节点
//此时uncle节点分3种情况,
//uncle节点存在并且为红色
//uncle节点存在并且为黑色
//uncle节点不存在
if (uncle != null && uncle.color == COLOR.RED) {
uncle.color = COLOR.BLACK;
parent.color = COLOR.BLACK;
cur = grandfather;
parent = cur.parent;
cur.color = COLOR.RED;
} else {
//uncle.color == BLACK || uncle == null
if (cur == parent.right) {
roateLeft(parent);
RBTreeNode tmp = parent;
parent = cur;
cur = tmp;
}
roateRight(grandfather);
parent.color = COLOR.BLACK;
grandfather.color = COLOR.RED;
}
} else {
RBTreeNode uncle = grandfather.left;
//if(parent == grandfather.right)
//所有的结果相反
//因为插入的节点颜色是红色,只有两个红色相邻才需要进行调整
if (uncle != null && uncle.color == COLOR.RED) {
uncle.color = COLOR.BLACK;
parent.color = COLOR.BLACK;
cur = grandfather;
parent = cur.parent;
cur.color = COLOR.RED;
} else {
//uncle.color == BLACK || uncle == null
if (cur == parent.left) {
roateRight(parent);
RBTreeNode tmp = parent;
parent = cur;
cur = tmp;
}
roateLeft(grandfather);
parent.color = COLOR.BLACK;
grandfather.color = COLOR.RED;
}
}
}
root.color = COLOR.BLACK;
return true;
}
红黑树的插入验证:
当我们自己画图写好插入代码以后,接下来要做的事就是要验证我们写的代码的正确性!
当然我们应该如何验证我们的代码是正确的呢?
很简单,就从红黑树的性质开始验证,如果这棵树满足红黑树的所有的性质,呢么肯定是一棵红黑树!
性质如下:
1. 每个结点不是红色就是黑色2. 根节点是黑色的3. 如果一个节点是红色的, 则它的两个孩子结点是黑色的 【没有 2 个连续的红色节点】4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点5. 每个叶子结点都是黑色的 ( 此处的叶子结点指的是空结点 )
代码如下:
public boolean isRBTree(RBTreeNode root) {
//性质1:根节点必须是黑色
if(root.color != COLOR.BLACK) {
return false;
}
//性质3:如果一个节点是红色,那么它的两个孩子节点肯定是黑色,没有两个连续的红色节点
//写一个方法
if(!isRBTree1(root)) {
return false;
}
//计算一条路上的黑色节点个数
int blackSum = caluateBlackSum(root);
//性质4:对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
if(!isRBTree2(root,0,blackSum)) {
return false;
}
return true;
}
public int caluateBlackSum(RBTreeNode root) {
int num = 0;
RBTreeNode cur = root;
while(cur != null) {
if(cur.color == COLOR.BLACK) {
num++;
}
cur = cur.left;
}
return num;
}
public boolean isRBTree2(RBTreeNode root,int blackNum,int blackSum) {
if(root == null) {
return true;
}
if(root.color == COLOR.BLACK) {
blackNum++;
}
if(root.right == null && root.left == null) {
if(blackNum != blackSum) {
System.out.println(root.val+"违反了性质4");
return false;
}
}
return isRBTree2(root.left,blackNum,blackSum) && isRBTree2(root.right,blackNum,blackSum);
}
public boolean isRBTree1(RBTreeNode root) {
if(root == null) {
return true;
}
if(root.color == COLOR.RED) {
if(root.left != null && root.left.color == COLOR.RED) {
System.out.println(root.val+"不满足性质3");
return false;
}
if(root.right != null && root.right.color == COLOR.RED) {
System.out.println(root.val+"不满足性质3");
return false;
}
}
return isRBTree1(root.left) && isRBTree1(root.right);
}
当然为了保险起见,还可以写一个中序遍历验证是否满足二叉搜索树:
public void inOrder(RBTreeNode root) {
if(root == null) {
return ;
}
inOrder(root.left);
System.out.print(root.val+" ");
inOrder(root.right);
}
最后结果如下:
要经过多次尝试才可以成功啊!!!