数据结构与算法st11:树的学习


为什么需要树结构:
在这里插入图片描述

数组的扩容(Arraylist扩容):
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

二叉树的基本概念:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
二叉树的遍历:
在这里插入图片描述
在这里插入图片描述

    //前序遍历(本-左-右)
    public void preorder(){
        System.out.println(this);
        if(this.left!=null){
            this.left.preorder();
        }
        if(this.right!=null){
            this.right.preorder();
        }
    }
    //中序遍历(左-本-右)
    public void midorder(){
        if(this.left!=null){
            this.left.midorder();
        }
        System.out.println(this);
        if(this.right!=null){
            this.right.midorder();
        }
    }
    //后序遍历(左-右-本)
    public void postorder(){

        if(this.left!=null){
            this.left.postorder();
        }
        if(this.right!=null){
            this.right.postorder();
        }
        System.out.println(this);
    }

二叉树的查找
在这里插入图片描述
在这里插入图片描述

    //前序查找
    public Heronode presearch(int no){
        //中间(真正返回的节点)
        if(this.no==no){
            return this;
        }

        //左边递归
        Heronode resnode=null;
        if(this.left!=null){
            resnode=this.left.presearch(no);
        }
        //开始回溯:两种极端情况:1.找到了停止; 2.没有找到继续往下别的查找()
        if(resnode!=null){//说明找到了
            return resnode;
        }

        //resnode==null,左边递归没找到,右边递归(最后的最后)
        if(this.right!=null){
            resnode=this.right.presearch(no);
        }
        //开始回溯:两种极端情况:1.找到了停止; 2.没有找到直接返回null
        return resnode;
    }




    //中序查找
    public Heronode midsearch(int no){

        //左边递归
        Heronode resnode=null;
        if(this.left!=null){//左边的子节点已经遍历完了,都没有找到
            resnode=this.left.midsearch(no);
        }
        //开始回溯:两种极端情况:1.找到了停止; 2.没有找到继续往下别的查找(中和右)
        if(resnode!=null){//说明找到了
            return resnode;
        }

        //resnode==null,左边递归没找到,中间(真正返回的节点)
        if(this.no==no){
            return this;
        }

        //右边递归(最后的最后)
        if(this.right!=null){
            resnode=this.right.presearch(no);
        }
        //开始回溯:两种极端情况:1.找到了停止; 2.没有找到直接返回null
        return resnode;
    }


    //后序查找
    public Heronode postsearch(int no){

        //左边递归
        Heronode resnode=null;
        if(this.left!=null){//左边的子节点已经遍历完了,都没有找到
            resnode=this.left.postsearch(no);
        }
        //开始回溯:两种极端情况:1.找到了停止; 2.没有找到继续往下别的查找(右和中)
        if(resnode!=null){//说明找到了
            return resnode;
        }

        //右边递归(最后的最后)
        if(this.right!=null){
            resnode=this.right.postsearch(no);
        }
        //开始回溯:两种极端情况:1.找到了停止; 2.没有找到继续往下别的查找()
        if(resnode!=null){//说明找到了
            return resnode;
        }

        //resnode==null,左右边递归没找到,中间(真正返回的节点)
        if(this.no==no){
            return this;
        }

        return null;//如果内有一个找到
        
    }

二叉树结点的删除
注意:
1.一定是判断是否删除当前节点的子节点,而不是判断节点本身是不是需要删除的节点。(这个方法写在节点类内部)
2.所以我们在二叉树的类中, 一上来要判断判断该树是不是空树------即root是否是空!!!!!
一定是调用当前root节点的删除方法来删除子节点的,所以我们还需要判断删除的节点是不是我们本来的root根节点。
在这里插入图片描述
本节点处删除左右的子节点

    //在当前的非空节点处根据no来查找要删除的节点并删除
    public void deleteno(int no){

        if(this.left!=null && this.left.no==no){//本节点的左节点正好是要删除的节点
            this.left=null;
            return;
        }
        if(this.right!=null&& this.right.no==no){//本节点的右节点正好是要删除的节点
            this.right=null;
            return;
        }

        if(this.left!=null){//继续向左递归,递归结束的条件
            this.left.deleteno(no);
        }

        if(this.right!=null){//继续向右递归
            this.right.deleteno(no);
        }
    }

