一、BST树
二叉查找树(Binary Search Tree),又名二叉搜索树或二叉排序树。可以是一颗空树,或者是具有下列性质的二叉树:
(1)若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
定义BST树:
/**
* BST树节点的实现
* @param <T>
*/
class BSTNode <T extends Comparable<T>> {
private T data;
private BSTNode<T> left;
private BSTNode<T> right;
public BSTNode(T data){
this.data = data;
}
public BSTNode(T data,BSTNode<T> left,BSTNode<T> right){
this.data = data;
this.left = left;
this.right = right;
}
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 树的实现
* @param <T>
*/
class BST<T extends Comparable<T>>{
private BSTNode<T> root; //指向BST树的根节点
public BST(){
this.root = null;
}
}
二、查找操作
BST树查找非常简单,从根节点开始查找,如果要查找的数比根节点小,就在左子树上继续查找,如果大于根节点的数,就在右子树上继续查找。整个查找过程就是从根节点开始一直向下的一条路径,若假设树的高度是n,那么查找过程的时间复杂度就是O(n)。
递归实现
public T query(T val){
return query(this.root,val);
}
private T query(BSTNode<T> root, T val) {
if (this.root == null){ //空树
return null;
}
if (root.getData().compareTo(val) > 0){ //val小于根节点的数
return query(root.getLeft(),val); //左子树查找
}else if (root.getData().compareTo(val) < 0){ //val大于根节点的数
return query(root.getRight(),val); //右子树查找
}else { //val等于根节点的数
return root.getData(); //返回该值
}
}
非递归实现
public T nonrecur_query(T val){
if (this.root == null){
return null;
}
//如果根节点不为空,则从根节点开始搜索
BSTNode<T> parent = null;
BSTNode<T> cur = this.root;
while (cur != null){
parent = cur;
if (cur.getData().compareTo(val) > 0){ //查找的数据小于cur
cur = cur.getLeft(); //左子树查找
}else if (cur.getData().compareTo(val) < 0){ //查找的数据大于cur
cur = cur.getRight(); //右子树查找
}else {
return cur.getData();
}
}
return null;
}
三、插入操作
插入操作同查询类似,如果要插入的数比根节点小,就在左子树上搜索可以插入的位置,反之在右子树上查找。
如果在上述BST树要插入7,如下图示:
递归实现
/**
* BST 树递归插入函数接口
* @param val
*/
public void insert(T val){
this.root = insert(this.root,val);
}
/**
* BST树递归插入的具体实现函数
* @param root
* @param val
* @return
*/
private BSTNode<T> insert(BSTNode<T> root, T val) {
if (root == null){ //找到插入的位置
return new BSTNode<>(val); //进行插入
}
if (root.getData().compareTo(val) > 0){ //val < 根节点的值
root.setLeft(insert(root.getLeft(),val)); //递归左子树
}
if (root.getData().compareTo(val) < 0){ //val > 根节点的值
root.setRight(insert(root.getRight(),val)); //递归右子树
}
return root; //将当前节点返回到父节点的函数调用中
}
非递归实现
public void noncur_insert(T val){
//如果根节点为空,直接插入为根节点
if (this.root == null){
this.root = new BSTNode<T>(val);
return;
}
//如果根节点不为空,则从根节点开始搜索
BSTNode<T> parent = null;
BSTNode<T> cur = this.root;
while (cur != null){
parent = cur;
if (cur.getData().compareTo(val) > 0){ //插入的数据小于cur
cur = cur.getLeft(); //左子树查找
}else if (cur.getData().compareTo(val) < 0){ //插入的数据大于cur
cur = cur.getRight(); //右子树查找
}else {
return;
}
}
//查到最后,大的话就给右子树加,小的话就给左子树加
if (parent.getData().compareTo(val) > 0){
parent.setLeft(new BSTNode<T>(val));
}else {
parent.setRight(new BSTNode<T>(val));
}
}
四、删除操作
在了解删除操作之前,先来介绍一下前驱节点和后继节点
- 前驱节点: 待删除节点左子树中值最大的节点
- 后继节点: 待删除节点右子树中值最小的节点
删除操作分为三种情况
- 待删除的节点没有孩子节点,把删除节点的父节点的相应地址域置为null即可
- 待删除的节点只有一个孩子节点,把删除节点的子树根节点写入父节点相应的地址域
- 待删除节点有两个孩子节点,找到删除节点的前驱结点或者后继节点,用前驱或者后继把当前删除节点的值覆盖掉,然后删除前驱或者后继
递归实现
/**
* 递归删除val元素的函数接口
* @param val
*/
public void remove(T val){
this.root = remove(root,val);
}
/**
* 递归实现BST删除函数
* @param root
* @param val
* @return
*/
public BSTNode<T> remove(BSTNode<T> root,T val){
if (root == null){
return null;
}
if (root.getData().compareTo(val) > 0){
root.setLeft(remove(root.getLeft(),val));
}else if (root.getData().compareTo(val) < 0){
root.setRight(remove(root.getRight(),val));
}else { //找到要删除的节点了
if (root.getLeft() != null && root.getRight()!= null){ //待删除的节点有两个孩子节点
BSTNode<T> pre = root.getLeft();
while (pre.getRight() != null){
pre = pre.getRight();
}
root.setData(pre.getData()); //用前驱覆盖掉root的值
root.setLeft(remove(root.getLeft(),pre.getData())); //删除前驱节点
}else if (root.getLeft() != null){ //只有一个节点,直接将孩子节点返回给父节点的函数调用
return root.getLeft();
}else if (root.getRight() != null){
return root.getRight();
}else {
return null;
}
}
return root;
}
非递归实现
/**
* BST树的非递归删除(前驱节点实现)
* @param val
*/
public void noncur_remove(T val){
BSTNode<T> parent= null; //待删除节点的父节点
BSTNode<T> cur = this.root;
//搜索BST树,找到待删除的节点
while (cur != null){
if (cur.getData().compareTo(val) > 0){ //待删除的节点的值小于当前节点的值
parent = cur;
cur = cur.getLeft(); //在左子树找
}else if (cur.getData().compareTo(val) < 0){ //待删除的节点的值大于当前节点的值
parent = cur;
cur = cur.getRight(); //在右子树找
}else { //cur就是要删除的节点
break; //跳出循环
}
}
/**
* 循环结束的两种情况:
* 1.找到cur即待删除的节点
* 2.cur=null,BST树里没有该节点
*/
if (cur == null){
System.out.println("该BST树中不存在值为"+val+"的节点,删除失败!");
return;
}
//先把情况1处理成后两种情况
//有两个孩子的节点
if (cur.getLeft() != null && cur.getRight() != null){
//用前驱节点代替删除节点,然后删除前驱节点
BSTNode<T> old = cur; //old记录要删除的节点
parent = cur;
cur = cur.getLeft(); //找前驱
//找左子树中值最大的节点
while (cur.getRight() != null){ //往右边找
parent = cur;
cur = cur.getRight();
}
//循环结束,cur指向前驱
//用前驱节点的值代替待删除的节点old的值
old.setData(cur.getData());
}
//处理情况 2 和 3
//删除只有一个孩子的节点或者没有孩子的节点
BSTNode<T> child = cur.getLeft();
if (child == null){ //如果没有左孩子
child = cur.getRight(); //指向右孩子节点
} //此时child指向cur唯一的孩子节点
if (parent == null){ //要删除的是根节点
this.root = child;
}else if (parent.getLeft() == cur){ //只有左子树有节点
parent.setLeft(child);
}else { //只有右子树有节点
parent.setRight(child);
}
}
下一步就可以了解AVL树了,可以参考这个博客哦AVL树