之前几篇博客写了关于HashMap源码的分析,到现在就只剩关于红黑树的部分。红黑树是一种数据结构,它是基于二叉查找树的。红黑树很好的继承了二叉查找树快速插入删除以及查找元素的特性,又因为红黑树自平衡,所以又克服了二叉查找树在输入线性数据时退化为链表的缺点,因此被广泛使用。
java version 1.8
红黑树,顾名思义,有一个特征就是树的节点要么是红色,要么是黑色。而其本身又是自平衡的二叉查找树,除了满足二叉查找树的特征之外,还应满足以下特征,来保证自平衡性。
红黑树在插入节点时,通过这些特性来保持自身的平衡性。而保持平衡性的操作主要有三个,改变节点颜色,左旋,右旋。
惯例,先看源码
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent; // red-black tree linksTreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev; // needed to unlink next upon deletionboolean red;
}
TreeNode是HashMap的内部类,继承自LinkedHashMap.Entry,而LinkedHashMap.Entry继承自HashMap的Node类。它的几个属性parent指向红黑树中的父节点,left和right分别指向左右孩子节点,prev指向原链表中的上一个节点。
final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;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 = = p;}tl = p;} while ((e = e.next) != null);if ((tab[index] = hd) != ify(tab);}}
当hashmap数组中某位置链表的长度大于等于7时,该链表就被转化为红黑树,使用treeifyBin方法。
这个方法的作用是将链表的每个节点都转化为红黑树节点,并将单链表转化为双链表。之后使用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 = d = false;root = x;}else {K k = x.key;int h = x.hash;Class<?> kc = null;for (TreeNode<K,V> p = root;;) {int dir, ph;K pk = p.key;if ((ph = p.hash) > h)dir = -1;else if (ph < h)dir = 1;else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||(dir = compareComparables(kc, k, pk)) == 0)dir = tieBreakOrder(k, pk);TreeNode<K,V> xp = p;if ((p = (dir <= 0) ? p.left : p.right) == null) {x.parent = xp;if (dir <= 0)xp.left = x;elsexp.right = x;root = balanceInsertion(root, x);break;}}}}moveRootToFront(tab, root);}
treeify方法遍历双链表的每个节点,将每个节点插入到二叉查找树中,每插入一个节点,调用一次balanceInsertion方法,为红黑树做插入平衡处理。
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,TreeNode<K,V> x) {x.red = true;for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {if ((xp = x.parent) == null) {// 当前红黑树为空,插入的节点作为根节点x.red = false;return x;}else if (!xp.red || (xpp = xp.parent) == null)// 当前插入节点的祖父节点为根节点,父节点为黑色。直接插入即可return root;if (xp == (xppl = xpp.left)) {// 父节点是祖父节点的左子节点if ((xppr = xpp.right) != null && d) {// xppr是叔叔节点,父节点和叔叔节点均为红色,给节点变色,然后将其祖父节点置为当前节点d = d = d = true;x = xpp;}else {if (x == xp.right) {// 父节点为红色,叔叔节点为黑色,当前节点为右子节点时,对父节点做左旋操作root = rotateLeft(root, x = xp);xpp = (xp = x.parent) == null ? null : xp.parent;}if (xp != null) {xp.red = false;if (xpp != null) {// 此时当前节点父节点为红色,祖父节点为黑色,当前节点为左子节点// 将父节点涂黑,祖父节点涂红,对祖父节点做右旋d = true;root = rotateRight(root, xpp);}}}}else {// 父节点是祖父节点的右子节点,具体情况与上边类似if (xppl != null && d) {d = d = d = 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) {d = true;root = rotateLeft(root, xpp);}}}}}}
balanceInsertion方法参数中root是红黑树的根节点,x是当前插入的节点。从源码中可以看出,插入的节点默认置为红色。
惯例,先看代码
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,boolean movable) {int n;if (tab == null || (n = tab.length) == 0)// 判空return;int index = (n - 1) & hash;// first是红黑树的第一个节点,succ是当前节点在hashmap链表中的后一个节点// pred是在hashmap链表中的前一个节点TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;if (pred == null)// 前一个结点为空,说明当前节点是根节点tab[index] = first = succ;else// 将前一个节点的后继指向后一个节点,也即删除链表中的这个节点 = succ;if (succ != null)// 节点后继的前驱指向前一个节点,双链表删除操作完成succ.prev = pred;if (first == null)return;if (root.parent != null)root = ();if (root == null|| (movable&& (root.right == null|| (rl = root.left) == null|| rl.left == null))) {tab[index] = first.untreeify(map); // too smallreturn;}TreeNode<K,V> p = this, pl = left, pr = right, replacement;if (pl != null && pr != null) {// 左右子树均不为空TreeNode<K,V> s = pr, sl;while ((sl = s.left) != null) // 右子树的最左节点s = sl;boolean c = s.red; s.red = p.red; p.red = c; // swap colorsTreeNode<K,V> sr = s.right;TreeNode<K,V> pp = p.parent;if (s == pr) { // p was s's direct parentp.parent = s;s.right = p;}else {TreeNode<K,V> sp = s.parent;if ((p.parent = sp) != null) {if (s == sp.left)sp.left = p;elsesp.right = p;}if ((s.right = pr) != null)pr.parent = s;}p.left = null;if ((p.right = sr) != null)sr.parent = p;if ((s.left = pl) != null)pl.parent = s;if ((s.parent = pp) == null)root = s;else if (p == pp.left)pp.left = s;elsepp.right = s;if (sr != null)replacement = sr;elsereplacement = p;}else if (pl != null)replacement = pl;else if (pr != null)replacement = pr;elsereplacement = p;//p是待删除节点if (replacement != p) {TreeNode<K,V> pp = replacement.parent = p.parent;if (pp == null)(root = replacement).red = false;else if (p == pp.left)pp.left = replacement;elsepp.right = replacement;p.left = p.right = p.parent = null;}// 删除节点为红色,红黑树平衡违背破坏TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);// 删除节点pif (replacement == p) { // detachTreeNode<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;}}if (movable)moveRootToFront(tab, r);}
这个方法是删除二叉查找树中的节点,平衡红黑树在balanceDeletion方法中。
就先到这里,具体逻辑下一次再分析。
学疏才浅,有不当之处,望指出。
本文发布于:2024-01-29 16:18:17,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170651630116536.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |