java数据结构YZP专栏-----二叉树 && 二叉搜索树(二叉排序树,BST)

本文详细介绍了二叉树的基本概念,包括根结点、父结点、子结点、叶子结点和路径等,并探讨了满二叉树和完全二叉树的特性。接着,重点讲解了二叉搜索树,这是一种特殊的二叉树,每个结点的左子结点小于当前结点,右子结点大于当前结点。文章还提供了二叉搜索树的Java实现,包括增、删、改、查操作,并给出了测试案例。通过实例解析,帮助读者更好地理解和运用二叉搜索树。
摘要由CSDN通过智能技术生成
主文章(数据结构的索引目录—进不去就说明我还没写完)
https://blog.csdn.net/grd_java/article/details/122377505
模拟数据结构的网站:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
源码(码云):https://gitee.com/yin_zhipeng/data_structures_and_algorithms_in_java.git
二叉搜索树动画演示

请添加图片描述

二叉树

就是一个看起来像一颗树的逻辑内存结构。由很多个结点(节点)组成,但是二叉树规定每个结点最多有两个子结点(两个分叉)
在这里插入图片描述

  1. 根结点:理解为树的根,树的入口,我们必须从根结点,才能找到其它结点
  2. 父结点:当前结点的父结点,比如图中A是B,C的父结点,C是F,G的父结点
  3. 子结点:就是当前结点的子结点,比如C和B是A的子结点
  4. 叶子结点:没有子结点的结点,比如H,E,F,G
  5. 路径:从根结点找到特定结点的路径
  6. 结点权:结点的额外值,有些情况下我们需要对比结点的权值,以判断开销最小的带权路径。
  7. 层(深度):树的层数,根结点自己算一层,如果它有子结点,子结点算一层,以此类推
  8. 树的高度:共有多少层
  9. 子树:上图中,A是整个二叉树,B是A的左子树,因为B结点是D、H、E结点的根结点。
  10. 森林:有多颗树,就是森林。
  11. 度:表示当前结点是一个什么样的结点。度可以看作结点间的连线。0度,代表这个结点没有子结点,也就是叶子结点。1度,代表这个结点又左或右子树。2度,表示这个结点同时有左右两颗子树
重要的二叉树概念(做题必备特性)

1. 第i层最多有2i-1个结点
2. 二叉树层数为n时,此二叉树最多有2n-1个结点(因为根结点是1个,自己算一层)
3. 叶子结点数为a个,度为2的结点数为b个。则a = b+1,也就是说,叶子结点,永远比度为2的结点多一个

在这里插入图片描述

  1. 满二叉树:除了叶子结点外,其它结点的度都为2。也就是除了最后一层的叶子结点,其它结点都有2个子结点
  1. 结点个数为(2^n)-1(n为层数,2的n次方,减一)
  2. 因为满二叉树,每一层都填满了(连续),所以满二叉树中第 i 层的节点数为 2n-1 个
  3. 深度为 k 的满二叉树必有 2k-1 个节点 ,叶子数为 2k-1
  4. 满二叉树中不存在度为 1 的节点,每一个分支点中都两棵深度相同的子树,且叶子节点都在最底层
  5. 具有 n 个节点的满二叉树的深度为 log2(n+1)----(对数公式都回吧?ax = N,x = logaN)---- 可以算出深度
  1. 完全二叉树:二叉树所有叶子结点,都在倒数第一和倒数第二层。倒数第一层叶子结点左边连续(值不用连续,就是不能缺一块,比如下图14没有左节点81,那么就缺了一块,不是完全二叉树了),倒数第二层叶子结点右边连续。
  1. 注意和满二叉树区分,如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。
    在这里插入图片描述
  2. X个结点的完全二叉树,深度为(log2X)+1
  3. 第X个元素(X表示二叉树的第几个元素,从0开始)的左子节点为2X+1,右子节点为2X+2,父节点为(X-1)/2.
  4. 总结点个数为total,当前结点下标为X,如果2X>total,则结点没有左子树,否则左子结点为2X,同理,2X+1>total,没有右子树否则2X+1是右子树
  1. 前序遍历:先输出父节点,再遍历左子树和右子树
  2. 中序遍历:先遍历左子树,再输出父节点,再遍历右子树
  3. 后序遍历:先遍历左子树,再遍历右子树,最后输出父节点
