文章目录
- 前言
- 一、红黑树是什么?
- 二、代码实现
- 1.构建存放键值对的节点类
- 2.构建树节点类
- 3. 插入方法
- 4.红黑树平衡
- 5.左旋、右旋和交换颜色
- 8.测试验证
- 总结
前言
java8的HashMap中,使用了红黑树,本文主要是通过手写红黑树插入和查找代码来理解其特性和作用。
一、红黑树是什么?
红黑树是一种数据结构,如果学过数据结构的同学,应该会比较了解,红黑树是一种平衡二叉树,是有234树转变而来。没学过的同学,只需要知道以下两点:
- 作用:有序且规则的结构,在HashMap中主要是方便查找元素
- 消耗:插入和删除的消耗相对较少(比平衡二叉树少,比链表多),查找的消耗相对较少(比链表少,比平衡二叉树多)。是一种折中或综合性的策略。
二、代码实现
1.构建存放键值对的节点类
/**
* 需要一个Node类来存放KV,且该Node可以指向下个Node(链表结构)
* 这里直接实现了Map.Entry接口,也可以自己手动写一个Entry接口
*
* @param <K> 键
* @param <V> 值
*/
class Node<K, V> implements Map.Entry<K, V>{
Node<K, V> next;
final K key;
V value;
int hash;
public Node(Node<K, V> next, K key, V value, int hash) {
this.next = next;
this.key = key;
this.value = value;
this.hash = hash;
}
@Override
public K getKey() {
return this.key;
}
@Override
public V getValue() {
return this.value;
}
@Override
public V setValue(V value) {
return this.value=value;
}
@Override
public boolean equals(Object obj) {
return (obj instanceof Node) &&
((Node)obj).key.equals(this.key) &&
((Node)obj).value.equals(this.value);
}
}
2.构建树节点类
继承Node,创建红黑树节点。代码如下(示例):
/**
* 红黑树节点:
* 有颜色red
* 有左右叶子结点 left、right
* 有父节点 superior
* @param <K>
* @param <V>
*/
class TreeNode<K,V> extends Node<K,V>{
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> superior;
boolean red;
public TreeNode(Node<K, V> node){super(node.next,node.key,node.value,node.hash);}
}
3. 插入方法
/**
* 该方法包含递归,从根节点开始,每次比对大小进行分路(左右),有空位即临时插入。
* 然后进行红黑树平衡操作
*
* @param superior 需要比较大小父节点
* @param newTNode 新插入的节点
* @return 返回平衡操作后的节点
*/
private TreeNode<K, V> insertTreeNode(TreeNode<K,V> superior, TreeNode<K,V> newTNode) {
newTNode.red = true;
boolean left;//插入方向
if(compareTreeNode(superior.key,newTNode.key)){
logger.trace("节点"+newTNode.key+"小于节点"+superior.key+",向左子树移动");
if(superior.left == null){
superior.left = newTNode;
newTNode.superior = superior;
logger.trace("节点"+newTNode.key+"临时插入"+superior.key+"的左子节点");
}else{
TreeNode<K,V> tempGrand = superior.superior;
superior = insertTreeNode(superior.left,newTNode);
superior.superior = tempGrand;
if(tempGrand != null) {
tempGrand.left = superior;
}
newTNode = superior.left;
}
left = true;
}else{
logger.trace("节点"+newTNode.key+"大于节点"+superior.key+",向右子树移动");
if(superior.right == null){
superior.right = newTNode;
newTNode.superior = superior;
logger.trace("节点"+newTNode.key+"临时插入"+superior.key+"的右子节点");
}else{
TreeNode<K,V> tempGrand = superior.superior;
superior = insertTreeNode(superior.right,newTNode);
superior.superior = tempGrand;
if(tempGrand != null)
tempGrand.right = superior;
newTNode = superior.right;
}
left = false;
}
return balanceRBTree(superior, newTNode, left);
}
4.红黑树平衡
黑红树的平衡实际上每次只对三层内的节点操作,如下图
1、
2、
3、
4、
代码如下:
/**
* 对三代节点做操作,保持红黑树平衡
*
* 红黑树平衡操作规律:
* 插入a节点
* <p>1.若插入后父节点是红色,且有红色叔节点,则爷节点变红,父叔节点变黑,若爷节点是根节点,变黑</p>
* <p>2.若操作后父节点是红色,且没有红色叔节点,且a和父节点同向,则父和爷节点左/右旋,互换颜色</p>
* <p>3.若操作后父节点是红色,且没有红色叔节点,且a和父节点反向,则先a和父节点左/右旋,再父和爷节点左/右旋,互换颜色</p>
* <p>其他直接插入</p>
*
* @param superior
* @param newTNode
* @param left
* @return
*/
private TreeNode<K, V> balanceRBTree(TreeNode<K, V> superior, TreeNode<K, V> newTNode, boolean left) {
TreeNode<K, V> grand,lUncle,rUncle,temp;
grand = superior.superior;
if(grand != null) {
lUncle = superior.superior.left;
rUncle = superior.superior.right;
//若操作后父节点是红色
if(superior.red && newTNode.red){
//若有红叔节点
if(lUncle != null && rUncle != null && lUncle.red && rUncle.red){
blackDown(grand,lUncle,rUncle);
logger.trace("节点"+rUncle.key+"和"+lUncle.key+"变黑,"+grand.key+"节点变红");
}else {
if (superior != (left ? superior.superior.left : superior.superior.right)) {//父节点是反向节点
//向父节点的方向旋
logger.trace("节点" + superior.key + (left ? "右旋" : "左旋"));
superior = left ? rotateRight(superior, newTNode) : rotateLeft(superior, newTNode);
//父爷节点再旋回来,互换颜色
logger.trace("节点" + grand.key + (left ? "左旋" : "右旋"));
temp = grand;//假设superior——>a地址内存,temp,grand——>b地址内存
grand = left ? rotateLeft(grand, superior) : rotateRight(grand, superior);//grand——>a地址内存
grand.red = false;
temp.red = true;//temp依旧指向——>b地址内存
logger.trace("节点" + temp.key + "变红,节点"+grand.key+"变黑");
} else {//父子节点方向相同
//父爷节点左旋,互换颜色
logger.trace("节点" + grand.key + (left ? "右旋" : "左旋"));
temp = grand;
grand = left ? rotateRight(grand, superior) : rotateLeft(grand, superior);
grand.red = false;
temp.red = true;//temp依旧指向——>b地址内存
logger.trace("节点" + temp.key + "变红,节点"+grand.key+"变黑");
}
}
}
return grand;
}else{
//根节点变黑
superior.red = false;
logger.trace("返回根节点(黑色):" + superior.key);
return superior;
}
}
5.左旋、右旋和交换颜色
/**
* 左旋,右子节点升为父节点,原父节点变成左子节点。原爷节点变为右节点的父节点
* 若原右节点的左子节点变成原父节点的右子节点。
* @param superior
* @param right
* @return
*/
private TreeNode<K,V> rotateLeft(TreeNode<K,V> superior, TreeNode<K,V> right) {
TreeNode<K,V> tempLeft = right.left;
right.left=superior;
right.superior = superior.superior;
superior.right = tempLeft;
superior.superior = right;
if(tempLeft != null)
tempLeft.superior = superior;
return right;
}
/**
* 右旋,参考左旋
* @param superior
* @param left
* @return
*/
private TreeNode<K,V> rotateRight(TreeNode<K,V> superior, TreeNode left) {
TreeNode<K,V> tempRight = left.right;
left.right=superior;
left.superior = superior.superior;
superior.left = tempRight;
superior.superior = left;
if(tempRight != null)
tempRight.superior = superior;
return left;
}
/**
* 交换颜色
*
* 父节点变红,子节点变黑
* @param grand
* @param lUncle
* @param rUncle
*/
private void blackDown(TreeNode<K,V> grand, TreeNode<K,V> lUncle, TreeNode<K,V> rUncle) {
grand.red = true;
lUncle.red = false;
rUncle.red = false;
}
8.测试验证
实际上我做了大量的单元测试,抽其中一段代码展示
@Test
void insertTreeNode_6() throws Exception {
//反射私有方法
testInsertTreeNode =
MyHashMap.class.getDeclaredMethod("insertTreeNode", MyHashMap.TreeNode.class, MyHashMap.TreeNode.class);
testInsertTreeNode.setAccessible(true);
//创建节点
node21 = myHashMap.new<String,String> Node<String,String>(null,"21","21",(int) testHash.invoke(myHashMap,"1"));
node31 = myHashMap.new<String,String> Node<String,String>(null,"31","31",(int) testHash.invoke(myHashMap,"21"));
node11 = myHashMap.new<String,String> Node<String,String>(null,"11","11",(int) testHash.invoke(myHashMap,"22"));
node10 = myHashMap.new<String,String> Node<String,String>(null,"10","10",(int) testHash.invoke(myHashMap,"22"));
node12 = myHashMap.new<String,String> Node<String,String>(null,"12","12",(int) testHash.invoke(myHashMap,"22"));
node32 = myHashMap.new<String,String> Node<String,String>(null,"32","32",(int) testHash.invoke(myHashMap,"31"));
node22 = myHashMap.new<String,String> Node<String,String>(null,"22","22",(int) testHash.invoke(myHashMap,"32"));
node33 = myHashMap.new<String,String> Node<String,String>(null,"33","33",(int) testHash.invoke(myHashMap,"32"));
node30 = myHashMap.new<String,String> Node<String,String>(null,"30","30",(int) testHash.invoke(myHashMap,"32"));
t21 = myHashMap.new<String,String> TreeNode<String,String>(node21);
t31 = myHashMap.new<String,String> TreeNode<String,String>(node31);
t11 = myHashMap.new<String,String> TreeNode<String,String>(node11);
t10 = myHashMap.new<String,String> TreeNode<String,String>(node10);
t12 = myHashMap.new<String,String> TreeNode<String,String>(node12);
t22 = myHashMap.new<String,String> TreeNode<String,String>(node22);
t32 = myHashMap.new<String,String> TreeNode<String,String>(node32);
t33 = myHashMap.new<String,String> TreeNode<String,String>(node33);
t30 = myHashMap.new<String,String> TreeNode<String,String>(node30);
//构造树
/*
黑11
/ \
黑10 红21
/ \
黑12 黑30
/ \
红22 红31
*/
t11.left = t10;
t11.right = t21;
t11.red = false;
t10.superior = t11;
t10.red = false;
t21.superior = t11;
t21.red = true;
t21.left=t12;
t21.right=t30;
t12.superior=t21;
t12.red=false;
t30.superior=t21;
t30.left=t22;
t30.right=t31;
t30.red=false;
t22.superior=t30;
t22.red=true;
t31.superior=t30;
t31.red=true;
/*插入32节点:
黑11 黑11 黑21
/ \ / \ / \
黑10 红21 黑10 红21 红11 红30
/ \ ————> / \ ————> / \ / \
黑12 黑30 黑12 黑30 黑10 黑12 黑22 黑31
/ \ / \ \
红22 红31 红22 红31 红32
\
红32
*/
MyHashMap<String,String>.TreeNode<String,String> top = (MyHashMap<String, String>.TreeNode<String, String>) testInsertTreeNode.invoke(myHashMap, t11, t32);
assertAll("红黑树平衡测试:",
()->assertEquals(t21.key,top.key),
()->assertEquals(false,t21.red),
()->assertEquals(t11.key, top.left.key),
()->assertEquals(true,top.left.red),
()->assertEquals(t10.key, top.left.left.key),
()->assertEquals(false,top.left.left.red),
()->assertEquals(t12.key, top.left.right.key),
()->assertEquals(false,top.left.right.red),
()->assertEquals(t30.key, top.right.key),
()->assertEquals(true,top.right.red),
()->assertEquals(t22.key, top.right.left.key),
()->assertEquals(false,top.right.left.red),
()->assertEquals(t31.key, top.right.right.key),
()->assertEquals(false,top.right.right.red),
()->assertEquals(t32.key, top.right.right.right.key),
()->assertEquals(true ,top.right.right.right.red),
()->assertNull(top.left.left.left),
()->assertNull(top.left.left.right),
()->assertNull(top.left.right.left),
()->assertNull(top.left.right.right),
()->assertNull(top.right.left.left),
()->assertNull(top.right.left.right),
()->assertNull(top.right.right.left),
()->assertNull(top.right.right.right.left),
()->assertNull(top.right.right.right.right)
);
}
测试结果为绿色通过。
总结
文中为了方便理解和阅读,代码中大量使用了递归,递归的效率会比使用迭代差。实际上源码主要是用的迭代方式处理,而且还包含了删除等其他操作,相比较更为复杂。