TreeMap数据结构及源码解析.跟学黑马

news2024/11/15 14:00:02

TreeMap数据结构及源码解析

    • 1.TreeMap的特点
    • 2.TreeMap的数据结构
      • 2.1二叉查找树
        • 2.1.1二叉查找树的定义
        • 2.1.2二叉查找树的查找操作
      • 2.2平衡二叉树
        • 2.2.1平衡二叉树的定义
        • 2.2.2平衡二叉树的旋转
      • 2.3红黑树
        • 2.3.1红黑树的定义
    • 2.TreeMap的源码分析
        • 2.1get()获取方法分析
        • 2.2put()添加方法分析
    • 3.自定义TreeMap集合

面试TP-LINK后感觉自己对TreeMap掌握存在疏漏,这里跟学一下TreeMap的知识,原视频如下:
java教程进阶-TreeMap数据结构及源码解析

1.TreeMap的特点

  • 概念:

    TreeMap是一个双列集合,是Map的子类。底层由红黑树结构构成。

  • 特点:

    • 元素中键不能重复
    • 元素会按照大小顺序排序

例证如下:
元素不能重复,重复会覆盖

public class Demo {
    @Test
    public void test() {
        // 创建对象
        TreeMap<Integer, String> map = new TreeMap<>();
        // 添加元素
        map.put(1, "abc");
        map.put(1, "def");
        map.put(1, "ghi");
        System.out.println(map);
    }
}

输出结果:

{1=ghi}

key的取出顺序和放入顺序无关,会按照key从小到大默认排序

@Test
    public void test() {
        // 创建对象
        TreeMap<Integer, String> map = new TreeMap<>();
        // 添加元素
        map.put(9, "abc");
        map.put(2, "def");
        map.put(1, "ghi");
        System.out.println(map);
    }

输出结果:

{1=ghi, 2=def, 9=abc}

2.TreeMap的数据结构

TreeMap底层由红黑树构成,而红黑树是一种特殊的二叉查找树。

常见的树型结构:
在这里插入图片描述

2.1二叉查找树

符合以下特点的树,称为二叉查找树

2.1.1二叉查找树的定义

  • 特点:

​ 1.若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
​ 2.若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
​ 3.左、右子树也分别为二叉排序树;
​ 4.没有相等的结点;

  • 结论:

    二叉查找树就是每个结点的值按照大小排列的二叉树,二叉查找树方便对结点的值进行查找。

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

2.1.2二叉查找树的查找操作

  • 查找方式:

​ 从根结点开始,如果要查找的数据等于结点的值, 那就返回。
​ 如果要查找的数据小于结点的值,那就在左子树中递归查找;
​ 如果要查找的数据大于结点的值,那就在右子树中递归查找。

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

2.2平衡二叉树

2.2.1平衡二叉树的定义

​ 为了避免出现"瘸子"的现象,减少树的高度,提高我们的搜素效率,又存在一种树的结构:“平衡二叉树”

​ 它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
在这里插入图片描述

2.2.2平衡二叉树的旋转

  • 概念:

​ 在构建一棵平衡二叉树的过程中,当有新的结点要插入时,检查是否因插入后而破坏了树的平衡,如果是,则需要做旋转去改变树的结构。

  • 两种旋转方式:

    • 左旋:

      左旋就是将结点的右支往左拉,右子结点变成父结点,并把晋升之后多余的左子结点出让给降级结点的 右子结点;
      在这里插入图片描述

    • 右旋:

      将结点的左支往右拉,左子结点变成了父结点,并把晋升之后多余的右子结点出让给降级结点的 左子结点
      在这里插入图片描述

  • 四种失衡情况:

    • 左左情况,需要以10为基准结点进行右旋
      在这里插入图片描述

    • 左右情况,先以7为基准结点进行左旋,再以11为基准结点进行右旋
      在这里插入图片描述

    • 右左情况,先以15为基准结点进行右旋,再以11为基准结点进行左旋
      在这里插入图片描述

    • 右右情况,以11未基准结点进行左旋
      在这里插入图片描述