如果看不懂上面满二叉树和完全二叉树的区别,可以往下看

在这里插入图片描述

  1. 仔细看满二叉树的编号,数字是一直连续的。它也是一棵完全二叉树。
  2. 而下面的完全二叉树,编号也要连续,并且同一个结点的编号和满二叉树一模一样,这就是完全二叉树。也就是编号连续,并且编号相同的结点和满二叉树相同。但是它的结点个数可能比满二叉树少。
  3. 因此说,满二叉树一定是完全二叉树,但是完全二叉树不一定是满二叉树。因为只要编号是连续的,同一个编号的结点和满二叉树相同,就可以称为是完全二叉树。
  4. 完全二叉树很重要,因为它可以无压力的表示在数组中,因为编号连续(下标)
二叉搜索树(二叉排序树,BST)

二叉树的一种,保留二叉树的概念和每个结点最多两个子结点的特性。但是二叉搜索树有特别的额外规定

  1. 每个结点的左子结点,要比当前结点小,也就是左子树都是更小的
  2. 每个结点的右子结点,要比当前结点大,右子树都是更大的

二叉搜索树的代码实现如下:

  1. 效果
    在这里插入图片描述
  2. 测试方法:我们想让这颗二叉树,能通过泛型,不局限与特定一种数据类型。所以需要用户根据自己使用的数据类型,自定义排序逻辑
    在这里插入图片描述
  3. 树节点
    在这里插入图片描述
  4. 添加结点操作
    在这里插入图片描述
  5. 中序查找和中序遍历,前序和后序,就是代码位置发生了变化而已
    在这里插入图片描述
  6. 删除:分为删除根结点和删除非根结点两种,删除根结点要在二叉树中,非根结点就在树结点类中完成。比较复杂的是,删除结点后,被删除结点的子树如何处理。具体大家直接看代码吧(代码在下面给出),图片这里就理一下逻辑。
    在这里插入图片描述
二叉搜索树
package com.yzpnb.data_structures.tree.binary_search_tree;

import java.util.Comparator;

/**
 * 二叉搜索树
 * 泛用式:使用泛型。泛型无法直接比较,通过用户自定义Comparator实现来进行节点大小比较
 */
