您的位置:首页 > 理论基础 > 数据结构算法

java数据结构和算法06(红黑树)

2019-05-10 22:28 1296 查看

  这一篇我们来看看红黑树,首先说一下我啃红黑树的一点想法,刚开始的时候比较蒙,what?这到底是什么鬼啊?还有这种操作?有好久的时间我都缓不过来,直到我玩了两把王者之后回头一看,好像有点儿意思,所以有的时候碰到一个问题困扰了很久可以先让自己的头脑放松一下,哈哈!

  不瞎扯咳,开始今天的正题;

  前提:看红黑树之前一定要先会搜索二叉树

1.红黑树的概念

  红黑树到底是个什么鬼呢?我最开始也在想这个问题,你说前面的搜索二叉树多牛,各种操作效率也不错,用起来很爽啊,为什么突然又冒出来了红黑树啊?

  确实,搜索二叉树一般情况下足够了,但是有个很大的缺陷,向搜索二叉树中插入的数据必须是随机性比较强大的;如果你是插入的顺序是按照一定的顺序的,比如10、9、8、7、6、5、4、3、2、1,你把这十个数据插入到搜索二叉树中你就会看到一个比较有趣的现象;玛德,这二叉树居然变成链表了(此时的链表也可以说是不平衡树),这就意味着变成链表之后就丧失了身为搜索二叉树的所有特性,这就很可怕,而且当这种有顺序的数据很多的时候,就特别坑爹,查询的效率贼慢;

  所以就出现了红黑树这种数据结构,可以说这是一种特殊的搜索二叉树,是对搜索二叉树进行改进之后的一种很完美的二叉树,这种数据结构最厉害的就是可以自动调整树的结构,就比如上面这种有顺序的数据插入到红黑树之后,红黑树就会自动的啪啪啪给你一顿调节最后还是一棵正常的搜索二叉树,不会变成链表就对了;

  那么就有人要问了,要怎么样才能将一个搜索二叉树变成红黑树呢?

  答:这很容易回答,字如其名,你把搜索二叉树的每个节点要么涂成红色要么涂成黑色,使得最后这个二叉树中所有节点只有红黑两种颜色,这就是一个红黑树;

  这时还有人要问了,是不是可以随意把搜索二叉树中的节点涂成红色或者黑色呢?

  答:emmmm.....你觉得有这么容易么?哪有这么随便的!肯定是要符合一些规则你才能涂啊,而且大佬们已经把这些规则总结出来了,我们只需要记好这些笔记就好了!

  下面我们就看看红黑树要满足的规则:

  (1):每个节点不是红色就是黑色;

  (2):根节点总是黑色;

  (3):不能有两个连续的红色节点;

  (4):从根节点到每一个叶节点或空子节点的黑色节点的数量一定要相同,这个黑色节点的数量叫做黑色高度,所以这个规则换句话来说就是根节点到每一个叶节点或空子节点的黑色高度相等;

  这四个规则很重要,任何红黑树都必须同时满足这四个规则,否则就不是红黑树,前三个很容易,话说第四个的空子节点是什么意思呢?字如其名,就是一个空的节点,里面什么都没有,可以当作一个null节点,比如下图所示,这个其实理解就好,不用在意;

  第四条规则为了好理解才从根节点开始的,其实从任意一个节点开始也是一样的;可以拆分为两条,某个节点到该节点每一个叶节点的黑色高度要一样,同时还要该节点到该节点的每一个空子节点的黑色高度要一样;

public class RBTree<T extends Comparable<T>> {

private RBTNode<T> mRoot;    // 根结点

private static final boolean RED   = false;
private static final boolean BLACK = true;

public class RBTNode<T extends Comparable<T>> {
boolean color;        // 颜色
T key;                // 关键字(键值)
RBTNode<T> left;    // 左孩子
RBTNode<T> right;    // 右孩子
RBTNode<T> parent;    // 父结点

public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {
this.key = key;
this.color = color;
this.parent = parent;
this.left = left;
this.right = right;
}

}

...
}
View Code  

 右旋

/*
* 对红黑树的节点(y)进行右旋转
*
* 右旋示意图(对节点y进行左旋):
*            py                               py
*           /                                /
*          y                                x
*         /  \      --(右旋)-.            /  \                     #
*        x   ry                           lx   y
*       / \                                   / \                   #
*      lx  rx                                rx  ry
*
*/
private void rightRotate(RBTNode<T> y) {
// 设置x是当前节点的左孩子。
RBTNode<T> x = y.left;

// 将 “x的右孩子” 设为 “y的左孩子”;
// 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”
y.left = x.right;
if (x.right != null)
x.right.parent = y;

// 将 “y的父亲” 设为 “x的父亲”
x.parent = y.parent;

if (y.parent == null) {
this.mRoot = x;            // 如果 “y的父亲” 是空节点,则将x设为根节点
} else {
if (y == y.parent.right)
y.parent.right = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
else
y.parent.left = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”
}

// 将 “y” 设为 “x的右孩子”
x.right = y;

// 将 “y的父节点” 设为 “x”
y.parent = x;
}
View Code

  

