数据结构与算法——二叉树遍历、查找、删除、顺序存储二叉树、线索化二叉树

一、树的存储方式

1.1 为什么需要树这种数据结构

1.1.1 数组存储方式分析:

优点:通过下标方式访问,速度快。对于有序数组,还可以使用二分查找提高检索效率

缺点:如果要检索某个集体之或者插入值会整体移动,效率低

ArrayList的底层也是一个数组,(transient Object[] elementData)此数组当空间不足的时候会自动进行扩容

既然到这里了,那我们可以先了解一下ArrayList底层源码分析

  • 当创建对象时,使用的无参构造器,初始elementData容量为0(JDK7是10)

  • 如果使用的是指定容量的capacity的构造器,则初始elementData的容量为capacity

建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)

  • 添加元素的时候,先判断是否要扩容,若扩容则调用grow方法,否则直接添加元素到指定的位置

  • 如果使用的是无参构造器,第一次添加需要扩容的话,则扩容elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍

  • 如果使用的是capacity构造器,需要扩容的话,则直接扩容elementData为1.5倍

1.1.2 链式存储方式的分析:

优点:在一定程度上对数组存储方式优化,比如插入一个数值节点,只需要将插入节点连接到链表中,删除同样效率较高

缺点:在进行检索时,效率仍然较低,比如检索某个值,需要从头节点遍历

LinkedList的底层是一个双向链表

LinkedList:双向链表,内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基本结构

         private static class Node<E> {
              E item;
              Node<E> next;
              Node<E> prev;
  
              Node(Node<E> prev, E element, Node<E> next) {
              this.item = element;
              this.next = next;     //next变量记录下一个元素的位置
              this.prev = prev;     //prev变量记录前一个元素的位置
              }
          }

1.1.3二叉树存储方式的分析:

能提高数据存储,读取的效率,比如利用二叉排序树,即可保证数据的检索速度,同时也可以保证数据的插入、删除、修改的速度

怎么构建树?

下图中的数组的数据构建成树。我们可以将数字的第一个节点设置为树的最上面,然后剩余的元素和节点比较,比节点大的在右侧添加,小的在节点左侧添加

假如我们要查找12的话,怎么查找呢?

12会先和7比较,比7大,则向7右侧寻找;

12再和7右侧的10比较,则再向10右侧寻找;

12再和10右侧的12比较,发现相等,则返回。

1.2 树的概念和常用术语

1.3 二叉树

  • 树有很多种,每个节点最多只能有两个子节点的形式成为二叉树;

  • 二叉树的子节点分为左节点和右节点;

  • 如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n 为层数,则我们称为满二叉树;

也可以说满二叉树的节点总数= 2^n -1 , n 为层数

  • 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树

二、二叉树的遍历

2.1 前序遍历、中序遍历、后序遍历

  • 前序遍历: 先输出父节点,再遍历左子树和右子树

先输出当前节点(初始时为根节点,即root节点),

如果左子节点不为空,则递归继续前序遍历

如果右子节点不为空,则递归继续前序遍历

按照图的输出顺序为:1 2 3 4

  • 中序遍历: 先遍历左子树,再输出父节点,再遍历右子树

如果当前节点的左子节点不为空,则递归继续中序遍历

先输出当前节点(初始时为根节点,即root节点)

如果当前节点的右子节点不为空,则递归继续中序遍历

按照图的输出顺序为 2 1 3 4

  • 后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点

如果当前节点的左子节点不为空,则递归继续后序遍历

如果当前节点的右子节点不为空,则递归继续后序遍历

先输出当前节点(初始时为根节点,即root节点)

按照图的输出顺序为 2 4 3 1

小结: 看输出父节点的顺序,就确定是前序,中序还是后序

假设我们再增加一个5号节点关胜

前序遍历: 1 2 3 5 4

中序遍历: 2 1 5 3 4

后序遍历: 2 5 4 3 1

2.2 代码实现

使用代码实现下图的二叉树

package org.example.suanfa;