2.3红黑树

2.3.1红黑树的定义

  • 概述:

    红黑树是一种自平衡的二叉查找树。

    红黑树的每一个结点上都有存储位表示结点的颜色,可以是红或者黑。

    红黑树不是高度平衡的,它的平衡是通过"红黑树的特性"进行实现的。

  • 红黑树的特性:

    1. 每一个结点或是红色的,或者是黑色的;
    2. 根结点必须是黑色;
    3. 每个叶结点是黑色的(叶结点是Nil)
    4. 如果某一个结点是红色,那么它的子结点必须是黑色(不能出现两个红色结点相连的情况)
    5. 对每一个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点;
    6. 红黑树中左右子树的深度差不能超过短的二倍(例如左子树深度2,右子树深度4,这种就是不符合的)
  • 图:
    在这里插入图片描述

2.TreeMap的源码分析

2.1get()获取方法分析

    @Test
    public void test() {
        // 创建对象
        TreeMap<Integer, String> map = new TreeMap<>();
        // 添加元素
        map.put(9, "abc");
        map.put(2, "def");
        map.put(1, "ghi");

        String s = map.get(2);
        System.out.println(s);
    }

输出结果:

def

我们看一下get()方法的源码

public V get(Object key) {
	//调用方法根据键获取Entry对象
    Entry<K,V> p = getEntry(key);
    //判断对象如果是null返回null,如果不是null返回对象中的值
    return (p==null ? null : p.value);
}

其中Entry<K,V>是什么呢,不妨继续看一下源码

//Entry类型表示结点
static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;					//key表示键
    V value;				//value表示值
    Entry<K,V> left;		//left表示左子结点的地址
    Entry<K,V> right;		//rigth表示右子结点的地址
    Entry<K,V> parent;		//parent表示父结点的地址
    boolean color = BLACK;  //color表示结点的颜色
    
    //下面方法省略…………
}

具体getEntry()方法如下:可以看出来
TreeMap中key不允许为null

final Entry<K,V> getEntry(Object key) {
        //判断有没有传入comparator
        if (comparator != null)
            //调用方法,使用比较器做查询
            return getEntryUsingComparator(key);
    	//判断传入的键是否为null
        if (key == null)
            //如果要查询的键是null则抛出空指针异常
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
    	//把Object类型的键向下转型为Comparable
        Comparable<? super K> k = (Comparable<? super K>) key;
    	//先把二叉树的根结点赋值给p
        Entry<K,V> p = root;
    	//如果p不为null,一直循环比较
        while (p != null) {
            //调用Comparable的compareTo()方法进行比较
            int cmp = k.compareTo(p.key);
            //如果cmp小于0,表示要查找的键小于结点的数字
            if (cmp < 0)
                //把p左子结点赋值给p对象
                p = p.left;
            //如果cmp大于0,表示要查找的键大于结点的数字
            else if (cmp > 0)
                //把P右子结点赋值给p对象
                p = p.right;
            else
                //要查找的键等于结点的值,就把当前Entry对象直接返回
                return p;
        }
    	//已经找到叶子结点,没有找到要查找的数字返回null
        return null;
    }

传入比较器之后,通过比较器查询

//传入比较器的情况下
final Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
    		//把Object类型的Key向下转型为对应的键的类型
            K k = (K) key;
    	//给比较器对象起名字cpr
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            //把二叉树的根结点赋值给P对象
            Entry<K,V> p = root;
            //循环用要查找的数字和结点中的数字进行比较
            while (p != null) {
                //调用比较器的compare()
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }

2.2put()添加方法分析

