纯比着葫芦画着瓢打出来的代码,没啥实际意义,当自己熟悉代码用了,注释里写了一些理解,不很透彻,错误也很多.
package Search;
import edu.princeton.cs.algs4.Queue;
import java.util.*;
//红黑二叉查找树的实现.这个和二三查找树之间的连接不太好找,总是不能一一对应起来,不知道详细的对应步骤.
public class redblackSearchtree<Key extends Comparable<Key>, Value> {
private static final boolean RED = true;
private static final boolean BLACK = false;
Node root;
public class Node {
Key key;
Value val;
Node left, right;
int N;
boolean color;
Node(Key key, Value val, int N, boolean color) {
this.key = key;
this.val = val;
this.N = N;
this.color = color;
}
}
// 判断指向一个结点的链接是否是红色的.
private boolean isRed(Node x) {
if (x == null)
return false;
return x.color == RED;
}
// 旋转的实现,这是算法的核心部分.
// 向左旋转,将右边的红链接的树旋转到左边
Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = h.color; // 将原根节点的链接颜色赋给新 根节点,以确保其保持不变.
h.color = RED;
x.N = h.N;
h.N = 1 + size(h.left) + size(h.right); // 需要重写一下size方法,因为继承过来的方法对现在的Node
// 不适用
return x;
}
// 向右旋转
Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.left = h;
x.color = h.color;
h.color = RED;
x.N = h.N;
h.N = 1 + size(h.left) + size(h.right); // 需要重写一下size方法,因为继承过来的方法对现在的Node
// 不适用
return x;
}
// 转换颜色,作用于左右链接都是红色的时候.将他们全部转换为黑色.
// 颜色取反
void flipColors(Node h) {
h.color = !h.color;
h.left.color = !h.left.color;
h.right.color = !h.right.color;
// 将父节点的链接设为红色(其实不太懂为什么),百度得知这是为了将一个4-结点的中键上移,将其转化为红色后,就和上面的2-结点组合成一个新的3-结点.这就和2-3树中的内容相对应起来了
}
// 插入操作
public Node put(Node h, Key key, Value val) {
if (h == null) // 只要Node h 未实例化,那么他就是null,每进行一次插入都会进行这个操作,并且默认将链接设置为红色.
return new Node(key, val, 1, RED);
// 先进行查找,查找到进行更新,直到查找到一个null值,递归调用,使得树可以成功构造
int cmp = key.compareTo(h.key);
if (cmp < 0)
h.left = put(h.left, key, val);
else if (cmp > 0)
h.right = put(h.right, key, val);
else
h.val = val;
// 进行旋转操作,使一个普通二叉树转换为红黑二叉树.
if (isRed(h.right) && !isRed(h.left))
h = rotateLeft(h); // 如果右链接是红色的,左转
if (isRed(h.left) && isRed(h.left.left))
h = rotateRight(h); // 如果出现两层红链接,右转.
if (isRed(h.left) && isRed(h.right))
flipColors(h); // 如果出现4-结点,转换为三个2-结点(左右都是红色的)
h.N = size(h.left) + size(h.right) + 1; // 更新结点总数
return h;
}
// 删除最小键的代码
// 这是难点,一直没有搞太清楚
// 这个方法定义了两种情况,第一种情况直接颜色反转,使两个黑链接变为红链接,此时就变成了4-结点.
// 第二种情况,在这个结点是2-结点的前提下,如果这个结点的右子树的左子树为红色,则将这个结点借给当时查找的左子树.
private Node moveRedLeft(Node h) {
// 假设结点h为红色,h.left和h.left.left都是黑色.表示当前结点就是根节点,最小结点是这个结点的左子树
// 在满足这个的前提下首先flipColors(h);使其变为3-结点,此时就可以进行删除了
flipColors(h);
// flipColors完成之后,还需要进行下一步检查,即判断时候情况更为复杂,无论哪种情况 flipColors都是要执行的.
if (isRed(h.right.left)) {
h.right = rotateRight(h.right);
h = rotateLeft(h);
// 原来一直在这一步上很困惑,不知道怎么弄.看了源码才想明白.
// 书本上少打了这一步,这是挺重要的一步,使借来链之后的树恢复正常状态.因为前面做了一步反转颜色
// 另外,结点和他的链接是对应在一起的..即一个结点的链接是红色的,那么无论怎么旋转他都是红色的.
flipColors(h);
}
return h;
}
public void deleteMin() {
if (!isRed(root.left) && !isRed(root.right))
root.color = RED;
root = deleteMin(root);
if (!isEmpty())
root.color = BLACK;
}
private Node deleteMin(Node h) {
// 直到遇到h.left== null,这说明没有比当前键更小的了,返回null,表示这个已经被删除了.
if (h.left == null)
return null;
// 这是重点方法,所做的转换都在这一步,这是从上向下的转换,如果碰见一个2-结点,则将他们转换成3-或者4-结点
if (!isRed(h.left) && !isRed(h.left.left))
h = moveRedLeft(h);
// 递归调用这个方法,直到遇见null值.
h.left = deleteMin(h.left);
// 返回一个平衡操作后的树,此时树就被重构了一次.每递归一次就平衡一次.
return balance(h);
}
// 删除最大键的代码
private Node moveRedRight(Node h) {
// 首先反转颜色.
flipColors(h);
// 和删除最小键一样,如果最大的那个子树为2-,那么由于上面的反转过了,
//如果left.left是红色的,那么说明左边是一个4-结点,右边是一个1-几点,需要将两个都变成3-结点.这次从左边借来一个链
if (isRed(h.left.left)) {
h = rotateRight(h);
flipColors(h);
}
return h;
}
private void deleteMax() {
//如果为空
if(isEmpty()) throw new NoSuchElementException("BST underflow");
//如果左右子树都不为红色
if(!isRed(root.left) && !isRed(root.right))
root.color = RED;
root = deleteMax(root);
if(!isEmpty()) root.color = BLACK;
}
private Node deleteMax(Node h){
//只要是红色的链接都进行右旋转
if(isRed(h.left))
h = rotateRight(h);
//这是已经找到并删除最大键
if(h.right == null)
return null;
//如果当前结点的右结点和右结点的左节点的链接都是黑色,则从左边树中借一个到右边树中
if (!isRed(h.right) && !isRed(h.right.left))
h = moveRedRight(h);
h.right = deleteMax(h.right);
return balance(h);
}
//删除具体键的方法
//这个实在太难,必须找时间好好看一下.
public void delete(Key key){
if(key == null) throw new IllegalArgumentException("argument to delete() is null");
//如果不存在这个key,返回
if(!contains(key)) return;
//先初始化根节点,将根节点转化为红色(后面可以balance回来)
if(!isRed(root.left) && !isRed(root.right))
root.color = RED;
root = delete(root,key);
if(!isEmpty) root.color = BLACK;
}
private Node delete(Node h,Key key){
//如果查找的键小于当前键,向左移动,继续查找.
if(key.compareTo(h.key)<0){
if(!isRed(h.left) && !isRed(h.left.left))
h = moveRedLeft(h);
h.left = delete(h.left,key);
//如果查找的值大于等于当前键
}else{
//这一步旋转是因为要删除比当前键更大的key,在右子树中必须有红色链接.
if(isRed(h.left))
h = rotateRight(h);
//这是已经在树底查找到了该键,删除了这个,那么只有左节点可以继任
//将这一步放在前面可能也是因为如果到达树底,那么左右子树就是黑色的.无法判断第三步
if(key.compareTo(h.key) == 0 && (h.right ==null))
return null;
//待删除的键不在树底的情况下.先进行旋转,确保右边必有红子树,因为如果刚好下一个就是要删除的2-结点,就必须借来一个结点使他成为一个3-结点,如此才能删除.
if(!isRed(h.right) && !isRed(h.right.left))
h = moveRedRight(h);
if(key.compareTo(h.key )==0){
Node x = min(h.right);
h.key = x.key;
h.key = x.key;
h.val = x.val;
// h.val = get(h.right, min(h.right).key);
// h.key = min(h.right).key;
h.right = deleteMin(h.right);
}
//在右子树中继续搜索.
else h.right = delete(h.right, key);
}
return balance(h);
}
// 返回平衡后的树,递归调用.
private Node balance(Node h) {
// assert (h != null);
if (isRed(h.right))
h = rotateLeft(h);
if (isRed(h.left) && isRed(h.left.left))
h = rotateRight(h);
if (isRed(h.left) && isRed(h.right))
flipColors(h);
h.size = size(h.left) + size(h.right) + 1;
return h;
}
}
其他的方法与普通的二叉树相同.
错误很多,我备忘一下.