数据结构与算法(四):树结构

news2024/11/24 19:50:57

前面讲到的顺序表、栈和队列都是一对一的线性结构,这节讲一对多的线性结构——树。「一对多」就是指一个元素只能有一个前驱,但可以有多个后继。

一、基本概念

树(tree)是n(n>=0)个结点的有穷集。n=0时称为空树。在任意一个非空树中:(1)每个元素称为结点(node);(2)仅有一个特定的结点被称为根结点或树根(root)。(3)当n>1时,其余结点可分为m(m≥0)个互不相交的集合T1,T2,……Tm,其中每一个集合Ti(1<=i<=m)本身也是一棵树,被称作根的子树(subtree)。

注意:

  • n>0时,根节点是唯一的。
  • m>0时,子树的个数没有限制,但它们一定是互不相交的。

结点拥有的子树数被称为结点的度(Degree)。度为0的结点称为叶节点(Leaf)或终端结点,度不为0的结点称为分支结点。除根结点外,分支结点也被称为内部结点。结点的子树的根称为该结点的孩子(Child),该结点称为孩子的双亲或父结点。同一个双亲的孩子之间互称为兄弟。树的度是树中各个结点度的最大值。

结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。双亲在同一层的结点互为堂兄弟。树中结点的最大层次称为树的深度(Depth)或高度。如果将树中结点的各个子树看成从左到右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。森林是m(m>=0)棵互不相交的树的集合。

树的定义:

在这里插入图片描述

二、树的存储结构

由于树中每个结点的孩子可以有多个,所以简单的顺序存储结构无法满足树的实现要求。下面介绍三种常用的表示树的方法:双亲表示法、孩子表示法和孩子兄弟表示法。

1、双亲表示法

由于树中每个结点都仅有一个双亲结点(根节点没有),我们可以使用指向双亲结点的指针来表示树中结点的关系。这种表示法有点类似于前面介绍的静态链表的表示方法。具体做法是以一组连续空间存储树的结点,同时在每个结点中,设一个「游标」指向其双亲结点在数组中的位置。代码如下:

public class PTree<E> {
    private static final int DEFAULT_CAPACITY = 100;
    private int size;
    private Node[] nodes;

    private class Node() {
        E data;
        int parent;

        Node(E data, int parent) {
            this.data = data;
            this.parent = parent;
        }
    }

    public PTree() {
        nodes = new PTree.Node[DEFAULT_CAPACITY];
    }
}

由于根结点没有双亲结点,我们约定根节点的parent域值为-1。树的双亲表示法如下所示:

在这里插入图片描述

这样的存储结构,我们可以根据结点的parent域在O(1)的时间找到其双亲结点,但是只能通过遍历整棵树才能找到它的孩子结点。一种解决办法是在结点结构中增加其孩子结点的域,但若结点的孩子结点很多,结点结构将会变的很复杂。

2、孩子表示法

由于树中每个结点可能有多个孩子,可以考虑用多重链表,即每个结点有多个指针域,每个指针指向一个孩子结点,我们把这种方法叫多重链表表示法。它有两种设计方案:

方案一:指针域的个数等于树的度。其结点结构可以表示为:

class Node() {
    E data;
    Node child1;
    Node child2;
    ...
    Node childn;
}

对于上一节中的树,树的度为3,其实现为:

在这里插入图片描述

显然,当树中各结点的度相差很大时,这种方法对空间有很大的浪费。

方案二,每个结点指针域的个数等于该结点的度,取一个位置来存储结点指针的个数。其结点结构可以表示为:

class Node() {
    E data;
    int degree;
    Node[] nodes;
    Node(int degree) {
        this.degree = degree;
        nodes = new Node[degree];
    }
}

对于上一节中的树,这种方法的实现为:

这种方法克服了浪费空间的缺点,但由于各结点结构不同,在运算上会带来时间上的损耗。

为了减少空指针的浪费,同时又使结点相同。我们可以将顺序存储结构和链式存储结构相结合。具体做法是:把每个结点的孩子结点以单链表的形式链接起来,若是叶子结点则此单链表为空。然后将所有链表存放进一个一维数组中。这种表示方法被称为孩子表示法。其结构为:

在这里插入图片描述

代码表示:

public class CTree<E> {
    private static final int DEFAULT_CAPACITY = 100;
    private int size;
    private Node[] nodes;