public V put(K key, V value) {
    //获取根结点赋值给变量t
    Entry<K,V> t = root;
    //判断根结点是否为null
    if (t == null) {
        //对key进行非空和类型校验
        compare(key, key);
		//新建一个结点
        root = new Entry<>(key, value, null);
        //设置集合长度为1
        size = 1;
        //记录集合被修改的次数
        modCount++;
		//添加成功返回null
        return null;
    }

这里看一下compare方法具体是啥
如果put的key是null的情况下会抛出空指针异常,或者说key即没有实现comparator比较器,也没有实现Comparable接口的话,也会抛出空指针异常。

// 非空和类型校验
    final int compare(Object k1, Object k2) {
        return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
            : comparator.compare((K)k1, (K)k2);
    }

接着看put()方法的下半部分

   	//如果根结点不是null则执行下面代码
    int cmp;
    Entry<K,V> parent;
    
    //把比较器对象赋值给变量cpr
    Comparator<? super K> cpr = comparator;
    //判断比较器对象如果不是空则执行下面代码
    if (cpr != null) {
        do {
            //把当前结点赋值给变量parent
            parent = t;
            //比较当前结点的键和要存储的键的大小
            cmp = cpr.compare(key, t.key);
            //如果要存储的键小于当前结点,则继续和左边的结点进行比较
            if (cmp < 0)
                t = t.left;
            //如果要存储的键大于当前结点,则继续和右边的结点进行比较
            else if (cmp > 0)
                t = t.right;
            else
                //如果要存储的键等于当前结点的键,则调用setValue()方法设置新的值
                //并结束循环
                return t.setValue(value);
          //循环直到遍历到叶子结点结束为止
        } while (t != null);
    }
    //如果比较器对象是空则执行下面代码
    else {
        //如果要保存的键为空,抛出空指针异常
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        	//把键转型为Comparable类型
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            //把当前结点赋值给变量parent
            parent = t;
            //比较要存储的键和当前结点的键
            cmp = k.compareTo(t.key);
            //如果要存储的键小于当前结点,则继续和左边的结点比较
            if (cmp < 0)
                t = t.left;
            //如果要存储的键大于当前结点,则继续和右边的结点比较
            else if (cmp > 0)
                t = t.right;
            else
                //如果要存储的键等于当前结点的键,则调用setValue()方法设置新的值
                //并结束循环
                return t.setValue(value);
           //循环直到遍历到叶子结点结束为止
        } while (t != null);
    }
    //遍历结束如果没有找到相同的键,则执行下面代码
    //创建新的结点对象,保存键值对,设置父结点
    Entry<K,V> e = new Entry<>(key, value, parent);
    //如果新的键小于父结点的键,则保存在左边
    if (cmp < 0)
        parent.left = e;
    else
        //如果新的键大于父结点的键,则保存在右边
        parent.right = e;
    //维持红黑树的平衡
    fixAfterInsertion(e);
    //集合长度加一
    size++;
    //集合修改次数加一
    modCount++;
    //返回被覆盖的值是null
    return null;
}

3.自定义TreeMap集合

​ 使用二叉树实现TreeMap集合,编写put(),get(),remove()等关键方法。

package com.exercise;

import java.util.Comparator;

/**
 * 自定义一个TreeMap
 *
 * @author wty
 * @date 2023/6/23 11:31
 */
public class MyTreeMap<K, V> {
    // 自定义一个内部类
    private class Entry<K, V> {
        // 键
        K key;
        // 值
        V value;
        // 左子结点
        Entry<K, V> left;
        // 右子结点
        Entry<K, V> right;
        // 父结点
        Entry<K, V> parent;

