红黑树是一种近似平衡的二叉查找树
分析之前,先把树的知识回顾一遍。文中实现了二叉查找树,在此基础上参考JDK1.8 TreeMap源码模拟红黑树的实现
在链表中,插入、删除速度很快,但查找速度较慢。
在数组中,查找速度很快,但插入删除速度很慢。
二叉树,本质上,是对链表和数组的一个折中
高度为h,并且由2^h-1个结点组成的二叉树,称为满二叉树
如果一个完全二叉树的结点总数为768个,求叶子结点的个数。
规律:如果一棵完全二叉树的结点总数为n,那么叶子结点等于(n+1)/2
这个方案很好的解决了二叉查找树退化成链表的问题,
节点类:
class Node<E> {
Node<E> left = null;
Node<E> right = null;
E data = null;
public Node(E data) {
this.data = data;
}
}
二叉查找树:
方法:插入 查找 遍历 删除
class BinSearchTree implements Comparator {
public Node root = null;
public List> list = null;
public void insert(E value) {
if (root == null) {
root = new Node(value);
return;
}
Node curNode = root;
Node parNode = root;
boolean isLeft = true;
while (curNode != null) {
parNode = curNode;
if (compare(value, curNode.data) == 1) {
isLeft = false;
curNode = curNode.right;
} else if (compare(value, curNode.data) == -1) {
isLeft = true;
curNode = curNode.left;
} else {
System.out.println("Insert error for repeating elements!");
return;
}
}
Node node = new Node(value);
if (isLeft)
parNode.left = node;
else
parNode.right = node;
}
public void find(E value) {
Node curNode = root;
while (curNode != null) {
if (compare(value, curNode.data) == 1)
curNode = curNode.right;
else if (compare(value, curNode.data) == -1) {
curNode = curNode.left;
} else {
System.out.println(value + " found");
return;
}
;
}
System.out.println(value + " Not found");
}
public void preOrder(Node
successor方法,寻找后继者
static TreeMap.Entry successor(Entry t) {
if (t == null)
return null;
//从右子树找到最小的节点
else if (t.right != null) {
Entry p = t.right;
while (p.left != null)
p = p.left;
return p;
} else {
//右子树为空 找到第一个左父节点
Entry p = t.parent;
Entry ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}
待删除的节点为叶子节点,可直接删除
待删除节点只有一个孩子节点
待删除节点既有左孩子,又有右孩子,需要寻找后继节点
后继节点就是比要删除的节点的关键值要大的节点集合中的最小值。
// 根据归纳法 如果两个子树为最大堆 根节点小 则交换 然后向下处理
class MaxHeap implements Comparator {
public List heap;
public void swap(int i, int j) {
if (i != j) {
E temp = heap.get(i);
heap.set(i, heap.get(j));
heap.set(j, temp);
}
}
// build heap
public void buildMaxHeap(List input) {
heap = input;
for (int i = heap.size() / 2 - 1; i >= 0; --i) {
maxHeapify(i);
}
}
// 向下调整堆
public void maxHeapify(int i) {
// 只要有左孩子
while (2 * i + 1 < heap.size()) {
// 默认当前最大
int max = i;
// 取与左孩子之间大者
if (compare(heap.get(2 * i + 1), heap.get(i)) > 0)
max = 2 * i + 1;
// 取与右孩子之间大者
if (2 * i + 2 < heap.size() && compare(heap.get(2 * i + 2), heap.get(max)) > 0)
max = 2 * i + 2;
// 根节点最大
if (max == i)
break;
else {
swap(i, max);
// 处理当前节点的子节点
i = max;
}
}
}
// 插入: 在末尾添加元素 向上调整堆
public void insert(E value) {
heap.add(value);
heapUp(heap.size() - 1);
}
// 向上调整堆
public void heapUp(int i) {
// 如果不为根节点
while (i > 0) {
// 默认当前最大
int max = i;
// 取与父节点之间大者
if (compare(heap.get(i / 2), heap.get(i)) > 0)
max = i / 2;
// 如果当前节点为大
if (max == i) {
swap(i, i / 2);
// 向上处理父节点
i /= 2;
} else
break;
}
}
// 删除元素 将末尾元素移至删除元素的位置 它的值较小 故向下调整 移除末尾元素
public void delete(int index) {
heap.set(index, heap.get(heap.size() - 1));
maxHeapify(index);
heap.remove(heap.size() - 1);
}
// 查处元素
public void find(E value){
int i = 0;
while(i < heap.size()){
if(compare(heap.get(i),value) > 0){
i = 2 * i +1;
}
else if(compare(heap.get(i),value) < 0){
i = 2 * i + 2;
}
else {
System.out.println(value + " found");
return;
}
}
System.out.println(value + " not found");
}
// 堆排序: 依次删除根节点
public void sort(List array) {
buildMaxHeap(array);
while (heap.size() > 1) {
System.out.print(heap.get(0) + " ");
delete(0);
}
}
public void out() {
for (E i : heap) {
System.out.print(i + " ");
}
System.out.println();
}
@Override
public int compare(E o1, E o2) {
if ((Integer) o1 > (Integer) o2)
return 1;
else if ((Integer) o1 < (Integer) o2)
return -1;
else
return 0;
}
}
当在对红黑树进行插入和删除等操作时可能会破坏红黑树的性质
需要通过调整使得查找树重新满足红黑树的条件。
左旋:
左旋的过程是将x的右子树绕x逆时针旋转,使得x的右子树成为x的父亲
使右子树的左孩子成为x的右孩子
右旋:
Tips:左旋和右旋之后 仍是二叉搜索树
左旋把右孩子移上去
右旋把左孩子移上去
实现
class RedBlackTree<E> implements Comparator<E> {
private static final boolean BLACK = true;
private static final boolean RED = false;
public Node root = null;
private static int size = 0;
在二叉搜索树的基础上进行调整
public void insert(E value) {
if (root == null) {
root = new Node(value, null);
return;
}
Node curNode = root;
Node parNode = root;
boolean isLeft = true;
while (curNode != null) {
// parNode指向上次循环后的节点
parNode = curNode;
if (compare(value, curNode.data) == 1) {
isLeft = false;
curNode = curNode.right;
} else if (compare(value, curNode.data) == -1) {
isLeft = true;
curNode = curNode.left;
} else {
System.out.println("Insert error for repeating elements " + value);
return;
}
}
// 循环结束后 parNode指向要插入的节点 curNode指向null 即待插入的位置
// 新增节点作为子节点 放入待插入的位置
Node node = new Node(value, parNode);
if (isLeft)
parNode.left = node;
else
parNode.right = node;
// 对这棵树进行调整、平衡
fixAfterInsertion(node);
size++;
}
红黑树的核心在于调整算法fixAfterInsertion
首先插入节点一定为红色
被插入的节点的父节点是红色。一定存在祖父节点。
3.1父节点是左孩子,叔节点为红
3.2父节点是左孩子,叔节点为黑,当前为右
3.3父节点是左孩子,叔节点为黑,当前为左
3.1
父、叔节点置黑,违背特性5,故祖父置红。
递归处理祖父节点,若祖父为根节点,置黑返回。
3.2
对父节点进行左旋,此时父左叔黑,当前为左,变成3
3.3
父节点置黑,祖父节点置红,对祖父节点右旋
父节点为右孩子同理。
总结:
fixAfterInsertion函数
public void fixAfterInsertion(Node node) {
// 新增节点为红色
node.color = RED;
// 为空或 为根节点 或父节点为黑 满足红黑树性质 返回
while (node != null && node != root && node.parent.color == RED) {
// 如果父节点为左孩子
if (parentOf(node) == leftOf(parentOf(parentOf(node)))) {
// 获取叔节点
Node y = rightOf(parentOf(parentOf(node)));
// 若父节点为红 叔节点为红
if (colorOf(y) == RED) {
// 父节点 叔节点设为黑
setColor(parentOf(node), BLACK);
setColor(y, BLACK);
// 父节点的父节点设为红
setColor(parentOf(parentOf(node)), RED);
// 递归处理node的父节点的父节点
node = parentOf(parentOf(node));
}
// 若父节点为红 叔节点为黑
else {
// 如果node为右子树 对父节点进行左旋
if (node == rightOf(parentOf(node))) {
// 将node的父节点作为node
node = parentOf(node);
rotateLeft(node);
}
// 父节点设为黑
setColor(parentOf(node), BLACK);
// 父节点的父节点设为黑
setColor(parentOf(parentOf(node)), RED);
// 对父节点的父节点右旋
rotateRight(parentOf(parentOf(node)));
}
}
// 如果父节点为右孩子
else {
// 获取叔节点
Node y = leftOf(parentOf(parentOf(node)));
// 若父节点为红 叔节点为红
if (colorOf(y) == RED) {
// 父节点 叔节点设为黑
setColor(parentOf(node), BLACK);
setColor(y, BLACK);
// 父节点的父节点设为红
setColor(parentOf(parentOf(node)), RED);
// 递归处理node的父节点的父节点
node = parentOf(parentOf(node));
}
// 若父节点为红 叔节点为黑
else {
// 如果node为左子树 右旋
if (node == leftOf(parentOf(node))) {
// 将node的父节点作为node
node = parentOf(node);
rotateRight(node);
}
// 父节点设为黑
setColor(parentOf(node), BLACK);
// 父节点的父节点设为黑
setColor(parentOf(parentOf(node)), RED);
// 对父节点的父节点左旋
rotateLeft(parentOf(parentOf(node)));
}
}
}
// 根节点设为黑
root.color = BLACK;
}
private void deleteEntry(Entry<K,V> p) {
modCount++;
size--;
// If strictly internal, copy successor's element to p and then make p
// point to successor.
if (p.left != null && p.right != null) {
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
// Link replacement to parent
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// Null out links so they are OK to use by fixAfterDeletion.
p.left = p.right = p.parent = null;
// Fix replacement
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
root = null;
} else { // No children. Use self as phantom replacement and unlink.
if (p.color == BLACK)
fixAfterDeletion(p);
if (p.parent != null) {
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
NavigableMap意味着它支持一系列的导航方法,具备针对给定搜索目标返回最接近匹配项的导航方法 。
//比较器,因为TreeMap是有序的,通过comparator接口我们可以对TreeMap的内部排序进行精密的控制
private final Comparator super K> comparator;
//TreeMap红-黑节点,为TreeMap的内部类
private transient Entry root = null;
//容器大小
private transient int size = 0;
//TreeMap修改次数
private transient int modCount = 0;
//红黑树的节点颜色--红色
private static final boolean RED = false;
//红黑树的节点颜色--黑色
private static final boolean BLACK = true;
左旋:
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
//获取P的右子节点
Entry<K,V> r = p.right;
//将R的左子树设置为P的右子树
p.right = r.left;
//若R的左子树不为空,则将P设置为R左子树的父亲
if (r.left != null)
r.left.parent = p;
//将P的父亲设置R的父亲
r.parent = p.parent;
//如果P的父亲为空,则将R设置为跟节点
if (p.parent == null)
root = r;
//如果P为其父节点(G)的左子树,则将R设置为P父节点(G)左子树
else if (p.parent.left == p)
p.parent.left = r;
//否则R设置为P的父节点(G)的右子树
else
p.parent.right = r;
//将P设置为R的左子树
r.left = p;
//将R设置为P的父节点
p.parent = r;
}
}
步骤:
规则:
1.为跟节点
新插入的节点N没有父节点,当做根节点插入,设置为黑色
2.父节点为黑色
父节点为黑色,直接插入,设置为红色
3.父节点P和P的兄弟节点U都为红色