二叉搜索树的Java实现

导读:本篇文章讲解 二叉搜索树的Java实现,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一.树的概念

树的定义:树是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

树的特点:
1.每个结点有零个或多个子结点;
2.没有父结点的结点为根结点;
3.每一个非根结点只有一个父结点;
4.每个结点及其后代结点整体上可以看做是一棵树,称为当前结点的父结点的一个子树;
补:树的检索效率较高!

在这里插入图片描述

结点的度
一个结点含有的子树的个数称为该结点的度; —如E有两个子树,度为2; F有 KLM三个子树,度为3。根节点的度为6。

叶结点: 度为0的结点称为叶结点,也可以叫做终端结点 ,如 B、C

分支结点: 度不为0的结点称为分支结点,即非终端结点

结点的层次: 从根结点开始,根结点的层次为1,根的直接后继层次为2,以此类推— 如 P、Q为第四层

结点的层序编号:将树中的结点,按照从上层到下层,然后同层从左到右的次序排成一个线性序列,把他们编成连续的自然数。

树的度: 树中所有结点的度的最大值 —上图中根节点的度最大,也有可能子节点的度更大
树的高度(深度): 树中结点的最大层次 —4

森林
m(m>=0)个互不相交的树的集合,将一颗非空树的根结点删去,树就变成一个森林;给森林增加一个统一的根
结点,森林就变成一棵树

孩子结点
一个结点的直接后继结点称为该结点的孩子结点—D是A的孩子结点,H不是。
双亲结点(父结点):
一个结点的直接前驱称为该结点的双亲结点 —-E为I的父节点
兄弟结点
同一双亲结点的孩子结点间互称兄弟结点 —-K、L、M的父节点都是F,即K、L、M是兄弟结点

二.二叉树

定义:二叉树就是不超过2的树 (每个结点最多有两个子结点)
满二叉树
如果每一个层的结点树都达到最大值,则这个二叉树就是满二叉树。

完全二叉树
叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树
即添加元素的时候都是从左往右依次放,保证叶结点只能出现在最下层or此下层。——L、G,倒数第三层就没有叶结点了

三.二叉查找树的实现

使用链表来实现二叉查找树,链表的首结点即为root根节点

BinaryTree类的实例变量和Node结点内部类

public class BinaryTree <Key extends Comparable,Value> { //泛型继承接口用的extends而不是 implements !

    private Node root; //根节点
    private int N;

    private class Node{ //结点内部类
        private Key key;
        private Value value;
        private Node left; //当前结点的左结点
        private Node right; //当前结点的右结点