    private class Node() {
        E data;
        ChildNode firstChild;
    }
    
    //链表结点
    private class ChildNode() {
        int cur; //存放结点在nodes数组中的下标
        ChildNode next;
    }

    public CTree() {
        nodes = new CTree.Node[DEFAULT_CAPACITY];
    }
}

这种结构对于查找某个结点的孩子结点比较容易,但若想要查找它的双亲或兄弟,则需要遍历整棵树,比较麻烦。可以将双亲表示法和孩子表示法相结合,这种方法被称为双亲孩子表示法。其结构如下:

在这里插入图片描述

其代码和孩子表示法的基本相同,只需在Node结点中增加parent域即可。

3、孩子兄弟表示法

任意一棵树,它的结点的第一个孩子如果存在则是唯一的,它的右兄弟如果存在也是唯一的。因此,我们可以使用两个分别指向该结点的第一个孩子和右兄弟的指针来表示一颗树。其结点结构为:

class Node() {
    E data;
    Node firstChild;
    Node rightSib;
}

其结构如下:

在这里插入图片描述

这个方法,可以方便的查找到某个结点的孩子,只需先通过firstChild找到它的第一个孩子,然后通过rightSib找到它的第二个孩子,接着一直下去,直到找到想要的孩子。若要查找某个结点的双亲和左兄弟,使用这个方法则比较麻烦。

这个方法最大的好处是将一颗复杂的树变成了一颗二叉树。这样就可以使用二叉树的一些特性和算法了。

三、二叉树

1、基本概念

二叉树(Binary Tree)是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。

二叉树的特点:

二叉树不存在度大于2的结点。
二叉树的子树有左右之分,次序不能颠倒。
如下图中,树1和树2是同一棵树,但它们是不同的二叉树。

在这里插入图片描述

1)、斜树

所有的结点都只有左子树的二叉树叫左斜树。所有的结点都只有右子树的二叉树叫右斜树。这两者统称为斜树。

斜树每一层只有一个结点,结点的个数与二叉树的深度相同。其实斜树就是线性表结构。

2)、满二叉树

在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。

满二叉树具有如下特点:

  • 叶子只能出现在最下一层
  • 非叶子结点的度一定是2
  • 同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。

3)、完全二叉树

若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。

完全二叉树的特点:

  • 叶子结点只能出现在最下两层
  • 最下层叶子在左部并且连续
  • 同样结点数的二叉树,完全二叉树的深度最小

4)、平衡二叉树

平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

2、二叉树的性质

在二叉树的第i层上至多有2^i-1个结点(i>=1)。

深度为k的二叉树至多有2^k-1个结点(k>=1)。

对任何一棵二叉树T,如果其终端结点个数为n0,度为2的结点数为n2,则n0 = n2 + 1。

具有n个结点的完全二叉树的深度为「log2n」+ 1(「x」表示不大于x的最大整数)。

如果对一棵有n个结点的完全二叉树的结点按层序编号(从第一层到第「log2n」+ 1层,每层从左到右),对任一结点i(1≤i≤n)有:

若i=1,则结点i是二叉树的根,无双亲;如i>1,则其双亲是结点「i/2」。
如2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
若2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。

3、二叉树的实现

二叉树是一种特殊的树,它的存储结构相对于前面谈到的一般树的存储结构要简单一些。

1)、顺序存储

二叉树的顺序存储结构就是用一维数组来存储二叉树中的结点。不使用数组的第一个位置。结点的存储位置反映了它们之间的逻辑关系:位置k的结点的双亲结点的位置为「k/2」,它的两个孩子结点的位置分别为2k和2k+1。

在这里插入图片描述

代码实现:

public class ArrayBinaryTree<E> {

    private static final int DEFAULT_DEPTH = 5;

    private int size = 0;
    private E[] datas;

    ArrayBinaryTree() {
        this(DEFAULT_DEPTH);
    }

    @SuppressWarnings("unchecked")
    ArrayBinaryTree(int depth) {
        datas = (E[]) new Object[(int)Math.pow(2, depth)];
    }

    public boolean isEmpty() { return size == 0; }

    public int size(){ return size; }

    public E getRoot() { return datas[1]; }

    // 返回指定节点的父节点    
    public E getParent(int index) {  
        checkIndex(index);  
        if (index == 1) {    
            throw new RuntimeException("根节点不存在父节点!");    
        }    
        return datas[index/2];    
    }    
        
