三颗大树
今天我们来捋以下三颗大树:ADT、AVL、RBT
ADT
二叉搜索树
插入操作
插入操作如果要插入数字比根节点大那么向右插入,如果插入数字比根节点小则向左插入
删除操作
如果要删除的节点是叶子节点,没有孩子那么直接删除
如果要删除的节点又一个孩子,则直接用孩子节点来替换
如果要删除的节点有两个孩子,用右子树的最小值或左子树的最大值;爱替换然后删除用于替换的那个节点
package com.suo.ADT;
import java.nio.BufferUnderflowException;
public class BinarySearchTree {
private static class BinaryNode {
BinaryNode(Integer theElement) {
this(theElement,null,null);
}
public BinaryNode(Integer element, BinaryNode left, BinaryNode right) {
this.element = element;
this.left = left;
this.right = right;
}
//节点中的数据
Integer element;
//左孩子
BinaryNode left;
//右孩子
BinaryNode right;
}
//根节点
private BinaryNode root;
//无参构造
public BinarySearchTree() {
root = null;
}
//清空树
public void makeEmpty() {
root = null;
}
//判断树是否为空
public boolean isEmpty() {
return root == null;
}
//判断树是否含有某个节点
public boolean contains(Integer x) {
return contains(x,root);
}
//寻找这颗树的最小值
public Integer findMin() {
if (isEmpty()) throw new BufferUnderflowException();
return findMin(root).element;
}
//寻找这颗树的最大值
public Integer findMax() {
if (isEmpty()) throw new BufferUnderflowException();
return findMax(root).element;
}
//插入操作
public void insert(Integer x) {
root = insert(x,root);
}
//删除操作
public void remove(Integer x) {
root = remove(x,root);
}
//遍历操作
public void printTree() {
printTree(root);
System.out.println();
}
/**
* 如果相等则返回本节点,如歌比根节点小则递归左子树,如果比根节点大则递归右子树
* @param x
* @param t
* @return
*/
private boolean contains(Integer x,BinaryNode t) {
if(t == null) return false;
if(x == t.element) return true;
else if(x < t.element) return contains(x,root.left);
else return contains(x,root.right);
}
/**
* 一直往左走
* @param t
* @return
*/
private BinaryNode findMin(BinaryNode t) {
if (t == null) return null;
while(t.left != null) {
t = t.left;
}
return t;
}
/**
* 一直往右走
* @param t
* @return
*/
private BinaryNode findMax(BinaryNode t) {
if (t == null) return null;
while(t.right != null) {
t = t.right;
}
return t;
}
/**
* 如果比根节点小往左插,反之往右插,不考虑相等的情况
* @param x
* @param t
* @return
*/
private BinaryNode insert(Integer x, BinaryNode t) {
if (t == null) {
return new BinaryNode(x,null,null);
}
if (x < t.element) {
t.left = insert(x, t.left);
}
else if(x > t.element) {
t.right = insert(x, t.right);
}
else {
//do nothing
}
return t;
}
/**
* 如果要删除的节点是叶子节点,没有孩子那么直接删除
* 如果要删除的节点又一个孩子,则直接用孩子节点来替换
* 如果要删除的节点有两个孩子,用右子树的最小值或左子树的最大值;爱替换然后删除用于替换的那个节点
* @param x
* @param t
* @return
*/
private BinaryNode remove(Integer x,BinaryNode t) {
if (t == null) {
return t;
}
if (x < t.element) {
t.left = remove(x,t.left);
}
else if (x > t.element) {
t.right = remove(x,t.right);
}
else {
if (t.left == null && t.right == null) {
t = null;
}
else if(t.left == null || t.right == null) {
t = (t.left == null) ? t.right : t.left;
}
else {
t.element = findMin(t.right).element;
t.right = remove(t.element,t.right);
}
}
return t;
}
private void printTree(BinaryNode t) {
if (t == null) {
return;
}
printTree(t.left);
System.out.print(t.element + " ");
printTree(t.right);
}
}
AVL
AVL是自平衡二叉搜索树,相较于ADT多了一个旋转自平衡的操作
明白这一点后其实就比较简单了所有函数和ADT完全相同只是添加一个自平衡函数balance()
原先insert返回t现在返回balance(t)删除同理
自平衡
那么AVL是怎么实现自平衡的呢其实很简单一共有四种情况
LL
LL 往左子树的左子树插入导致的不平衡 右旋:
第一步:将根节点的左孩子替换此节点 AvlNode k = t.left;
第二步:将 k节点的右孩子替换根节点的左孩子 t.left = k.right;
第三步:将根节点替换为 k节点的右孩子 k.right = t;
RR
RR 往右子树的右子树插入导致的不平衡 左旋:
第一步:将根节点的右孩子替换此节点 AvlNode k = t.right;
第二步:将 k节点的左孩子替换根节点的右孩子 t.right = k.left;
第三步:将根节点替换为 k节点的左孩子 k.left = t;
LR
LR 往左子树的右子树插入导致的不平衡 先对左子树左旋,再对根右旋
RL
RL 往右子树的左子树插入导致的不平衡 先对右子树右旋,再对根左旋
package com.suo.AVL;
import java.nio.BufferUnderflowException;
public class AVL {
private static class AvlNode {
AvlNode(Integer theElement) {
this(theElement,null,null);
}
public AvlNode(Integer element, AVL.AvlNode left, AVL.AvlNode right) {
this.element = element;
this.left = left;
this.right = right;
height = 0;
}
//节点中的数据
Integer element;
//左孩子
AVL.AvlNode left;
//右孩子
AVL.AvlNode right;
//树高
int height;
}
//根节点
private AvlNode root;
//允许的平衡因子
private static final int ALLOWED_IMBALANCE = 1;
//无参构造
public AVL() {
root = null;
}
//清空树
public void makeEmpty() {
root = null;
}
//判断树是否为空
public boolean isEmpty() {
return root == null;
}
//判断树是否含有某个节点
public boolean contains(Integer x) {
return contains(x,root);
}
//寻找这颗树的最小值
public Integer findMin() {
if (isEmpty()) throw new BufferUnderflowException();
return findMin(root).element;
}
//寻找这颗树的最大值
public Integer findMax() {
if (isEmpty()) throw new BufferUnderflowException();
return findMax(root).element;
}
//插入操作
public void insert(Integer x) {
root = insert(x,root);
}
//删除操作
public void remove(Integer x) {
root = remove(x,root);
}
//遍历操作
public void printTree() {
printTree(root);
System.out.println();
}
/**
* 获取树高
* @param t
* @return
*/
private int height(AvlNode t) {
return t == null ? -1 : t.height;
}
/**
* 如果相等则返回本节点,如歌比根节点小则递归左子树,如果比根节点大则递归右子树
* @param x
* @param t
* @return
*/
private boolean contains(Integer x, AvlNode t) {
if(t == null) return false;
if(x == t.element) return true;
else if(x < t.element) return contains(x,root.left);
else return contains(x,root.right);
}
/**
* 一直往左走
* @param t
* @return
*/
private AvlNode findMin(AvlNode t) {
if (t == null) return null;
while(t.left != null) {
t = t.left;
}
return t;
}
/**
* 一直往右走
* @param t
* @return
*/
private AvlNode findMax(AvlNode t) {
if (t == null) return null;
while(t.right != null) {
t = t.right;
}
return t;
}
/**
* 与ADT插入操作完全相同,只是多了一个平衡操作
* @param x
* @param t
* @return
*/
private AvlNode insert(Integer x, AvlNode t) {
if (t == null) {
return new AvlNode(x,null,null);
}
if (x < t.element) {
t.left = insert(x, t.left);
}
else if(x > t.element) {
t.right = insert(x, t.right);
}
else {
//do nothing
}
return balance(t);
}
/**
* 与ADT的删除操作完全一样,只是多了哥平衡操作
* @param x
* @param t
* @return
*/
private AvlNode remove(Integer x, AvlNode t) {
if (t == null) {
return t;
}
if (x < t.element) {
t.left = remove(x,t.left);
}
else if (x > t.element) {
t.right = remove(x,t.right);
}
else {
if (t.left == null && t.right == null) {
t = null;
}
else if(t.left == null || t.right == null) {
t = (t.left == null) ? t.right : t.left;
}
else {
t.element = findMin(t.right).element;
t.right = remove(t.element,t.right);
}
}
return balance(t);
}
/**
* 分为四种情况
* LL 往左子树的左子树插入导致的不平衡 右旋
* RR 往右子树的右子树插入导致的不平衡 左旋
* LR 往左子树的右子树插入导致的不平衡 先对左子树左旋,再对根右旋
* RL 往右子树的左子树插入导致的不平衡 先对右子树右旋,再对根左旋
* @param t
* @return
*/
private AvlNode balance(AvlNode t) {
if(t == null) return null;
//L
if (height(t.left) - height(t.right) > ALLOWED_IMBALANCE) {
//LL
//因为删除时可能会产生等于的情况,等号加不加无所谓,加等号只是为了相等的时候采用单旋转而不是双旋转提高效率
if (height(t.left.left) - height(t.left.right) > 0) {
t = rotateWithLeftChild(t);
}
//LR
else {
t = doubleWithLeftChild(t);
}
}
//R
else if (height(t.right) - height(t.left) > ALLOWED_IMBALANCE){
//RR
//因为删除时可能会产生等于的情况,等号加不加无所谓,加等号只是为了相等的时候采用单旋转而不是双旋转提高效率
if(height(t.right.right) - height(t.right.left) > 0) {
t = rotateWithRightChild(t);
}
//RL
else {
t = doubleWithRightChild(t);
}
}
else {
//do nothing
}
t.height = Math.max(height(t.left),height(t.right)) + 1;
return t;
}
/**
* LL 往左子树的左子树插入导致的不平衡 右旋:
* 第一步:将根节点的左孩子替换此节点 AvlNode k = t.left;
* 第二步:将 k节点的右孩子替换根节点的左孩子 t.left = k.right;
* 第三步:将根节点替换为 k节点的右孩子 k.right = t;
* @param t
* @return
*/
private AvlNode rotateWithLeftChild(AvlNode t) {
AvlNode k = t.left;
t.left = k.right;
k.right = t;
t.height = Math.max(height(t.left),height((t.right))) + 1;
k.height = Math.max(height(k.left),t.height) + 1;
return k;
}
/**
* RR 往右子树的右子树插入导致的不平衡 左旋:
* 第一步:将根节点的右孩子替换此节点 AvlNode k = t.right;
* 第二步:将 k节点的左孩子替换根节点的右孩子 t.right = k.left;
* 第三步:将根节点替换为 k节点的左孩子 k.left = t;
* @param t
* @return
*/
private AvlNode rotateWithRightChild(AvlNode t) {
AvlNode k = t.right;
t.right = k.left;
k.left = t;
t.height = Math.max(height(t.left),height((t.right))) + 1;
k.height = Math.max(height(k.left),t.height) + 1;
return k;
}
/**
* LR 往左子树的右子树插入导致的不平衡 先对左子树左旋,再对根右旋
* @param t
* @return
*/
private AvlNode doubleWithLeftChild(AvlNode t) {
t.left = rotateWithRightChild(t.left);
return rotateWithLeftChild(t);
}
/**
* RL 往右子树的左子树插入导致的不平衡 先对右子树右旋,再对根左旋
* @param t
* @return
*/
private AvlNode doubleWithRightChild(AvlNode t) {
t.right = rotateWithLeftChild(t.right);
return rotateWithRightChild(t);
}
private void printTree(AvlNode t) {
if (t == null) {
return;
}
printTree(t.left);
System.out.print(t.element + " ");
printTree(t.right);
}
}
RBT
红黑树有是对AVL的改进,因为AVL需要大量的旋转导致查询提升的效率不足以弥补旋转造成的损失
红黑树相比于AVL多了个着色操作而且平衡判断条件为黑高
红黑树的调整方法
1. 如果插入节点的父节点为黑色则插入成功不需要调整
2. 如果插入节点的父节点是红色
2.1 如果叔叔节点为红色则将父节点与叔叔节点变为黑色,爷爷节点变为红色,在将爷爷节点作为子节点,循环执行直到根节点为止,最后再将根节点变为黑色
2.2 如果叔叔节点为黑色则与AVL相同(LL,RR,LR,RL)但需要添加一步着色操作
着色操作:
2.2.1. 将插入节点x的父节点p和叔叔节点u的颜色变为黑色
2.2.2. 将x的爷爷节点变为红色
2.2.3. 将爷爷节点看作子节点,循环执行直到根节点为止,最后再将根节点变为黑色
package com.suo.RedBlackTree;
public class RedBlackTree {
public static final int RED = 0;
public static final int BLACK = 1;
//节点
private static class RBTreeNode {
int color;
Integer key;
RBTreeNode left;
RBTreeNode right;
RBTreeNode parent;
public RBTreeNode(Integer key) {
this(key,null,null,null);
}
public RBTreeNode(Integer key, RBTreeNode left, RBTreeNode right, RBTreeNode parent) {
this.key = key;
this.left = left;
this.right = right;
this.parent = parent;
this.color = BLACK;
}
}
//根节点
private RBTreeNode header;
public RedBlackTree() {
header = new RBTreeNode(null);
}
public void insert(Integer key) {
insert(header,key);
}
public void printTree() {
printTree(header);
}
//插入方法
private void insert(RBTreeNode root, Integer key) {
RBTreeNode node = new RBTreeNode(key);
insert(root,node);
}
//插入方法
private void insert(RBTreeNode root, RBTreeNode node) {
RBTreeNode x = root;//跟着x周 用于指向父节点
RBTreeNode y = null;//查询指针
//1. 将红黑树当成一个正常的排序树插入
while(x != null && x.key != null) {
y = x;
if (node.key < x.key) {
x = x.left;
}
else {
x = x.right;
}
}
node.parent = y;
//判断当前插入节点是父节点左孩子还是有孩子
//当y == NULL 时说明此时x为根节点
if (y == null) {
header = node;
}
else {
if (node.key < y.key) {
y.left = node;
}
else {
y.right = node;
}
}
//2. 将出插入节点设置为红色
node.color = RED;
//3. 将二叉搜索树修正为红黑树
fix(node);
}
/**
* 红黑树的调整方法
* 1. 如果插入节点的父节点为黑色则插入成功不需要调整
* 2. 如果插入节点的父节点是红色
* 2.1 如果叔叔节点为红色则将父节点与叔叔节点变为黑色,爷爷节点变为红色,在将爷爷节点作为子节点,循环执行直到根节点为止,最后再将根节点变为黑色
* 2.2 如果叔叔节点为黑色则与AVL相同(LL,RR,LR,RL)但需要添加一步着色操作
* 着色操作:
* 2.2.1. 将插入节点x的父节点p和叔叔节点u的颜色变为黑色
* 2.2.2. 将x的爷爷节点变为红色
* 2.2.3. 将爷爷节点看作子节点,循环执行直到根节点为止,最后再将根节点变为黑色
* @param node
*/
private void fix(RBTreeNode node) {
RBTreeNode parent = null;
RBTreeNode grandParent = null;
//如果我的父节点存在 并且父节点颜色时红色
while((parent = node.parent) != null && parent.color == RED) {
grandParent = parent.parent;
//如果父节点时祖父节点的左孩子
if (parent == grandParent.left) {
//条件1 叔叔节点是红色
RBTreeNode uncle = grandParent.right;
if (uncle != null && uncle.color == RED) {
uncle.color = BLACK;
parent.color = BLACK;
grandParent.color = RED;
node = grandParent;
continue;
}
//条件2 叔叔节点是黑色 且当前节点是右孩子
else if (parent.right == node) {
//LR先左旋后右旋
leftRotate(parent);
RBTreeNode temp = parent;
parent = node;
node = temp;
}
parent.color = BLACK;
grandParent.color = RED;
//条件3 叔叔是黑色的并且当前节点是左孩子
rightRotate(grandParent);
}
//如果父节点是租房节点的右孩子
else {
RBTreeNode uncle = grandParent.left;
if (uncle != null && uncle.color == RED) {
uncle.color = BLACK;
parent.color = BLACK;
grandParent.color = RED;
node = grandParent;
continue;
}
//如果是黑色 并且当前结点是左孩子
if (parent.left == node) {
rightRotate(parent);
RBTreeNode temp = parent;
parent = node;
node = temp;
}
parent.color = BLACK;
grandParent.color = RED;
//如果叔叔是黑色 并且当前结点是右孩子
leftRotate(grandParent);
}
}
header.color = BLACK;
}
//左旋
public void leftRotate(RBTreeNode p) {
// 在当前节点不为null时,才进行左旋操作
if (p != null) {
// 先记录p的右儿子
RBTreeNode rightChild = p.right;
// 1. 空出右儿子的左子树
p.right = rightChild.left;
// 左子树不为空,需要更新父节点
if (rightChild.left != null) {
rightChild.left.parent = p;
}
// 2. 空出节点p的父节点
rightChild.parent = p.parent;
// 父节点指向右儿子
if (p.parent == null) { // 右儿子成为新的根节点
this.header = rightChild;
} else if (p == p.parent.left) { // 右儿子成为父节点的左儿子
p.parent.left = rightChild;
} else { // 右儿子成为父节点的右儿子
p.parent.right = rightChild;
}
// 3. 右儿子和节点p成功会师,节点p成为左子树
rightChild.left = p;
p.parent = rightChild;
}
}
//右旋
public void rightRotate(RBTreeNode p) {
if (p != null) {
// 记录p的左儿子
RBTreeNode leftChild = p.left;
// 1. 空出左儿子的右子树
p.left = leftChild.right;
// 右子树不为空,需要更新父节点
if (leftChild.right != null) {
leftChild.right.parent = p;
}
// 2. 空出节点p的父节点
leftChild.parent = p.parent;
// 父节点指向左儿子
if (p.parent == null) { // 左儿子成为整棵树根节点
this.header = leftChild;
} else if (p.parent.left == p) { // 左儿子成为父节点左儿子
p.parent.left = leftChild;
} else { // 左儿子成为父节点的右儿子
p.parent.right = leftChild;
}
// 3. 顺利会师
leftChild.right = p;
p.parent = leftChild;
}
}
//遍历
private void printTree(RBTreeNode root) {
if (root == null) return;
printTree(root.left);
System.out.print(root.key + " ");
printTree(root.right);
}
}


被折叠的 条评论
为什么被折叠?