        //有参构造器
        public Entry(K key, V value, Entry<K, V> left, Entry<K, V> right, Entry<K, V> parent) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
            this.parent = parent;
        }
    }

    // 定义一个比较器
    private final Comparator<? super K> comparator;

    // 无参构造给comparator赋值
    public MyTreeMap() {
        comparator = null;
    }


    // 有参构造给comparator赋值
    public MyTreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    // 根结点
    private Entry<K, V> root;

    // 定义集合的长度
    private int size;

    /**
     * @param
     * @return int
     * @description //获取长度
     * @date 2023/6/23 19:09
     * @author wty
     **/
    public int size() {
        return size;
    }

    /**
     * @param
     * @return V
     * @description //根据键获取值
     * @param: k
     * @date 2023/6/23 19:10
     * @author wty
     **/
    public V get(K key) {
        Entry<K, V> entry = getEntry(key);
        return null == entry ? null : entry.value;
    }

    /**
     * @param
     * @return com.exercise.MyTreeMap<K, V>.Entry<K,V>
     * @description //根据键获取值(通用方法)
     * @param: key
     * @date 2023/6/23 19:11
     * @author wty
     **/
    private Entry<K, V> getEntry(Object key) {
        // 非空校验
        if (null == key) {
            throw new NullPointerException();
        }

        // 给跟结点起一个名字
        Entry<K, V> t = root;

        // 判断有没有传入比较器
        // 传入了比较器
        if (null != comparator) {
            // 向下转型
            K k = (K) key;

            // 循环
            while (null != t) {
                int cmp = comparator.compare(k, t.key);

                if (cmp < 0) {
                    t = t.left;
                } else if (cmp > 0) {
                    t = t.right;
                } else {
                    return t;
                }
            }
        } else {
            // 没有传入比较器
            Comparable<K> k = (Comparable<K>) key;

            while (null != t) {
                int cmp = k.compareTo(t.key);

                if (cmp > 0) {
                    t = t.right;
                } else if (cmp < 0) {
                    t = t.left;

                } else {
                    return t;
                }
            }
        }
        // 如果找不到,就返回null
        return null;
    }

    /**
     * @param
     * @return java.lang.String
     * @description //添加元素
     * @param: key
     * @param: value
     * @date 2023/6/23 19:19
     * @author wty
     **/
    public V put(K key, V value) {
        //给根结点赋值
        Entry<K, V> t = root;

        // 非空校验
        if (null == key) {
            throw new NullPointerException();
        }

        // 判断集合是否为空
        if (null == t) {
            // 创建一个新的结点
            Entry<K, V> entry = new Entry<>(key, value, null, null, null);

            // 给根结点赋值
            root = entry;

            // 集合长度+1
            size++;
            return null;
        }

        // 创建键值对,表示新增结点的父结点
        Entry<K, V> parent = t;
        int cmp = 0;

        // 判断是否有比较器
        // 有比较器
        if (null != comparator) {
            while (null != t) {
                parent = t;
                //判断键
                cmp = comparator.compare(key, t.key);

                if (cmp > 0) {
                    t = t.right;
                } else if (cmp < 0) {
                    t = t.left;

                } else {
                    // 用新的值替换旧的值,把旧的值替换掉
                    V v = t.value;
                    t.value = value;
                    return v;
                }
            }
        } else {
            // 没有比较器
            Comparable<? super K> k = (Comparable<? super K>) key;
            while (null != t) {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp > 0) {
                    t = t.right;
                } else if (cmp < 0) {
                    t = t.left;

                } else {
                    // 用新的值替换旧的值,把旧的值替换掉
                    V v = t.value;
                    t.value = value;
                    return v;
                }
            }

        }
        // 要添加的键值对 键不重复
        Entry<K, V> entry = new Entry<>(key, value, null, null, parent);
        if (cmp > 0) {
            parent.right = entry;
        } else {
            parent.left = entry;
        }

        // 集合长度增加
        size++;
        return null;
    }

    /**
     * @param
     * @return V
     * @description //移除元素
     * @param: key
     * @date 2023/6/23 19:30
     * @author wty
     **/
    public V remove(K key) {
        Entry<K, V> entry = getEntry(key);

        if (null == entry) {
            return null;
        }

        // 删除操作


        // 1.删除中间结点
        // 1.1没有左子树,只有右子树
        if (entry.left == null && entry.right != null) {
            // 判断要删除的结点是父结点的右子结点
            if (entry.parent.right == entry) {
                entry.parent.right = entry.right;
            } else if (entry.parent.left == entry) {
                entry.parent.left = entry.right;
            } else {
                root = entry.right;
            }

            // 让被删除结点的子结点,指向父结点
            entry.right.parent = entry.parent;
        }
        // 1.2没有右子树,只有左子树
        else if (entry.right == null && entry.left != null) {
            // 判断要删除的结点是父结点的右子结点
            if (entry.parent.right == entry) {
                entry.parent.right = entry.left;
            } else if (entry.parent.left == entry) {
                entry.parent.left = entry.left;
            } else {
                root = entry.left;
            }

            // 让被删除结点的子结点,指向父结点
            entry.left.parent = entry.parent;
        }

        // 2.删除根结点 既有右子树,又有左子树
        else if (entry.right != null && entry.left != null) {

            //找到后继结点
            Entry<K, V> target = entry.right;
            // 用右子树的最左子结点去替换
            while (target.left != null) {
                target = target.left;
            }

            // 右子结点作为后继结点
            if (entry.right == target) {
                target.parent = entry.parent;

                if (entry == root) {
                    root = target;
                } else if (entry.parent.right == entry) {
                    entry.parent.right = target;
                } else if (entry.parent.left == entry) {
                    entry.parent.left = target;
                }

                // 被删除结点左子结点重新指向新的父结点
                entry.left.parent = target;
                target.left = entry.left;
            } else {
                // 右子树的最左子结点作为后继结点
                if (target.right == null) {
                    // 后继结点没有子结点
                    target.parent.left = null;

                } else {
                    // 后继结点有子结点
                    target.parent.left = target.right;
                    target.right = target.parent;
                }

                // 让后继结点替换掉被删除结点
                if (entry == root) {
                    root = target;
                } else if (entry.parent.right == entry) {
                    entry.parent.right = target;
                } else if (entry.parent.left == entry) {
                    entry.parent.left = target;
                }

                // 被删除结点左右子树需要指向后继结点
                entry.left.parent = target;
                entry.right.parent = target;
                target.left = entry.left;
                target.right = entry.right;
            }


        } else {
            // 3.删除叶子结点
            if (entry.parent.right == entry) {
                entry.parent.right = null;
            } else if (entry.parent.left == entry) {
                entry.parent.left = null;
            } else {
                root = null;
            }

        }

        // 给集合长度减少1
        size--;

        return entry.value;
    }


    /**
     * @param
     * @return java.lang.String
     * @description //打印树的结构
     * @date 2023/6/23 19:55
     * @author wty
     **/
    @Override
    public String toString() {
        // 非空检验
        if (null == root) {
            return "{}";
        }

        String s = "{";
        String s1 = methodToString(root);
        s = s + s1.substring(0, s1.length() - 2) + "}";
        return s;
    }

    /**
     * @param
     * @return java.lang.String
     * @description //打印树的结构(递归调用)
     * @param: entry
     * @date 2023/6/23 19:55
     * @author wty
     **/
    private String methodToString(Entry<K, V> entry) {
        String s = "";

        // 拼接左子树
        if (entry.left != null) {
            s += methodToString(entry.left);
        }

        // 拼接中间结点
        s += entry.key + "=" + entry.value + ", ";

        // 拼接右子树
        if (entry.right != null) {
            s += methodToString(entry.right);
        }

        return s;
    }
}


  • 测试类