import lombok.Data;

public class BinaryTreeDemo {
    public static void main(String[] args) {
//      先创建一个二叉树
        BinaryTree binaryTree = new BinaryTree();
//      创建需要的节点
        HeroNode root = new HeroNode(1, "宋江");
        HeroNode node2 = new HeroNode(2, "吴用");
        HeroNode node3 = new HeroNode(3, "卢俊义");
        HeroNode node4 = new HeroNode(4, "林冲");

       root.setLeft(node2);
       root.setRight(node3);
       node3.setRight(node4);
       binaryTree.setRoot(root);

        System.out.println("前序遍历");
        binaryTree.preOrder();
        System.out.println("中序遍历");
        binaryTree.midOrder();
        System.out.println("后续遍历");
        binaryTree.postOrder();
    }
}

@Data
class BinaryTree {
    private HeroNode root;

    //   前序遍历
    public void preOrder() {
        if (this.root != null) {
            this.root.preOrder();
        } else {
            System.out.println("当前二叉树为空,无法便利");
        }
    }

    //   中序遍历
    public void midOrder() {
        if (this.root != null) {
            this.root.midOrder();
        } else {
            System.out.println("当前二叉树为空,无法便利");
        }
    }

    //   后序遍历
    public void postOrder() {
        if (this.root != null) {
            this.root.postOrder();
        } else {
            System.out.println("当前二叉树为空,无法便利");
        }
    }



}

@Data
class HeroNode {
    private int no;
    private String name;
    private HeroNode left;
    private HeroNode right;


    /**
     * 编写前序遍历的方法
     */
    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(int no, String name) {
        this.no = no;
        this.name = name;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }
}

三、二叉树的查找

要求:

请编写前序查找,中序查找,后续查找的方法

并分别使用三种查找方式,查找heroNo=5的节点

并分析各种查找方式比较了多少次

3.1 前序查找,中序查找,后续查找的分析

3.2 代码实现

public class BinaryTreeDemo {
    public static void main(String[] args) {
//      先创建一个二叉树
        BinaryTree binaryTree = new BinaryTree();
//      创建需要的节点
        HeroNode root = new HeroNode(1, "宋江");
        HeroNode node2 = new HeroNode(2, "吴用");
        HeroNode node3 = new HeroNode(3, "卢俊义");
        HeroNode node4 = new HeroNode(4, "林冲");
        HeroNode node5 = new HeroNode(5, "关胜");

        root.setLeft(node2);
        root.setRight(node3);
        node3.setRight(node4);
        node3.setLeft(node5);
        binaryTree.setRoot(root);

        System.out.println("前序查找");
        System.out.println(binaryTree.preOrderSearch(5));

        System.out.println("中序查找");
        System.out.println(binaryTree.midOrderSearch(5));

        System.out.println("后续查找");
        System.out.println(binaryTree.postOrderSearch(5));


    }
}

@Data
class BinaryTree {
    private HeroNode root;
 //   前序查找
    public HeroNode preOrderSearch(int no) {
        if (root != null) {
            return root.postOrderSearch(no);
        } else {
            return null;
        }
    }

    //   中序查找
    public HeroNode midOrderSearch(int no) {
        if (root != null) {
            return root.midOrderSearch(no);
        } else {
            return null;
        }
    }

    //   后续查找
    public HeroNode postOrderSearch(int no) {
        if (root != null) {
            return root.postOrderSearch(no);
        } else {
            return null;
        }
    }

}

@Data
class HeroNode {
    private int no;
    private String name;
    private HeroNode left;
    private HeroNode right;


    /**
     * @param no 要查找的编号
     * @return 对应的Node,没有找到返回null
     */
    public HeroNode preOrderSearch(int no) {

        if (this.no == no) {
            return this;
        }
        HeroNode heroNode = null;

        if (this.left != null) {
//           运行到这里肯定不是自己要找的,向左递归
            heroNode = this.left.preOrderSearch(no);
        }
        if (heroNode != null) {
//          找到了
            return heroNode;
        }

//       this.right != null,为了调用的时候没有空指针异常
        if (this.right != null) {
//          运行到这里说明还没有找到
            heroNode = this.right.preOrderSearch(no);
        }

        return heroNode;
    }

