1.概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
2.性质
最长路径最多是最短路径的2倍
每个结点不是红色就是黑色
根节点是黑色的
如果一个结点是红色的,则它的俩个孩子结点是黑色的【没有俩个连续的红色结点】
对于每个结点,从该节点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
3.实现红黑树
新增的结点不能是黑色的,因为如果是黑色的,那么就需要保证每条路径上的黑色结点必须是相同的。弄不好还得新增加一堆黑色结点。
所以我们新增的结点默认是红色的。
package test;
enum COLOR {
RED, BLACK
}
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode parent;
COLOR color;
public TreeNode(int val) {
this.val = val;
this.color = COLOR.RED;
}
}
public class RBTree {
public TreeNode root;
public boolean insert(int val) {
TreeNode node = new TreeNode(val);
if (root == null) {
root = node;
root.color = COLOR.BLACK;
return true;
}
TreeNode cur = root;
TreeNode pre = null;
while (cur != null) {
if (cur.val < val) {
pre = cur;
cur = cur.right;
} else if (cur.val > val) {
pre = cur;
cur = cur.left;
} else {
return false;
}
}
if (pre.val < val) {
pre.right = node;
} else {
pre.left = node;
}
node.parent = pre;
cur = node;
while (pre != null && pre.color == COLOR.RED) {
TreeNode grandFather = pre.parent;
if (pre == grandFather.left) {
TreeNode uncle = grandFather.right;
if (uncle != null && uncle.color == COLOR.RED) {
pre.color = COLOR.BLACK;
uncle.color = COLOR.BLACK;
grandFather.color = COLOR.RED;
cur = grandFather;
pre = cur.parent;
} else {
//uncle不存在 或者uncle是黑色的
//情况三
if (cur == pre.right) {
rotateLeft(pre);
TreeNode tmp = pre;
pre = cur;
cur = tmp;
}//
//情况二
rotateRight(grandFather);
grandFather.color = COLOR.RED;
pre.color = COLOR.BLACK;
}
} else {
//pre == grandFather.left
TreeNode uncle = grandFather.left;
if (uncle != null && uncle.color == COLOR.RED) {
pre.color = COLOR.BLACK;
uncle.color = COLOR.BLACK;
grandFather.color = COLOR.RED;
cur = grandFather;
pre = cur.parent;
} else {
if (cur == pre.left) {
rotateRight(pre);
TreeNode tmp = pre;
pre = cur;
cur = tmp;
}
rotateLeft(grandFather);
grandFather.color = COLOR.RED;
pre.color = COLOR.BLACK;
}
}
}
root.color = COLOR.BLACK;
return true;
}
//左单旋
public void rotateLeft(TreeNode pre) {
TreeNode subR = pre.right;
TreeNode subRL = subR.left;
pre.right = subRL;
subR.left = pre;
if (subRL != null) {
subRL.parent = pre;
}
TreeNode pPre = pre.parent;
pre.parent = subR;
if (root == pre) {
root = subR;
root.parent = null;
} else {
if (pPre.left == pre) {
pPre.left = subR;
} else {
pPre.right = subR;
}
subR.parent = pPre;
}
}
//右单旋
public void rotateRight(TreeNode pre) {
TreeNode subL = pre.left;
TreeNode subLR = subL.right;
pre.left = subLR;
subL.right = pre;
//没有subLR的时候
if (subLR != null) {
subLR.parent = pre;
}
TreeNode pPre = pre.parent;
pre.parent = subL;
//检查当前是不是根节点
if (pre == root) {
root = subL;
root.parent = null;
} else {
//不是根节点,判断这棵树是左子树还是右子树
if (pPre.left == pre) {
pPre.left = subL;
} else {
pPre.right = subL;
}
subL.parent = pPre;
}
}
//判断当前树是不是红黑树
public boolean isRBTree() {
if (root == null) {
//如果一棵树是空树,那么这棵树就是红黑树
return true;
}
if (root.color != COLOR.BLACK) {
System.out.println("违反了性质,根节点必须是黑色的!");
}
//最左边路径黑色结点的个数
int blackNum = 0;
TreeNode cur = root;
while (cur != null) {
if (cur.color == COLOR.BLACK) {
blackNum++;
}
cur = cur.left;
}
//检查是否存在俩个连续的红色结点
return checkRedColor(root) && checkBlackNum(root, 0, blackNum);
}
/**
* @param root
* @param pathBlackNum 每次递归的时候计算黑色结点的个数
* @param blackNum 实现计算好的某条路径上的黑色结点个数
* @return
*/
public boolean checkBlackNum(TreeNode root, int pathBlackNum, int blackNum) {
if (root == null) {
return true;
}
if (root.color == COLOR.BLACK) {
pathBlackNum++;
}
if (root.left == null && root.right == null) {
if (pathBlackNum != blackNum) {
System.out.println("违反了性质:每条路径上黑色结点个数是不一样的");
return false;
}
}
return checkBlackNum(root.left, pathBlackNum, blackNum) && checkBlackNum(root.right, pathBlackNum, blackNum);
}
public boolean checkRedColor(TreeNode root) {
if (root == null) {
return true;
}
if (root.color == COLOR.RED) {
TreeNode pre = root.parent;
if (pre.color == COLOR.RED) {
System.out.println("违反了性质:连续俩个红色的结点连在一起");
return false;
}
}
return checkRedColor(root.left) && checkRedColor(root.right);
}
public void inorder(TreeNode root) {
if (root == null) {
return;
}
inorder(root.left);
System.out.print(" " + root.val + " ");
inorder(root.right);
}
public static void main(String[] args) {
int[] array = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
RBTree rbTree = new RBTree();
for (int i = 0; i < array.length; i++) {
rbTree.insert(array[i]);
}
rbTree.inorder(rbTree.root);
System.out.println();
System.out.println(rbTree.isRBTree());
}
}
4.AVL树和红黑树比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log2^N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
5.红黑树应用
java集合框架中的:TreeMap、TreeSet底层使用的就是红黑树
未完待续~