数据结构与算法-12_二叉搜索树

news2024/11/16 14:58:03

文章目录

      • 1.概述
      • 2.实现
        • 定义节点
        • 查询
        • Comparable
        • 最小
        • 最大
        • 新增
        • 前驱后继
        • 删除
        • 找小的
        • 找大的
        • 找之间
        • 小结
      • 3.习题
        • E01. 删除节点-Leetcode 450
        • E02. 新增节点-Leetcode 701
        • E03. 查询节点-Leetcode 700
        • E04. 验证二叉搜索树-Leetcode 98
        • E05. 求范围和-Leetcode 938
        • E06. 根据前序遍历结果构造二叉搜索树-Leetcode 1008
        • E07. 二叉搜索树的最近公共祖先-Leetcode 235

1.概述

二叉搜索树(也称二叉排序树)是符合下面特征的二叉树:

  1. 树节点增加 key 属性,用来比较谁大谁小,key 不可以重复
  2. 对于任意一个树节点,它的 key 比左子树的 key 都大,同时也比右子树的 key 都小,例如下图所示

在这里插入图片描述

查找 7 (从根开始)应用二分查找算法,只需三次比较

  • 与 4 比,较之大,向右找
  • 与 6 比,较之大,继续向右找
  • 与 7 比,找到

查找、插入、删除的时间复杂度与树高相关

  • 如果这棵树左右平衡,时间复杂度均是 O ( log ⁡ N ) O(\log{N}) O(logN)
  • 如果这棵树左右高度相差过大,这时是最糟的情况,时间复杂度是 O ( N ) O(N) O(N)
    在这里插入图片描述
  • 二叉搜索树 - binary search tree,简称 BST
  • 二叉排序树 - binary ordered tree 或 binary sorted tree

2.实现

定义节点
static class BSTNode {
    int key; // 若希望任意类型作为 key, 则可以将其设计为 Comparable 接口
    Object value;
    BSTNode left;
    BSTNode right;

    public BSTNode(int key) {
        this.key = key;
        this.value = key;
    }

    public BSTNode(int key, Object value) {
        this.key = key;
        this.value = value;
    }

    public BSTNode(int key, Object value, BSTNode left, BSTNode right) {
        this.key = key;
        this.value = value;
        this.left = left;
        this.right = right;
    }
}
查询

递归实现

public Object get(int key) {
    return doGet(root, key);
}

private Object doGet(BSTNode node, int key) {
    if (node == null) {
        return null; // 没找到
    }
    if (key < node.key) {
        return doGet(node.left, key); // 向左找
    } else if (node.key < key) {
        return doGet(node.right, key); // 向右找
    } else {
        return node.value; // 找到了
    }
}

非递归实现

public Object get(int key) {
    BSTNode node = root;
    while (node != null) {
        if (key < node.key) {
            node = node.left;
        } else if (node.key < key) {
            node = node.right;
        } else {
            return node.value;
        }
    }
    return null;
}
Comparable

让除 int 外更多的类型能够作为 key,一种方式是 key 必须实现 Comparable 接口

public class BSTTree2<T extends Comparable<T>> {
    static class BSTNode<T> {
        T key; // 若希望任意类型作为 key, 则可以将其设计为 Comparable 接口
        Object value;
        BSTNode<T> left;
        BSTNode<T> right;

        public BSTNode(T key) {
            this.key = key;
            this.value = key;
        }

        public BSTNode(T key, Object value) {
            this.key = key;
            this.value = value;
        }