    //    中序遍历查找
    public HeroNode midOrderSearch(int no) {
        HeroNode heroNode = null;
//       向左
        if (this.left != null) {
//           运行到这里肯定不是自己要找的,向左递归
            heroNode = this.left.midOrderSearch(no);
        }
        if (heroNode != null) {
//          找到了
            return heroNode;
        }

//      中
        if (this.no == no) {
            return this;
        }

//      向右
        if (this.right != null) {
//          运行到这里说明还没有找到
            heroNode = this.right.midOrderSearch(no);
        }


        return heroNode;
    }

    //    后续遍历查找
    public HeroNode postOrderSearch(int no) {
        HeroNode heroNode = null;
//       向左
        if (this.left != null) {
//           运行到这里肯定不是自己要找的,向左递归
            heroNode = this.left.postOrderSearch(no);
        }
        if (heroNode != null) {
//          找到了
            return heroNode;
        }

//      向右
        if (this.right != null) {
//          运行到这里说明还没有找到
            heroNode = this.right.postOrderSearch(no);
        }

//      这个地方再写一个if判断heroNode是不是空也行

//      中
        if (this.no == no) {
            return this;
        }

        return heroNode;
    }

}

四、二叉树的删除操作

4.1 图解

因为现在还没有学二叉排序树,那现在如果删除的不是子节点的话,我们就将整个子节点的树给删掉

4.2 代码实现

public class BinaryTreeDemo {
    public static void main(String[] args) {
//      先创建一个二叉树
        BinaryTree binaryTree = new BinaryTree();
//      创建需要的节点
        HeroNode root = new HeroNode(1, "宋江");
        HeroNode node2 = new HeroNode(2, "吴用");
        HeroNode node3 = new HeroNode(3, "卢俊义");
        HeroNode node4 = new HeroNode(4, "林冲");
        HeroNode node5 = new HeroNode(5, "关胜");

        root.setLeft(node2);
        root.setRight(node3);
        node3.setRight(node4);
        node3.setLeft(node5);
        binaryTree.setRoot(root);

        binaryTree.delNode(5);
        binaryTree.preOrder();

    }
}

@Data
class BinaryTree {
    private HeroNode root;
    //   前序遍历
    public void preOrder() {
        if (this.root != null) {
            this.root.preOrder();
        } else {
            System.out.println("当前二叉树为空,无法便利");
        }
    }

public boolean delNode(int no){
        if(root !=null){
//       如果只有一个root节点,这里立即判断root是不是要删除的节点
            if(root.getNo() == no){
                root =null;
                return true;
            }
        }else {
            System.out.println("空树,不能删除");
            return false;
        }

//       运行到这里肯定是没有删除的
//        boolean nodeBoolean = root.deleteNode(no);
        return  root.deleteNode(no) ;
    }

}

@Data
class HeroNode {
    private int no;
    private String name;
    private HeroNode left;
    private HeroNode right;
    /**
     * 编写前序遍历的方法
     */
    public void preOrder() {
//      先输出父节点
        System.out.println(this);
//      向左子树递归前序遍历
        if (this.left != null) {
            this.left.preOrder();
        }
//      向右子树递归前序遍历
        if (this.right != null) {
            this.right.preOrder();
        }
    }