@Test
    public void test() {
        MyTreeMap<Integer, String> treeMap = new MyTreeMap<>();

        treeMap.put(5, "abc");
        treeMap.put(3, "def");
        treeMap.put(6, "ghi");
        treeMap.put(1, "jkl");
        treeMap.put(4, "mno");

        System.out.println(treeMap);
    }

运行结果:

{1=jkl, 3=def, 4=mno, 5=abc, 6=ghi}

继续测试get

@Test
    public void test() {
        MyTreeMap<Integer, String> treeMap = new MyTreeMap<>();

        treeMap.put(5, "abc");
        treeMap.put(3, "def");
        treeMap.put(6, "ghi");
        treeMap.put(1, "jkl");
        treeMap.put(4, "mno");

        System.out.println(treeMap);
        System.out.println(treeMap.get(3));
    }

运行结果:

{1=jkl, 3=def, 4=mno, 5=abc, 6=ghi}
def

最后测试一下删除remove方法

@Test
    public void test() {
        MyTreeMap<Integer, String> treeMap = new MyTreeMap<>();

        treeMap.put(5, "abc");
        treeMap.put(3, "def");
        treeMap.put(6, "ghi");
        treeMap.put(1, "jkl");
        treeMap.put(4, "mno");

        System.out.println(treeMap);
        System.out.println(treeMap.get(3));

        treeMap.remove(4);
        System.out.println(treeMap);
    }

