红黑树之旅 | 手撕红黑树视频
备注:4.红黑树简单测试,在补充文章中。
我们只要牢牢抓住红黑树的5个性质不放,而不论是树的左旋还是右旋,不论是红黑树的插入,还是删除,都只为了保持和修复红黑树的5个性质而已。
红黑树的5个性质:
每个节点要么是红,要么是黑色;
根节点是黑色;
每个叶节点,即空节点(NIL)是黑色;
如果一个节点是红色,那么它的2个孩子节点必为黑色;
对于每个节点,从该节点到其子孙的叶子节点的路径上包含相同数目的黑色节点;
代码清单:
package zhuguozhu.algods;import java.util.*;/*** 红黑树** @Author guozhu_zhu* @Date 2020/04/30 14:47*/public class TreeMap001<K extends Comparable<K>, V> {private transient Entry<K, V> root;private transient int size = 0;public TreeMap001() {}public int size() {return size;}public boolean isEmpty() {return size == 0;}public boolean containsKey(K key) {return getEntry(key) != null;}public boolean containsValue(Object value) {for (Entry<K, V> e = getFirstEntry(); e != null; e = successor(e)) {if (valEquals(value, e.value)) {return true;}}return false;}public V get(K key) {Entry<K, V> p = getEntry(key);return (p == null ? null : p.value);}public K firstKey() {return key(getFirstEntry());}public K lastKey() {return key(getLastEntry());}final Entry<K, V> getEntry(K key) {if (key == null) {throw new NullPointerException();}Entry<K, V> p = root;while (p != null) {int cmp = key.compareTo(p.key);if (cmp > 0) {p = p.right;} else if (cmp < 0) {p = p.left;} else {return p;}}return null;}public V put(K key, V value) {Entry<K, V> t = root;if (t == null) {root = new Entry<>(key, value, null);size++;return null;}int cmp;Entry<K, V> parent;if (key == null) {throw new NullPointerException();}do {parent = t;cmp = key.compareTo(t.key);if (cmp < 0) {t = t.left;} else if (cmp > 0) {t = t.right;} else {return t.setValue(value);}} while (t != null);Entry<K, V> e = new Entry<K, V>(key, value, parent);if (cmp < 0) {parent.left = e;} else {parent.right = e;}fixAfterInsertion(e);size++;return null;}public V remove(K key) {Entry<K, V> p = getEntry(key);if (p == null) {return null;}V oldValue = p.value;delteEntry(p);return oldValue;}public V replace(K key, V value) {Entry<K, V> p = getEntry(key);if (p != null) {V oldValue = p.value;p.value = value;return oldValue;}return null;}static final boolean valEquals(Object o1, Object o2) {return (o1 == null ? o2 == null : o1.equals(o2));}static <K, V> K keyOrNull(Entry<K, V> e) {return (e == null) ? null : e.key;}static <K> K key(Entry<K, ?> e) {if (e == null) {throw new NoSuchElementException();}return e.key;}// RB-TREEprivate static final boolean RED = false;private static final boolean BLACK = true;static final class Entry<K, V> {K key;V value;Entry<K, V> left;Entry<K, V> right;Entry<K, V> parent;boolean color = BLACK;Entry(K key, V value, Entry<K, V> parent) {this.key = key;this.value = value;this.parent = parent;}public K getKey() {return key;}public V getValue() {return value;}public V setValue(V value) {V oldValue = this.value;this.value = value;return oldValue;}public boolean equals(Object o) {if (!(o instanceof Entry)) {return false;}Entry<?, ?> e = (Entry<?, ?>)o;return valEquals(key, e.getKey()) && valEquals(value, e.getValue());}public int hashCode() {int keyHash = (key == null) ? 0 : key.hashCode();int valueHash = (value == null)? 0 : value.hashCode();return keyHash ^ valueHash;}public String toString() {return key + "=" + value;}}final Entry<K, V> getFirstEntry() {Entry<K, V> p = root;if (p != null) {while (p.left != null) {p = p.left;}}return p;}final Entry<K, V> getLastEntry() {Entry<K, V> p = root;if (p != null) {while (p.right != null) {p = p.right;}}return p;}// 返回被删除节点继承者节点static <K, V> Entry<K, V> successor(Entry<K, V> t) {if (t == null) {// 如果被删除节点为空,则直接返回nullreturn null;} else if (t.right != null) {// 如果被删除节点的右子节点不为空// 将被删除节点的右子节点记录下来// 从该节点开始循环向下查找最左子节点,直到查找到叶子节点后返回叶子节点Entry<K, V> p = t.right;while (p.left != null) {p = p.left;}return p;} else {// 如果被删除节点的右子节点为空// 向上回溯搜索// 将被删除节点用p变量记录Entry<K, V> p = t.parent;Entry<K, V> ch = t;while (p != null && ch == p.right) {ch = p;p = p.parent;}return p;}}static <K, V> Entry<K, V> predecessor(Entry<K, V> t) {if (t == null) {return null;} else if (t.left != null) {Entry<K, V> p = t.left;while (p.right != null) {p = p.right;}return p;} else {Entry<K, V> p = t.parent;Entry<K, V> ch = t;while (p != null && ch == p.left) {ch = p;p = p.parent;}return p;}}private static <K, V> boolean colorOf(Entry<K, V> p) {return p == null ? BLACK : p.color;}private static <K, V> Entry<K, V> parentOf(Entry<K, V> p) {return p == null ? null : p.parent;}private static <K, V> Entry<K, V> leftOf(Entry<K, V> p) {return p == null ? null : p.left;}private static <K, V> Entry<K, V> rightOf(Entry<K, V> p) {return p == null ? null : p.right;}private static <K, V> void setColor(Entry<K, V> p, boolean c) {if (p != null) {p.color = c;}}// 左旋private void rotateLeft(Entry<K, V> p) {if (p != null) {Entry<K, V> r = p.right;p.right = r.left;if (r.left != null) {r.left.parent = p;}r.parent = p.parent;if (p.parent == null) {root = r;} else if (p.parent.left == p) {p.parent.left = r;} else if (p.parent.right == p) {p.parent.right = r;}r.left = p;p.parent = r;}}// 右旋private void rotateRight(Entry<K, V> p) {if (p != null) {Entry<K, V> l = p.left;p.left = l.right;if (l.right != null) {l.right.parent = p;}l.parent = p.parent;if (p.parent == null) {root = l;} else if (p.parent.left == p) {p.parent.left = l;} else if (p.parent.right == p) {p.parent.right = l;}l.right = p;p.parent = l;}}// 树插入一个新节点后,将其根据红黑树的规则进行修正private void fixAfterInsertion(Entry<K, V> x) {// 默认将当前插入树的节点颜色设置为红色, 为什么???// 因为红黑树有一个特点:"从根节点到所有叶子节点上的黑色节点个数是相同的// 如果当前插入的节点是黑色,那么必然会违反这个特性,所以必须将插入节点的颜色先设置为红色x.color = RED;// 第一次变量时,X变量保存的是当前新插入的节点// 为什么要用while循环// 因为在旋转过程中有可能还会出现父子节点均为红色的情况,所以要不断上变量直到整个树都符合红黑树的规则while (x != null && x != root && x.parent.color == RED) {// 如果当前节点不为空切不是根节点,并且当前节点的父节点颜色为红色if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {// 如果当前节点的父节点等于当前节点父节点的父节点的左子节点(即当前节点为左子树插入)Entry<K, V> y = rightOf(parentOf(parentOf(x)));// 获取当前节点的叔父节点if (colorOf(y) == RED) {// 如果叔父节点的颜色为红色// 以4步用来保证不会连续出现两个红色节点// 将当前节点的父节点设置为黑色setColor(parentOf(x), BLACK);// 将当前节点的叔父节点设置为黑色setColor(y, BLACK);// 将当前节点的祖父节点设置为红色setColor(parentOf(parentOf(x)), RED);// 当前遍历节点变更为当前节点的祖父节点x = parentOf(parentOf(x));} else {// 如果叔父节点的颜色为黑色,或没有叔父节点// 如果当前节点为左子树内侧插入if (x == rightOf(parentOf(x))) {// 将x变更为当前节点的父节点x = parentOf(x);// 对当前节点的父节点进行一次左旋操作(旋转完毕后x对应的就是最左边的叶子节点)rotateLeft(x);}// 如果当前节点为左子树外侧插入// 将当前节点的父节点设置为黑色setColor(parentOf(x), BLACK);// 将当前节点的祖父节点设置为红色setColor(parentOf(parentOf(x)), RED);// 对当前节点的祖父节点进行一次右旋rotateRight(parentOf(parentOf(x)));}} else {Entry<K, V> y = leftOf(parentOf(parentOf(x)));if (colorOf(y) == RED) {setColor(parentOf(x), BLACK);setColor(y, BLACK);setColor(parentOf(parentOf(x)), RED);x = parentOf(parentOf(x));} else {if (x == leftOf(parentOf(x))) {x = parentOf(x);rotateRight(x);}setColor(parentOf(x), BLACK);setColor(parentOf(parentOf(x)), RED);rotateLeft(parentOf(parentOf(x)));}}}// 注意在选择的过程中可能会将根节点变更为红色的,但是红黑树特性要求根节点必须为黑色,所以无论如何最好总要执行这行代码,将根节点设置为黑色root.color = BLACK;}private void delteEntry(Entry<K, V> p) {// 节点总数-1size--;// 1. 这里的情况是两个节点不为空,转化为存在1个子节点为空的情况,便于处理if (p.left != null && p.right != null) {// 当前要删除节点的左右节点都不为空,找后继节点// 采用前驱替代也是可以,predecessor(p);// https://www.cs.usfca.edu/~galles/visualization/RedBlack.html// 这个网站的删除操作就是通过前驱替代的,而TreeMap中采用的是后继替代Entry<K, V> s = successor(p);// 找到一个待删除节点的继承者节点sp.key = s.key;p.value = s.value;p = s;}// 2. 替代节点选择为当前删除节点的左子节点或右子节点Entry<K, V> replacement = (p.left != null ? p.left : p.right);// 2.1 替代节点(被删除节点的子节点)不为空if (replacement != null) {replacement.parent = p.parent;// 如果被删除节点的父节点为nullif (p.parent == null) {// 将根节点设置为替换节点root = replacement;} else if (p == p.parent.left) {// 若原先被删除节点是父的左子p.parent.left = replacement;} else {p.parent.right = replacement;}// 将被删除节点的左子节点、右子节点,父节点引用都设置为nullp.left = p.right = p.parent = null;// 删除后要执行后续的保证红黑树规则的平衡调整// 如果被删除节点是黑色if (p.color == BLACK) {// 调用删除后修正红黑树规则的方法fixAfterDeletion(replacement);}} else if (p.parent == null) {// 2.2 被删除节点就是树根节点,直接删除即可root = null;} else {// 2.3 被删除节点没有节点可替代的情况(即被删除节点是叶子节点)if (p.color == BLACK) {fixAfterDeletion(p);}// 如果被删除节点的父节点不为nullif (p.parent != null) {// 如果原先被删除节点是左子树插入if (p == p.parent.left) {// 删除节点后将删除节点父节点的左子节点设置为null;p.parent.left = null;} else if (p == p.parent.right) {p.parent.right = null;}// 将被删除节点的父节点引用设置为null;p.parent = null;}}}private void fixAfterDeletion(Entry<K, V> x) {// 循环遍历, X刚开始是为被删除节点while (x != root && colorOf(x) == BLACK) {// 如果当前遍历到的节点不是根节点且为黑色// 如果当前遍历的节点是父节点的左子节点if (x == leftOf(parentOf(x))) {// 将当前遍历到的节点的父节点的右节点用sib(兄弟节点)保存Entry<K, V> sib = rightOf(parentOf(x));if (colorOf(sib) == RED) {// 如果sib引用的节点是红色// 将sib引用的节点设置为黑色setColor(sib, BLACK);// 将当前遍历到的节点的父节点设置为红色setColor(parentOf(x), RED);// 对当前遍历到的父节点进行一次左旋rotateLeft(parentOf(x));// sib节点变更为旋转后节点的父节点的右子节点sib = rightOf(parentOf(x));}if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) {// 如果sib引用的左右节点都是黑色// 将sib引用的节点设置为红色setColor(sib, RED);// 下次遍历的节点变更为当前遍历到节点的父节点x = parentOf(x);} else {// 如果sib引用节点的左右节点不全是黑色if (colorOf(rightOf(sib)) == BLACK) {// 如果sib引用节点的右节点为黑色// 将sib引用的左子节点设置为黑色setColor(leftOf(sib), BLACK);// sib引用节点设置为红色setColor(sib, RED);// 对sib节点进行一次右旋操作rotateRight(sib);// sib引用的节点变更为当前遍历到的节点的父节点的右子节点sib = rightOf(parentOf(x));}// 将sib引用节点的颜色设置为当前遍历到节点父节点的颜色setColor(sib, colorOf(parentOf(x)));// 将当前遍历到节点的父节点设置为黑色setColor(parentOf(x), BLACK);// 将sib引用节点的右子节点设置为黑色setColor(rightOf(sib), BLACK);// 对当前遍历到的节点的父节点进行一次左旋rotateLeft(parentOf(x));// 下一次遍历的节点变更为根节点x = root;}} else {// 当前遍历到的节点是其父节点的右子节点// 与上述代码类似,可对比分析Entry<K, V> sib = leftOf(parentOf(x));if (colorOf(sib) == RED) {setColor(sib, BLACK);setColor(parentOf(x), RED);rotateRight(parentOf(x));sib = leftOf(parentOf(x));}if (colorOf(rightOf(sib)) == BLACK && colorOf(leftOf(sib)) == BLACK) {setColor(sib, RED);x = parentOf(x);} else {if (colorOf(leftOf(sib)) == BLACK) {setColor(rightOf(sib), BLACK);setColor(sib, RED);rotateLeft(sib);sib = leftOf(parentOf(x));}setColor(sib, colorOf(parentOf(x)));setColor(parentOf(x), BLACK);setColor(leftOf(sib), BLACK);rotateRight(parentOf(x));x = root;}}}setColor(x, BLACK);}// 层次遍历public ArrayList<ArrayList<V>> levelOrder() {ArrayList<ArrayList<V>> resList = new ArrayList<ArrayList<V>>();ArrayList<V> list = new ArrayList<V>();Queue<Entry> queue = new LinkedList<Entry>();queue.offer(root);int cur = 1;int next = 0;while (!queue.isEmpty()) {Entry<K, V> curNode = queue.poll();list.add(curNode.value);cur--;if (curNode.left != null) {queue.offer(curNode.left);next++;}if (curNode.right != null) {queue.offer(curNode.right);next++;}if (cur == 0) {cur = next;next = 0;resList.add(list);list = new ArrayList<V>();}}return resList;}}
加油,加油,加油~~~:D