    /**
     * 递归删除节点
     *    如果删除的节点是叶子节点,则删除改节点
     *    如果删除的节点是非叶子节点,则删除该子树
     * @param no
     */
    public boolean deleteNode(int no){

//      2.如果当前节点的左子节点不为空,并且左子节点就是要删除的节点,就将this.left=null,并且结束递归
        if(this.left !=null && this.left.no == no){
            this.left = null;
            return true;
        }
//      3.如果当前节点的右子节点不为空,并且右子节点就是要删除的节点,就将this.left=null,并且结束递归
        if(this.right !=null && this.right.no ==no){
            this.right =null;
            return true;
        }
//       4.如果第2,3步没有删除节点,那我们就需要向左子树递归删除
        boolean deleteBoolean = false;  //true表示删除了

        if(this.left !=null){
             deleteBoolean = this.left.deleteNode(no);
             if (deleteBoolean){
                 return true;
             }
        }

//      5.再向右子树递归删除
        if(this.right !=null){
            deleteBoolean = this.right.deleteNode(no);
            if (deleteBoolean){
                return true;
            }
        }
        return deleteBoolean;

    }
}

五、顺序存储二叉树

八大排序算法中的堆排序,就会使用到顺序存储二叉树

5.1 顺序存储二叉树基本说明

从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也能转换成数组。

要求:

  • 下图的二叉树的结点,要求以数组的方式来存放 ,如,arr : [1, 2, 3, 4, 5, 6, 6]

  • 要求在遍历数组 arr时,仍然可以以前序遍历,中序遍历和后序遍历的方式完成结点的遍历

简单的说要就是,数据以数组的方式存储,但是读取的时候却是按照树的方式进行读取

5.2 顺序存储二叉树的特点

  • 顺序二叉树通常只考虑完全二叉树

  • 第n个元素的左子节点的下标为 2 * n + 1

假设n=1,此时对应的数据为2,左子节点对应的下标为2*1+1=3,数据刚好是4

  • 第n个元素的右子节点的下标为 2 * n + 2

假设n=1,此时对应的数据为2,右子节点对应的下标为2*1+2=4,数据刚好是5

  • 第n个元素的父节点的下标为 (n-1) / 2

假设n=1,此时对应的数据为2,父节点对应的下标为0,数据刚好是1

  • n : 表示二叉树中的第几个元素(按0开始编号如图所示,也可以理解为数组的下标)

5.3 代码实现

public class ArrBinaryTreeDemo {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7};
        ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
//      从0号节点开始遍历
        arrBinaryTree.preOrder(0);
        System.out.println("*********************");
        arrBinaryTree.midOrder(0);
        System.out.println("*********************");
        arrBinaryTree.postOrder(0);
    }
}
//编写一个ArrBinaryTree,实现顺序存储二叉树遍历
class ArrBinaryTree {
    private int[] arr;  //崔楚数据节点的数组

    //  构造器
    public ArrBinaryTree(int[] arr) {
        this.arr = arr;
    }
    /**
     * 编写一个方法,完成顺序存储二叉树的前序遍历
     *
     * @param index 数组的下标
     */
    public void preOrder(int index) {
        if (arr == null || arr.length == 0) {
            System.out.println("数组为空,不能按照二叉树的前序遍历");
            return;
        }
//      运行到这里说明数组中还有数据
//      输出当前的这个元素
        System.out.println(arr[index]);

//     向左节点遍历,记得判断一下,可能存在数组下标越界的可能
        if ((index * 2 + 1) < arr.length) {
            preOrder(index * 2 + 1);
        }

//      左递归完成后再向右节点遍历
        if ((index * 2 + 2) < arr.length) {
            preOrder(index * 2 + 2);
        }

    }
//  中序遍历
    public void midOrder(int index){
        if (arr == null || arr.length == 0) {
            System.out.println("数组为空,不能按照二叉树的前序遍历");
            return;
        }
        //     向左节点遍历,记得判断一下,可能存在数组下标越界的可能
        if ((index * 2 + 1) < arr.length) {
            midOrder(index * 2 + 1);
        }
        //      输出当前的这个元素
        System.out.println(arr[index]);

        // 再向右节点遍历
        if ((index * 2 + 2) < arr.length) {
            midOrder(index * 2 + 2);
        }
    }
//   后续遍历
    public void postOrder(int index){
        if (arr == null || arr.length == 0) {
            System.out.println("数组为空,不能按照二叉树的前序遍历");
            return;
        }

        //     向左节点遍历,记得判断一下,可能存在数组下标越界的可能
        if ((index * 2 + 1) < arr.length) {
            postOrder(index * 2 + 1);
        }

        // 再向右节点遍历
        if ((index * 2 + 2) < arr.length) {
            postOrder(index * 2 + 2);
        }

        //      输出当前的这个元素
        System.out.println(arr[index]);
    }
}

