一、BST树
BST树是具有下列性质的二叉树
- 若它的左子树不为空,则左子树上所有结点的值都小于根结点的值;
- 若右子树不为空,则右子树上所有结点的值都大于根结点的值;
- 它的左右子树也分别为BST树;
二、定义BST树
首先定义一个BST树的数据结构
/**
* BST树的实现
* @param <T>
*/
class BST<T extends Comparable<T>> {
private BSTNode<T> root; //指向根节点
public BST() { //BST树的初始化
this.root = null;
}
//增删查等方法
}
/**
* BST树的节点类型
* @param <T>
*/
class BSTNode<T extends Comparable<T>> {
private T data; //数据域
private BSTNode<T> left; //左孩子
private BSTNode<T> right; //右孩子
public BSTNode(T data , BSTNode<T> leftChild , BSTNode<T> rightChild) {
this.data = data;
this.left = leftChild;
this.right = rightChild;
}
public BSTNode(T data) {
this.data = data;
this.left = null;
this.right = null;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public BSTNode<T> getLeft() {
return left;
}
public void setLeft(BSTNode<T> left) {
this.left = left;
}
public BSTNode<T> getRight() {
return right;
}
public void setRight(BSTNode<T> right) {
this.right = right;
}
}
三、BST树的相关操作
1.BST树的插入
非递归插入
public void non_insert(T data) {
// 1、判断root根节点是否为null,null则说明BST树为空,直接让root指向
if (this.root == null) {
this.root = new BSTNode<>(data);
return;
}
//BST树不为null,则从根节点开始寻找一个合适的位置放置新元素
BSTNode<T> cur = this.root; //用cur遍历查找BST树
BSTNode<T> parent = null; //parent是cur的父亲节点
while (cur != null) {
if (cur.getData().compareTo(data) > 0) { //cur.getData() > data
parent = cur;
cur = cur.getLeft();
} else if (cur.getData().compareTo(data) < 0) { //cur.getData() < data
parent = cur;
cur = cur.getRight();
} else { //cur.getData() == data,说明BST树中存在data的元素节点
return;
}
}
//结束上面的循环说明cur == null,生成新的节点,将新节点写入其父亲节点parent的孩子域
if (parent.getData().compareTo(data) > 0) { //parent.getData > data ,新节点应为parent的左孩子
parent.setLeft(new BSTNode<T>(data));
} else { //data大于父亲节点,应为parent的右孩子
parent.setRight(new BSTNode<T>(data));
}
}
递归插入
//BST树的递归插入
public void insert(T data) {
this.root == insert(this.root, data);
}
//以root为起始节点,寻找合适位置插入data,并把插入好的二叉树的根节点返回
private BSTNode<T> insert(BSTNode<T> root, T data) {
if (root == null) { //说明找到了data插入的位置,在这里创建结点
return new BSTNode<>(data);
}
if (root.getData().compareTo(data) > 0) {
root.setLeft(insert(root.getLeft(), data));
} else if (root.getData().compareTo(data) < 0){
root.setRight(insert(root.getRight(), data));
} else {
return null;
}
return root; //将当前节点返回到父节点的函数调用中
}
2.BST树的删除
非递归删除
public void non_remove(T data) {
if (this.root == null) {
return;
}
//1、寻找待删除的节点
BSTNode<T> cur = this.root;
BSTNode<T> parent = null; //待删除节点的父亲节点
while (cur != null) {
if (cur.getData().compareTo(data) > 0) { //cur.getData > data,应该去cur的左子树寻找
parent = cur;
cur = cur.getLeft();
} else if (cur.getData().compareTo(data) < 0) { //cur.getData < data,则去cur的左子树中寻找待删除节点
parent = cur;
cur = cur.getRight();
} else { //cur就是需要删除的节点,跳出循环
break;
}
}
//循环结束:1、cur就是需要删除的节点 2、cur == null,则说明BST树中不存在值为data的节点
if (cur == null) {
System.out.println("BST树中不存在值为data的节点");
return;
}
//2、判断删除节点是否有两个孩子,如果有,用前驱的值替代
if (cur.getLeft() != null && cur.getRight() != null) {
BSTNode<T> old = cur; //old 记录要删除的结点
parent = cur;
cur = cur.getLeft(); //此时cur是要寻找old节点的前驱结点,进入当前节点的左子树
while (cur.getRight() != null) { //寻找old和其父亲节点
parent = cur;
cur = cur.getRight(); //cur寻找左子树中值最大的节点
}
//循环结束说明cur是old的前驱结点
old.setData(cur.getData()); //用前驱节点的值覆盖待删除节点old的值
}
//3、删除只有一个孩子的结点或者没有孩子的节点(视作其只有一个孩子节点null)
//事实上第二步中的cur的值覆盖old的值后,cur节点也还没有被删除
BSTNode<T> child = cur.getLeft();
if (child.getLeft() == null) {
child.getRight();
} //child已经指向cur唯一的孩子节点了
if (parent == null) { //要删除的是根节点( cur == this.root , parent == null )
this.root = child;
} else {
//判断cur跟parent的关系
if (cur == parent.getLeft()) { //cur是parent的左孩子
parent.setLeft(child);
} else { //cur是parent的右孩子
parent.setRight(child);
}
}
}
递归删除
//递归删除
public void remove(T data) {
this.root == remove(this.root, data);
}
private BSTNode<T> remove(BSTNode<T> root, T data) {
if (root != null) {
return null;
}
if (root.getData().compareTo(data) > 0) {
root.setLeft(remove(root.getLeft(), data));
} else if (root.getData().compareTo(data) < 0) {
root.setRight(remove(root.getRight(), data));
} else { //找到要删除的结点了
if (root.getLeft() != null && root.getRight() != null) { //root有两个孩子结点,则用其前驱的值替代root的值,再删除前驱
BSTNode<T> pre = root;
pre = pre.getLeft();
while (pre.getRight() != null) {
pre = pre.getRight();
}
root.setData(pre.getData());
root.setLeft(remove(root.getLeft(), pre.getData()));
} else { //root只有一个孩子结点(叶子节点看作有一个为null的孩子结点),将其孩子结点直接返回给父节点的函数调用
if (root.getLeft() != null) {
return root.getLeft();
} else if (root.getRight() != null) {
return root.getRight();
} else {
return null;
}
}
}
return root;
}
3.查询BST树中是否存在值为data的结点
非递归方式查找
//BST树的查询值为data的值是否存在,存在返回true,不存在返回false
public boolean non_reserch(T data) {
if (this.root == null) {
return false;
}
BSTNode<T> cur = this.root;
while (cur != null) {
if (cur.getData().compareTo(data) > 0) {
cur = cur.getLeft();
} else if (cur.getData().compareTo(data) < 0) {
cur = cur.getRight();
} else {
return true;
}
}
System.out.println("BST树中未找到值为data的节点");
return false;
}
递归方式查找
public boolean reserch(T dada) {
return reserch(this.root , dada);
}
private boolean reserch(BSTNode<T> root, T dada) {
if (root == null) {
return false;
}
if (root.getData().compareTo(dada) > 0) {
return reserch(root.getLeft(), dada);
} else if (root.getData().compareTo(dada) < 0) {
return reserch(root.getRight(), dada);
} else { // root.getData().compareTo(dada) == 0 找到值为data的结点
return true;
}
}
4.返回BST树中所有结点的个数(递归方式)
//返回BST树中所有节点的个数
public int number() {
return number(this.root);
}
//以root为根节点计算BST树中的节点数
private int number(BSTNode<T> root) {
int num = number(root.getLeft()) + number(root.getRight()) + 1;
return num;
}
5.计算BST树的高度(递归方式)
//计算BST树的高度
public int level() {
return level(this.root);
}
//计算以root为根节点的BST树的高度
public int level(BSTNode<T> root) {
if (root == null) {
return 0;
} else {
int left = level(root.getLeft()) + 1;
int right = level(root.getRight()) + 1;
return left > right ? left : right;
}
}
6.BST树的遍历
前序遍历(VLR)
//递归方式前序遍历BST树的API
public void preOrder() {
System.out.println("递归方式前序遍历BST树");
preOrder(this.root);
System.out.println();
}
//前序遍历的递归实现
private void preOrder(BSTNode<T> root) {
if (root != null) {
System.out.print(root.getData() + " ");
preOrder(root.getLeft());
preOrder(root.getRight());
}
}
中序遍历(LVR)
//递归方式中序遍历BST树的API
public void inOrder() {
System.out.println("递归方式中序遍历BST树");
inOrder(this.root);
System.out.println();
}
//中序遍历的递归实现
private void inOrder(BSTNode<T> root) {
if (root != null) {
inOrder(root.getLeft());
System.out.print(root.getData() + " ");
inOrder(root.getRight());
}
}
后序遍历(RVL)
//递归方式后序遍历BST树的API
public void postOrder() {
System.out.println("递归方式后序遍历BST树");
postOrder(this.root);
System.out.println();
}
//后序遍历的递归实现
private void postOrder(BSTNode<T> root) {
if (root != null) {
postOrder(root.getLeft());
postOrder(root.getRight());
System.out.print(root.getData() + " ");
}
}
层序遍历
//层序遍历BST树
public void levelOrder() {
System.out.println("递归层序遍历");
int high = level(this.root); //level()是求BST树高度的函数
for (int i = 1;i < high;i++) {
levelOrder(this.root,i);
}
System.out.println();
}
//层序遍历的递归实现
private void levelOrder(BSTNode<T> root , int i) {
if (root != null) {
if (i == 1) {
System.out.print(root.getData() + " ");
return;
}
levelOrder(root.getLeft(), i-1);
levelOrder(root.getRight(), i-1);
}
}
四、BST树常见的问题
1.BST树的镜像翻转
//BST树的镜像翻转 API
public void mirror() {
mirror(this.root);
}
//BST镜像翻转递归实现
private void mirror(BSTNode<T> root) {
if (root == null) {
return;
}
BSTNode<T> tmp = root.getLeft(); //定义tmp为中间结点
root.setLeft(root.getRight()); //交换root的左右孩子结点
root.setRight(tmp);
mirror(root.getLeft());
mirror(root.getRight());
}
2.打印BST树中在[begin, end]区间的所有元素
//打印BST树中满足[begin , end]区间的所有元素
public void prinAreaDatas(T begin, T end) {
prinAreaDatas(this.root, begin, end);
System.out.println();
}
public void prinAreaDatas(BSTNode<T> root , T begin , T end) {
if (root == null) { //当root为null时,结束递归
return;
}
if (root.getData().compareTo(begin) > 0) { //当root的值大于begin时,再有必要递归root的左子树
prinAreaDatas(root.getLeft(), begin, end);
}
//当root的值满足[begin, end]时,打印root
if (root.getData().compareTo(begin) >= 0 && root.getData().compareTo(end) <= 0) {
System.out.print(root.getData() + " ");
}
if (root.getData().compareTo(end) < 0) { //当root的值小于end时,再有必要递归root的右子树
prinAreaDatas(root.getRight(), begin, end);
}
}
3.判断一个二叉树是否是BST树
乍一看这个题目,似乎只要满足root的值大于左孩子小于右孩子的值就可以了,代码如下:
public boolean isBSTTree() {
return isBSTTree(this.root);
}
private boolean isBSTTree(BSTNode<T> root) {
if (root == null) {
return true;
}
if (root.getLeft() != null && root.getData().compareTo(root.getLeft().getData()) < 0) {
return false;
}
if (root.getRight() != null && root.getData().compareTo(root.getRight().getData()) > 0) {
return false;
}
return isBSTTree(root.getLeft()) && isBSTTree(root.getRight());
}
我们来测试一下
首先手动建立一个二叉树:
public static void test(){
BST<Integer> bst = new BST<>();
BSTNode<Integer> node1 = new BSTNode<>(40, null, null);
BSTNode<Integer> node2 = new BSTNode<>(20, null, null);
BSTNode<Integer> node3 = new BSTNode<>(10, null, null);
BSTNode<Integer> node4 = new BSTNode<>(50, null, null);
BSTNode<Integer> node5 = new BSTNode<>(80, null, null);
BSTNode<Integer> node6 = new BSTNode<>(30, null, null);
BSTNode<Integer> node7 = new BSTNode<>(90, null, null);
node1.setLeft(node2);
node1.setRight(node5);
node2.setLeft(node3);
node2.setRight(node4);
node5.setLeft(node6);
node5.setRight(node7);
bst.setRoot(node1);
System.out.println(bst.isBSTTree());
}
长这个样子,每个结点都满足root.left.data < root.data < root.right.data,但是很明显不是BST树
而测试的结果是这样
所以要明确的就是,不能只简单的判断root和两个孩子结点的大小关系,在BST树的在中序遍历中可以得到顺序的元素排列,所以只要按照中序遍历的思想,从根节点的左子树中最小的元素开始,让每个结点的值都满足大于它的直接前驱pre,即root.left.data < pre,更新pre = root.data,pre < root.right.data
//判断一个二叉树是不是BST树
public boolean isBSTTree() {
T pre = null;
return isBSTTree(this.root, pre);
}
//node记录的是中序遍历root的上一个结点
private boolean isBSTTree(BSTNode<T> root, T pre) {
if (root == null) {
return true;
}
//递归root的左子树,若不满足BST树的性质,返回false
if (!isBSTTree(root.getLeft(), pre)) {
return false;
}
//递归的判断条件:若root比node的值小说明不满足BST树的性质,返回false
if (pre != null && root.getData().compareTo(pre) < 0) {
return false;
}
pre = root.getData(); //更新value
//到这里说明root的左子树符合性质,再继续递归root的右子树
return isBSTTree(root.getRight(), pre);
}
4.返回两个结点的最近公共祖先
//返回两个结点的最近公共祖先
public T getLCA(T data1, T data2) {
return getLCA(this.root, data1, data2);
}
private T getLCA(BSTNode<T> root, T data1, T data2) {
if (root == this.root) {
return null;
}
//data1和data2的值都小于root,则递归去以root左子树上查找
if (root.getData().compareTo(data1) > 0 && root.getData().compareTo(data2) > 0) {
return getLCA(root.getLeft(), data1, data2);
} else if (root.getData().compareTo(data1) < 0 && root.getData().compareTo(data2) < 0) {
//data1和data2的值都大于root,则递归去以root的右子树上查找
return getLCA(root.getRight(), data1, data2);
} else {
//说明data1和data2分布在root的左右子树上,或者root的值等于data1或者data2,则root就是data1和data2的最近公共祖先
return root.getData();
}
}
5.返回中序遍历倒数第K个结点的值
6.判断tree是不是当前BST树的一颗子树
//判断tree是不是当前BST树的一颗子树
public boolean isChildTree(BST<T> tree) {
if (this.root == null || tree.root == null) {
return false;
}
//目的是寻找BST树中有无与tree根节点值相同的结点
BSTNode<T> cur = this.root;
while (cur != null) {
if (cur.getData().compareTo(tree.root.getData()) > 0) {
cur = cur.getLeft();
} else if (cur.getData().compareTo(tree.root.getData()) < 0) {
cur = cur.getRight();
} else {
break;
}
}
//结束循环有两种可能,遍历完BST树未找到与tree.root值相等的结点,则cur == null
if (cur == null) {
return false;
}
//找到cur.data == tree.root.data
return isChildTree(cur, tree.root);
}
private boolean isChildTree(BSTNode<T> cur, BSTNode<T> node) {
if (cur == null && node == null) { //当cur与troot都为null则说明tree与BST树结点完全相同
return true;
}
if (cur == null) { //cur == null说明tree中存在BST树没有的元素,返回false
return false;
}
if (node == null) { //node == null说明tree中仅有BST树中的一部分,是BST树的子树
return true;
}
//判断当前结点cur和node的值是否相等,不等则返回false
if (cur.getData().compareTo(node.getData()) != 0) {
return false;
}
//再分别递归当前节点cur和node的左子树、右子树
return isChildTree(cur.getLeft(), node.getLeft())
&& isChildTree(cur.getRight(), node.getRight());
}
7.根据参数传入的pre(BST树的前序遍历)和in(BST树的中序遍历)数组重建BST树
//根据参数传入的pre和in数组重建BST树
public void rebuild(T[] pre, T[] in) {
this.root = rebuild(pre, 0, pre.length-1,
in, 0, in.length-1);
}
/**
* @param pre 前序遍历的数组
* @param i pre数组元素起始位置
* @param j pre数组最后一个元素对应的位置
* @param in 中序遍历的数组
* @param m in数组元素起始位置
* @param n in数组最后一个元素对应的位置
* @return
*/
private BSTNode<T> rebuild(T[] pre, int i, int j,
T[] in, int m, int n) {
if (i > j || m > n) { //参数异常说明递归应该结束了
return null;
}
BSTNode<T> node = new BSTNode<>(pre[i]); //i代表先序数组的第一个元素,以pre[i]创建根节点
for (int k = 0; k <= m; k++) { //寻找pre[i]在in[]中的位置k
if (pre[i].compareTo(in[k]) == 0) {
node.setLeft(rebuild(pre, i+1, i+k-m,
in, m, k-1)); //递归创建node的左子树
node.setRight(rebuild(pre, i+k-m+1, j,
in, k+1, n)); //递归创建node的右子树
break;
}
}
return node;
}