        public Node(Key key, Value value, Node left, Node right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

put方法
1.如果当前结点中没有任何一个结点,则直接把新结点当做根结点使用 (递归终止条件)
2.如果当前树不为空,则从根结点开始:
2.1如果新结点的key<当前结点的key,则继续找当前结点的左子结点;
2.2如果新结点的key>当前结点的key,则继续找当前结点的右子结点;
2.3如果新结点的key等于当前结点的key,则树中已经存在这样的结点,覆盖该结点的value值即可。
put和get都有两个重载的方法,第一个是public修饰供外部使用,第二个private修饰的 有树结点参数的重载的方法是为了内部递归调用,将不同的子树结点作为当前结点依次去和添加的元素进行比较,直到再没有子节点时,用添加的K、V创造为新结点,作为当前结点的子节点,递归结束。

public void put(Key key,Value value){ //向整个树中插入一个键值对
        root = put(root, key, value);// 调用重载的put,从root开始,把根节点传入参数x(指定的某树x)
        //再将新的树更新为根节点root
    }

private Node put(Node x, Key key,Value value){ //在指定树x中添加一个键值对
        //如果x为空,x直接作为根节点
            if(x==null){
                N++;
                return new Node(key,value,null,null);  //返回新的根节点作为上一层的子节点, 递归出口。
            }

        //如果x不为空
        int com = key.compareTo(x.key); //先比较
            if(com>0){//如果key大于x结点的键,就继续找x结点的右子树
                x.right= put(x.right, key, value);
                //递归调用,把x的右子树作为当前结点再进行比较并put,如果右子树为null,则key作为x的右子树
                //在添加了新的子结点后,树结构发生了变化! 要将当前结点进行更新。而get方法没有改变树结构,就不需要更新。


            }else if(com<0) {//如果key小于x结点的键,就继续找x结点的左子树
                x.left=put(x.left,key,value);
                //递归调用,把x的左子树作为当前结点再进行比较并put,如果左子树为null,则key作为x的右子树

            }else{//如果key等于x结点的键,就覆盖x结点的值
                x.value=value;
                }
        return x;  //返回更改过的x
    }

get方法
查询方法get实现思想:
从根节点root开始:
1.如果要查询的key小于当前结点的key,则继续找当前结点的左子结点;
2.如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;
3.如果要查询的key等于当前结点的key,则树中返回当前结点的value。
get方法要根据key查找出value,需要从整个树查找,所以入口是root;从根节点开始依次比较key,递归调用重载的get方法,直到发现没有key相同的结点返回null(递归结束条件1); 或者找到key相同的结点就返回该结点的value(递归结束条件1)。

public Value get(Key key){  //根据key找出value,需要从整个树查找,所以入口是root
        return get(root,key); //调用重载的get方法,从根节点root开始找

    }

private Value get(Node x,Key key){ //从指定的树x中,根据key找出value
    if(x==null) {//如果x为null
        return null; //递归到最后没有key相同结点,直接返回null,递归出口1
    }else{ //如果x不为null时

        int com = key.compareTo(x.key); //先比较
        if(com>0){//如果key大于x结点的键,就继续找x结点的右子树
           return get(x.right, key);
            //递归调用,把x的右子树作为当前结点再进行比较并get,如果右子树为null,则返回null;或者找到key相同的就返回value

        }else if(com<0) {//如果key小于x结点的键,就继续找x结点的左子树
            return get(x.left,key);
            //递归调用,把x的左子树作为当前结点再进行比较并put,如果左子树为null,则返回null;或者找到key相同的就返回value

        }else{//如果key等于x结点的键,就返回value, 递归出口2
            return x.value;
        }
      }
    }

delete删除方法

1.先找到被删除结点;(这部分和get代码类似)
2.找到被删除结点右子树中的最小结点minNode
3.删除右子树中的最小结点minNode
4.让被删除结点的左子树 变成 最小结点minNode的左子树,让被删除结点的右子树 变成 最小结点minNode的右子

5.让被删除结点的父节点指向最小结点minNode
(即让要删除的结点x的右子节点的最小结点minNode,代替被删除的结点)
(若要删除的结点x(如14)没有右子树,则让左子树的最大结点(12)代替x(14)。 左子树的最大结点即x的左结点)
(若要删除的结点x没有左子树,则让右边子树的最小结点代替x。)
在这里插入图片描述

 public void delete(Key key){// 删除结点 ※
        delete(root,key);    //需要从整个树查找,所以从root开始
    }

    private Node delete(Node x,Key key){// 删除指定树x上的键值对,并返回该树
        // 树x为null,即找不到该结点,直接返回null
        if(x==null){
            return null;
        //即最后没有找到该结点,返回null,即树并没有任何变化!

        }else{
            N--;
            int com = key.compareTo(x.key); //先比较
            if(com>0){//如果key大于x结点的键,就继续找x结点的右子树
                x.right= delete(x.right, key);
                //递归调用,把x的右子树作为当前结点再进行比较,如果右子树为null,则返回null;或者找到key相同的进入删除部分的逻辑,
                //最后若删除了结点,则树的结构改变,需要更新x.right

            }else if(com<0) {//如果key小于x结点的键,就继续找x结点的左子树
                x.left= delete(x.left,key);
                //同理

            }else{//如果key等于x结点的键,就用要删除的结点x的右子树的最小结点代替 要删除的结点x
                //删除结点的逻辑: 令右子树中最小的结点minNode 替换被删除的结点

                if(x.right==null){ //若没有右结点,则令x的左结点代替X
                return x.left;
                }

                if(x.left==null){   //若没有左结点,则令x的右结点代替X
                    return x.right;
                }
                //以上两种情况特殊,和两边都有结点的情况不一样

                //如果左右结点都不为空,找有右子树的最小值来代替x;
                //找minNode:即右子树最左边的结点,只需要在右子树中一直找左子结点

                Node minNode=x.right;//默认为右子树的第一个
                while(minNode.left!=null){
                    minNode=minNode.left;
                }
                //删除minNode,
                Node n=x.right;
                while(n.left.left!=null){
                    if(n.left.left==null){ //即证明n为倒数第二个结点
                        n.left=null; //即删除了一个结点minNode
                    }else{
                        n=n.left; //如果还没到则继续向下更新n
                    }
                }
                //让x结点的左子树成为minNode的左子树
                    minNode.left=x.left;
                //让x节点的右子树成为minNode的右子树
                    minNode.right=x.right;
                //让x结点的父结点指向minNode
                    x=minNode; //把x重新赋值为minNode,此时minNode的左右结点已更新

            }
                return x;
        }
    }

二叉树的其他便捷方法:

  • 查找二叉树中最小的键key: 查找最小即从根节点开始找子左边的结点
//查找树中最小的key
    public Key min(){
        return min(root).key; //查找则需要从根节点root开始查找
    }

    //在指定的树中找出最小key所在的结点
    public Node min(Node x){
        if(x.left!=null){
            return min(x.left);  //递归

        }else{   //x.left=null ,即已经找到最小结点,递归结束条件
            return x;
        }
    }

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/89372.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!