六、线索化二叉树

下图中有7个空指针域,空指针域的个数可以看6.1的计算方式

当我们对上面的二叉树进行中序遍历时,数列为 {8, 3, 10, 1, 6, 14 }

但是 6, 8, 10, 14 这几个节点的 左右指针,并没有完全的利用上.

如果我们希望充分的利用 各个节点的左右指针, 让各个节点可以指向自己的前后节点,怎么办?

解决方案-线索二叉树

6.1 介绍

  • n个结点的二叉链表中含有n+1 【公式 2n-(n-1)=n+1】 个空指针域。

利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索"

公式来历:一共有n个结点,一个结点有2个指针,n个结点有2n个指针;n个结点的话会占用n-1个指针,则还剩下2n-(n-1)个空指针域

  • 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。

根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种

  • 一个结点的前一个结点,称为前驱结点

  • 一个结点的后一个结点,称为后继结点

6.2 图解

说明:线索二叉树最终形成的结果和遍历的顺序有直接的关系

说明: 当线索化二叉树后,Node节点的 属性 left 和 right ,有如下情况:

  • left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树, 而 ⑩ 节点的 left 指向的就是前驱节点.

  • right指向的是右子树,也可能是指向后继节点,比如 ① 节点right 指向的是右子树,而⑩ 节点的right 指向的是后继节点.

6.3 线索化二叉树代码实现

省略所有get、set、构造方法

public class ThreadedBinaryTreeDemo {
    public static void main(String[] args) {
//     测试一把中序线索二叉树
        HeroNode root = new HeroNode(1, "tom");
        HeroNode node2 = new HeroNode(3, "jack");
        HeroNode node3 = new HeroNode(6, "smith");
        HeroNode node4 = new HeroNode(8, "mary");
        HeroNode node5 = new HeroNode(10, "king");
        HeroNode node6 = new HeroNode(14, "dim");

//      二叉树,手动创建
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);

//      测试线索化
        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
        threadedBinaryTree.setRoot(root);
        threadedBinaryTree.threadedNodes();

//      以10结点做测试
        System.out.println("left--->"+node5.getLeft());
        System.out.println("right--->"+node5.getRight());

    }
}
class ThreadedBinaryTree {
    private HeroNode root;

//   为了实现线索化,需要创建当前结点的前驱结点指针,简单的来说,这个pre总是保留前一个结点地址
    private HeroNode pre =null;

    public void threadedNodes() {
        this.threadedNodes(root);
    }
    /**
     * 中序线索化的代码
     *  对某个结点进行线索化
     * @param node  当前需要线索化的结点
     */
    public void threadedNodes(HeroNode node) {
//     node ==null,不能线索化
        if(node == null) {
            return;
        }
//       1. 先线索化左子树
        threadedNodes(node.getLeft());
//       2. 当前结点线索化(有点难度)
//           处理前驱
        if(node.getLeft() == null) {
//         如果当前结点的左指针是空,那就指向前驱指针
            node.setLeft(pre);
//          修改当前结点的左指针的类型,指向前驱结点
            node.setLeftType(1);
        }
//          处理后继
//      这个处理后继很巧妙,此时不处理当前node节点的后继节点,只有当node到下一个节点的时候 才会去处理上一个节点的后继节点
        if (pre != null && pre.getRight() == null) {
            pre.setRight(node);
            pre.setRightType(1);
        }

//      !!!!后移指针
//      每处理一个结点后,让当前结点是下一个结点的前驱结点
        pre = node;

//       3. 再线索化右子树
        threadedNodes(node.getRight());
    }
}