左旋

/*
* 对红黑树的节点(x)进行左旋转
*
* 左旋示意图(对节点x进行左旋):
*      px                              px
*     /                               /
*    x                               y
*   /  \      --(左旋)-.           / \                #
*  lx   y                          x  ry
*     /   \                       /  \
*    ly   ry                     lx  ly
*
*
*/
private void leftRotate(RBTNode<T> x) {
// 设置x的右孩子为y
RBTNode<T> y = x.right;

// 将 “y的左孩子” 设为 “x的右孩子”;
// 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”
x.right = y.left;
if (y.left != null)
y.left.parent = x;

// 将 “x的父亲” 设为 “y的父亲”
y.parent = x.parent;

if (x.parent == null) {
this.mRoot = y;            // 如果 “x的父亲” 是空节点,则将y设为根节点
} else {
if (x.parent.left == x)
x.parent.left = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
else
x.parent.right = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
}

// 将 “x” 设为 “y的左孩子”
y.left = x;
// 将 “x的父节点” 设为 “y”
x.parent = y;
}
View Code

  

插入节点

/*
* 将结点插入到红黑树中
*
* 参数说明:
*     node 插入的结点        // 对应《算法导论》中的node
*/
private void insert(RBTNode<T> node) {
int cmp;
RBTNode<T> y = null;
RBTNode<T> x = this.mRoot;

// 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。
while (x != null) {
y = x;
cmp = node.key.compareTo(x.key);
if (cmp < 0)
x = x.left;
else
x = x.right;
}

node.parent = y;
if (y!=null) {
cmp = node.key.compareTo(y.key);
if (cmp < 0)
y.left = node;
else
y.right = node;
} else {
this.mRoot = node;
}

// 2. 设置节点的颜色为红色
node.color = RED;

// 3. 将它重新修正为一颗二叉查找树
insertFixUp(node);
}

/*
* 新建结点(key),并将其插入到红黑树中
*
* 参数说明:
*     key 插入结点的键值
*/
public void insert(T key) {
RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null);

// 如果新建结点失败,则返回。
if (node != null)
insert(node);
}

/*
* 红黑树插入修正函数
*
* 在向红黑树中插入节点之后(失去平衡),再调用该函数;
* 目的是将它重新塑造成一颗红黑树。
*
* 参数说明:
*     node 插入的结点        // 对应《算法导论》中的z
*/
private void insertFixUp(RBTNode<T> node) {
RBTNode<T> parent, gparent;

// 若“父节点存在,并且父节点的颜色是红色”
while (((parent = parentOf(node))!=null) && isRed(parent)) {
gparent = parentOf(parent);

//若“父节点”是“祖父节点的左孩子”
if (parent == gparent.left) {
// Case 1条件:叔叔节点是红色
RBTNode<T> uncle = gparent.right;
if ((uncle!=null) && isRed(uncle)) {
setBlack(uncle);
setBlack(parent);
setRed(gparent);
node = gparent;
continue;
}

// Case 2条件:叔叔是黑色,且当前节点是右孩子
if (parent.right == node) {
RBTNode<T> tmp;
leftRotate(parent);
tmp = parent;
parent = node;
node = tmp;
}

// Case 3条件:叔叔是黑色,且当前节点是左孩子。
setBlack(parent);
setRed(gparent);
rightRotate(gparent);
} else {    //若“z的父节点”是“z的祖父节点的右孩子”
// Case 1条件:叔叔节点是红色
RBTNode<T> uncle = gparent.left;
if ((uncle!=null) && isRed(uncle)) {
setBlack(uncle);
setBlack(parent);
setRed(gparent);
node = gparent;
continue;
}

// Case 2条件:叔叔是黑色,且当前节点是左孩子
if (parent.left == node) {
RBTNode<T> tmp;
rightRotate(parent);
tmp = parent;
parent = node;
node = tmp;
}

// Case 3条件:叔叔是黑色,且当前节点是右孩子。
setBlack(parent);
setRed(gparent);
leftRotate(gparent);
}
}

// 将根节点设为黑色
setBlack(this.mRoot);
}
View Code

  

参考大佬博客:https://www.cnblogs.com/skywang12345/p/3624343.html

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: