红黑树之旅 | 手撕红黑树视频
Posted 朱国柱点滴回忆
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了红黑树之旅 | 手撕红黑树视频相关的知识,希望对你有一定的参考价值。
备注: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-TREE
private 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) {
// 如果被删除节点为空,则直接返回null
return 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) {
// 节点总数-1
size--;
// 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);
// 找到一个待删除节点的继承者节点s
p.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;
// 如果被删除节点的父节点为null
if (p.parent == null) {
// 将根节点设置为替换节点
root = replacement;
} else if (p == p.parent.left) {
// 若原先被删除节点是父的左子
p.parent.left = replacement;
} else {
p.parent.right = replacement;
}
// 将被删除节点的左子节点、右子节点,父节点引用都设置为null
p.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);
}
// 如果被删除节点的父节点不为null
if (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
以上是关于红黑树之旅 | 手撕红黑树视频的主要内容,如果未能解决你的问题,请参考以下文章