    //获取右子节点    
    public E getRight(int index){    
        checkIndex(index*2 + 1);  
        return datas[index * 2 + 1];    
    }    
        
    //获取左子节点    
    public E getLeft(int index){    
        checkIndex(index*2);    
        return datas[index * 2];    
    }     
        
    //返回指定数据的位置    
    public int indexOf(E data){    
       if(data==null){   
         throw new NullPointerException();  
       } else {  
           for(int i=0;i<datas.length;i++) {  
               if(data.equals(datas[i])) {  
                   return i;  
               }  
           }  
       }  
        return -1;    
    }

    //顺序添加元素
    public void add(E element) {
        checkIndex(size + 1);
        datas[size + 1] = element;
        size++;
    }

    //在指定位置添加元素
    public void add(E element, int parent, boolean isLeft) {

        if(datas[parent] == null) {  
            throw new RuntimeException("index["+parent+"] is not Exist!");  
        }  
        if(element == null) {  
            throw new NullPointerException();  
        } 

        if(isLeft) {
            checkIndex(2*parent);
            if(datas[parent*2] != null) {  
                throw new RuntimeException("index["+parent*2+"] is  Exist!");  
            }
            datas[2*parent] = element;
        }else {
            checkIndex(2*parent + 1);
            if(datas[(parent+1)*2]!=null) {  
                throw new RuntimeException("index["+ parent*2+1 +"] is  Exist!");  
            } 
            datas[2*parent + 1] = element;
        }
        size++;
    }

    //检查下标是否越界
    private void checkIndex(int index) {  
        if(index <= 0 || index >= datas.length) {  
            throw new IndexOutOfBoundsException();  
        }  
    } 
    public static void main(String[] args) {
        char[] data = {'A','B','C','D','E','F','G','H','I','J'};
        ArrayBinaryTree<Character> abt = new ArrayBinaryTree<>();
        for(int i=0; i<data.length; i++) {
            abt.add(data[i]);
        }
        System.out.print(abt.getParent(abt.indexOf('J')));
    }
}

一棵深度为k的右斜树,只有k个结点,但却需要分配2k-1个顺序存储空间。所以顺序存储结构一般只用于完全二叉树。

2)、链式存储

二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域即可。我们称这样的链表为二叉链表。其结构如下图:

在这里插入图片描述

代码如下:

import java.util.*;
public class LinkedBinaryTree<E> {    
    private List<Node> nodeList = null;  
   
    private class Node {  
        Node leftChild;  
        Node rightChild;  
        E data;  
  
        Node(E data) {  
            this.data = data;  
        }  
    }  
  
    public Node getRoot() {
        return nodeList.get(0);
    }

    public void createBinTree(E[] array) {  
        nodeList = new LinkedList<Node>();  

        for (int i = 0; i < array.length; i++) {  
            nodeList.add(new Node(array[i]));  
        }  
        // 对前lasti-1个父节点按照父节点与孩子节点的数字关系建立二叉树  
        for (int i = 0; i < array.length / 2 - 1; i++) {   
            nodeList.get(i).leftChild = nodeList.get(i * 2 + 1);    
            nodeList.get(i).rightChild = nodeList.get(i * 2 + 2);  
        }  
        // 最后一个父节点:因为最后一个父节点可能没有右孩子,所以单独拿出来处理  
        int lastParent = array.length / 2 - 1;  
        nodeList.get(lastParent).leftChild = nodeList  .get(lastParent * 2 + 1);  

        // 右孩子,如果数组的长度为奇数才建立右孩子  
        if (array.length % 2 == 1) {  
            nodeList.get(lastParent).rightChild = nodeList.get(lastParent * 2 + 2);  
        }  
    } 

    public static void main(String[] args) {
        Character[] data = {'A','B','C','D','E','F','G','H','I','J'};
        LinkedBinaryTree<Character> ldt = new LinkedBinaryTree<>();
        ldt.createBinTree(data);
    }
}

4、二叉树的遍历

二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。

二叉树的遍历主要有四种:前序遍历、中序遍历、后序遍历和层序遍历。

1)、前序遍历

先访问根结点,然后遍历左子树,最后遍历右子树。

代码:

//顺序存储
public void preOrderTraverse(int index) {  
    if (datas[index] == null)  
        return;  
    System.out.print(datas[index] + " ");  
    preOrderTraverse(index*2);  
    preOrderTraverse(index*2+1);  
} 