root根节点出的删除情况判断

    //根据根节点来删除要删除的节点
    public void deletenode(int no){
        //注意根节点的情况需要首先判断,原因根节点没有父节点
        if(root!=null){
            if(root.getNo()==no){//注意no这个成员变量是private类型的
                root=null;
            }else{
                root.deleteno(no);//调用本节点删除子节点的方法
            }
        }else{
            System.out.println("这是个空树");
        }
    }

顺序存储二叉树:
二叉树和数组之间的相互转化,抓住“本节点若为n,则它的左节点是2n+1,它的右节点是2n+2”的特点
在这里插入图片描述
在这里插入图片描述
具体的实现顺序存储二叉树的代码:
在这里插入图片描述

线索化二叉树:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

*对二叉树进行“—中序—线索化(原来空指针的节点)----”(具体参照中序遍历的代码学习)

    //lefttype==0:指向左子树,lefttype==1:指向前驱结点
    //righttype==0:指向y右子树,righttype==1:指向后驱结点
    private int lefttype;
    private int righttype;
    //为了实现线索化,需要一个给当前节点的前驱结点的一个指针
    //中序遍历的第一个节点的前序节点本身就是空的,所以初始化这个pre节点时为null合理
    private HeronodeTr pre=null;//递归时,总是保留前一个节点
    //***********对二叉树进行“---中序---线索化----”***************

    /**
     *
     * @param :node就是要线索化的节点(进行左右节点是否为空判断,需不需要线索化)
     */
    public void threadedNode(HeronodeTr node){

        //判断节点是空,就不需要进行任何的操作
        if(node==null){
            return;//线索化左子树的递归结束的标志
        }

        //1.先线索化左子树(递归结束的标志在上面)
        threadedNode(node.getLeft());

        //2.线索化当前节点
        //2.1先处理当前节点的前驱结点
        if(node.getLeft()==null){//当前节点的左指针是空的,则指向前驱结点
            node.setLeft(pre);
            node.setLefttype(1);
        }
        //2.2处理后继节点
        if(pre!=null && pre.getRight()==null){
            pre.setRight(node);//让前驱结点的有指针指向当前的节点
            pre.setRighttype(1);
        }
        
        //每处理一个节点后,让当前节点的下一个节点的前驱结点指向当前节点
        pre=node;

        //3.线索化右子树
        threadedNode(node.getRight());

    }

线索化二叉树的遍历:不能使用原来的中序遍历(出现死龟)