public class BinarySearchTree<E> {
    //静态内部类,二叉树结点
    private static class TreeNode<E> {
        TreeNode<E> leftNode;//左子节点,左子树
        E data;//数据域
        TreeNode<E> rightNode;//右子节点,右子树
        //构造方法
        TreeNode(TreeNode<E> leftNode,E data,TreeNode<E> rightNode){
            this.leftNode = leftNode;
            this.data = data;
            this.rightNode = rightNode;
        }
        /**增====需要传入比较规则Comparator,因为我们是泛型。**/
        //根据data元素添加
        public void add(E e, Comparator<? super E> c){
            //compare(o1,o2)正数表示o2>o1,0表示o1=o2,负数表示o2小于o1
            if(c.compare(e,data)>=0){//如果要插入的元素,比如当前结点小,就插入左子树
                //如果当前结点没有左子树,就直接让它当左子树
                if(leftNode == null) leftNode = new TreeNode<E>(null,e,null);
                else leftNode.add(e,c);//否则去左子树继续判断
            }else{//如果比当前结点大,或者和当前结点一样,就插到右子树
                //如果当前结点没有左子树,就直接让它当左子树
                if(rightNode == null) rightNode = new TreeNode<E>(null,e,null);
                else rightNode.add(e,c);//否则去左子树继续判断
            }
        }
        //直接添加结点,代码重复,可以优化
        void add(TreeNode<E> e, Comparator<? super E> c){
            //compare(o1,o2)正数表示o2>o1,0表示o1=o2,负数表示o2小于o1
            if(c.compare(e.data,data)>=0){//如果要插入的元素,比如当前结点小,就插入左子树
                //如果当前结点没有左子树,就直接让它当左子树
                if(leftNode == null) leftNode = e;
                else leftNode.add(e,c);//否则去左子树继续判断
            }else{//如果比当前结点大,或者和当前结点一样,就插到右子树
                //如果当前结点没有左子树,就直接让它当左子树
                if(rightNode == null) rightNode = e;
                else rightNode.add(e,c);//否则去左子树继续判断
            }
        }
        /**删====删除指定非root元素===如果有多个,就删除第一个遇见的,根据要删除的父节点删除。**/
        //如果删除的元素,有左右子节点,就先让左子树上移,然后将右子树添加进入
        public TreeNode<E> deleteOne(E e,Comparator<? super E> c){
            TreeNode<E> parent = null;//为了代码阅读性而定义,要删除结点的父节点
            TreeNode<E> deleted = null;//被删除的节点
            if(leftNode != null){//如果有左子树
                if(leftNode.data.equals(e)){//如果左子节点,恰好是要删除的元素
                    parent = this;//父节点
                    //删除结点,删除结点是父节点的左子树
                    deleted = doDeleteOne(parent,parent.leftNode,true,c);
                    return deleted;//返回被删除结点
                }else{//如果不是左子结点,继续遍历
                    deleted = leftNode.deleteOne(e,c);
                }
            }
            //在左子树找到,直接return,结束递归
            if(deleted != null) return deleted;
            //否则用同样的逻辑找右子树
            if(rightNode != null){
                if(rightNode.data.equals(e)){
                    parent = this;
                    deleted = doDeleteOne(parent,parent.rightNode,false,c);
                    return deleted;
                }else{
                    deleted = rightNode.deleteOne(e,c);
                }
            }
            return deleted;
        }

        /**
         * 负责删除逻辑
         * @param parent 要删除结点的父节点
         * @param deleted 被删除结点
         * @param leftOrRight true表示deleted是parent的leftNode。false表示deleted是parent的rightNode
         * @param c 比较器
         * @return deleted
         */
        public TreeNode<E> doDeleteOne(TreeNode<E> parent,TreeNode<E> deleted,boolean leftOrRight,Comparator<? super E> c){
            //被删除结点
            TreeNode<E> left = deleted.leftNode;//被删除结点的左子树
            TreeNode<E> right = deleted.rightNode;
            //结点反转
            if(leftOrRight){//删除的是parent的左子节点
                parent.leftNode = left; //直接让parent的leftNode指向deleted的左子树
                if(right != null)//如果deleted有右子树
                    parent.add(right,c);//按照逻辑将其插入
            }else{
                parent.rightNode = left;
                if(right != null)
                    parent.add(right,c);
            }
            return deleted;
        }