//链式存储
 public void preOrderTraverse(Node node) {  
    if (node == null)  
        return;  
    System.out.print(node.data + " ");  
    preOrderTraverse(node.leftChild);  
    preOrderTraverse(node.rightChild);  
} 

2)、中序遍历

先遍历左子树,然后遍历根结点,最后遍历右子树。

//链式存储
 public void inOrderTraverse(Node node) {  
    if (node == null)  
        return;  
    inOrderTraverse(node.leftChild);
    System.out.print(node.data + " ");  
    inOrderTraverse(node.rightChild);  
} 

3)、后序遍历

先遍历左子树,然后遍历右子树,最后遍历根结点。

//链式存储
 public void postOrderTraverse(Node node) {  
    if (node == null)  
        return;  
    postOrderTraverse(node.leftChild);
    postOrderTraverse(node.rightChild);  
    System.out.print(node.data + " ");  
} 

4)、层序遍历

从上到下逐层遍历,在同一层中,按从左到右的顺序遍历。如上一节中的二叉树层序遍历的结果为ABCDEFGHIJ。

注意:

  • 已知前序遍历和中序遍历,可以唯一确定一棵二叉树。
  • 已知后序遍历和中序遍历,可以唯一确定一棵二叉树。
  • 已知前序遍历和后序遍历,不能确定一棵二叉树。

如前序遍历是ABC,后序遍历是CBA的二叉树有:

在这里插入图片描述

四、线索二叉树

对于n个结点的二叉树,在二叉链存储结构中有n+1个空指针域,利用这些空指针域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针被称为线索,加上线索的二叉树称为线索二叉树。

结点结构如下:

在这里插入图片描述

其中:

lTag为0时,lChild指向该结点的左孩子,为1时指向该结点的前驱
rTag为0时,rChild指向该结点的右孩子,为1时指向该结点的后继。
线索二叉树的结构图为:图中蓝色虚线为前驱,红色虚线为后继

代码如下:

public class ThreadedBinaryTree<E> {
    private TBTreeNode root;
    private int size;          // 大小  
    private TBTreeNode pre;   // 线索化的时候保存前驱  

    class TBTreeNode {
        E element;
        boolean lTag; //false表示指向孩子结点,true表示指向前驱或后继的线索
        boolean rTag;
        TBTreeNode lChild;
        TBTreeNode rChild;

        public TBTreeNode(E element) {
            this.element = element;
        }
    }
    
    public ThreadedBinaryTree(E[] data) {
        this.pre = null;  
        this.size = data.length;  
        this.root = createTBTree(data, 1);
    }

    //构建二叉树
    public TBTreeNode createTBTree(E[] data, int index) {  
        if (index > data.length){  
            return null;  
        }  
        TBTreeNode node = new TBTreeNode(data[index - 1]);  
        TBTreeNode left = createTBTree(data, 2 * index);  
        TBTreeNode right = createTBTree(data, 2 * index + 1);  
        node.lChild = left;  
        node.rChild = right;  
        return node;  
    } 

    /** 
     * 将二叉树线索化   
     */  
    public void inThreading(TBTreeNode node) {  
        if (node != null) {  
            inThreading(node.lChild);     // 线索化左孩子 

            if (node.lChild == null) {  // 左孩子为空  
                node.lTag = true;    // 将左孩子设置为线索  
                node.lChild = pre;  
            }  
            if (pre != null && pre.rChild == null) {  // 右孩子为空  
                pre.rTag = true;  
                pre.rChild = node;  
            }  
            pre = node;  

            inThreading(node.rChild);  // 线索化右孩子  
        }  
    }  
  
    /** 
     * 中序遍历线索二叉树 
     */  
    public void inOrderTraverseWithThread(TBTreeNode node) {

        while(node != null) {
            while(!node.lTag) { //找到中序遍历的第一个结点
                node = node.lChild;
            }
            System.out.print(node.element + " "); 
            while(node.rTag && node.rChild != null) { //若rTag为true,则打印后继结点
                node = node.rChild;
                System.out.print(node.element + " "); 
            }
            node = node.rChild;
        }
    }  
  
    /** 
     * 中序遍历,线索化后不能使用
     */  
    public void inOrderTraverse(TBTreeNode node) {  
        if(node == null)
            return;
        inOrderTraverse(node.lChild);  
        System.out.print(node.element + " ");  
        inOrderTraverse(node.rChild);  
    } 

