【UCB CS 61B SP24】Lecture 17 - Data Structures 3: B-Trees学习笔记
本文以 2-3-4 树详细讲解了 B 树的概念,逐步分析其操作,并用 Java 实现了标准的 B 树。
1. 2-3 & 2-3-4 Trees
上一节课中讲到的二叉搜索树当数据是随机顺序插入的时候能够使得树变得比较茂密,如下图右侧所示,时间复杂度也就近似 O ( l o g n ) O(log n) O(logn)。但是当数据按顺序插入时,二叉搜索树就退化为了链表(每个节点都向同一侧倾斜),如下图左侧所示,这样时间复杂度就退化为 O ( n ) O(n) O(n)。有什么更优化的数据结构呢?

B 树(B-Trees)是一种自平衡的树数据结构,适用于在磁盘等存储设备上高效管理大量数据。它通过保持平衡来确保查找、插入、删除操作的时间复杂度为 O ( l o g n ) O(log n) O(logn)。B 树广泛应用于数据库和文件系统。
1.1 插入
假设我们现在有一颗还算完美的二叉搜索树,如下图右上角所示,接下来如果我们需要插入 {17, 18, 19, ...} 怎么办?我们可以通过在叶节点中“过度填充”来避免产生新的叶节点,也就是把插入的元素都塞到 16 节点中:

但是如果一个节点过于充斥,如下图所示,那么我们可能就得遍历节点中的所有元素才能找到我们想要的,这样效率同样会下降:

我们的解决方法是设定一个限制 L L L,表示一个节点中最多有几个键值,假设我们令 L = 3 L = 3 L=3,那么当节点中的键值已经到 {16, 17, 18, 19} 时就已经超过限制了,这时我们需要选择一个键值提升到父节点中。
我们会将中间键(记为 node[mid])提升到父节点,在我们这个例子中键值数量为偶数,那么就选择中间偏左的键值 17,如下图所示:

仔细观察又会发现这样有个问题,那就是这时 16 在 {15, 17} 的右侧了,这就不是个合法的搜索树了。因此再提完中间键后我们需要将中间键的左右两部分分裂开变成两个节点,假设 node 表示提升键值前的原节点 {16, 17, 18, 19},那么分裂操作就是将 node[0 ~ mid - 1] 与 node[mid + 1, node.size - 1] 分裂开。
因此我们会将 16 与 {18, 19} 分裂开,如下图所示,这样小于 15 的键值在左侧子节点(可以表示为 {15, 17}.children[0]),在 15 ~ 17 之间的键值在左侧第二个子节点(可以表示为 {15, 17}.children[1]),大于 17 的键值在右侧子节点(可以表示为 {15, 17}.children[2]):

假设我们继续插入 {20, 21},如下图所示,当插入 21 时,节点又爆满了,将中间靠左的键值 19 提升到父节点中,接着原节点分裂开:

我们继续插入 {25, 26},流程如下图所示,可以看到当非叶子节点分裂时,还需要同步处理子节点的引用,即分裂非叶子节点 node[0 ~ mid - 1] 与 node[mid + 1, node.size - 1] 时,还需要顺带分裂 node.children[0, mid] 与 node.children[mid + 1, node.size - 1](注意左半部分需要将 mid 包含进去才正确,可以结合图片理解):

如果我们一直添加到根节点都塞满了怎么办?那么就同样将根节点中的中间键往上提,这时候就成为了新的根节点,树的高度在这时候才加了一层,即树的高度只有在分裂根时才会增加,此时树还是保持着完美的平衡:

我们此前设定的限制 L = 3 L = 3 L=3 就最后就形成了这棵 2-3-4 树,当 L = 2 L = 2 L=2 时我们称其为 2-3 树,这两种就是相对最常见的 B 树。
1.2 删除
B 树的删除与 BST 一样是比较复杂的,有多种情况需要讨论。
(1)如果要删除的节点为内部节点(无论节点中有几个键值),那么思想与 BST 类似,找到前驱(左子树最大键)或后继(右子树最小键)替换要删除的键值,然后递归删除叶子节点中的键:

在这种情况中我们找到了 18,最后将其删去,这样看起来很简单,因为如果我们从具有多个键值的叶子节点中删除某个值只需要简单将其删去即可。
(2)如果我们的叶子节点只有一个键,我们就不能简单地完全删除节点,因为根据 B 树的性质(先见第二小节),每个拥有 k k k 个键值的节点(除叶子)都有 k + 1 k + 1 k+1 个子节点,因此我们将留下一个必须填充的空节点:

如何填充空节点是比较复杂的,同样也有多种情况要讨论:
Case 1:空节点的相邻兄弟节点有多个键值(非常难的情况),如下图所示,我们用哪个键来填充呢?

解决思路为:
X先把父节点的键值拿过来,然后父节点再从X的兄弟节点中拿一个键值过来;- 如果
X不是叶节点,再将其兄弟节点的一个子树拿过来(维持 B 树性质)。

结合例子看看,我们要删除 17,首先在右子树找到了后继键值 19,将其与 17 交换,然后删除 17,删除后留下了一个空节点,填充时从父节点拿来 21,父节点再从另一个兄弟节点拿来 22,由于空节点为叶节点,因此不进一步拿兄弟节点的子树:

Case 2:空节点右侧的所有兄弟节点都只有一个键值,但是父节点有多个键值(同样很困难),如下图所示:

解决思路为:
X和最右侧的兄弟节点把父节点的键值拿来,中间子节点的键值提到父节点中;- 传递中间子节点的子树,以便每个节点都有正确的子节点

结合例子看看,我们要删除 3,首先在右子树中找到了后继键值 4,将其与 3 交换,然后删除 4,删除后留下了一个空节点,填充时右侧兄弟节点都只有一个键值,因此和最右边的兄弟节点 9 一起分别将父节点的键值拿来,然后将中间兄弟节点的键值提到父节点中,已经是叶节点了因此不用再调整子树了:

Case 3:父节点和所有兄弟节点都只有一个键值,这种简单点,解决思路就是将一个兄弟节点和父节点合并成一个节点,替换到 X 上,然后将空节点上移一层,如果空节点最终作为了根节点,那么直接删除空节点即可:

结合例子看看,我们要删除 6,首先在右子树中找到了后继键值 7,将其与 6 交换,然后删除 7,删除后留下了一个空节点,填充时右侧兄弟节点与父节点都只有一个键值,因此合并兄弟节点和父节点变为 {8, 9},然后将空节点上移一层,此时空节点并不是根节点,回到了第一种情况(兄弟节点有多个键值),也就是先把父节点键值 7 拿来,然后父节点从有多个键值的子节点那把 4 拿来,空节点不是叶节点,最后再把兄弟节点的子树 5 拿来当自己的子树:

2. Java实现多阶B树
通过上面演示的 B 树我们能发现其具有以下特性,我们此处以 m m m 阶 B 树为例进行概括:
- 节点容量:
- 根节点:至少有1个键,最多 m − 1 m - 1 m−1 个键。
- 内部节点:至少 ⌈ m / 2 ⌉ − 1 \lceil m / 2\rceil - 1 ⌈m/2⌉−1个键,最多 m − 1 m - 1 m−1 个键。
- 子节点数量:每个拥有 k k k 个键值的节点(除叶子节点)都有 k + 1 k + 1 k+1 个子节点。
- 平衡性:所有叶子节点位于同一层(相同深度),树是完全平衡的,无论怎么添加键值时间复杂度都为 O ( l o g n ) O(log n) O(logn)。
- 有序性:节点内的键按升序排列,子树遵循二叉搜索树性质。
总结一下 B 树的操作:
(1)查找
从根节点开始,逐层向下比较键值:
- 若找到目标键,返回
true。 - 否则,根据键的大小选择对应的子节点递归查找。
- 到达叶子节点仍未找到,返回
false。
(2)插入
- 寻找插入位置:递归找到对应的叶子节点。
- 插入键:将键插入叶子节点。
- 分裂处理:
- 若节点键数超过 m − 1 m - 1 m−1,则分裂:
- 中间键提升到父节点;
- 原节点分裂为两个子节点。
- 递归检查父节点是否需要分裂,直到根节点。
- 若节点键数超过 m − 1 m - 1 m−1,则分裂:
(3)删除
- 定位键:找到待删除键的位置。
- 处理内部节点键:若键在内部节点,用前驱(左子树最大键)或后继(右子树最小键)替换,转为删除叶子节点中的键。
- 删除叶子键:直接删除。
- 处理下溢:
- 借键:若兄弟节点有富余键,从兄弟借一个键并调整父节点;
- 合并:若兄弟节点无富余,合并当前节点与兄弟,并递归调整父节点。
Java 实现 m m m 阶 B 树代码如下,可以简单参考一下,不一定要完全看明白:
package CS61B.Lecture17;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** 标准 m 阶 B 树实现(支持插入、查找、删除)* 特性:* 1. 每个节点最多包含 m - 1 个键* 2. 根节点最少包含 1 个键,非根节点最少包含 ⌈m / 2⌉ - 1 个键* 3. 所有叶子节点位于同一层*/
public class BTree {private final int m; // B 树的阶private Node root;public BTree(int m) {this.m = m;this.root = new Node(true);}/** 节点 */private static class Node {List<Integer> keys = new ArrayList<>(); // 存储键值(始终保持有序)List<Node> children = new ArrayList<>(); // 子节点引用(非叶子节点使用)boolean isLeaf; // 是否为叶子节点Node(boolean isLeaf) {this.isLeaf = isLeaf;}}/** 核心操作:查找 */public boolean contains(int key) {return search(root, key) != null;}/** 递归查找实现 */private Integer search(Node node, int key) {// 找到当前节点中第一个不小于 key 的键的位置int i = 0;while (i < node.keys.size() && key > node.keys.get(i)) i++;if (i < node.keys.size() && key == node.keys.get(i)) { // 在当前节点找到目标键return key;} else if (node.isLeaf) {return null;} else { // 未找到但当前节点非叶子节点return search(node.children.get(i), key); // 递归查找子节点}}/** 核心操作:插入 */public void insert(int key) {insert(root, key);// 根节点分裂处理if (root.keys.size() == m) {Node newRoot = new Node(false);newRoot.children.add(root);splitChild(newRoot, 0);root = newRoot;}}/** 递归插入实现 */private void insert(Node node, int key) {int i = node.keys.size() - 1;if (node.isLeaf) {// 叶子节点:直接插入while (i >= 0 && key < node.keys.get(i)) i--; // 找到小于等于 key 的最大值位置node.keys.add(i + 1, key); // 在其右侧插入 key} else {// 内部节点:找到子节点位置while (i >= 0 && key < node.keys.get(i)) i--;i++; // 调整到正确的子节点索引,因为 node[i] 小于等于 key,node.children[i] 是小于 node[i] 的子树// 子节点已满时先分裂if (node.children.get(i).keys.size() == m - 1) {splitChild(node, i);if (key > node.keys.get(i)) i++; // 分裂后可能需要调整目标子节点索引}insert(node.children.get(i), key);}}/** 分裂子节点(核心辅助方法) */private void splitChild(Node parent, int childIndex) {Node child = parent.children.get(childIndex);Node sibling = new Node(child.isLeaf); // 与原节点在同一层int mid = m - 1 >> 1; // 中间键索引// 将右半部分键移动到新节点sibling.keys.addAll(child.keys.subList(mid + 1, child.keys.size()));child.keys.subList(mid + 1, child.keys.size()).clear(); // 清除原节点右半部分// 非叶子节点:处理子节点引用if (!child.isLeaf) {sibling.children.addAll(child.children.subList(mid + 1, child.children.size()));child.children.subList(mid + 1, child.children.size()).clear();}// 将中间键提升到父节点,新节点在原节点的右边parent.keys.add(childIndex, child.keys.remove(mid));parent.children.add(childIndex + 1, sibling);}/** 核心操作:删除 */public void delete(int key) {delete(root, key);/*根节点为空时降低树高度,选择其第一个子节点作为新的根节点当根节点被删除到空时,唯一可能的场景是:根节点原本只有一个键,且该键被删除根节点此时仅剩一个子节点(因为如果根节点有多个子节点,它必须至少保留一个键来分隔子节点)*/if (root.keys.isEmpty() && !root.isLeaf) {root = root.children.get(0);}}/** 递归删除实现 */private void delete(Node node, int key) {int i = 0;while (i < node.keys.size() && key > node.keys.get(i)) i++; // 找到大于等于 key 的最小值// Case 1: 当前节点包含目标键if (i < node.keys.size() && key == node.keys.get(i)) {if (node.isLeaf) { // 如果为叶子节点的键则直接删除node.keys.remove(i);} else { // 如果是内部节点则用前驱/后继替换后递归删除handleInternalKey(node, i);}}// Case 2: 目标键可能在子节点中else if (!node.isLeaf) {Node child = node.children.get(i);// 子节点键不足时先调整if (child.keys.size() < (m + 1) / 2) {// 尝试从左兄弟借键if (i > 0 && node.children.get(i - 1).keys.size() >= (m + 1) / 2) {borrowFromLeftSibling(node, i);}// 尝试从右兄弟借键else if (i < node.children.size() - 1 && node.children.get(i + 1).keys.size() >= (m + 1) / 2) {borrowFromRightSibling(node, i);}// 需要合并节点else {if (i < node.children.size() - 1) {mergeChildren(node, i);} else {mergeChildren(node, i - 1);i--; // 合并后索引调整}}}delete(node.children.get(i), key);}}/** 处理内部节点键的删除,选择前驱后继时需要注意非根节点最少包含 ⌈m / 2⌉ - 1 个键的性质 */private void handleInternalKey(Node node, int index) {Node leftChild = node.children.get(index);Node rightChild = node.children.get(index + 1);// Case 1: 左子节点的键足够多,用前驱替换if (leftChild.keys.size() >= (m + 1) / 2) {int predecessor = getPredecessor(leftChild);node.keys.set(index, predecessor);delete(leftChild, predecessor);}// Case 2: 右子节点的键足够多,用后继替换else if (rightChild.keys.size() >= (m + 1) / 2) {int successor = getSuccessor(rightChild);node.keys.set(index, successor);delete(rightChild, successor);}// Case 3: 否则合并 leftChild 与 rightChild 两个子节点后递归删除else {int keyToDelete = node.keys.get(index); // 合并后 node.keys.get(index) 可能已变更,需要提前保存mergeChildren(node, index);delete(leftChild, keyToDelete); // 删除已下移到子节点的原键}}/** 获取左子树的最大键(前驱) */private int getPredecessor(Node node) {while (!node.isLeaf) {node = node.children.get(node.children.size() - 1);}return node.keys.get(node.keys.size() - 1);}/** 获取右子树的最小键(后继) */private int getSuccessor(Node node) {while (!node.isLeaf) {node = node.children.get(0);}return node.keys.get(0);}/** 从左兄弟借键 */private void borrowFromLeftSibling(Node parent, int childIndex) {Node child = parent.children.get(childIndex);Node leftSibling = parent.children.get(childIndex - 1);// 父节点键下移,左兄弟键上移child.keys.add(0, parent.keys.get(childIndex - 1));parent.keys.set(childIndex - 1, leftSibling.keys.remove(leftSibling.keys.size() - 1));// 移动子节点引用(非叶子节点)if (!child.isLeaf) {child.children.add(0, leftSibling.children.remove(leftSibling.children.size() - 1));}}/** 从右兄弟借键 */private void borrowFromRightSibling(Node parent, int childIndex) {Node child = parent.children.get(childIndex);Node rightSibling = parent.children.get(childIndex + 1);// 父节点键下移,右兄弟键上移child.keys.add(parent.keys.get(childIndex));parent.keys.set(childIndex, rightSibling.keys.remove(0));// 移动子节点引用(非叶子节点)if (!child.isLeaf) {child.children.add(rightSibling.children.remove(0));}}/** 合并 childIndex 与 childIndex + 1 两个位置的子节点 */private void mergeChildren(Node parent, int childIndex) {Node left = parent.children.get(childIndex);Node right = parent.children.get(childIndex + 1);// 提取父节点的键并下移int parentKey = parent.keys.get(childIndex);left.keys.add(parentKey);parent.keys.remove(childIndex);// 合并右子节点的键和子节点left.keys.addAll(right.keys);left.children.addAll(right.children);parent.children.remove(childIndex + 1);// 若父节点是根且无键,降低树高度if (parent == root && parent.keys.isEmpty()) {root = left;}}/** 打印 B 树结构 */public void printTree() {printTree(root, 0);}/** 递归打印 B 树结构 */private void printTree(Node node, int level) {StringBuilder indent = new StringBuilder();for (int i = 0; i < level; i++) {indent.append("│ "); // 每层缩进 4 个字符}// 打印当前节点键值System.out.print(indent);if (level > 0) {System.out.print("├── ");}System.out.print("[" + String.join(", ", node.keys.stream().map(Object::toString).toArray(String[]::new)) + "]");if (node.isLeaf) {System.out.print(" (Leaf)");}System.out.println();// 递归打印子节点for (int i = 0; i < node.children.size(); i++) {Node child = node.children.get(i);printTree(child, level + 1);}}/** 递归打印 B 树结构(添加箭头符号的增强版) */private void printTreeEnhancement(Node node, int level) {// 生成缩进前缀StringBuilder prefix = new StringBuilder();for (int i = 0; i < level; i++) {prefix.append(i == level - 1 ? "│ " : " ");}// 打印当前节点System.out.print(prefix);if (level > 0) {System.out.print("└── ");}System.out.print("[" + String.join(", ", Arrays.toString(node.keys.stream().map(Object::toString).toArray(String[]::new)) + "]"));if (node.isLeaf) System.out.print(" (Leaf)");System.out.println();// 递归子节点for (int i = 0; i < node.children.size(); i++) {Node child = node.children.get(i);printTree(child, level + 1);}}/** 测试 */public static void main(String[] args) {BTree tree = new BTree(4);// 插入测试数据int[] keys = {10, 20, 30, 40, 50, 60, 70, 80, 90};for (int key : keys) tree.insert(key);// 验证存在性System.out.println("Contains 30: " + tree.contains(30)); // trueSystem.out.println("Contains 10: " + tree.contains(10)); // truetree.printTree();// 删除内部节点键tree.delete(30);System.out.println("Contains 30 after deletion: " + tree.contains(30)); // falsetree.printTree();// 边界测试:删除后树结构调整tree.delete(10);tree.delete(20);tree.delete(40);System.out.println("Contains 50: " + tree.contains(50)); // trueSystem.out.println("Contains 10 after deletion: " + tree.contains(10)); // false}
}
相关文章:
【UCB CS 61B SP24】Lecture 17 - Data Structures 3: B-Trees学习笔记
本文以 2-3-4 树详细讲解了 B 树的概念,逐步分析其操作,并用 Java 实现了标准的 B 树。 1. 2-3 & 2-3-4 Trees 上一节课中讲到的二叉搜索树当数据是随机顺序插入的时候能够使得树变得比较茂密,如下图右侧所示,时间复杂度也就…...
机器学习决策树
一、香农公式 熵: 信息增益: 信息增益信息熵-条件熵 前者是初始信息熵大小,后者是因为条件加入后带来的确定性增加 信息增益表示得知特征X的信息而使得类Y的信息的不确定性减少的程度 信息增益越大说明影响越大 二、代码 ""&…...
Spring Boot + MyBatis 实现 RESTful API 的完整流程
后端开发:Spring Boot 快速开发实战 引言 在现代后端开发中,Spring Boot 因其轻量级、快速开发的特性而备受开发者青睐。本文将带你从零开始,使用 Spring Boot MyBatis 实现一个完整的 RESTful API,并深入探讨如何优雅地处理异…...
通过 ANSYS Discovery 进行 CFD 分析,增强工程设计
概括 工程师使用计算流体动力学 (CFD) 分析来研究和优化各种应用中的流体流动和传热分析。ANSYS Discovery 是一个用户友好的软件平台,使工程师能够轻松设置和解决 CFD 模型,并能够通知设计修改 在这篇博文中,我们将重点介绍在 Ansys Disc…...
家用可燃气体探测器——家庭燃气安全的坚实防线
随着社会的发展和变迁,天然气为我们的生活带来了诸多便利,无论是烹饪美食,还是温暖取暖,都离不开它的支持。然而,燃气安全隐患如影随形,一旦发生泄漏,可能引发爆炸、火灾等严重事故,…...
ListControl双击实现可编辑
为Edit Control控件添加丢失输入焦点事件,可见设为false 为List Control控件添加双击事件 控件和成员变量之间交换数据 CListCtrl ListPrint1; //列表输出 CEdit...
ave-form.vue 组件中 如何将产品名称发送给后端 ?
如何将产品名称发送给后端。 在这段代码中,产品名称(productName)的处理和发送主要发生在 save() 方法中。让我逐步分析: 产品ID的选择: <w-form-selectv-model"form.productId"label"涉及产品&q…...
DeepSeek行业应用实践报告-智灵动力【112页PPT全】
DeepSeek(深度搜索)近期引发广泛关注并成为众多企业/开发者争相接入的现象,主要源于其在技术突破、市场需求适配性及生态建设等方面的综合优势。以下是关键原因分析: 一、技术核心优势 开源与低成本 DeepSeek基于开源架构…...
【Markdown 语法简洁讲解】
Markdown 语法简洁语法讲解 什么是 Markdown1. 标题2. 列表3.文本样式4. 链接与图片5. 代码6. 表格7. 分割线8. 流程图9. 数学公式10. 快捷键11. 字体、字号与颜色 什么是 Markdown Markdown 是一种轻量级标记语言,通过简单的符号实现排版格式化,专注于…...
250301-OpenWebUI配置DeepSeek-火山方舟+硅基流动+联网搜索+推理显示
A. 最终效果 B. 火山方舟配置(一定要点击添加) C. 硅基流动配置(最好要点击添加,否则会自动弹出所有模型) D. 联网搜索配置 E. 推理过程显示 默认是没有下面的推理过程的显示的 设置步骤: 在Functions函…...
【3天快速入门WPF】12-MVVM
目录 1. 什么是MVVM2. 实现简单MVVM2.1. Part 12.2. Part 21. 什么是MVVM MVVM 是 Model-View-ViewModel 的缩写,是一种用于构建用户界面的设计模式,是一种简化用户界面的事件驱动编程方式。 MVVM 的目标是实现用户界面和业务逻辑之间的彻底分离,以便更好地管理和维护应用…...
查找Excel包含关键字的行(の几种简单快速方法)
需求:数据在后缀为xlsx的Excel的sheet1中且量比较大,比如几十万行几百列;想查找一个关键字所在的行,比如"全网首发"; 情况①知道关键字在哪一列 情况②不确定在哪一列,很多列相似又不同,本文演…...
性能测试分析和调优
步骤 性能调优的步骤 性能调优的步骤: 1.确定问题:根据性能测试的结果来分析确定bug。–测试人员职责 2.分析原因:分析问题产生的原因。----开发人员职责 3.给出解决方案:可以是修改软件配置、增加硬件资源配置、修改代码等----…...
(视频教程)Compass代谢分析详细流程及python版-R语言版下游分析和可视化
不想做太多的前情解说了,有点累了,做了很久的内容,包括整个分析,从软件安装和报错解决到后期下游python版-R语言版下游分析和可视化!单细胞代谢分析我们写过很多了,唯独少了最“高级”的compass,…...
【SQL】MySQL中的字符串处理函数:concat 函数拼接字符串,COALESCE函数处理NULL字符串
MySQL中的字符串处理函数:concat 函数 一、concat ()函数 1.1、基本语法1.2、示例1.3、特殊用途 二、COALESCE()函数 2.1、基本语法2.2、示例2.3、用途 三、进阶练习 3.1 条件和 SQL 语句3.2、解释 一、concat &…...
c++中深拷贝和浅拷贝的联系和区别
在 C 编程里,深拷贝和浅拷贝是两种不同的对象复制方式,它们在实现方式、资源管理和适用场景等方面存在显著差异。下面为你详细介绍它们的区别。 1. 基本概念 浅拷贝:浅拷贝仅仅复制对象的成员变量值。对于基本数据类型(如 int、d…...
Autotestplat 在多个平台和公司推荐使用!
1、 51Testing软件测试网 开源好用!推荐一款更轻量化的自动化测试平台! 2、程序员杨叔 从繁琐到简单!Autotestplat自动化测试平台搭建使用 3、一飞开源 [开源]一站式自动化测试平台及解决方案,支持接口、性能、UI测试 4、github h…...
字符串最后一个单词的长度
一:题目 二:思路 用rfind()函数倒着找第一个空格,返回的值为pos,然后打印size()-(pos1),posnpos就代表只有一个单词,则直接返回size #include <iostream> using namespace std; int main() {strin…...
【Linux】learning notes(3)make、copy、move、remove
文章目录 1、mkdir (make directory)2、rmdir (remove directory)3、rm(remove)4、>5、touch 新建文件6、mv(move)7、cp(copy) 1、mkdir (make…...
一、图像图像的基本概念
文章目录 一、分辨率概念二、图形图像的区别三、位图和矢量图的区别 一、分辨率概念 图形显示计数中的分辨率概念有三种,即屏幕分辨率、显示分辨率和显卡分辨率。它们既有区别又有着密切的联系,对图形显示的处理有极大的影响。 1.屏幕分辨率 显示器分辨…...
LaTeX论文排版集成:自动调用万象熔炉·丹青幻境生成论文插图
LaTeX论文排版集成:自动调用万象熔炉丹青幻境生成论文插图 写论文最头疼的是什么?对我而言,除了反复修改的正文,就是那些永远也画不完的插图。尤其是写综述或者理论性强的文章,需要大量的概念图、流程图、示意图来辅助…...
LY68L6400 SRAM的QSPI驱动优化:RT-Thread在STM32H743上的性能调优指南
LY68L6400 SRAM的QSPI驱动优化:RT-Thread在STM32H743上的性能调优指南 在嵌入式系统开发中,外部SRAM常被用作高速缓存或扩展内存,而QSPI接口因其高带宽特性成为连接SRAM的理想选择。LY68L6400作为一款64Mb的QSPI SRAM,在STM32H743…...
鸿蒙音频开发避坑指南:用AVPlayer实现音乐App的熄屏播放,这3个权限和配置项别忘了
鸿蒙音频开发实战:熄屏播放的三大核心配置与避坑策略 在移动应用生态中,音频播放功能始终占据重要地位——无论是音乐流媒体、播客平台还是语音社交应用,流畅的后台播放体验都是用户留存的关键指标。鸿蒙系统通过AVPlayer与Media Kit为开发者…...
OpenClaw对接千问3.5-27B实战:本地部署与接口调用完整指南
OpenClaw对接千问3.5-27B实战:本地部署与接口调用完整指南 1. 为什么选择OpenClaw千问3.5-27B组合? 去年我在尝试自动化办公流程时,发现市面上的RPA工具要么功能臃肿,要么无法灵活调用本地AI模型。直到遇到OpenClaw这个开源框架…...
Unity3D实战:从零构建竖屏飞机大战游戏
1. 竖屏游戏的基础设置 第一次打开Unity时,默认是横屏模式。我们需要做的第一件事就是把游戏改成竖屏。这个操作看似简单,但很多新手容易忽略几个关键点。在Game窗口右上角找到分辨率设置,点击加号新建一个预设。这里要特别注意选择"Asp…...
网盘直链下载助手:一键解锁8大平台高速下载通道
网盘直链下载助手:一键解锁8大平台高速下载通道 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云盘 /…...
现代化前端架构设计的10个黄金原则:从Este项目学习最佳实践
现代化前端架构设计的10个黄金原则:从Este项目学习最佳实践 【免费下载链接】este This repo is suspended. 项目地址: https://gitcode.com/gh_mirrors/es/este 在当今快速发展的前端开发领域,构建可维护、可扩展且高效的应用程序架构至关重要。…...
LLMLingua未来展望:AI推理加速技术的终极发展趋势
LLMLingua未来展望:AI推理加速技术的终极发展趋势 【免费下载链接】LLMLingua [EMNLP23, ACL24] To speed up LLMs inference and enhance LLMs perceive of key information, compress the prompt and KV-Cache, which achieves up to 20x compression with minima…...
020、深度学习入门:神经网络基础与反向传播
昨天调一个三层的全连接网络,loss死活不降。打印梯度发现第一层的权重全是零——反向传播根本没传过去。同事凑过来看了一眼:“你激活函数梯度写错了吧?”一查代码,果然在tanh求导的地方少了个平方。这种低级错误让我想起刚入门时…...
【源-荷-储协同互动】考虑源-荷-储协同互动的主动配电网优化调度研究附Matlab代码
✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。👇 关注我领取海量matlab电子书和数学建模资料🍊个人信条:格物致知,完整Matl…...