        public BSTNode(T key, Object value, BSTNode<T> left, BSTNode<T> right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

    BSTNode<T> root;

    public Object get(T key) {
        return doGet(root, key);
    }

    private Object doGet(BSTNode<T> node, T key) {
        if (node == null) {
            return null;
        }
        int result = node.key.compareTo(key);
        if (result > 0) {
            return doGet(node.left, key);
        } else if (result < 0) {
            return doGet(node.right, key);
        } else {
            return node.value;
        }
    }

}
最小

递归实现

public Object min() {
    return doMin(root);
}

public Object doMin(BSTNode node) {
    if (node == null) {
        return null;
    }
    // 左边已走到头
    if (node.left == null) { 
        return node.value;
    }
    return doMin(node.left);
}

非递归实现

public Object min() {
    if (root == null) {
        return null;
    }
    BSTNode p = root;
    // 左边未走到头
    while (p.left != null) {
        p = p.left;
    }
    return p.value;
}
最大

递归实现

public Object max() {
    return doMax(root);
}

public Object doMax(BSTNode node) {
    if (node == null) {
        return null;
    }
    // 右边已走到头
    if (node.left == null) { 
        return node.value;
    }
    return doMin(node.right);
}

非递归实现

public Object max() {
    if (root == null) {
        return null;
    }
    BSTNode p = root;
    // 右边未走到头
    while (p.right != null) {
        p = p.right;
    }
    return p.value;
}
新增

递归实现

public void put(int key, Object value) {
    root = doPut(root, key, value);
}

private BSTNode doPut(BSTNode node, int key, Object value) {
    if (node == null) {
        return new BSTNode(key, value);
    }
    if (key < node.key) {
        node.left = doPut(node.left, key, value);
    } else if (node.key < key) {
        node.right = doPut(node.right, key, value);
    } else {
        node.value = value;
    }
    return node;
}
  • 若找到 key,走 else 更新找到节点的值
  • 若没找到 key,走第一个 if,创建并返回新节点
    • 返回的新节点,作为上次递归时 node 的左孩子或右孩子
    • 缺点会有很多不必要的赋值操作

非递归实现

public void put(int key, Object value) {
    BSTNode node = root;
    BSTNode parent = null;
    while (node != null) {
        parent = node;
        if (key < node.key) {
            node = node.left;
        } else if (node.key < key) {
            node = node.right;
        } else {
            // 1. key 存在则更新
            node.value = value;
            return;
        }
    }
    // 2. key 不存在则新增
    if (parent == null) {
        root = new BSTNode(key, value);
    } else if (key < parent.key) {
        parent.left = new BSTNode(key, value);
    } else {
        parent.right = new BSTNode(key, value);
    }
}
前驱后继

一个节点的前驱(前任)节点是指比它小的节点中,最大的那个

一个节点的后继(后任)节点是指比它大的节点中,最小的那个

中序遍历,获得排序结果,很容易找到前驱后继

找前驱
在这里插入图片描述

  1. 节点有左子树,前驱节点就是左子树的最大值
    • 2 的前驱是1
    • 4 的前驱是 3
    • 6 的前驱是 5
    • 7 的前驱是 6
  2. 节点没有左子树,若离它最近的祖先自从左而来,此祖先即为前驱
    • 3 的祖先 2 自左而来,前驱 2
    • 5 的祖先 4 自左而来,前驱 4
    • 8 的祖先 7 自左而来,前驱 7
    • 1 没有这样的祖先,前驱 null

找后继

在这里插入图片描述

  1. 节点有右子树,后继节点即为右子树的最小值
    • 2 的后继 3
    • 3 的后继 4
    • 5 的后继 6
    • 7 的后继 8
  2. 节点没有右子树,若离它最近的祖先自从右而来,此祖先即为后继
    • 1 的祖先 2 自右而来,后继 2
    • 4 的祖先 5 自右而来,后继 5
    • 6 的祖先 7 自右而来,后继 7
    • 8 没有这样的祖先,后继 null
public Object predecessor(int key) {
    BSTNode ancestorFromLeft = null;
    BSTNode p = root;
    while (p != null) {
        if (key < p.key) {
            p = p.left;
        } else if (p.key < key) {
            ancestorFromLeft = p;
            p = p.right;
        } else {
            break;
        }
    }

    if (p == null) {
        return null;
    }
    // 情况1 - 有左孩子
    if (p.left != null) {
        return max(p.left);
    }
    // 情况2 - 有祖先自左而来
    return ancestorFromLeft != null ? ancestorFromLeft.value : null;
}


public Object successor(int key) {
    BSTNode ancestorFromRight = null;
    BSTNode p = root;
    while (p != null) {
        if (key < p.key) {
            ancestorFromRight = p;
            p = p.left;
        } else if (p.key < key) {
            p = p.right;
        } else {
            break;
        }
    }

    if (p == null) {
        return null;
    }
    // 情况1 - 有右孩子
    if (p.right != null) {
        return min(p.right);
    }
    // 情况2 - 有祖先自右而来
    return ancestorFromRight != null ? ancestorFromRight.value : null;
}
删除

删除某节点(称为 D),必须先找到被删除节点的父节点,这里称为 Parent