    public TBTreeNode getRoot() { return root;}

    public static void main(String[] args) {
        Character[] data = {'A','B','C','D','E','F','G','H','I','J'};
        ThreadedBinaryTree<Character> tbt = new ThreadedBinaryTree<>(data);
        tbt.inOrderTraverse(tbt.getRoot());
        System.out.println();
        tbt.inThreading(tbt.getRoot());
        tbt.inOrderTraverseWithThread(tbt.getRoot());
    }
}

线索二叉树充分利用了空指针域的空间,提高了遍历二叉树的效率。

五、总结

到此,树的知识基本总结完了,这一节开头讲了树的一些基本概念,重点介绍了树的三种不同的存储方法:双亲表示法、孩子表示法和孩子兄弟表示法。由兄弟表示法引入了一种特殊的树:二叉树,并详细介绍了它的性质、不同结构的实现方法和遍历方法。最后介绍了线索二叉树的实现方法。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

魔兽世界私服架设教程—GM命令大全

如果需要什么命令可以按ctrlf查找&#xff0c;比如泰坦之握&#xff0c;就会直接定位到你想要的命令那里。橙色武器&#xff1a;85版本橙匕首&#xff0c;龙父之牙戈拉德&#xff0c;龙王之暮提里奥什&#xff0c;远古噩梦7794977950橙杖&#xff0c;可以变龙巨龙之怒&#xff…

Ae:场景编辑检测

场景编辑检测基于 Adobe Sensei 技术&#xff0c;能够自动检测已编辑剪辑中的场景变化&#xff0c;并将场景创建为标记或图层&#xff0c;以便加快项目设置。首先将素材添加到时间轴&#xff0c;然后选择一个或多个素材图层&#xff0c;右键单击并选择“场景编辑检测”。Ae菜单…

【华为OD机试真题】用 C++ 实现 - 数字加减游戏

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

从「雄狮」到「瑶光」,奇瑞历史突破背后的十字路口

传统自主品牌&#xff0c;正走到关键的十字路口。 奇瑞官方数据显示&#xff0c;2022年奇瑞集团创造了四个“历史首次”突破&#xff0c;包括年营业收入首次突破2000亿元&#xff1b;年销量首次超100万辆&#xff1b;年出口首次抵达45万辆&#xff1b;新能源年销量首次超过20万…

华为OD机试 - 组装新的数组(Python)【2023-Q1 新题】

华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:blog.csdn.net/hihell/category_12199275.html 华为OD详细说明:https://dream.blog.csdn.net/article/details/128980730 组装新的数组 题…

Linux | 网络通信 | http协议介绍 | cookie策略讲解

文章目录url统一资源定位符http协议介绍GET vs POSThttp状态码http常见headercookie session上篇博客定制了一个协议&#xff0c;该协议用来进行简单的计算&#xff0c;其包含了数据的序列化和反序列化&#xff0c;编码和解码的定制&#xff0c;并且该协议基于TCP通信&#xf…

STM32f103封装 led程序CubeMX

本文代码使用 HAL 库。 文章目录前言一、LED 原理图二、CubeMX创建工程三、LED 相关函数1. 输出电平函数&#xff1a;2. 延时函数&#xff1a;3. 翻转电平函数&#xff1a;四、详细代码实验现象 &#xff1a;总结代码 源码&#xff1a;前言 从这篇文章开始&#xff0c;我们讲解…

关于Kubernetes不兼容Docker

本博客地址&#xff1a;https://security.blog.csdn.net/article/details/129153459 参考文献&#xff1a;https://www.cnblogs.com/1234roro/p/16892031.html 一、总结 总结起来就是一句话&#xff1a; k8s只是弃用了dockershim&#xff0c;并不是弃用了整个Docker&#xf…

JS代码可以重复进行混淆加密吗?

JS代码可以重复进行混淆加密吗&#xff1f;问题 同一段JS代码&#xff0c;是否可以反复、重复进行混淆加密&#xff1f; 本文&#xff0c;用实验给出答案。 实验过程 准备一段代码&#xff0c;如下&#xff1a; 运行&#xff0c;可以显示出代码执行耗时&#xff1a; 可以看…

华为OD机试 - 上班之路(Python)【2023-Q1 新题】

华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:blog.csdn.net/hihell/category_12199275.html 华为OD详细说明:https://dream.blog.csdn.net/article/details/128980730 上班之路 题目描…