运行结果:

{1=jkl, 3=def, 4=mno, 5=abc, 6=ghi}
def
{1=jkl, 3=def, 5=abc, 6=ghi}

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

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

相关文章

企业级开发项目和自学项目到底有什么区别

前言 好久不见了各位&#xff01;最近几个月都未更新&#xff0c;是因为从春招开始就在投简历面试实习岗位&#xff0c;然后入职&#xff0c;最后成功成为了一个半成品后端练习生&#xff0c;想说的话有太多太多 下面就站在一个在校实习生的身份&#xff0c;结合自己最近几个月…

科普 | 眼图

本文简要说明眼图相关的知识&#xff0c;参考是德科技的文章 1。 科普 | 眼图 基本知识串扰眼图眼图的产生原理及作用创建眼图 - 眼图波形的采样过程眼图的产生原理及作用眼图可以看出哪些性能指标&#xff1f;如何评判眼图质量&#xff1f;眼图测试模板眼图与存储深度实时的眼…

短视频seo源码开发部署技术解析

短视频seo开发需要哪些技术 应用程序优化技术&#xff1a;包括应用程序的各种元素&#xff08;如标题、描述、关键字、图标等&#xff09;的优化和设置&#xff0c;以及应用程序内部链接和导航的合理布局和设置。 视频内容优化技术&#xff1a;包括视频标题、描述、标签、封面…

人工智能数据集处理——数据获取

目录 1、从csv和txt文件中读取数据 pandas中可使用read_csv读取csv或txt文件中的数据 使用read_csv()函数读取phones.csv文件中的数据,并指定编码格式为gbk 使用head()方法指定获取phones.csv文件中前3行的数据 使用read_csv() 函数读取 itheima_books.txt文件中的数据,并指…

【Redis】2、Redis 的 Java 客户端(Jedis 和 SpringDataRedis)

目录 零、Redis 的 Java 客户端有哪些&#xff1f;二、Jedis 客户端(1) 引依赖(2) 连接 Redis 服务并测试(3) Redis 连接池 三、SpringDataRedis 介绍四、SpringBoot 中集成 SpringDataRedis(1) 引入依赖(2) 配置文件中书写相关配置(3) RedisTemplate 的默认序列化方式(4) 自定…

高校学生考勤系统

摘 要 在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括高校学生考勤系统的网络应用&#xff0c;在外国高校学生考勤系统已经是很普遍的方式&#xff0c;不过国内的高校学生考勤可能还处于起步阶段。高校学生考勤系统具有管理…

Linux网络-数据链路层,MAC帧解析,ARP协议

目录 数据链路层VS网络层 以太网概念 以太网的帧格式&#xff08;报文格式&#xff09;&#xff08;也可以称之为MAC帧&#xff09; MAC地址的概念 MAC帧格式 局域网通信原理 MTU MTU说明 MTU对IP协议的影响 MTU对UDP协议的影响 MTU对TCP协议的影响 ARP协议 ARP协…

【算法题解】41. 二叉树的中序遍历

这是一道 简单 题 https://leetcode.cn/problems/binary-tree-inorder-traversal/ 题目 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2] 示例 2&#xff1a; 输入&#…

[易语言][部署]使用易语言部署paddleocr的onnx模型api接口推理直接调用

易语言如何部署paddleocr模型&#xff0c;如今paddleocr模型广泛被使用各种编程&#xff0c;为了能在易语言上使用因此开发一个通用接口&#xff0c;开发基本思路如下&#xff1a; 可见我们并没有使用什么通信协议或者命令行之类的方法&#xff0c;这种直接封装接口比其他方法更…

TCP 学习笔记