class HeroNode {
    private int no;
    private String name;
    private HeroNode left;
    private HeroNode right;

//  说明:
//    如果leftType ==0 表示指向的是左子树,如果1则表示指向的前驱结点
//    如果rightType ==0 表示指向的是右子树,如果1表示指向后继结点
    private int leftType;
    private int rightType;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/2339.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】环形链表

给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&#xff08;索…

大数据模型、离线架构、实时架构

一.大数据模型 8种常见的大数据分析模型&#xff1a;1、留存分析模型&#xff1b;2、漏斗分析模型&#xff1b;3、全行为路径分析&#xff1b;4、热图分析模型&#xff1b;5、事件分析模型&#xff1b;6、用户分群模型&#xff1b;7、用户分析模型&#xff1b;8、黏性分析模型…

Qt实践项目:仿Everything软件实现一个QtEverything

⭐️我叫忆_恒心&#xff0c;一名喜欢书写博客的在读研究生&#x1f468;‍&#x1f393;。 如果觉得本文能帮到您&#xff0c;麻烦点个赞&#x1f44d;呗&#xff01; 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧&#xff0c;喜欢的小伙伴给个三…

提高曝光率:外贸网站如何充分利用谷歌优化赢得客户

自从我从事外贸行业以来&#xff0c;谷歌优化一直是我关注的重点。 作为一个外贸从业者&#xff0c;我深知提高网站在谷歌搜索引擎中的排名对企业的重要性。 那么&#xff0c;如何利用谷歌优化来提高外贸网站的曝光率&#xff0c;从而赢得更多客户呢&#xff1f; 以下是我在…

C++成神之路 | 第一课【步入C++的世界】

目录 一、认识C++ 1.1、关于 C++ 1.2、C++的前世今生 1.2.1、C+

23种设计模式

参考链接&#xff1a; 【狂神说Java】通俗易懂的23种设计模式教学&#xff08;停更&#xff09;_哔哩哔哩_bilibili 23种设计模式【狂神说】_狂神说设计模式_miss_you1213的博客-CSDN博客 1. 单例模式 参考链接&#xff1a; 【狂神说Java】单例模式-23种设计模式系列_哔哩哔哩…

Linux第一个小程序git三板斧

目录 1.Linux项目自动化构建工具 - make/makefile 1.1.makefile原理 1.2.项目清理 2.第一个小程序 - 进度条 3.git三板斧 1.Linux项目自动化构建工具 - make/makefile make是一条命令&#xff0c;makefile是一个文件&#xff0c;两个搭配使用&#xff0c;完成项目自动化构建。 …

JAVA开发(自研项目的开发与推广)

案例背景&#xff1a; 作为JAVA开发人员&#xff0c;我们可以开发无数多的web项目&#xff0c;电商系统&#xff0c;小程序&#xff0c;H5商城。有时候作为技术研发负责人&#xff0c;项目做成了有时候也需要对内进行内测&#xff0c;对外进行推广。在此老司机分享自己主导开发…

BeanPostProcessor原理分析

文章目录一、BeanPostProcessor的作用1. 源码2. 使用案例二、Spring生命周期中的BeanPostProcessor三、BeanPostProcessor对PostConstruct的支持四、BeanPostProcessor中的顺序性五、总结一、BeanPostProcessor的作用 BeanPostProcessor提供了初始化前后回调的方法&#xff0c;…

Flutter内阴影

前言 在前几天的业务需求中&#xff0c;UI给出的页面中有新拟态的按钮&#xff0c;就是带内部阴影的按钮&#xff0c;如果是利用css中box-shadow的属性&#xff0c;那么实现起来很简单&#xff0c;但是奈何Flutter中的Container的BoxShadow不具备inset内部阴影的功能&#xff…

人脸识别经典网络-MTCNN(含Python源码实现)

人脸检测-mtcnn 本文参加新星计划人工智能赛道&#xff1a;https://bbs.csdn.net/topics/613989052 文章目录人脸检测-mtcnn1. 人脸检测1.1 人脸检测概述1.2 人脸检测的难点1.3 人脸检测的应用场景2. mtcnn2.1 mtcnn概述2.2 mtcnn的网络结构2.3 图像金字塔2.4 P-Net2.5 R-Net2…

Java语言-----类与对象的秘密

目录 前言 一、类与对象的介绍 二、类的实例化 三.类与对象的使用方法 3.1对象的初始化 3.2内存显示图 四.this的使用方法 总结 &#x1f63d;个人主页&#xff1a; tq02的博客_CSDN博客-C语言,Java领域博主 &#x1f308;理想目标&#xff1a;努力学习&#xff0c;向Java进…

Django(一)安装

好久没更新了 学习的内容太多了有点杂 一时不知道从何说起 !!! 对于Django我也不是很了解 在网上搜了个词条就是以下显示 我目前的了解也仅限于此 希望在接下来的学习过程中 有更多的学习体会可以和大家分享 一涉及到在对应python环境 下载东西时思维就会很混乱 这里再把之前…

计算机网络(第十二弹) --- 传统访问过程与 CDN 访问过程对比

CDN 是构建在现有网络基础之上的智能虚拟网络, 依靠部署在各地的边缘服务器, 通过中心平台的负载均衡 / 内容分发 / 调度等功能模块, 使用户就近获取所需内容, 降低网络拥塞, 提高用户访问响应速度和命中率;   当用户访问网站时, 利用全局负载均衡技术将用户的访问指向距离最近…

CSS 实现六边形柱状图

前言 &#x1f44f;CSS 实现六边形柱状图 速速来Get吧~ &#x1f947;文末分享源代码。记得点赞关注收藏&#xff01; 1.实现效果 2.实现步骤 定义全局css变量&#xff0c;柱状宽度为–w&#xff0c;最大高度为–h&#xff0c;柱形整体为渐变色&#xff0c;定义上部分颜色为…

MySQL注入秘籍【绕过篇】

MySQL注入秘籍【绕过篇】1.通用方法2.绕过空格3.绕过引号4.绕过逗号,5.绕过等号6.绕过and/or7.绕过注释符8.绕过函数检测1.通用方法 编码 编码无非就是hex、url等等编码&#xff0c;让传到数据库的数据能够解析的即可&#xff0c;比如URL编码一般在传给业务的时候就会自动解码…

【Java Spring基本问题】记录面试题宝典中自己不熟悉的Spring问题

文章目录Spring Bean定义装配Spring Bean生命周期Spring Bean容器Spring 循环依赖Spring 事务Autowired和ResourceSpring Bean定义装配 参考文章 1. 定义Spring Bean的三种方式 XML文件定义Spring Bean JavaConfig定义Spring Bean Component注解定义SpringBean 2. 装配Spri…

Java现在好找工作吗?

Java到2023年已经28岁了&#xff0c;可能你会怀疑它是否还一如当年一样的强大&#xff0c;在应用层领域独占鳌头。但是基于Java庞大的市场占有率和需求&#xff0c;它依然在保持着更新迭代&#xff0c;依然是最常用的底层开发语言&#xff0c;基于其安全性、开放性、稳定性和跨…

C语言运算符和表达式

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、运算符1.算术运算符加()、减(-)、乘(*)、除(/)、取余(%)自增()、自减(–)2.关系运算符3.逻辑运算符4.运算符优先级二、表达式总结前言 本篇文章将带大家学习…

python基于XGBoost开发构建海上船舶航行轨迹多变量序列预测分析模型

在船舶航行状态评估、船舶碰撞概率检测等场景种有着对海面船舶航行轨迹较高的预测需求&#xff0c;准确实时地对航行轨迹进行预测分析有助于评估船舶航行的状态&#xff0c;及时对可能存在的潜在威胁进行发现预警处理&#xff0c;航行轨迹预测本质上来讲就是时间序列建模&#…
最新文章