打不过就拉拢!ChatGPT和MidJourney已经成我小秘书!太爽了

大家好&#xff0c;我是晓衡。这两周&#xff0c;我战斗力爆棚了&#xff01;每天大概睡了四~五个小时&#xff0c;而且中午也没有休息过&#xff0c;但精神却还很亢奋。直到周一下午&#xff0c;身体才有种被掏空的感觉&#xff0c;晚上 10 点就睡了。可能是兴奋劲还在&#x…

Python 刚学习就放弃的原因?

很大一部分小伙伴根本不清楚自己学的知识重点是什么&#xff0c;今天咱们就来整理一下python自学容易混淆的知识点【收藏有用】 Python 2 和 Python 3 的区别&#xff1a; Python 2 和 Python 3 有一些不兼容的语法和库&#xff0c;例如 Python 3 默认使用 Unicode 编码&#…

MySql_基础篇从下载到DDL语法

本笔记基于b站up主黑马程序员的视频整理 用于记录与回顾 数据库相关概念 数据库 DS 存储数据的仓库 数据库管理系统 DSMS 操纵和管理数据库的大型软件 sql 操作关系型数据库的编程语言是一套标准 mysql 默认端口号是3306 下载数据库 //下载部分建议转移到b站 搜索“MyS…

深度学习实战16(进阶版)-虚拟截图识别文字-可以做纸质合同和表格识别

大家好&#xff0c;我是微学AI&#xff0c;今天给大家带来一个关于虚拟截图识别文字的应用&#xff0c;可以运用在多个领域。 案例主要结合Mediapipe手势识别模型&#xff0c;识别出手势的21个关键点的坐标&#xff0c;计算机的摄像头根据食指的坐标信息获取用户想要截取的图片…

面试加分项:JVM 锁优化和逃逸分析详解

1 锁优化JVM 在加锁的过程中&#xff0c;会采用自旋、自适应、锁消除、锁粗化等优化手段来提升代码执行效率。1.1 自旋锁和自适应自旋现在大多的处理器都是多核处理器 &#xff0c;如果在多核心处理器&#xff0c;有让两个或者以上的线程并行执行&#xff0c;我们可以让一个等待…

JavaScript前端中的伪类元素before和after使用详解

在前端开发中&#xff0c;伪类是一种让你可以选择元素的某个状态或位置的 CSS 选择器。其中&#xff0c;:before 和 :after 伪类允许你在一个元素之前或之后插入内容。 :before 和 :after 伪类创建的元素是不在 HTML 文档中的&#xff0c;它们是通过 CSS 生成的。可以用它们来…

PMP考试详解,新考纲有什么变化?

一&#xff0c;为什么优先考虑PMP持证人员&#xff1f; PMP证书在我国大型企业、跨国企业、央企/国企等单位的招聘中都比较重视&#xff0c;特别是在许多项目投标环节中&#xff0c;明确标明需要有PMP持证人员&#xff0c;才能在投标、竞标中代表公司有资格承担项目。 除此之…

Unity中的Mathf数学运算讲解(值得收藏)

Unity中的Mathf数学运算有哪些&#xff1f; Mathf.Abs(f)绝对值 计算并返回指定参数 f 绝对值 例如&#xff1a; // 输出 10 Debug.log(Mathf.Abs(-10)) Debug.log(Mathf.Abs(10))Mathf.Sin正弦 static function Sin (f : float) : float 计算并返回以弧度为单位指定的角 f 的…

分布式架构之详解幂等

幂等的概念 在数学里&#xff0c;幂等有两种主要的定义。 1、在某二元运算下&#xff0c;幂等元素是指被自己重复运算(或对于函数是为复合)的结果等于它自己的元素。例如&#xff0c;乘法下仅有两个幂等实数&#xff0c;为0和1。 2、某一元运算为幂等的时&#xff0c;其作用在…

醒醒吧,外包测试哪有前途,你只是一块干电池而已,随时会被替换掉

我25岁的时候&#xff0c;外包测试&#xff0c;薪资13.5k&#xff0c;人在深圳。 内卷什么的就不说了&#xff0c;而且人在外包那些高级精英年薪大几十的咱也接触不到&#xff0c;就说说外包吧。假设以我为界限&#xff0c;25岁一线城市13.5k&#xff0c;那22-24大部分情况下是…