  1. 删除节点没有左孩子,将右孩子托孤给 Parent
  2. 删除节点没有右孩子,将左孩子托孤给 Parent
  3. 删除节点左右孩子都没有,已经被涵盖在情况1、情况2 当中,把 null 托孤给 Parent
  4. 删除节点左右孩子都有,可以将它的后继节点(称为 S)托孤给 Parent,设 S 的父亲为 SP,又分两种情况
    1. SP 就是被删除节点,此时 D 与 S 紧邻,只需将 S 托孤给 Parent
    2. SP 不是被删除节点,此时 D 与 S 不相邻,此时需要将 S 的后代托孤给 SP,再将 S 托孤给 Parent

非递归实现

/**
 * <h3>根据关键字删除</h3>
 *
 * @param key 关键字
 * @return 被删除关键字对应值
 */
public Object delete(int key) {
    BSTNode p = root;
    BSTNode parent = null;
    while (p != null) {
        if (key < p.key) {
            parent = p;
            p = p.left;
        } else if (p.key < key) {
            parent = p;
            p = p.right;
        } else {
            break;
        }
    }
    if (p == null) {
        return null;
    }
    // 删除操作
    if (p.left == null) {
        shift(parent, p, p.right); // 情况1
    } else if (p.right == null) {
        shift(parent, p, p.left); // 情况2
    } else {
        // 情况4
        // 4.1 被删除节点找后继
        BSTNode s = p.right;
        BSTNode sParent = p; // 后继父亲
        while (s.left != null) {
            sParent = s;
            s = s.left;
        }
        // 4.2 删除和后继不相邻, 处理后继的后事
        if (sParent != p) {                
            shift(sParent, s, s.right); // 不可能有左孩子
            s.right = p.right;
        }
        // 4.3 后继取代被删除节点
        shift(parent, p, s);
        s.left = p.left;
    }
    return p.value;
}

/**
 * 托孤方法
 *
 * @param parent  被删除节点的父亲
 * @param deleted 被删除节点
 * @param child   被顶上去的节点
 */
// 只考虑让 n1父亲的左或右孩子指向 n2, n1自己的左或右孩子并未在方法内改变
private void shift(BSTNode parent, BSTNode deleted, BSTNode child) {
    if (parent == null) {
        root = child;
    } else if (deleted == parent.left) {
        parent.left = child;
    } else {
        parent.right = child;
    }
}

递归实现

public Object delete(int key) {
    ArrayList<Object> result = new ArrayList<>();
    root = doDelete(root, key, result);
    return result.isEmpty() ? null : result.get(0);
}

public BSTNode doDelete(BSTNode node, int key, ArrayList<Object> result) {
    if (node == null) {
        return null;
    }
    if (key < node.key) {
        node.left = doDelete(node.left, key, result);
        return node;
    }
    if (node.key < key) {
        node.right = doDelete(node.right, key, result);
        return node;
    }
    result.add(node.value);
    if (node.left != null && node.right != null) {
        BSTNode s = node.right;
        while (s.left != null) {
            s = s.left;
        }
        s.right = doDelete(node.right, s.key, new ArrayList<>());
        s.left = node.left;
        return s;
    }
    return node.left != null ? node.left : node.right;
}

说明

  1. ArrayList<Object> result 用来保存被删除节点的值
  2. 第二、第三个 if 对应没找到的情况,继续递归查找和删除, doDelete 返回值代表删剩下的,需要更新
  3. 最后一个 return 对应删除节点只有一个孩子的情况,返回那个不为空的孩子,待删节点自己因没有返回而被删除
  4. 第四个 if 对应删除节点有两个孩子的情况,此时需要找到后继节点,并在待删除节点的右子树中删掉后继节点,最后用后继节点替代掉待删除节点返回,改变后继节点的左右指针
找小的
public List<Object> less(int key) {
    ArrayList<Object> result = new ArrayList<>();
    BSTNode p = root;
    LinkedList<BSTNode> stack = new LinkedList<>();
    while (p != null || !stack.isEmpty()) {
        if (p != null) {
            stack.push(p);
            p = p.left;
        } else {
            BSTNode pop = stack.pop();
            if (pop.key < key) {
                result.add(pop.value);
            } else {
                break;
            }
            p = pop.right;
        }
    }
    return result;
}
找大的
public List<Object> greater(int key) {
    ArrayList<Object> result = new ArrayList<>();
    BSTNode p = root;
    LinkedList<BSTNode> stack = new LinkedList<>();
    while (p != null || !stack.isEmpty()) {
        if (p != null) {
            stack.push(p);
            p = p.left;
        } else {
            BSTNode pop = stack.pop();
            if (pop.key > key) {
                result.add(pop.value);
            }
            p = pop.right;
        }
    }
    return result;
}

效率不高,可以用 RNL 遍历

注:

  • Pre-order, NLR
  • In-order, LNR
  • Post-order, LRN
  • Reverse pre-order, NRL
  • Reverse in-order, RNL
  • Reverse post-order, RLN
public List<Object> greater(int key) {
    ArrayList<Object> result = new ArrayList<>();
    BSTNode p = root;
    LinkedList<BSTNode> stack = new LinkedList<>();
    while (p != null || !stack.isEmpty()) {
        if (p != null) {
            stack.push(p);
            p = p.right;
        } else {
            BSTNode pop = stack.pop();
            if (pop.key > key) {
                result.add(pop.value);
            } else {
                break;
            }
            p = pop.left;
        }
    }
    return result;
}
找之间
public List<Object> between(int key1, int key2) {
    ArrayList<Object> result = new ArrayList<>();
    BSTNode p = root;
    LinkedList<BSTNode> stack = new LinkedList<>();
    while (p != null || !stack.isEmpty()) {
        if (p != null) {
            stack.push(p);
            p = p.left;
        } else {
            BSTNode pop = stack.pop();
            if (pop.key >= key1 && pop.key <= key2) {
                result.add(pop.value);
            } else if (pop.key > key2) {
                break;
            }
            p = pop.right;
        }
    }
    return result;
}
小结

优点:

  1. 如果每个节点的左子树和右子树的大小差距不超过一,可以保证搜索操作的时间复杂度是 O(log n),效率高。
  2. 插入、删除结点等操作也比较容易实现,效率也比较高。
  3. 对于有序数据的查询和处理,二叉查找树非常适用,可以使用中序遍历得到有序序列。

缺点:

  1. 如果输入的数据是有序或者近似有序的,就会出现极度不平衡的情况,可能导致搜索效率下降,时间复杂度退化成O(n)。
  2. 对于频繁地插入、删除操作,需要维护平衡二叉查找树,例如红黑树、AVL 树等,否则搜索效率也会下降。
  3. 对于存在大量重复数据的情况,需要做相应的处理,否则会导致树的深度增加,搜索效率下降。
  4. 对于结点过多的情况,由于树的空间开销较大,可能导致内存消耗过大,不适合对内存要求高的场景。

3.习题

E01. 删除节点-Leetcode 450
public TreeNode deleteNode(TreeNode node, int key) {
    if (node == null) {
        return null;
    }
    if (key < node.val) {
        node.left = deleteNode(node.left, key);
        return node;
    }
    if (node.val < key) {
        node.right = deleteNode(node.right, key);
        return node;
    }
    if (node.left == null) { // 情况1 - 只有右孩子
        return node.right;
    }
    if (node.right == null) { // 情况2 - 只有左孩子
        return node.left;
    }
    TreeNode s = node.right; // 情况3 - 有两个孩子
    while (s.left != null) {
        s = s.left;
    }
    s.right = deleteNode(node.right, s.val);
    s.left = node.left;
    return s;
}
E02. 新增节点-Leetcode 701
public TreeNode insertIntoBST(TreeNode node, int val) {
    if(node == null) {
        return new TreeNode(val);
    }
    if(val < node.val) {
        node.left = insertIntoBST(node.left, val);
    } else if(node.val < val) {
        node.right = insertIntoBST(node.right, val);
    }
    return node;
}
E03. 查询节点-Leetcode 700
public TreeNode searchBST(TreeNode node, int val) {
    if(node == null) {
        return null;
    }
    if(val < node.val) {
        return searchBST(node.left, val);
    } else if(node.val < val) {
        return searchBST(node.right, val);
    } else {
        return node;
    }
}
E04. 验证二叉搜索树-Leetcode 98

中序非递归实现

public boolean isValidBST(TreeNode root) {
    TreeNode p = root;
    LinkedList<TreeNode> stack = new LinkedList<>();
    long prev = Long.MIN_VALUE;
    while (p != null || !stack.isEmpty()) {
        if (p != null) {
            stack.push(p);
            p = p.left;
        } else {
            TreeNode pop = stack.pop();
            if (prev >= pop.val) {
                return false;
            }
            prev = pop.val;
            p = pop.right;
        }
    }
    return true;
}
  • 记录 prev 需要用 long,否则若测试用例中最小的节点为 Integer.MIN_VALUE 则测试会失败
  • 如果相邻两个节点相等,也不应当通过测试,例如,下面的树也是不合法的
	2
   / 
  2 

中序递归实现

public boolean isValidBST(TreeNode root) {
    if (root == null) {
        return true;
    }
    return doValid(new AtomicLong(Long.MIN_VALUE),root);
}

public boolean doValid(AtomicLong prev, TreeNode node) {
    if (node == null) {
        return true;
    }
    boolean a = doValid(prev, node.left);
    if (prev.get() >= node.val) {
        return false;
    }
    prev.set(node.val);
    boolean b = doValid(prev, node.right);
    return a && b;
}
  • 为何不能用 Long 或 long?因为它们都是局部变量且不可变,因此每次赋值时,并不会改变其它方法调用时的 prev
  • 要么把 prev 设置为 AtomicLong,要么把 prev 设置为全局变量,而不要采用方法参数这样的局部变量

上下限递归

public boolean isValidBST(TreeNode node) {
    return doValid(node, Long.MIN_VALUE, Long.MAX_VALUE);
}

private boolean doValid(TreeNode node, long min, long max) {
    if (node == null) {
        return true;
    }
    if (node.val <= min || node.val >= max) {
        return false;
    }
    return doValid(node.left, min, node.val) && doValid(node.right, node.val, max);
}
  • 设每个节点必须在一个范围内: ( m i n , m a x ) (min, max) (min,max),不包含边界,若节点值超过这个范围,则返回 false
  • 对于 node.left 范围肯定是 ( m i n , n o d e . v a l ) (min, node.val) (min,node.val)
  • 对于 node.right 范围肯定是 ( n o d e . v a l , m a x ) (node.val, max) (node.val,max)
  • 一开始 min,max 则取 java 中长整数的最小、最大值
  • 上下限递归 = 前序遍历 + 剪枝
E05. 求范围和-Leetcode 938

中序递归实现

public int rangeSumBST(TreeNode node, int low, int high) {
    if (node == null) {
        return 0;
    }
    int a = rangeSumBST(node.left, low, high);
    int b = 0;
    if (node.val >= low && node.val <= high) {
        b = node.val;
    }
    return a + b + rangeSumBST(node.right, low, high);
}

中序非递归实现

public int rangeSumBST(TreeNode node, int low, int high) {
    TreeNode p = node;
    LinkedList<TreeNode> stack = new LinkedList<>();
    int sum = 0;
    while(p != null || !stack.isEmpty()) {
        if (p != null) {
            stack.push(p);
            p = p.left;
        } else {
            TreeNode pop = stack.pop();
            if (pop.val > high) {
                break;
            }
            if (pop.val >= low) {
                sum += pop.val;
            }
            p = pop.right;
        }
    }
    return sum;
}

上下限递归实现

public int rangeSumBST(TreeNode node, int low, int high) {
    if (node == null) {
        return 0;
    }
    if (node.val < low) {
        return rangeSumBST(node.right, low, high);
    } 
    if (node.val > high) {
        return rangeSumBST(node.left, low, high);
    }
    return node.val + 
        rangeSumBST(node.left, low, high) + 
        rangeSumBST(node.right, low, high);
}
  • node.val < low 只需考虑它右子树的累加结果
  • node.val > high 只需考虑它左子树的累加结果
  • node.val 在范围内,需要把当前节点的值加上其左右子树的累加结果
E06. 根据前序遍历结果构造二叉搜索树-Leetcode 1008

直接插入

注意:根据前序遍历的结果,可以唯一地构造出一个二叉搜索树

public TreeNode bstFromPreorder(int[] preorder) {
    TreeNode root = insert(null, preorder[0]);
    for (int i = 1; i < preorder.length; i++) {
        insert(root, preorder[i]);
    }
    return root;
}

private TreeNode insert(TreeNode node, int val) {
    if (node == null) {
        return new TreeNode(val);
    }
    if(val < node.val) {
        node.left = insert(node.left, val);
    } else if(node.val < val){
        node.right = insert(node.right, val);
    }
    return node;
}

上限法

public TreeNode bstFromPreorder(int[] preorder) {
    return insert(preorder, Integer.MAX_VALUE);
}

int i = 0;
private TreeNode insert(int[] preorder, int max) {
    if (i == preorder.length) {
        return null;
    }
    int val = preorder[i];
    System.out.println(val + String.format("[%d]", max));
    if (val > max) {
        return null;
    }
    TreeNode node = new TreeNode(val);
    i++;
    node.left = insert(preorder, node.val); 
    node.right = insert(preorder, max);     
    return node;
}

依次处理 prevorder 中每个值, 返回创建好的节点或 null 作为上个节点的孩子

