二叉搜索树
性质:
1.对于二叉搜索树,对于任意节点,左子树上的节点值都小于该节点值,右子树上的节点值都大于该节点值。
2.对二叉搜索树进行中序遍历可以得到有序序列
定义:
public class BSTreeNode<T extends Comparable<T>> {
public T val;
public BSTreeNode<T> left;
public BSTreeNode<T> right;
public BSTreeNode<T> parent;
public BSTreeNode(T val) {
this.val = val;
}
@Override
public String toString() {
return val.toString();
}
}
操作:
1.查找
直接二分查找,上代码:
public BSTreeNode<T> find(T val) {
return doFind(root, val);
}
private BSTreeNode<T> doFind(BSTreeNode<T> root, T val) {
if (root == null) {
return null;
}
if (root.val.equals(val)) {
return root;
} else if (val.compareTo(root.val) > 0) {
return doFind(root.right, val);
} else {
return doFind(root.left, val);
}
}
2.遍历
递归算法实现遍历,代码如下:
public List<T> inorderTravel() {
List<T> res = new ArrayList<>();
doInorderTravel(root, res);
return res;
}
private void doInorderTravel(BSTreeNode<T> root, List<T> res) {
if (root == null) {
return;
}
doInorderTravel(root.left, res);
res.add(root.val);
doInorderTravel(root.right, res);
}
3.插入
递归插入,如果已经存在,结束操作,代码如下:
注意,这里处理parent,主要是为了删除的时候使用。
public BSTreeNode<T> insert(BSTreeNode<T> root, T val) {
if (root == null) {
return new BSTreeNode<>(val);
}
if (val.equals(root.val)) {
return root;
} else if (val.compareTo(root.val) > 0) {
root.right = insert(root.right, val);
root.right.parent = root;
} else if (val.compareTo(root.val) < 0) {
root.left = insert(root.left, val);
root.left.parent = root;
}
return root;
}
4.删除
分三种不同情况:
(1)如果删除的节点是叶子结点;
直接删除即可
(2)如果删除的节点只有左子树或者右子树;
替换节点
(3)如果删除的节点包含了左子树和右子树;
找到后继节点并替换
代码如下:
public void delete(T val) {
doDelete(root, val);
}
private void doDelete(BSTreeNode<T> root, T val) {
if (root== null) {
return;
}
if (val.compareTo(root.val) > 0) {
doDelete(root.right, val);
} else if (val.compareTo(root.val) < 0) {
doDelete(root.left, val);
} else {
// case1:叶子结点,直接删除
if (root.left == null && root.right == null) {
if (root.parent.left == root) {
root.parent.left = null;
} else if (root.parent.right == root) {
root.parent.right = null;
}
} else if (root.left == null) {
// case2:只有右子树,没有左子树
transplant(root, root.right);
} else if (root.right == null) {
// case3:只有左子树,没有右子树
transplant(root, root.left);
} else {
// case4:左右子树都有
BSTreeNode<T> tmp = root.right;
while (tmp.left != null) {
tmp = tmp.left;
}
if (tmp.parent != root) {
transplant(tmp, tmp.right);
tmp.right = root.right;
tmp.right.parent = tmp;
}
transplant(root, tmp);
tmp.left = root.left;
tmp.left.parent = tmp;
}
}
}
private void transplant(BSTreeNode<T> u, BSTreeNode<T> v) {
if (u.parent == null) {
return;
}
if (u == u.parent.left) {
u.parent.left = v;
} else {
u.parent.right = v;
}
if (v != null) {
v.parent = u.parent;
}
}
红黑树
性质:
一颗红黑树是满足下面红黑性质的二叉搜索树: 1、每个节点是红色或者黑色的; 2、根节点是黑色的; 3、每个叶子结点是黑色的;
4、如果一个节点是红色的,它的两个子节点都是黑色的; 5、对每个节点,从该节点到其所有后代也节点的简单路径上,均包含相同数目的黑色节点。
定义:
public class RBTreeNode {
public int val;
public RBTreeNode left;
public RBTreeNode right;
public RBTreeNode parent;
public boolean red;
public RBTreeNode(int val) {
this.val = val;
}
}
操作:
1.查找
递归查找,代码如下:
public RBTreeNode find(int target) {
return doFind(root, target);
}
2.旋转
画图操作:
public void leftRotate(RBTreeNode u) {
if (u == null) {
return;
}
RBTreeNode v = u.right;
if (u.parent == null) {
root = v;
} else if (u.parent.left == u) {
u.parent.left = v;
} else if (u.parent.right == u) {
u.parent.right = u;
}
v.parent = u.parent;
u.parent = v;
u.right = v.left;
if (v.left != null) {
v.left.parent = u;
}
v.left = u;
}
3.插入
红黑树新增节点一定是红色的,它插入到红黑树的时候有三种情况:
1.如果新增节点的叔叔节点是红色的;
变色,转移到情况2
2.如果新增节点的叔叔节点是黑色的并且新增节点是一个右孩子;
左旋,转移到情况3
3.如果新增节点的叔叔节点是黑色的并且新增节点是一个左孩子;
变色,右旋
右旋
代码:
首先找到插入位置
public RBTreeNode doInsert(RBTreeNode root, int val) {
RBTreeNode newNode = new RBTreeNode(val);
RBTreeNode node = root;
RBTreeNode tmp = null;
while (node != null) {
tmp = node;
if (val < node.val) {
node = node.left;
} else {
node = node.right;
}
}
newNode.parent = tmp;
if (tmp == null) {
root = newNode;
} else if (val < tmp.val) {
tmp.left = newNode;
} else {
tmp.right = newNode;
}
newNode.left = null;
newNode.right = null;
// 红黑树的性质4,保持自平衡
newNode.red = true;
insertFixup(root, newNode);
return root;
}
随后进行变色,保持红黑树的性质
代码:
while (newNode != root && newNode.parent != root && newNode.parent.red) {
RBTreeNode tmp;
// 找到叔叔节点,记为tmp
if (newNode.parent == newNode.parent.parent.left) {
tmp = newNode.parent.parent.right;
} else {
tmp = newNode.parent.parent.right;
}
if (tmp.red) {
// 第一种情况,父亲节点和叔叔节点都为红色,修改颜色,指针上移
newNode.parent.red = false;
tmp.red = false;
newNode.parent.parent.red = true;
newNode = newNode.parent.parent;
} else if (newNode == newNode.parent.right) {
// 第二种情况,父亲节点是红色,但是叔叔节点是黑色,且是右孩子,执行左旋
newNode = newNode.parent;
leftRotate(newNode);
} else {
// 第三种情况,父亲节点是红色,但是叔叔节点是黑色, 且是左孩子,执行右旋
newNode.parent.red = false;
newNode.parent.parent.red = true;
rightRotate(newNode.parent.parent);
}
}
root.red = false;