在这里插入图片描述
//实际上线索化后,本节点中,若lefttype=1的那么它的左节点一定是它的前驱结点,设置lefttype=1说明右结点一定是它的后驱节点!!!!!
//本节点中,若lefttype=1的那么它的左节点一定是它的前驱结点,可以直接输出本节点
// 若righttype=1说明右结点一定是它的后驱节点,可以直接接下来直接输出本节点的右节点
//线索化后,如果当前节点的lefttype=0和lefttype=0说明他完全不是一个空指针的节点,这时它如果是某个节点的后驱节点那么他已经输出了,那么它之后输出的下一个节点,一定是那个“某个节点的前驱结点指向本节点”得一个节点,也就是说需要移动指针来寻找这个“某节点”!!!!
//(某节点的左节点可能是前驱结点,也可能不是—不是就不能输出;右节点可能是前驱结点,也可能不是–不是就不能输出)

    //中序线索化的--遍历--
    // (某节点的左节点可能是前驱结点,也可能不是---不是就不能输出;
    // 右节点可能是前驱结点,也可能不是--不是就不能输出)
    public void misorderX(){

        //注意根节点不一定是第一个打印的节点
        HeronodeTr node=root;

        //本节点中,若lefttype=1的那么它的左节点一定是它的前驱结点,可以直接输出本节点
        // 若righttype=1说明右结点一定是它的后驱节点,可以直接接下来直接输出本节点的右节点

        while(node!=null){

            //寻找有前驱结点的那个节点,没有不能输出了(这样设置的原因是,初始化的前驱结点是个空)
            while (node.getLefttype()==0){
                node=node.getLeft();//终止时的节点就是有前驱结点的那个节点
            }

            //直接打印输出有前驱结点的那个节点,原因他一定是前驱结点要打印的下一个节点
            System.out.println(node);

            //是本节点有后驱节点(来自于本节点的右节点)就直接打印这个后驱节点
            while(node.getRighttype()==1){
                node=node.getRight();
                System.out.println(node);
            }

            //本节点没有后驱节点,就直接下一个(即指向右节点),回去循环直接判断这个节点有没有前驱结点
            node=node.getRight();

        }
    }

树的应用:赫夫曼编树

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
==规则:==首先将每个元素看成单独的一个简单的二叉树(根节点就是本身),每次挑选出最小的两个根节点来生成一个新的二叉树,然后将新生产的这个二叉树的顶节点作为根节点,继续比较剩下的根节点和这个新生根节点,继续挑选出最小的两个根节点来生成一个新的二叉树。。。。。重复上述的操作,直到数组的元素都被用完!!!!
在这里插入图片描述
使用到comparable接口的排序学习

    //创建哈夫曼树(返回树的头结点)
    public static Node createHuffman(int[] arr){

        List<Node> nodes=new ArrayList<>();
        for (int i = 0; i < arr.length; i++) {
            nodes.add(new Node(arr[i]));
        }
        Collections.sort(nodes);//列表排序

        while(nodes.size()>1){//最后列表只剩一个最终的根节点
            //逐一进来的列表排完序的
            Node leftnode=nodes.get(0);//左节点权值更小
            Node righttnode=nodes.get(1);//左节点权值更小
            Node parentnode=new Node(leftnode.value+ righttnode.value);

            //这个父节点的左右节点还需要重新生成
            parentnode.left=leftnode;
            parentnode.right=righttnode;

            //更新待排序的列表结点
            nodes.remove(leftnode);
            nodes.remove(righttnode);
            nodes.add(parentnode);

            Collections.sort(nodes);//列表重新排序

        }
        return nodes.get(0);
    }
}


class Node implements Comparable<Node>{
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }


    //使用前序遍历
    public void preorder(){
        System.out.println(this.value);
        //向左递归
        if(this.left!=null){
            this.left.preorder();//这个左节点的中序遍历
        }

        //向右递归
        if(this.right!=null){
            this.right.preorder();//这个右节点的中序遍历
        }
    }

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                '}';
    }

    @Override
    public int compareTo(Node o) {
        return this.value-o.value;//实现从小到大排列
    }
}

(霍夫曼编码应用还没有学习:p115-p126,12集,再说吧)

二叉–排序–树BST(按中序遍历的结果是排好序的------得益于:比本节点小的放在左边,比本节点大的放在右边)

在这里插入图片描述
//创建和遍历顺序二叉树
在这里插入图片描述

    //添加节点的方法,满足二叉排序树的要求:比本节点小的放在左边,比本节点大的放在右边(递归方式)
    public void add(NodeB node){
        if(node==null){
            return;
        }

        if(node.value<this.value){//比本节点小的放在左边
            if(this.left==null){
                this.left=node;
            }else{
                this.left.add(node);
            }
        }else{//比本节点大的放在右边
            if(this.right==null){
                this.right=node;
            }else{
                this.right.add(node);
            }
        }
    }

//二叉排序树的删除(分情况讨论)

