实现二叉搜索树的添加,查找和删除(JAVA)

1.二叉搜索树

1.1 概念

二叉搜索树又称为二叉排序树,它是一颗空树或者具有以下性质的树:

  1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  3. 它的左右子树也分别为二叉搜索树
    在这里插入图片描述

例如对上图二叉树采用中序遍历,得到的结果为:{0,1,2,3,4,5,6,7,8,9}

即一颗二叉搜索树的中序遍历一定是有序的,且是从小到大进行排列

2.二叉搜索树的操作

创建节点,这里使用静态内部类来创建

代码示例:

calss BinarySearchTree {
	static class Node {
		public int val;
		public Node left;
		public Node right;
		public Node(int val) {
			this.val = val;
		}
	}
	//定义根节点为外部类的成员变量
	Node root = null;
}

2.1 插入

定义插入的值为val,则插入节点要考虑两种情况:

  1. 树为空树,即root == null,直接插入即可
    在这里插入图片描述

  2. 树不为空树,则先比较root的值和val大小关系,如果小于插入值,则说明去root的右边寻找;如果大于val,则说明去root的左边寻找;等于说明找到了,拼接即可

代码示例:

//插入节点
public void put(int val) {
	Node node = new Node(val);
	//第一次插入,root == null
	if(root == null) {
		root = node;
		return;
	}
	//不是第一次插入 root != null
	Node pre = null;//用来指向cur的父亲节点
	Node cur = root;
	while (cur != null) {
		//去右边找
		if (cur.val < val) {
			pre = cur;//记录下此时cur
			cur = pre.right;//cur向右节点移动
		}else if (cur.val > val) {//该去左边找
			pre = cur;
			cur = pre.left;
		}else {//已经有相等的值,则不做任何处理,直接退出
			return;
		}
	}
	//此时cur = null,pre = cur的根节点
	//判断当前val和pre的val大小,决定插入左边还是右边
	if (pre.val < val) {//插入到右节点
		pre.right = node;
	}else {//插入到左节点
		pre.left = node;
	}
}

2.2 查找

在这里插入图片描述
查找的大体思路跟插入差不多,也是从根节点的值开始比较,大的就往右边找,小的往左边找,直到找到或者找不到,也就是说循环退出会有两种结果,一种是找到节点并返回,一种是没找到返回null

代码示例:

public Node get(int val) {
	Node cur = root;
	while (cur != null) {
		//找到该节点
		if (cur.val == val) {
			return cur;
		}else if (cur.val < val) {//去右边找
			cur = cur.right;
		}else {//去左边找
			cur = cur.left;
		}
	}
	//说明没找到
	return null;
}

2.3 删除(难点)

对于二叉搜索树来说,插入和查询都很方便,因为这两个操作只有插入会用到父亲节点,而父亲节点自上而下遍历很容易寻找;但是对于删除操作来说,我们要找到当前需要删除节点的下一个节点,并且让这个节点覆盖掉需要删除的节点,这样才能完成删除操作,当然这是大体上的思路

设当前节点为cur,当前节点的父亲节点为pre,我们需要考虑一下3种情况

  1. cur.left == null
    ① cur == root,则 root = cur.right
    ② cur == pre.left,则 pre.left = cur.right
    ③ cur == pre.right,则 pre.right = cur.right
  2. cur.right == null
    ① cur == root,则 root = cur.left
    ② cur == pre.left,则 pre.left = cur.left
    ③ cur == pre.right,则 pre.right = cur.left
  3. cur.left != null && cur.right == null
    ① 找到cur的下一个节点 minCur
    ② cur = minCur 覆盖掉cur
    ③ 再覆盖掉之前的minCur,因此还需要定义一个minCur的根节点minPre

代码示例:

public boolean remove(int val) {
        Node cur =  root;//当前结点
        Node pre = null;//当前节点的根节点
        //先找到要删除的节点
        while (cur != null) {
            if (cur.val == val) {
                break;
            }else if (cur.val < val) {
                pre = cur;
                cur = pre.right;
            }else {
                pre = cur;
                cur = pre.left;
            }
        }
        //判断是否找到
        //cur为null,说明没有找到
        if (cur == null) return false;
        //说明找到了cur,开始判断cur的位置
        if (cur.left == null) {
            if (cur == root) {//是根节点
                root = cur.right;//替换
            }else {//不是根节点
                if (cur == pre.left) {
                    pre.left = cur.right;
                }else {
                    pre.right = cur.right;
                }
            }
        }else if (cur.right == null) {
            if (cur == root) {
                root = cur.left;
            }else {
                if (cur == pre.left) {
                    pre.left = cur.left;
                }else {
                    pre.right = cur.left;
                }
            }
        }else {//cur的左右节点都不为null
           	Node minPre = cur;//指向cur下一节点的根节点
            Node minCur = cur.right;//cur的下一个节点
            while (minCur.left != null) {
                minPre = minCur;
                minCur = minPre.left;
            }
            cur.val = minCur.val;//先覆盖掉原来节点
            //判断下一节点在根节点的哪一边
            if (minCur == minPre.left) {
                minPre.left = minCur.right;//再覆盖掉原来的下一节点
            }else {
                minPre.right = minCur.right;
            }
        }
        return true;
    }

