HashMap的红黑树
HashMap中的红黑树源码
了解红黑树,首先要了解234树
234树
-
是一颗满树,就是,一定会挂满叶子节点,不会缺少
-
有2节点,3节点,4节点
-
2节点可以挂2个子节点,3节点可以挂3个子节点,四节点可以挂4个子节点
234树转化成红黑树与红黑树的对应关系
234树转化成红黑树,根据转换关系,会有两种情况,其实就是第一种情况左旋后得到的
红黑树的特性来源
- 根节点一定是黑色。原因:对照234树转换关系,发现转换后永远都是上黑下红,所以根节点一定是黑色
- 节点要么红,要么黑。原因:根据对照关系,只有红色和黑色节点,所以节点要么红要么黑
- 叶子节点一定是黑色。原因:红黑树的叶子节点包括空节点,上图9不是叶子节点,9下面还有两个黑色的空节点没画出来,所以叶子节点一定是黑色
- 每一个红色节点一定会连两个黑色子节点,也就是不能有两个红色节点相连。原因:由转换图可以看出当转换3节点时,3节点下面那个节点就是红的,然后3节点可以连3个子节点,这三个子节点只有三种情况,1:是一个单独的2节点,根据转换图,二节点转换成红黑树是黑色,所以红色节点下面会连一个黑色了,2:是一个3节点,根据转换图,三节点转化成红黑树有两种情况,但是这两种情况都是上黑下红,转换后连到红色节点的一定是黑色,所以红色节点下面也会连黑色,3:是一个4节点,根据转换图,4节点转换成红黑树是上黑下两红,连上红节点的还是黑色。所以无论如何红色节点下面一定连着两个黑色子节点。注意:当是叶子节点时,红色节点下面也有两个看不见的黑色空节点,不能忽略,所以还是满足的。
- 从任意一个节点到它的叶子节点的路径上黑色节点的个数总是相同的(黑色平衡)。原因:红黑树之前是一颗234树,而234树一定是满树,就是他的每个子节点一定是有的,不会缺,再由转换图可以看出,不管是2节点,3节点,4节点,转换成红黑树后黑色节点的个数都是1,也就是说此时如果只看黑色节点,那么这颗234树可以看成是一颗满2叉树,满二叉树,那么每条路径上的节点个数肯定相同,所以,转换成红黑树后,每条路径上的黑色节点的个数也一定是相同的。
红黑树添加操作
红黑树的添加要对比234树的添加,在红黑树山的添加操作,本质上就是在234树上添加元素,然后添加以后只要不违反那几条转换规则就算添加成功。
-
在2节点添加元素,直接添加
-
在3节点添加元素,需要考虑如图四种情况
-
在4节点添加元素,只需要将元素改为裂变态,然后指针上移到爷爷节点,进行循环平衡就可以了
总之,红黑树的添加操作本质上就是在添加后依然维持上面的那三种转换关系,这样就算达到平衡
put方法
public V put(K key, V value) {
//调用putValue存值
return putVal(hash(key), key, value, false, true);
}
hash方法
static final int hash(Object key) {
int h;
//让hash高16位和低16位进行异或得到hash值,这样得到的hash比较散列
//保证键可以均匀的分布在桶上面
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
putValue方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict){
Node<K, V>[] tab;
Node<K, V> p;
int n, i;
//表是空的,用resize创建一个新表
if ((tab = table) == null || (n = tab.length) == 0) {
n = (tab = resize()).length;
}
//当前索引位置没有东西,直接放上去
if((p=tab[i=(n-1)&hash])==null){
tab[i]=newNode(hash,key,value,null);
}
else{
//当前索引有值,则需要按key来查找,找到的话就将值给覆盖,
//没找到就插入一个节点
Node<K,V> e;K k;
//hash相同key相同,找到了
if(p.hash==hash&&((k=p.key)==key||(key!=null&&key.equals(k)))){
e=p;
}else if(p instanceof TreeNode){
//如果是一个树节点,则插入到树上然后返回或者找到节点返回
e=(( TreeNode<K,V>)p).putTreeVal(this,tab,hash,key,value);
}
else{
//说明这个索引上的值不是一颗红黑树,是一条链表,
//需要遍历链表来增加节点,bitcount是节点的个数
for(int binCount=0;;++binCount){
if((e=p.next)==null){
//这里说明遍历了一整条链表都没有找到节点
//所以直接创建一个新节点放在链表的尾部
p.next=newNode(hash,key,value,null);
//TREEIFY_THRESHOLD,最小树化阈值,默认为8
if(binCount>=TREEIFY_THRESHOLD-1){
//增加节点后链表长度大于最小树化阈值,
//则树化,将链表转为红黑树
treeifyBin(tab,hash);
}
break;
}
//hash相同key相同,找到了
if(e.hash==hash&&((k=e.key)==key||(key!=null&&key.equals(k)))){
break;
}
//每次循环向后移动一个节点
p=e;
}
}
//寻找结束,判断是否找到
if(e!=null){
//找到了
V oldValue= e.value;
//可以替换值的话,就替换值
//onlyIfAbsent 如果当前位置已存在一个值,是否替换,
//false是替换,true是不替换
if(!onlyIfAbsent||oldValue==null){
e.value=value;
}
return oldValue;
}
}
++modCount;
//增加节点后,如果大小超过阈值,要扩容
if(++size>threshold){
resize();
}
return null;
}
resize方法
final Node<K, V>[] resize() {
//保存老的表
Node<K, V>[] oldTab = table;
//保存老的容量
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//保存老的阈值
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
//若老数组的容量已经到最大容量,则不再重新创建数组,而是设置阈值为最大值直接返回
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//若老容量的两倍还小于最大容量且老容量大于默认初始化容量,
//则可以将阈值扩大两倍给新阈值
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) {
newThr = oldThr << 1;
}
} else if (oldThr > 0) {
newCap = oldThr;
} else {
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int) DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY;
}
if (newThr == 0) {
float ft = (float) newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?(int) ft : Integer.MAX_VALUE);
}
threshold = newThr;
Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
table = newTab;
if (oldTab != null) {
//旧表的数据不为空,把旧表的数据导入新表
for(int j = 0; j < oldTab.length; ++j) {
Node<K, V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null) {
//next为空,说明在当前索引位置只有这一个元素,
//直接求出在新表的索引然后添加到新表即可
newTab[e.hash & (newCap - 1)] = e;
} else if (e instanceof TreeNode) {
//如果这是一棵树的话,需要将链表分化成两条链表然后重新放入新表
//分化,将老表的数据分化成两颗树插入新表
(( TreeNode<K, V>) e).split(this, newTab, j, oldCap);
} else {
//说明不是一棵树,就是一条链表,一样先分化成两条链表
//遍历链表然后添加到新表即可
Node<K, V> loHead = null, loTail = null;
Node<K, V> hiHead = null, hiTail = null;
Node<K, V> next;
do {
next = e.next;
//oldCap代表数组容量,此处必定是100000这样的数,
//因为hashmap的容量必须是2的n次幂,因此容量的二进制数一定是这样的
//所以相与结果要么0要么1,为0放在低链表,为1放在高链表
//所以Node在新数组的位置要么是原位置,要么是原位置加上旧数组容量值的位置
if ((e.hash & oldCap) == 0) {
if (loTail == null) {
//插入的是第一个元素
loHead = e;
} else {
//采用尾插法
loTail.next = e;
}
loTail = e;
} else {
//放在高链表
if (hiTail == null) {
hiHead = e;
} else {
hiTail.next = e;
}
hiTail = e;
}
} while((e = next) != null);
//判断链表不为空,然后低链表放在原索引位置,
//高链表放在原位置加上旧容量的位置
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
split方法:红黑树的链表分化
final void split(HashMap<K, V> map, Node<K, V>[] tab, int index, int bit) {
TreeNode<K, V> b = this;
//低位链表
TreeNode<K, V> loHead = null, loTail = null;
//高位链表
TreeNode<K, V> hiHead = null, hiTail = null;
//节点数
int lc = 0, hc = 0;
for( TreeNode<K, V> e = b, next; e != null; e = next) {
next = ( TreeNode<K, V>) e.next;
e.next = null;
//bit代表数组容量,此处必定是100000,因此结果要么0要么1,为0放在低链表,为1放在高链表
//所以TreeNode在新数组的位置要么是原位置,要么是原位置加上旧数组容量值的位置
//这里与普通链表分化不同的是,这里分化后是一个双向链表,而普通链表分化后则是单向链表
if ((e.hash & bit) == 0) {
//采用的还是尾插法,将e的前驱指向尾部
if ((e.prev = loTail) == null) {
loHead = e;
} else {
loTail.next = e;
}
loTail = e;
++lc;
} else {
if ((e.prev = hiTail) == null) {
hiHead = e;
} else {
hiTail.next = e;
}
hiTail = e;
++hc;
}
}
//判断低链表是否为空
if (loHead != null) {
//如果小于了链化阈值则需要解树化,将红黑树变成普通链表
//链化阈值为6
if (lc <= UNTREEIFY_THRESHOLD) {
//因为原来的节点上也是一条链表和树,所以现在要把这颗树解树化
tab[index] = loHead.untreeify(map);
} else {
//大于链化阈值,无须解树化,设置当前索引位置为低链表
tab[index] = loHead;
//由于高链表不为空,说明在分化时有的节点被分走了,这颗新树是不对的,
// 需要重新构建一个红黑树
if (hiHead != null) {
//将链表树化
loHead.treeify(tab);
}
}
}
//这里与上面相同
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD) {
tab[index + bit] = hiHead.untreeify(map);
} else {
tab[index + bit] = hiHead;
//低链不为空,说明分开了,也需要重新构建
if (loHead != null) {
hiHead.treeify(tab);
}
}
}
}
untreeify方法:将红黑树转化为链表
final Node<K, V> untreeify( HashMap<K, V> map) {
Node<K, V> hd = null, tl = null;
for(Node<K, V> q = this; q != null; q = q.next) {
//简单粗暴,直接拿节点新建一个链表节点返回,然后用新节点创建一条链表返回
Node<K, V> p = map.replacementNode(q, null);
if (tl == null) {
hd = p;
} else {
tl.next = p;
}
tl = p;
}
return hd;
}
replacementNode方法:创建链表节点返回
Node<K, V> replacementNode(Node<K, V> p, Node<K, V> next) {
return new Node<>(p.hash, p.key, p.value, next);
}
treeify方法:把链表转化成红黑树
final void treeify(Node<K, V>[] tab) {
//定义根节点
TreeNode<K, V> root = null;
//遍历当前链表
for( TreeNode<K, V> x = this, next; x != null; x = next) {
//保存后继节点
next = ( TreeNode<K, V>) x.next;
//左右孩子节点置空
x.left = x.right = null;
//如果为空,说明是插入的第一个节点
if (root == null) {
//这是第一个要树化的节点,根节点,父亲为空
x.parent = null;
//根节点设置为黑色
x.red = false;
root = x;
} else {
//不是第一个,要遍历当前树判断放这个节点在哪
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for( TreeNode<K, V> p = root; ; ) {
//dir,插入的方向,ph,遍历节点的hash
int dir, ph;
K pk = p.key;
//判断hash值得到往那边走,小于走左子树,大于,走右子树
if ((ph = p.hash) > h) {
dir = -1;
} else if (ph < h) {
dir = 1;
//hash相同,比较key
} else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) {
//当key比较也相同
//tieBreakOrder这个方法用系统的hash函数求得hash值来比较,
//且一定会返回-1或者1,不会返回0,保证节点一定有一个位置可以插入
dir = tieBreakOrder(k, pk);
}
TreeNode<K, V> xp = p;
//xp,遍历到的节点,x,要插入的节点
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//已经遍历到了叶子节点,直接把当前链表节点插入
//把链表节点的父亲设置为当前叶子节点
x.parent = xp;
//判断是放在叶子节点左边还是右边
if (dir <= 0) {
xp.left = x;
} else {
xp.right = x;
}
//插入后需要平衡插入,维持红黑树的黑色平衡
root = balanceInsertion(root, x);
break;
}
}
}
}
//将根节点移动到表的索引位置
//因为插入后不保证根节点与索引位置的节点是同一个节点
moveRootToFront(tab, root);
}
balanceInsertion方法:平衡插入
static <K, V> TreeNode<K, V> balanceInsertion(TreeNode<K, V> root,
TreeNode<K, V> x) {
//插入节点都是红色,所以设置x为红色
x.red = true;
for(TreeNode<K, V> xp, xpp, xppl, xppr; ; ) {
// x的父亲是空,说明插入的是根节点,把节点染黑直接返回
if ((xp = x.parent) == null) {
x.red = false;
return x;
//x的父亲是黑色,说明是在黑色节点下插入一个红色节点,红黑树在黑色节点下插入红色节点,无须调整
// 完全无影响,或者爷爷是空,说明父亲是根,根节点一定是黑色
//也完全没有影响,所以直接返回
} else if (!xp.red || (xpp = xp.parent) == null)
return root;
//下面这里是对称操作,当父亲是爷爷的左孩子--------------
if (xp == (xppl = xpp.left)) {
//这里是叔叔不为空且是红色的,当叔叔不为空,在234树就是一个4节点,此时的情况是:
//父亲,叔叔,自己,都是红,然后祖父是黑,需要变色
// 将爷爷,父亲,叔叔,三个节点改为裂变态(爷爷红,叔叔,父亲黑),然后x指向爷爷进行下一次循环
//因为爷爷变红以后相当于插入的节点是爷爷,此时需要指向爷爷然后进行循环进行平衡
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
} else {
//说明叔叔是空的,此时的情况是,自己,父亲,爷爷在一条直线上
/*
xpp(b)
/
xp(r)
/
x(r)
*/
if (x == xp.right) {
//此时是一种特殊情况,当x在父亲的右边,
// 此时还不在一条直线上,需要左旋一次调整父亲,自己,爷爷到一条线上,然后再右旋
//才能完成平衡
/*
xpp xpp
/ 左旋 / 右旋 x(b)
xp ===> x ==> /\
\ / xp(r) xpp(r)
x xp
*/
//沿着父亲左旋
root = rotateLeft(root, x = xp);
//重新赋值父亲和祖父,因为左旋了
xpp = (xp = x.parent) == null ? null : xp.parent;
}
//父亲不为空
if (xp != null) {
//此时右旋,右旋调整,把父亲置为黑色,然后爷爷红色,自己是红色,
//然后爷爷右旋就可以完成平衡操作
xp.red = false;
if (xpp != null) {
//把爷爷变成红色
xpp.red = true;
//右旋
root = rotateRight(root, xpp);
}
}
}
} else {//对称操作
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
} else {
if (x == xp.left) {
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
rotateLeft方法:左旋,让右孩子变成当前节点的父亲
/*
xpp xpp
/ 左旋 / 右旋 x(b)
xp ===> x ==> /\
\ / xp(r) xpp(r)
x xp
/ \
xl xl
*/
static <K, V> TreeNode<K, V> rotateLeft( TreeNode<K, V> root, TreeNode<K, V> p) {
TreeNode<K, V> r, pp, rl;
//左旋的节点不为空且左旋的右孩子不为空
if (p != null && (r = p.right) != null) {
//将r的左孩子设置为p的右孩子
if ((rl = p.right = r.left) != null) {
//将p设置为r的左孩子的父亲
rl.parent = p;
}
//将p的父亲设为r的父亲
//case1:p的父亲为空,则将r设为根
if ((pp = r.parent = p.parent) == null) {
(root = r).red = false;
//case2:若p是父节点的左孩子
} else if (pp.left == p) {
//将r设为p父节点的左孩子
pp.left = r;
//case3: p是父节点的右孩子
} else {
//将r设为p父节点的右孩子
pp.right = r;
}
//将p设为r的左孩子
r.left = p;
//将p的父节点设为r
p.parent = r;
}
return root;
}
rotateRight方法:右旋
static <K, V> TreeNode<K, V> rotateRight( TreeNode<K, V> root, TreeNode<K, V> p) {
TreeNode<K, V> l, pp, lr;
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null) {
lr.parent = p;
}
if ((pp = l.parent = p.parent) == null) {
(root = l).red = false;
} else if (pp.right == p) {
pp.right = l;
} else {
pp.left = l;
}
l.right = p;
p.parent = l;
}
return root;
}
putTreeVal方法:在红黑树上插入一个节点
final TreeNode<K, V> putTreeVal(HashMap<K, V> map, Node<K, V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
//标识是否被搜索过
boolean searched = false;
//保存根节点
TreeNode<K, V> root = (parent != null) ? root() : this;
//遍历整棵树,找到插入位置插入
for(TreeNode<K, V> p = root; ; ) {
int dir, ph;
K pk;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
//hash相同key相同,说明要插入的节点已经存在,返回节点
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
//还未被搜索过
TreeNode<K, V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
return q;
}
dir = tieBreakOrder(k, pk);
}
TreeNode<K, V> xp = p;
//找到了叶子节点,说明没有符合的key,则需要插入一个新节点
if ((p = (dir <= 0) ? p.left : p.right) == null) {
Node<K, V> xpn = xp.next;
TreeNode<K, V> x = map.newTreeNode(h, k, v, xpn);
if (dir <= 0)
xp.left = x;
else
xp.right = x;
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K, V>) xpn).prev = x;
//平衡插入的节点
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
treeifyBin方法:链表节点转换为红黑树节点
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n,index;Node<K,V> e;
//如果表的大小还没超过最小树化阈值64,则只进行扩容,不树化
if(tab==null||(n=tab.length)<MIN_TREEIFY_CAPACITY){
resize();
}else if((e=tab[index=(n-1)&hash])!=null){
TreeNode<K,V> hd=null,tl=null;
//树化步骤:首先将所有链表节点替换成树节点,然后重新生成一个双向链表
//最后调用树化方法树化
do{
TreeNode<K,V> p=replacementTreeNode(e,null);
if(tl==null){
hd=p;
}else{
p.prev=tl;
tl.next=p;
}
tl=p;
}while((e=e.next)!=null);
if((tab[index]=hd)!=null){
hd.treeify(tab);
}
}
}
红黑树删除操作
remove方法
public V remove(Object key) {
Node<K, V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
removeNode方法:查找要删除的节点,然后删除
1、删除的是叶子节点:直接删除
2、删除的节点有一个孩子:删除后孩子替代删除节点的位置
3、删除的节点有左右子树:先找到后继节点,然后交换后继节点和删除节点的值,最后删除后继节点,删除方式按1和2
final Node<K, V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K, V>[] tab;
Node<K, V> p;
//n,长度 index,索引
int n, index;
//****************找节点start********************
//表不为空且长度大于0且当前元素不为空
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K, V> node = null, e;
K k;
V v;
//case1当前数组的元素就是要找的,直接找到
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
//否则往下找
else if ((e = p.next) != null) {
//如果是树,按树的来找节点
if (p instanceof TreeNode)
//case2查找到树节点
node = ((TreeNode<K, V>) p).getTreeNode(hash, key);
else {
//否则不是树,按链表的来找
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
//case3
node = e;
break;
}
//注意!!!此时不断把p=e,所以p总是要删除节点的前驱节点
p = e;
} while((e = e.next) != null);
}
}
//***********找节点end*******************
//此时已经查完了,查找到要删除的节点了
//!matchvalue代表无须匹配值,或者需要匹配值且值也相等则进行删除操作
//也就是说,找到了节点且无须匹配值或者值是空的,或者值不为空但是是相等的
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
//若是红黑树节点,则按红黑树的删除来删除节点
((TreeNode<K, V>) node).removeTreeNode(this, tab, movable);
//说明是一个链表
else if (node == p)
//查到的这个节点就是数组索引的当前元素节点,对应上面的case1
tab[index] = node.next;
else
//node的前一个元素是p,对应case3,删除查找到的节点
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
getTreeNode方法:获取树节点
final TreeNode<K, V> getTreeNode(int h, Object k) {
//从根节点开始找
return ((parent != null) ? root() : this).find(h, k, null);
}
find方法:查找指定节点
final TreeNode<K, V> find(int h, Object k, Class<?> kc) {
TreeNode<K, V> p = this;
do {
int ph, dir;
K pk;
TreeNode<K, V> pl = p.left, pr = p.right, q;
//根据hash值判断查右子树还是左子树
if ((ph = p.hash) > h)
p = pl;
else if (ph < h)
p = pr;
//hash相同key相同,说明找到了,直接返回
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
//判断左右子树为空的情况,去另一边找
else if (pl == null)
p = pr;
else if (pr == null)
p = pl;
//左右子树不为空hash相同key不相同,则按类型来判断走哪边
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
//左右子树递归查找
else if ((q = pr.find(h, k, kc)) != null)
return q;
else
p = pl;
} while(p != null);
return null;
}
removeTreeNode方法:删除树节点
final void removeTreeNode(HashMap<K, V> map, Node<K, V>[] tab,
boolean movable) {
int n;
//如果表是空的或长度为0,直接返回
if (tab == null || (n = tab.length) == 0)
return;
//求出删除的节点所在的索引
int index = (n - 1) & hash;
//将默认根节点设置成头节点
TreeNode<K, V> first = (TreeNode<K, V>) tab[index], root = first, rl;
//保存后继和前驱
TreeNode<K, V> succ = (TreeNode<K, V>) next, pred = prev;
//完成作为链表的删除操作**********start**********
if (pred == null)
//前驱为空,说明删除的是头节点,重新设置first节点为后继,相当于删除了当前节点
tab[index] = first = succ;
else
//不为空就将前驱的后继设置为后继,也相当于删除了当前节点
pred.next = succ;
if (succ != null)
//后继不为空,再将后继的前驱设置为前驱,当前节点就真的删除了
succ.prev = pred;
//*************end*********************
if (first == null)
return;
//重新查找根节点,若这个索引节点不是根节点,
//因为之前把根节点设置成了first节点,但是first节点不一定就真的是根节点
if (root.parent != null)
root = root.root();
if (root == null
|| (movable
&& (root.right == null
|| (rl = root.left) == null
|| rl.left == null))) {
//这里是在删除当前节点后,树的节点太少的情况,
//要进行解树化,将红黑树转化为链表
/*根为空,说明没有节点
根的左右子树有一个为空,说明最多4个节点
根的左右子树的左右孩子有空的,说明最多6个节点
*/
tab[index] = first.untreeify(map); // too small
return;
}
//replacement:替换节点,p:删除节点,pl:左子树,pr:右子树
TreeNode<K, V> p = this, pl = left, pr = right, replacement;
if (pl != null && pr != null) {
//左右子树不为空,则按第3条规则来,去找后继
TreeNode<K, V> s = pr, sl;
//找后继找的是右子树的最左子节点
/*
2
/\
6(s)
/\
4
/\
(sl)1 5
*/
//找到右子树的最小左子节点
while((sl = s.left) != null) // find successor
s = sl;
//与当前节点交换颜色
//交换后继节点和删除节点的颜色,
// 最终的删除是删除后继节点,故平衡是否是以后继节点的颜色来判断的
//把当前节点的颜色变成后继节点的颜色
boolean c = s.red;
s.red = p.red;
p.red = c; // swap colors
TreeNode<K, V> sr = s.right;
TreeNode<K, V> pp = p.parent;
//当后继节点的父亲是当前要删除的节点
if (s == pr) { // p was s's direct parent
//直接交换两个节点,相当于交换颜色,key,value等数值
p.parent = s;
s.right = p;
} else {
//后继节点的父亲不是p,保存后继的父亲
TreeNode<K, V> sp = s.parent;
//将p节点移到后继节点的位置,交换s和p的位置
if ((p.parent = sp) != null) {
//判断后继节点是左孩子还是右孩子
if (s == sp.left)
sp.left = p;
else
sp.right = p;
}
//使p的右子树指向后继节点
if ((s.right = pr) != null)
pr.parent = s;
}
//p移到了s的位置,将左孩子置空,因为找的是后继节点,
// 左孩子是一定没有的
p.left = null;
//有右孩子,将右孩子指向p
if ((p.right = sr) != null)
sr.parent = p;
//p有左子树,将左子树指向s
if ((s.left = pl) != null)
pl.parent = s;
//s指向p的父亲,若为空,则是根节点,所以重新赋值根节点
if ((s.parent = pp) == null)
root = s;
//否则说明不是根节点,若是p父亲的左孩子则放在左边,右孩子则放在右边
else if (p == pp.left)
pp.left = s;
else
pp.right = s;
//这一步开始找替换节点start****************
//如果s右孩子不为空,则要替换,替换节点为右孩子
/* p(s)
/\
pl pr
/\
s(p)
\
sr
*/
if (sr != null)
replacement = sr;
else
//说明没有孩子,要删除的是叶子节点,就是自己,按规则1来删除,所以替换节点是自己
replacement = p;
//前面都建立在左右子树并不为空
// 此时是若左子树不为空但右子树是空的
} else if (pl != null)
//替换节点就是左孩子
replacement = pl;
//否则就是反过来,右子树不为空,左为空
else if (pr != null)
//替换节点就是右孩子
replacement = pr;
else
//否则左右子树都为空,删除的就是自己,按规则1,替换节点为自己
replacement = p;
//查找替换节点end********************
//当替换节点不为自己,说明需要平衡的节点和删除的节点不是同一个
// 则可以先把p删除再进行平衡
if (replacement != p) {
//替换节点的父亲置为p的父亲
TreeNode<K, V> pp = replacement.parent = p.parent;
if (pp == null)
//若是空的话,说明替换节点是根节点,重置根节点
root = replacement;
//不为空的话判断是左孩子还是右孩子,然后放在对应的位置
else if (p == pp.left)
pp.left = replacement;
else
pp.right = replacement;
//最后将p完全删除
p.left = p.right = p.parent = null;
}
//作为二叉搜索树的删除操作已经完成了,接下来是作为红黑树的平衡删除操作
//这里做了判断,若删除的是红色节点,则啥也不做,直接返回根,
//因为红黑树删除红色节点对树无影响,不需要平衡
//否则要进行平衡
TreeNode<K, V> r = p.red ? root : balanceDeletion(root, replacement);
//在平衡完成后,若平衡的是原来的节点p,则要把他删除,
// 之前不删是因为替换节点与删除节点相同,
// 要保留他的数据来做平衡使用,现在平衡完了,
// 发现要删除的就是p,那就得把他删掉
if (replacement == p) { // detach
TreeNode<K, V> pp = p.parent;
p.parent = null;
if (pp != null) {
if (p == pp.left)
pp.left = null;
else if (p == pp.right)
pp.right = null;
}
}
//若需要移动根节点到索引的位置,则进行移动
//因为有时候平衡完了,根节点不一定就会在tab[index]这个位置,可能在其他位置
if (movable)
moveRootToFront(tab, r);
}
balanceDeletion方法:在红黑树删除节点后进行平衡操作
static <K, V> TreeNode<K, V> balanceDeletion(TreeNode<K, V> root,
TreeNode<K, V> x) {
//遍历整棵树,完成平衡操作
for(TreeNode<K, V> xp, xpl, xpr; ; ) {
//若x是空或者是根节点,则直接返回,无须平衡
//x==root直接返回的原因是
/*
* p
* /
* root->rep(x)
* 像这种情况,直接把p删了,求出的rep是新的根,由于rep也是一个子树
* 一定是满足黑色平衡的,而此时他又是根,所以直接返回就行,无须平衡
* */
if (x == null || x == root)
return root;
else if ((xp = x.parent) == null) {
//要平衡的是根节点,直接把x置为黑色,然后就可以直接返回了
//因为根节点不论那条路径都要经过,所以直接把根节点置黑
//不会违反黑色平衡
x.red = false;
return x;
//这里判断是红色就是在下面有一个自损操作会递归到上一层,
// 若为红色,则置为黑色使得黑色平衡再次达到,然后返回
} else if (x.red) {
//若x是红色,则把x置黑,然后返回
x.red = false;
return root;
//这里开始是两个完全对称的操作,替换节点是左孩子------------
} else if ((xpl = xp.left) == x) {
//右孩子不为空且是红色,说明这个兄弟不是可以借节点给当前节点的真兄弟
//此时需要进行左旋,找到真正的兄弟节点
if ((xpr = xp.right) != null && xpr.red) {
//把兄弟置为黑色
xpr.red = false;
//父亲置为红色
xp.red = true;
//左旋
root = rotateLeft(root, xp);
//重新赋值兄弟
xpr = (xp = x.parent) == null ? null : xp.right;
}
//若兄弟节点为空,说明无节点可以借,则需要自损
// 往上走一层,直到找到一个节点是红色,把他变成黑色
//因为删除节点是红色的时候不会进来,所以x一定是黑色,
//删除一个黑色,此时他的兄弟没有可以借来用的黑色,黑色平衡不满足,怎么办呢
//如果兄弟是空的,那x指向父亲,进行下一次循环,直到找到一个兄弟有节点可以借
if (xpr == null)
x = xp;
else {
//兄弟不为空,说明可能有节点可以借
TreeNode<K, V> sl = xpr.left, sr = xpr.right;
//当左右两个孩子都是黑色,此时也是没有节点可以借,需要自损,向上面的操作一样
//不过此时兄弟是存在的,所以需要把兄弟染红,
//相当于删除了兄弟节点,然后指向父亲,递归到上一层,
//此时父亲又去找自己的兄弟看有没有能借来保持黑色平衡的
//如此往复,一直往上递归,直到找到为止
if ((sr == null || !sr.red) &&
(sl == null || !sl.red)) {
//将右节点置为红色,相当于删除了一个节点
xpr.red = true;
//递归到上一级
x = xp;
} else {
//此时说明兄弟有孩子可以借
/*
* xp(r) xp sl
* /\ 右旋 /\ 左旋 /\
* x s ==> x sl ==> xp s
* /\ \ /
* sl(r) nil s x
* */
//右子树为空或者为黑色(也相当于空)
if (sr == null || !sr.red) {
//左子树不为空,说明红色节点在兄弟的左孩子,
//需要先把孩子变黑,兄弟变红,然后沿着兄弟右旋,拉成一条直线,之后再左旋借给替换节点
if (sl != null)
//左孩子变黑
sl.red = false;
//兄弟节点变红
xpr.red = true;
//右旋
root = rotateRight(root, xpr);
//重新定位兄弟节点
xpr = (xp = x.parent) == null ?
null : xp.right;
}
//右旋后如果兄弟不为空,此时再次左旋就可以完成平衡操作
if (xpr != null) {
//将兄弟节点变为父亲的颜色
xpr.red = (xp == null) ? false : xp.red;
//兄弟的右孩子变成黑色
if ((sr = xpr.right) != null)
sr.red = false;
}
if (xp != null) {
//父亲变成黑色
xp.red = false;
//左旋
root = rotateLeft(root, xp);
}
//赋值为根节点,为了跳出循环
x = root;
}
}
} else { // symmetric对称的
//替换节点是右孩子,左孩子不为空且是红色,
// 需要右旋找到真正的兄弟
if (xpl != null && xpl.red) {
//兄弟置黑,父亲置红
xpl.red = false;
xp.red = true;
root = rotateRight(root, xp);
xpl = (xp = x.parent) == null ? null : xp.left;
}
if (xpl == null)
x = xp;
else {
TreeNode<K, V> sl = xpl.left, sr = xpl.right;
//若两个孩子节点都是黑色,说明也没有孩子可以借,直接递归到上一层
if ((sl == null || !sl.red) &&
(sr == null || !sr.red)) {
//自损,把兄弟置为红色
xpl.red = true;
x = xp;
} else {
//右孩子不为空
if (sl == null || !sl.red) {
if (sr != null)
//右孩子置为黑色
sr.red = false;
//兄弟节点置为红色
xpl.red = true;
//左旋
root = rotateLeft(root, xpl);
xpl = (xp = x.parent) == null ?
null : xp.left;
}
if (xpl != null) {
//先把兄弟节点的颜色设置成父亲的颜色
xpl.red = (xp == null) ? false : xp.red;
if ((sl = xpl.left) != null)
//把父亲和孩子置黑
sl.red = false;
}
if (xp != null) {
//把父亲和孩子置黑
xp.red = false;
//右旋
root = rotateRight(root, xp);
}
x = root;
}
}
}
}
}