这是力扣题的原题–删除二叉搜索树的节点
在这里插入图片描述
找到要删除的节点和待删除节点的父节点(否则无法删除和连接原先的节点)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

平衡二叉树AVL(针对二叉排序树的弊端:左大右旋,右大左旋)

在这里插入图片描述
左旋转AVL:(当你节点的右子树的高度比左子树的超过1时,就进行对这个节点的左旋转)
在这里插入图片描述

    //左旋转AVL(针对这个节点的右子树的高度比左子树的高度大的值超过1的节点)
    public void LeftRotate(){
        NodeA newnode=new NodeA(this.value);
        newnode.left=this.left;
        newnode.right=this.right.left;
        this.value=this.right.value;
        this.right=this.right.right;
        this.left=newnode;
    }

右旋转AVL:(当你节点的左子树的高度比右子树的超过1时,就进行对这个节点的右旋转)
在这里插入图片描述

    //右旋转AVL(针对这个节点的左子树的高度比右子树的高度大的值超过1的节点)
    public void RightRotate(){
        NodeA newnode=new NodeA(this.value);
        newnode.right=this.right;
        newnode.left=this.left.right;
        this.value=this.left.value;
        this.left=this.left.left;
        this.right=newnode;
    }

双旋转AVL:(右大左旋,左大右旋)
(针对:(左子树高度-右子树高度)大于1&&当前节点的左子树的右子树高度都大于它的左子树的高度《=先对当前节点的左子节点进行左旋转,再对当前节点进行右旋转)
(针对:(右子树高度-左子树高度)大于1&&当前节点的右子树的左子树高度都大于它的右子树的高度《=先对当前节点的右子节点进行右旋转,再对当前节点进行左旋转)
在这里插入图片描述
针对每次添加一个节点在原来的AVL树中时,都进行判断是不是添加完后仍然是AVL树,然后进行左旋转与右旋转

        //添加完结点后,判断当前添加的节点是不是满足AVL树
        if(this.rightHeight()-this.leftHeight()>1){
            if(right!=null&&right.leftHeight()>right.rightHeight()){
                //双旋转
                //首先对当前节点的右节点进行右旋转
                this.right.RightRotate();
                //在进行左旋转
                this.LeftRotate();
            }else {
                this.LeftRotate();//左旋转
            }
            return;//平衡后直接返回,否则继续执行下面的代码
        }


        if(this.leftHeight()-this.rightHeight()>1){
            if(left!=null&&left.rightHeight()>left.leftHeight()){
                //双旋转
                //首先对当前节点的左节点进行左旋转
                this.left.LeftRotate();
                //再对当前节点进行右旋转
                this.RightRotate();
            }else{
                this.RightRotate();//右旋转
            }
            return;//平衡后直接返回

        }

多叉树

多用于mysql中的查找
在这里插入图片描述

2-3树

仍然要满足排序树的特点(左小-中中-右大)
在这里插入图片描述

在这里插入图片描述
没懂怎么拆????(好难)
step1:首先来考虑插在同层,必须满足2,3节点的要求,即一个节点内必须不超过2个关键字,同时不能破坏同层的深度
step2:同层不满足,就需要先向上层拆,不行就拆本层,但是不能破坏同层的深度!!!

B树、B+树、B*树(B:balance)----了解原理(无代码要求)

B树的每个节点都存放关键字(要查找的数据)
在这里插入图片描述
B+树只有叶子节点才真正的存放关键字,而且叶子节点存放的关键字排好了序
非叶子节点是稀疏索引,比二分查找能够更快的找到索引!!!相当于是把一段数据进行无数的分割了分割,每次在非叶子结点定位数据在分割的哪一段,而不是一直二分查找。最后在叶子节点进行稠密索引查找,此时查找可以使用二分查找。
在这里插入图片描述
在这里插入图片描述
B*树(了解一下):分配新节点的概率比B+树要低,空间使用率更高

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值