2.4 演示

将刚刚的代码加上中序遍历整合到一起,然后给定一组测试数据来测试代码

代码示例:

public class HomeWork {
    public static void main(String[] args) {
        int[] arr = {7,5,45,9,26,41,2,6};
        BinarySearchTree binarySearchTree = new BinarySearchTree();
        for (int i = 0; i < arr.length; i++) {
            binarySearchTree.put(arr[i]);
        }
        binarySearchTree.inOrder(binarySearchTree.root);
        System.out.println();
        binarySearchTree.preOrder(binarySearchTree.root);
        System.out.println();
        try {
            System.out.println(binarySearchTree.get(45).val);
        }catch (NullPointerException e) {
            e.printStackTrace();
            System.out.println("没有该节点");
        }
        System.out.println(binarySearchTree.remove(45));
        binarySearchTree.inOrder(binarySearchTree.root);
    }
}
class BinarySearchTree {
    static class Node {
        public int val;
        public Node left;
        public Node right;

        public Node(int val) {
            this.val = val;
        }
    }
    Node root = null;
    public void put(int val) {
        Node node = new Node(val);
        //第一次插入,root == null
        if(root == null) {
            root = node;
            return;
        }
        //不是第一次插入 root != null
        Node pre = null;//用来指向cur的父亲节点
        Node cur = root;
        while (cur != null) {
            //去右边找
            if (cur.val < val) {
                pre = cur;//记录下此时cur
                cur = pre.right;//cur向右节点移动
            }else if (cur.val > val) {//该去左边找
                pre = cur;
                cur = pre.left;
            }else {//已经有相等的值,则不做任何处理,直接退出
                return;
            }
        }
        //此时cur = null,pre = cur的根节点
        //判断当前val和pre的val大小,决定插入左边还是右边
        if (pre.val < val) {//插入到右节点
            pre.right = node;
        }else {//插入到左节点
            pre.left = node;
        }
    }
    public Node get(int val) {
        Node cur = root;
        while (cur != null) {
            //找到该节点
            if (cur.val == val) {
                return cur;
            }else if (cur.val < val) {//去右边找
                cur = cur.right;
            }else {//去左边找
                cur = cur.left;
            }
        }
        //说明没找到
        return null;
    }
    //删除
    public boolean remove(int val) {
        Node cur =  root;//当前结点
        Node pre = null;//当前节点的根节点
        //先找到要删除的节点
        while (cur != null) {
            if (cur.val == val) {
                break;
            }else if (cur.val < val) {
                pre = cur;
                cur = pre.right;
            }else {
                pre = cur;
                cur = pre.left;
            }
        }
        //判断是否找到
        //cur为null,说明没有找到
        if (cur == null) return false;
        //说明找到了cur,开始判断cur的位置
        if (cur.left == null) {
            if (cur == root) {//是根节点
                root = cur.right;//替换
            }else {//不是根节点
                if (cur == pre.left) {
                    pre.left = cur.right;
                }else {
                    pre.right = cur.right;
                }
            }
        }else if (cur.right == null) {
            if (cur == root) {
                root = cur.left;
            }else {
                if (cur == pre.left) {
                    pre.left = cur.left;
                }else {
                    pre.right = cur.left;
                }
            }
        }else {//cur的左右节点都不为null
            Node minPre = cur;//指向cur下一节点的根节点
            Node minCur = cur.right;//cur的下一个节点
            while (minCur.left != null) {
                minPre = minCur;
                minCur = minPre.left;
            }
            cur.val = minCur.val;//先覆盖掉原来节点
            //判断下一节点在根节点的哪一边
            if (minCur == minPre.left) {
                minPre.left = minCur.right;//再覆盖掉原来的下一节点
            }else {
                minPre.right = minCur.right;
            }
        }
        return true;
    }
    //中序遍历
    public void inOrder(Node root) {
        if (root == null) return;
        inOrder(root.left);
        System.out.print(root.val + " ");
        inOrder(root.right);
    }
    //前序遍历
    public void preOrder(Node root) {
        if (root == null) return;
        System.out.print(root.val + " ");
        preOrder(root.left);
        preOrder(root.right);
    }
}

结果演示

2 5 6 7 9 26 41 45 
7 5 2 6 45 9 26 41 
45
true
2 5 6 7 9 26 41 

以上就是二叉搜索树的插入,查找和删除,此博客最想让大家掌握的是二叉搜索树的删除操作,希望各位小伙伴都能多多练习!

2.5 性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

在这里插入图片描述
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:logN

最差情况下,二叉搜索树退化为单支树,其平均比较次数为:2/N

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,都可以使二叉搜索树的性能最佳?关于这个改进我将再写一篇博客来讲解

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值