  1. 如果超过上限, 返回 null
  2. 如果没超过上限, 创建节点, 并将其左右孩子设置完整后返回
    • i++ 需要放在设置左右孩子之前,是从剩下的元素中挑选左右孩子

分治法

public TreeNode bstFromPreorder(int[] preorder) {
    return partition(preorder, 0, preorder.length - 1);
}

private TreeNode partition(int[] preorder, int start, int end) {
    if (start > end) {
        return null;
    }
    TreeNode root = new TreeNode(preorder[start]);
    int index = start + 1;
    while (index <= end) {
        if (preorder[index] > preorder[start]) {
            break;
        }
        index++;
    }
    // index 就是右子树的起点
    root.left = partition(preorder, start + 1, index - 1);
    root.right = partition(preorder, index, end);
    return root;
}
  • 刚开始 8, 5, 1, 7, 10, 12,方法每次执行,确定本次的根节点和左右子树的分界线
  • 第一次确定根节点为 8,左子树 5, 1, 7,右子树 10, 12
  • 对 5, 1, 7 做递归操作,确定根节点是 5, 左子树是 1, 右子树是 7
  • 对 1 做递归操作,确定根节点是 1,左右子树为 null
  • 对 7 做递归操作,确定根节点是 7,左右子树为 null
  • 对 10, 12 做递归操作,确定根节点是 10,左子树为 null,右子树为 12
  • 对 12 做递归操作,确定根节点是 12,左右子树为 null
  • 递归结束,返回本范围内的根节点
E07. 二叉搜索树的最近公共祖先-Leetcode 235

要点:若 p,q 在 ancestor 的两侧,则 ancestor 就是它们的最近公共祖先

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    TreeNode ancestor = root;
    while (ancestor.val > p.val && ancestor.val > q.val || 
           ancestor.val < p.val && ancestor.val < q.val) {
        if (ancestor.val > p.val) {
            ancestor = ancestor.left;
        } else {
            ancestor = ancestor.right;
        }
    }
    return ancestor;
}

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

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

相关文章

【面试题】创建两个线程交替打印100以内数字(一个打印偶数一个打印奇数)

阅读导航 一、问题概述二、解决思路三、代码实现四、代码优化 一、问题概述 面试官&#xff1a;C多线程了解吗&#xff1f;你给我写一下&#xff0c;起两个线程交替打印0~100的奇偶数。就是有两个线程&#xff0c;一个线程打印奇数另一个打印偶数&#xff0c;它们交替输出&…

读AI未来进行式笔记04数字医疗与机器人

1. 数字医疗 1.1. 20世纪的“现代医学”得益于史无前例的科学突破&#xff0c;使得医疗的方方面面都得到改善&#xff0c;让人类预期寿命从1900年的31岁提高到2017年的72岁 1.2. 现有的医疗数据库和流程将实现数字化 1.2.1. 患者记录 1.2.…

泛微开发修炼之旅--06自定义Action接口开发示例、源码及使用场景

文章链接&#xff1a;泛微开发修炼之旅--06自定义Action接口开发示例、源码及使用场景

创新实训2024.06.02日志:SSE、流式输出以及基于MTPE技术的MT-SSE技术

1. Why SSE&#xff1f; 之所以要做SSE&#xff0c;是因为在开发、调试以及使用我们开发的软件时&#xff0c;我发现消息的响应时间会很长。之所以会这样最主要的原因是&#xff0c;MTPE这项基于CoT的技术&#xff0c;本质上是多个单一的提示工程有机地组合在一起对大模型生成…

Java中常见错误-泛型擦除及桥接方法问题及解决方案

Java中泛型擦除及桥接方法 泛型擦除无界擦除上界擦除下界擦除 桥接方法演示案例wrong1wrong2wrong3right 原理总结 泛型擦除 ​ 泛型擦除是Java泛型机制的一个特性&#xff0c;它意味着**在编译期间&#xff0c;所有的泛型信息都会被移除&#xff0c;而在运行时&#xff0c;所…

html+CSS+js部分基础运用15

1、完成输入框内容的实时反向输出。 2、银行账户余额变动自动通知项目。 设计要求&#xff1a;单击按钮后&#xff0c;余额按照输入框的数额减少&#xff0c;同时将按钮式的提示信息&#xff08;金额&#xff09;同步改变。利用侦听属性实现余额发生变化时发出提示信息&#x…

python-flask项目的服务器线上部署

在部署这部分我首先尝试了宝塔面板&#xff0c;始终连接失败 换了一种思路选择了Xshell成功连接 首先我们需要下载个免费版本的Xshell 免费的&#xff1a;家庭/学校免费 - NetSarang Website 下载完毕打开 1新建-> 输入服务器的账号密码&#xff1a; 在所有会话中点击自…

NDIS Filter开发-PNP响应和安装

NDIS filter驱动可能是最容易生成的驱动之一&#xff0c;如果你安装了VS 2015 WDK之后&#xff0c;你可以直接生成一个能运行的Filter驱动&#xff0c;它一般是ndislwf。 和大部分硬件不同&#xff0c;NDIS Filter驱动介于软件和硬件抽象层之上&#xff0c;它和硬件相关&…

工业无线wifi系统搭配高速路由,解决联网及数据传输

​面对日益复杂的工业应用场景,企业对无线网络的高速、可靠和安全提出了更高要求。星创易联SR600系列多网口4G路由器应运而生,为工业无线WiFi系统提供了一个性能卓越的高速路由方案。&#xff08;key-iot.com/iotlist/sr600-5.html&#xff09; SR600路由器集4G LTE、虚拟专用…

c++(内存分配,构造,析构)

#include <iostream>using namespace std; class Per { private:string name;int age;double *height;double *weigh; public://无参构造Per(){cout << "Per::无参构造" << endl;}//有参构造Per(string name,int age,double height,double weigh):…

C++候捷stl-视频笔记4

一个万用的hash function 哈希函数的形式&#xff0c;一种是一般函数(右边)&#xff0c;一种是成员函数(左边)&#xff0c;类的对象将成为函数对象 具体做法例子。直接把属性的所有hash值加起来&#xff0c;会在hashtable中会产生很多的碰撞&#xff0c;放在同一个bucket中的元…

Nginx的https功能

一.HTTPS功能简介 Web网站的登录页面都是使用https加密传输的&#xff0c;加密数据以保障数据的安全&#xff0c;HTTPS能够加密信息&#xff0c;以免敏感信息被第三方获取&#xff0c;所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议&#xff0c;HTTPS其实…

优化家庭网络,路由器无线中继配置全攻略(中兴E1600无线中继设置/如何解决没有预埋有线网络接口的问题/使用闲置路由实现WIFI扩展)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 网络优化 📒📒 操作步骤 📒💡适用场景🚨 常见问题及解决方案⚓️ 相关链接 ⚓️📖 介绍 📖 在现代家庭生活中,WiFi已经渗透到我们生活的每一个角落,成为了日常生活中不可或缺的一部分。然而,不少用户常常遇到W…

Bytebase 作为唯一数据库工具厂商,亮相亚马逊云科技中国峰会

作为云计算行业的风向标&#xff0c;亚马逊云科技中国峰会每年都吸引着全球顶尖企业和行业精英。此次峰会不仅展示了最新的 AI 技术趋势和解决方案&#xff0c;还为参展商和与会者提供了一个卓越的交流与合作平台。 Bytebase 作为全场唯一的数据库工具厂商亮相数据区&#xff0…

Windows下Qt5.14.2连接华为IoTDA平台

一、华为IoTDA简介 华为云物联网平台&#xff08;IoT 设备接入云服务&#xff09;提供海量设备的接入和管理能力&#xff0c;将物理设备联接到云&#xff0c;支撑设备数据采集上云和云端下发命令给设备进行远程控制&#xff0c;配合华为云其他产品&#xff0c;帮助您快速构筑物…

学习笔记——IP地址网络协议——VLSM-可变长子网掩码(子网划分)

四、VLSM-可变长子网掩码(子网划分) 1、为什么要子网划分 为什么要子网划分&#xff1a;有类IP地址规划的缺陷。IP地址空间只能按照默认的类别使用&#xff0c;例如一个B类地址&#xff0c;默认掩码为255.255.0.0&#xff0c;意味着这个地址空间里有2的16次方个IP&#xff0c;…

从零开始实现自己的串口调试助手(3) - 显示底部收发,优化串口打开/关闭

注意: 1. 我们要实现自发自收&#xff0c;要将tx&#xff0c;rx连起来 2.发送的 不能是中文符号&#xff0c;因为这可能导致&#xff0c;读取到的是英文符号 --> 导致接收到的size 和发送的size 大小不一致 3.注意同时定义两个槽函数的时候两个槽函数都会被调用&#xff0c;…

2024 年最新安装MAC-vue教学包括常见错误

花了一上午时间终于将 vue 的工程文件安装好了&#xff0c;本教材是傻瓜式操作&#xff0c;按着教程一步一步操作最后就可以看到页面了。 安装Node 1.在线地址&#xff1a; https://nodejs.org/en 2、点击 Download Node.js下载即可&#xff0c;下载完成后&#xff0c;傻瓜式的…

【数智化CIO展】吉家宠物CIO张志伟:深度挖掘数据价值是数字化发展趋势,才能实现企业精细化运营...

张志伟 本文由吉家宠物CIO张志伟投递并参与由数据猿联合上海大数据联盟共同推出的《2024中国数智化转型升级优秀CIO》榜单/奖项评选。丨推荐企业&#xff1a;观远数据 大数据产业创新服务媒体 ——聚焦数据 改变商业 中国“宠物经济”热潮不断攀升&#xff0c;国内宠物市场的竞…

共享使用模型以节省磁盘空间

如果同时使用了多个工具&#xff08;例如 Easy Diffusion, Stable Diffusion UI, Comfy)&#xff0c;则可以通过共享使用保存在某个目录下的模型文件来节省磁盘空间。 1. Easy Diffusion 在Easy Diffusion中可以创建一个链接文件夹&#xff0c;以便在不同的 Stable Diffusion…