Win R 打开控制台输入CMD 打开小黑窗&#xff0c; 输入ipconfig 查询本机地址 “外网IP是全世界唯一的IP地址,仅分配给一个网络设备。而内网IP是由路由器分配给每一部内部使用的IP地址,而内网的所有用户都是通过同一个外网IP地址进行上网的,而内网的IP地址每个人的都不一样…

(四)WPF - 布局

一、布局过程 WPF 布局包括两个阶段&#xff1a;一个测量阶段和排列阶段 在测量阶段&#xff0c;容器遍历所有子元素&#xff0c;并询问子元素它们所期望的尺寸。在排列阶段&#xff0c;容器在合适的位置放置子元素。&#xff08;每个元素都被其父元素告知它自己的尺寸是多少…

【软件设计师暴击考点】下午题高频考点暴击系列

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;软件…

电脑选购必备的六大技巧

目录 1、CPU方面 2、显卡方面 3、电脑主板方面 4、内存和硬盘方面 5、电脑机箱和电源方面 6、装机过程要全程参与 今天小编给大家分享电脑选购组装必备的六大技巧&#xff0c;希望对大家实际选购、组装电脑提供一些帮助&#xff01; 买电脑要注意哪些问题 1、CPU方面 C…

Qt Model-View架构领悟

1.架构的选择 1.1是否需要委托 模型视图架构图如下所示&#xff0c;模型视图架构源于MVC模式&#xff1a;模型&#xff08;Model&#xff09;是应用对象&#xff0c;表示数据&#xff1b;视图&#xff08;View&#xff09;是模型的用户界面&#xff0c;用以显示数据&#xff…

springboot基础(78):Freemarker模板生成word文档

文章目录 前言如何使用Freemakrer生成word文档1. 制作模板2. 编写工具类 遇到的问题下载失败如何只生成文件不下载 前言 利用Freemarker模板生成word文档。示例&#xff0c;将左侧的模板生成为右侧的文档并下载。 如何使用Freemakrer生成word文档 1. 制作模板 1.编辑一份a…

实现注册与登录(企业级)

目录 实现注册超级管理员功能&#xff08;持久层&#xff09; 一、判定系统是否已经绑定超级管理员 二、编写保存用户记录的代码 三、编写查询用户ID的代码 实现注册超级管理员功能&#xff08;业务层&#xff09; 一、获取OpenId 二、编写注册新用户的业务代码 掌握 R…

tuple 和数组区别

元组&#xff08;tuple&#xff09;和数组&#xff08;array&#xff09;都是 Python 中用于存储多个值的数据结构&#xff0c;但它们在实现和使用上有一些区别。 元组是不可变的&#xff0c;而数组是可变的。即元组一旦创建&#xff0c;其内容就不能被修改&#xff0c;而数组…

高数笔记1(第一章函数 极限 连续 第一节函数第二节极限-极限的概念与性质)

目录 第一章 函数 极限 连续第一节 函数第二节 极限一、极限的概念与性质数列的极限例1例2 函数的极限极限的性质&#xff08;保号性重点 有界性&#xff09;例12例13例14 函数极限与数列极限的关系例15 第一章 函数 极限 连续 第一节 函数 判断有界要用函数的绝对值&#xff…

Toolformer:可以教会自己使用工具的语言模型

Toolformer&#xff1a;可以教会自己使用工具的语言模型 摘要Introduction现有大模型的局限处理办法本文的idea Approach样例化API调用执行API调用筛选API调用模型微调 实验局限 论文地址点这里 摘要 语言模型&#xff08;LMs&#xff09;呈现了令人深刻的仅使用少量的范例或…

2022(一等奖)D1649基于多源卫星遥感的干旱区农作物耗水精细模拟

作品介绍 1 研究背景及目标 1.1 研究区概况 本次研究的研究区位于甘肃省张掖市内。张掖市位于甘肃省西部&#xff0c;河西走廊中段&#xff0c;属干旱和半干旱两种气候类型&#xff0c;其特点是夏季短而酷热&#xff0c;冬季长而严寒&#xff0c;干旱少雨&#xff0c;且降水分…