        /**改====查到指定元素,然后直接改就行**/
        /**查**/
        //中序查找指定元素,先走左,再走当前结点,再走右
        public TreeNode<E> midFind(E e) {
            TreeNode<E> node = null;//用来保存最终结果
            if(leftNode!=null)//如果有左子树,先走左
                node = leftNode.midFind(e);
            //左子树遍历完成
            //判断在左子树是否找到了目标
            if(node != null) return node;
            //没找着就判断当前结点是不是
            if(data.equals(e))
                return this;
            //不是就找右子树
            if(rightNode!=null)
                node = rightNode.midFind(e);
            return node;
        }
        /**遍历**/
        //中序遍历
        public void midPrint() {
            if(leftNode != null) leftNode.midPrint();//先遍历左子树
            System.out.print(" ,"+this);//然后输出自己
            if(rightNode!=null) rightNode.midPrint();//遍历右子树
        }
        @Override
        public String toString() {
            return "TreeNode{" +
                    "data=" + data +
                    '}';
        }
    }
    private TreeNode<E> root;//根结点
    Comparator<? super E> c;
    //构造二叉搜索树
    public BinarySearchTree(Comparator<? super E> c){
        this.c = c;
    }//可以初始不指定根结点
    public BinarySearchTree(TreeNode<E> root,Comparator<? super E> c) {//可以初始化指定root
        this.root = root;
        this.c = c;
    }
    //判断二叉树是否为空
    public boolean isEmpty(){
        return root==null?true:false;
    }
    //如果为空,提示用户
    public boolean isEmptyError(){
        if(isEmpty()) {
            System.out.println("树为空,无法操作!!!");
            return true;
        }else{
            return false;
        }
    }
    /**增====需要传入比较规则Comparator,因为我们是泛型。**/
    //从根结点开始查找正确的插入位置,满足小的在左边,大的在右边,正确的插入位置应该是,找到满足条件且没有子节点的节点,然后当前要插入的节点,成为它的子节点
    public E addNode(E data){
        if(isEmpty()) //如果没有根结点,让新插入的成为根结点。
            root = new TreeNode<>(null,data,null);
        else {
            root.add(data,c);
        }
        return data;
    }
    /**删**/
    public TreeNode<E> deleteOne(E e){
        if(!isEmptyError()) {
            TreeNode<E> deleted = null;
            if(e.equals(root.data)){//根结点需要特殊删除
                deleted = root;
                TreeNode<E> left = root.leftNode,right = root.rightNode;
                if(left != null) {
                    root = left;
                    if(right != null)
                        root.add(right,c);
                }
                else
                    root = right;
            }else{//删除的是非根结点
                deleted = root.deleteOne(e,c);
            }

            if(deleted == null){
                System.out.println("没有找到要删除的节点!!!");
                return deleted;
            }
            return deleted;
        }
        return null;
    }
    /**改====查到指定元素,然后直接改就行**/
    /**查**/
    //中序查找指定元素,先走左,再走当前结点,再走右
    public TreeNode<E> midFind(E e){
        if(!isEmptyError()) {
            TreeNode<E> node = root.midFind(e);
            if( node == null) System.out.println("没找着!!!");
            return node;
        }
        return null;
    }
    /**遍历**/
    //中序遍历先走左,再走当前结点,再走右
    public void midPrint(){
        if(isEmptyError()) return ;
        System.out.print("中序遍历:{");
        root.midPrint();
        System.out.println("}");
    }

}
测试类
package com.yzpnb.data_structures.tree.binary_search_tree;

import java.util.Comparator;

public class Test {
    public static void main(String[] args) {
        BinarySearchTree<String> binarySearchTree = new BinarySearchTree<>(new Comparator<String>() {
            //正数表示o2>o1,0表示o1=o2,负数表示o2小于o1
            @Override
            public int compare(String o1, String o2) {
                return o2.compareTo(o1);
            }
        });
        binarySearchTree.addNode("7");
        binarySearchTree.addNode("4");
        binarySearchTree.addNode("5");
        binarySearchTree.addNode("3");
        binarySearchTree.addNode("3");
        binarySearchTree.addNode("7");
        binarySearchTree.addNode("2");
        binarySearchTree.addNode("1");
        binarySearchTree.addNode("3");
        binarySearchTree.midPrint();//中序遍历
        System.out.println("返回data为7的节点:"+binarySearchTree.midFind("7"));
        System.out.println("删除data为7的节点"+binarySearchTree.deleteOne("7"));
        binarySearchTree.midPrint();
        System.out.println("删除data为7的节点"+binarySearchTree.deleteOne("7"));
        binarySearchTree.midPrint();
        System.out.println("删除data为4的节点"+binarySearchTree.deleteOne("4"));
        binarySearchTree.midPrint();
        System.out.println("删除data为3的节点"+binarySearchTree.deleteOne("3"));
        binarySearchTree.midPrint();
        System.out.println("删除data为1的节点"+binarySearchTree.deleteOne("1"));
        binarySearchTree.midPrint();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

殷丿grd_志鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值