手撕B-树
一、概述
1.历史
B树(B-Tree)结构是一种高效存储和查询数据的方法,它的历史可以追溯到1970年代早期。B树的发明人Rudolf Bayer和Edward M. McCreight分别发表了一篇论文介绍了B树。这篇论文是1972年发表于《ACM Transactions on Database Systems》中的,题目为"Organization and Maintenance of Large Ordered Indexes"。
这篇论文提出了一种能够高效地维护大型有序索引的方法,这种方法的主要思想是将每个节点扩展成多个子节点,以减少查找所需的次数。B树结构非常适合应用于磁盘等大型存储器的高效操作,被广泛应用于关系数据库和文件系统中。
B树结构有很多变种和升级版,例如B+树,B*树和SB树等。这些变种和升级版本都基于B树的核心思想,通过调整B树的参数和结构,提高了B树在不同场景下的性能表现。
总的来说,B树结构是一个非常重要的数据结构,为高效存储和查询大量数据提供了可靠的方法。它的历史可以追溯到上个世纪70年代,而且在今天仍然被广泛应用于各种场景。
2.B-树的优势
B树和AVL树、红黑树相比,B树更适合磁盘的增删改查,而AVL和红黑树更适合内存的增删改查。
假设存储100万的数据:
- 使用AVL来存储,树高为: l o g 2 1000000 ≈ 20 log_21000000≈20 log21000000≈20 (20次的磁盘IO很慢,但是20次的内存操作很快)
- 使用B-树存储,最小度数为500,树高为:3
B树优势:
- 磁盘存储比内存存储慢很多,尤其是访问磁盘的延迟相对较高。每次访问磁盘都需要消耗更多的时间,而B树的设计可以最大化地减少对磁盘的访问次数。
- 磁盘访问一般是按块读取的,而B树的节点通常设计为与磁盘块大小一致。由于B树是多路的,单次磁盘访问通常会加载多个数据项,而不是像AVL树和红黑树那样每次只读取一个节点。
- 在磁盘中存储B树时,操作系统通常会将树的部分结构加载到内存中以便快速查询,避免了频繁的磁盘访问。
- 在数据库和文件系统中,数据通常是大规模的,存储在外部存储介质上。B树特别适合大规模数据的增删改查,因为它减少了不必要的磁盘访问,能够高效地执行复杂的数据操作。
二、特性
1.度和阶
- 度(degree):节点的孩子数
- 阶(order):所有节点孩子最大值
2.特性
-
每个节点具有
- 属性 n,表示节点中 key 的个数
- 属性 leaf,表示节点是否是叶子节点
- 节点 key 可以有多个,以升序存储
-
每个非叶子节点中的孩子数是 n + 1、叶子节点没有孩子
-
最小度数t(节点的孩子数称为度)和节点中键数量的关系如下:
| 最小度数t | 键数量范围 |
|---|---|
| 2 | 1 ~ 3 |
| 3 | 2 ~ 5 |
| 4 | 3 ~ 7 |
| … | … |
| n | (n-1) ~ (2n-1) |
其中,当节点中键数量达到其最大值时,即 3、5、7 … 2n-1,需要分裂
- 叶子节点的深度都相同
三、实现
1.定义节点类
static class Node {// 关键字int[] keys;// 关键字数量int keyNum;// 孩子节点Node[] children;// 是否是叶子节点boolean leafFlag = true;// 最小度数:最少孩子数(决定树的高度,度数越大,高度越小)int t;// ≥2public Node(int t) {this.t = t;// 最多的孩子数(约定)this.children = new Node[2 * t];this.keys = new int[2 * t -1];}
}
1.1 节点类相关方法
查找key:查找目标22,在当前节点的关键字数组中依次查找,找到了返回;没找到则从孩子节点找:
- 当前节点是叶子节点:目标不存在
- 非叶子结点:当key循环到25,大于目标22,此时从索引4对应的孩子key数组中继续查找,依次递归,直到找到为止。

根据key获取节点
/*** 根据key获取节点* @param key* @return*/
Node get(int key) {// 先从当前key数组中找int i = 0;while (i < keyNum) {if (keys[i] == key) {// 在当前的keys关键字数组中找到了return this;}if (keys[i] > key) {// 当数组比当前key大还未找到时,退出循环break;}i++;}// 如果是叶子节点,没有孩子了,说明key不存在if (leafFlag) {return null;} else {// 非叶子节点,退出时i的值就是对应范围的孩子节点数组的索引,从对应的这个孩子数组中继续找return children[i].get(key);}
}
向指定索引插入key
/*** 向keys数组中指定的索引位置插入key* @param key* @param index*/
void insertKey(int key,int index) {/*** [0,1,2,3]* src:源数组* srcPos:起始索引* dest:目标数组* destPos: 目标索引* length:拷贝的长度*/System.arraycopy(keys, index, keys, index + 1, keyNum - index);keys[index] = key;keyNum++;
}
向指定索引插入child
/*** 向children指定索引插入child** @param child* @param index*/
void insertChild(Node child, int index) {System.arraycopy(children, index, children, index + 1, keyNum - index);children[index] = child;
}
2.定义树
public class BTree {// 根节点private Node root;// 树中节点最小度数int t;// 最小key数量 在创建树的时候就指定好final int MIN_KEY_NUM;// 最大key数量final int MAX_KEY_NUM;public BTree() {// 默认度数设置为2this(2);}public BTree(int t) {this.t = t;root = new Node(t);MIN_KEY_NUM = t - 1;MAX_KEY_NUM = 2 * t - 1;}
}
判断key在树中是否存在
/*** 判断key在树中是否存在* @param key* @return*/
public boolean contains(int key) {return root.get(key) != null;
}
3.新增key:
- 1.查找插入位置:从根节点开始,沿着树向下查找,直到找到一个叶子节点,这个叶子节点包含的键值范围覆盖了要插入的键值。
- 2.插入键值:在找到的叶子节点中插入新的键值。如果叶子节点中的键值数量没有超过B树的阶数(即每个节点最多可以包含的键值数量),则插入操作完成。
- 3.分裂节点:如果叶子节点中的键值数量超过了B树的阶数,那么这个节点需要分裂。
如果度为3,最大key数量为:2*3-1=5,当插入了8后,此时达到了最大数量5,需要分裂:

分裂逻辑:
分裂节点数据一分为三:
- 左侧数据:本身左侧的数据留在该节点
- 中间数据:中间索引2(度-1)的数据6移动到父节点的索引1(被分裂节点的索引)处
- 右侧数据:从索引3(度)开始的数据,移动到新节点,新节点的索引值为分裂节点的index+1
如果分裂的节点是非叶子节点:
需要多一步操作:右侧数据需要和孩子一起连带到新节点去:

分裂的是根节点:
需要再创建多一个节点来当做根节点,此根节点为父亲,存入中间的数据。
其他步骤同上。

分裂方法:
/*** 节点分裂* 左侧数据:本身左侧的数据留在该节点* 中间数据:中间索引2(度-1)的数据6移动到父节点的索引1(被分裂节点的索引)处* 右侧数据:从索引3(度)开始的数据,移动到新节点,新节点的索引值为分裂节点的index+1* @param node 要分裂的节点* @param index 分裂节点的索引* @param parent 要分裂节点的父节点**/
public void split(Node node, int index, Node parent) {// 没有父节点,当前node为根节点if (parent == null) {// 创建出新的根来存储中间数据Node newRoot = new Node(t);newRoot.leafFlag = false;newRoot.insertChild(node, 0);// 更新根节点为新创建的newRootthis.root = newRoot;parent = newRoot;}// 1.处理右侧数据:创建新节点存储右侧数据Node newNode = new Node(t);// 新创建的节点跟原本分裂节点同级newNode.leafFlag = node.leafFlag;// 新创建节点的数据从 原本节点【度】位置索引开始拷贝 拷贝长度:t-1System.arraycopy(node.keys, t, newNode.keys, 0, t - 1);// 如果node不是叶子节点,还需要把node的一部分孩子也同时拷贝到新节点的孩子中if (!node.leafFlag) {System.arraycopy(node.children, t, newNode.children, 0, t);}// 更新新节点的keyNumnewNode.keyNum = t - 1;// 更新原本节点的keyNumnode.keyNum = t - 1;// 2.处理中间数据:【度-1】索引处的数据 移动到父节点【分裂节点的索引】索引处// 要插入父节点的数据:int midKey = node.keys[t - 1];parent.insertKey(midKey, index);// 3. 新创建的节点作为父亲的孩子parent.insertChild(newNode, index + 1);// parent的keyNum在对应的方法中已经更新了
}
新增key:
/*** 新增key** @param key*/
public void put(int key) {doPut(root, key, 0, null);
}/*** 执行新增key* 1.查找插入位置:从根节点开始,沿着树向下查找,直到找到一个叶子节点,这个叶子节点包含的键值范围覆盖了要插入的键值。* 2.插入键值:在找到的叶子节点中插入新的键值。如果叶子节点中的键值数量没有超过B树的阶数(即每个节点最多可以包含的键值数量),则插入操作完成。* 3.分裂节点:如果叶子节点中的键值数量超过了B树的阶数,那么这个节点需要分裂。* @param node 待插入元素的节点* @param key 插入的key* @param nodeIndex 待插入元素节点的索引* @param nodeParent 待插入节点的父节点*/
public void doPut(Node node, int key, int nodeIndex, Node nodeParent) {// 查找插入位置int index = 0;while (index < node.keyNum) {if (node.keys[index] == key ) {// 找到了 做更新操作 (因为没有维护value,所以就不用处理了)return;}if (node.keys[index] > key) {// 没找到该key, 退出循环,index的值就是要插入的位置break;}index++;}// 如果是叶子节点,直接插入if (node.leafFlag) {node.insertKey(key, index);} else {// 非叶子节点,继续从孩子中找到插入位置 父亲的这个待插入的index正好就是元素要插入的第x个孩子的位置doPut(node.children[index], key , index, node);}// 处理节点分裂逻辑 : keyNum数量达到上限,节点分裂if (node.keyNum == MAX_KEY_NUM) {split(node, nodeIndex, nodeParent);}
}
4.删除key
情况一:删除的是叶子节点的key
节点是叶子节点,找到了直接删除,没找到返回。
情况二:删除的是非叶子节点的key
没有找到key,继续在孩子中找。
找到了,把要删除的key和替换为后继key,删掉后继key。
平衡树:该key被删除后,key数目<key下限(t-1),树不平衡,需要调整
- 如果左边兄弟节点的key是富裕的,可以直接找他借:右旋,把父亲一个节点的旋转下来(在父亲中找到失衡节点的前驱节点),把兄弟的一个节点旋转上去(旋转上去的是兄弟中最大的key)。

- 如果右边兄弟节点的key是富裕的,可以直接找他借:左旋,把父亲的旋转下来,把兄弟的旋转上去。

- 当没有兄弟是富裕时,没办法借,采用向左合并:父亲和失衡节点都合并到左侧的节点中。

右旋详细流程:

处理孩子:

向左合并详细流程:

根节点调整的情况:

调整平衡代码:
/*** 树的平衡* @param node 失衡节点* @param index 失衡节点索引* @param parent 失衡节点父节点*/
public void balance(Node node, int index, Node parent) {if (node == root) {// 如果是根节点 当调整到根节点只剩下一个key时,要替换根节点 (根节点不能为null,要保证右孩子才替换)if (root.keyNum == 0 && root.children[0] != null) {root = root.children[0];}return;}// 拿到该节点的左右兄弟,判断节点是不是富裕的,如果富裕,则找兄弟借Node leftBrother = parent.childLeftBrother(index);Node rightBrother = parent.childRightBrother(index);// 左边的兄弟富裕:右旋if (leftBrother != null && leftBrother.keyNum > MIN_KEY_NUM) {// 1.要旋转下来的key:父节点中【失衡节点索引-1】的key:parent.keys[index-1];插入到失衡节点索引0位置// (这里父亲节点旋转走的不用删除,因为等会左侧的兄弟旋转上来会覆盖掉)node.insertKey(parent.keys[index - 1], 0);// 2.0 如果左侧节点不是叶子节点,有孩子,当旋转一个时,只需要留下原本孩子数-1 ,把最大的孩子过继给失衡节点的最小索引处(先处理后事)if (!leftBrother.leafFlag) {node.insertChild(leftBrother.removeRightMostChild(), 0);}// 2.1 要旋转上去的key:左侧兄弟最大的索引key,删除掉,插入到父节点中【失衡节点索引-1】位置(此位置就是刚才在父节点旋转走的key的位置)// 这里要直接覆盖,不能调插入方法,因为这个是当初旋转下去的key。parent.keys[index - 1] = leftBrother.removeRightMostKey();return;}// 右边的兄弟富裕:左旋if (rightBrother != null && rightBrother.keyNum > MIN_KEY_NUM) {// 1.要旋转下来的key:父节点中【失衡节点索引】的key:parent.keys[index];插入到失衡节点索引最大位置keyNum位置// (这里父亲节点旋转走的不用删除,因为等会右侧的兄弟旋转上来会覆盖掉)node.insertKey(parent.keys[index], node.keyNum);// 2.0 如果右侧节点不是叶子节点,有孩子,当旋转一个时,只需要留下原本孩子数-1 ,把最小的孩子过继给失衡节点的最大索引处(孩子节点的索引比父亲要多1)if (!rightBrother.leafFlag) {node.insertChild(rightBrother.removeLeftMostChild(), node.keyNum + 1);}// 2.1 要旋转上去的key:右侧兄弟最小的索引key,删除掉,插入到父节点中【失衡节点索引-1】位置(此位置就是刚才在父节点旋转走的key的位置)// 这里要直接覆盖,不能调插入方法,因为这个是当初旋转下去的key。parent.keys[index] = rightBrother.removeLeftMostKey();return;}// 左右兄弟都不够,往左合并if (leftBrother != null) {// 向左兄弟合并// 1.把失衡节点从父亲中移除parent.removeChild(index);// 2.插入父节点的key到左兄弟 将父节点中【失衡节点索引-1】的key移动到左侧leftBrother.insertKey(parent.removeKey(index - 1), leftBrother.keyNum);// 3.插入失衡节点的key及其孩子到左兄弟node.moveToTarget(leftBrother);} else {// 右兄弟向自己合并// 1.把右兄弟从父亲中移除parent.removeChild(index + 1);// 2.把父亲的【失衡节点索引】 处的key移动到自己这里node.insertKey(parent.removeKey(index), node.keyNum);// 3.把右兄弟完整移动到自己这里rightBrother.moveToTarget(node);}
}
删除key:
/*** 删除指定key* @param node 查找待删除key的起点* @param parent 待删除key的父亲* @param nodeIndex 待删除的key的索引* @param key 待删除的key*/
public void doRemove(Node node, Node parent, int nodeIndex, int key) {// 找到被删除的keyint index = 0;// 循环查找待删除的keywhile (index < node.keyNum) {if (node.keys[index] >= key) {//找到了或者没找到break;}index++;}// 如果找到了 index就是要删除的key索引;// 如果没找到,index就是要在children的index索引位置继续找// 一、是叶子节点if (node.leafFlag) {// 1.1 没找到if (!found(node, key, index)) {return;}// 1.2 找到了else {// 删除当前节点index处的keynode.removeKey(index);}}// 二、不是叶子节点else {// 1.1 没找到if (!found(node, key, index)) {// 继续在孩子中找 查找的孩子的索引就是当前indexdoRemove(node.children[index], node, index, key);}// 1.2 找到了else {// 找到后继节点,把后继节点复制给当前的key,然后删除后继节点。// 在索引+1的孩子里开始,一直往左找,直到节点是叶子节点为止,就找到了后继节点Node deletedSuccessor = node.children[index + 1];while (!deletedSuccessor.leafFlag) {// 更新为最左侧的孩子deletedSuccessor = deletedSuccessor.children[0];}// 1.2.1 当找到叶子节点之后,最左侧的key就是后继keyint deletedSuccessorKey = deletedSuccessor.keys[0];// 1.2.2 把后继key赋值给待删除的keynode.keys[index] = deletedSuccessorKey;// 1.2.3 删除后继key 再调用该方法,走到情况一,删除掉该后继key: 起点为索引+1的孩子处,删除掉后继keydoRemove(node.children[index + 1], node, index + 1, deletedSuccessorKey);}}// 树的平衡:if (node.keyNum < MIN_KEY_NUM) {balance(node, nodeIndex, parent);}
}
节点相关方法:
/*** 移除指定索引处的key* @param index* @return*/int removeKey(int index) {int deleted = keys[index];System.arraycopy(keys, index + 1, keys, index, --keyNum - index);return deleted;}/*** 移除最左索引处的key* @return*/int removeLeftMostKey(){return removeKey(0);}/*** 移除最右边索引处的key* @return*/int removeRightMostKey() {return removeKey(keyNum - 1);}/*** 移除指定索引处的child* @param index* @return*/Node removeChild(int index) {Node deleted = children[index];System.arraycopy(children, index + 1, children, index, keyNum - index);children[keyNum] = null;return deleted;}/*** 移除最左边的child* @return*/Node removeLeftMostChild() {return removeChild(0);}/*** 移除最右边的child* @return*/Node removeRightMostChild() {return removeChild(keyNum);}/*** 获取指定children处左边的兄弟* @param index* @return*/Node childLeftBrother(int index) {return index > 0 ? children[index - 1] : null;}/*** 获取指定children处右边的兄弟* @param index* @return*/Node childRightBrother(int index) {return index == keyNum ? null : children[index + 1];}/*** 复制当前节点到目标节点(key和child)* @param target*/void moveToTarget(Node target) {int start = target.keyNum;// 当前节点不是叶子节点 说明有孩子if (!leafFlag) {// 复制当前节点的孩子到目标节点的孩子中for (int i = 0; i <= keyNum; i++) {target.children[start + i] = children[i];}}// 复制key到目标节点的keys中for (int i = 0; i < keyNum; i++) {target.keys[target.keyNum++] = keys[i];}}
相关文章:
手撕B-树
一、概述 1.历史 B树(B-Tree)结构是一种高效存储和查询数据的方法,它的历史可以追溯到1970年代早期。B树的发明人Rudolf Bayer和Edward M. McCreight分别发表了一篇论文介绍了B树。这篇论文是1972年发表于《ACM Transactions on Database S…...
SQL 指南
SQL 指南 引言 SQL(Structured Query Language,结构化查询语言)是一种用于管理关系数据库系统的标准计算机语言。自1970年代问世以来,SQL已经成为了数据库管理和数据操作的事实标准。本文旨在为初学者和有经验的数据库用户提供一个全面的SQL指南,涵盖SQL的基础知识、高级…...
一文简单回顾复习Java基础概念
还是和往常一样,我以提问的方式回顾复习,今天回顾下Java小白入门应该知道的一些基础知识 Java语言有哪些特点呢? Java语言的特点有: 面向对象,主要是封装、继承、多态;平台无关性,“一次编写…...
Git上传了秘钥如何彻底修改包括历史记录【从安装到实战详细版】
使用 BFG Repo-Cleaner 清除 Git 仓库中的敏感信息 1. 背景介绍 在使用 Git 进行版本控制时,有时会不小心将敏感信息(如 API 密钥、密码等)提交到仓库中。即使后续删除,这些信息仍然存在于 Git 的历史记录中。本文将介绍如何使用…...
GCC之编译(8)AR打包命令
GCC之(8)AR二进制打包命令 Author: Once Day Date: 2025年1月23日 一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦… 漫漫长路,有人对你微笑过嘛… 全系列文章请查看专栏: Linux实践记录_Once-Day的博客-C…...
Linux二进制部署K8s集群的平滑升级教程
一、升级前的准备工作 备份集群配置和数据 备份/etc/kubernetes/目录,其中包含Kubernetes集群的配置文件。 备份/var/lib/etcd/目录,其中存储了etcd数据库的数据。 使用etcdctl工具备份etcd数据: bash复制 ETCDCTL_API3 etcdctl snapshot s…...
2.1.3 第一个工程,点灯!
新建工程 点击菜单栏左上角,新建工程或者选择“文件”-“新建工程”,选择工程类型“标准工程”选择设备类型和编程语言,并指定工程文件名及保存路径,如下图所示: 选择工程类型为“标准工程” 选择主模块机型&#x…...
图像处理算法研究的程序框架
目录 1 程序框架简介 2 C#图像读取、显示、保存模块 3 C动态库图像算法模块 4 C#调用C动态库 5 演示Demo 5.1 开发环境 5.2 功能介绍 5.3 下载地址 参考 1 程序框架简介 一个图像处理算法研究的常用程序逻辑框架,如下图所示 在该框架中,将图像处…...
计算机工程:解锁未来科技之门!
计算机工程与应用是一个充满无限可能性的领域。随着科技的迅猛发展,计算机技术已经深深渗透到我们生活的方方面面,从医疗、金融到教育,无一不在彰显着计算机工程的巨大魅力和潜力。 在医疗行业,计算机技术的应用尤为突出。比如&a…...
Linux初识——基本指令(2)
本文将继续从上篇末尾讲起,讲解我们剩下的基本指令 一、剩余的基本指令 1、mv mv指令是move(移动)的缩写,其功能为:1.剪切文件、目录。2.重命名 先演示下重命名,假设我想把当前目录下的di34改成dir5 那…...
单片机-STM32 WIFI模块--ESP8266 (十二)
1.WIFI模块--ESP8266 名字由来: Wi-Fi这个术语被人们普遍误以为是指无线保真(Wireless Fidelity),并且即便是Wi-Fi联盟本身也经常在新闻稿和文件中使用“Wireless Fidelity”这个词,Wi-Fi还出现在ITAA的一个论文中。…...
[C++技能提升]类注册
最近在做AI信息在各个平台流转的框架设计,想要设计一种可以灵活扩展、不改变原有代码的框架,了解到了类注册。 具体需求是这样的:AI算法在客户本地电脑和云端都有部署,原先AI在这两个平台下的输出格式并不统一,且每个…...
OpenHarmony 5.0.2 Release来了!
版本概述 OpenHarmony 5.0.2 Release版本对标准系统的能力进行持续完善,以快速迭代的方式推出API 14,相比5.0.1 Release版本,重点做出了如下特性新增或增强: 进一步增强ArkUI、图形图像的能力,提供更多组件的高级属性…...
80,【4】BUUCTF WEB [SUCTF 2018]MultiSQL
53,【3】BUUCTF WEB october 2019 Twice SQLinjection-CSDN博客 上面这个链接是我第一次接触二次注入 这道题也涉及了 对二次注入不熟悉的可以看看 BUUCTF出了点问题,打不开,以下面这两篇wp作为学习对象 [SUCTF 2018]MultiSQL-CSDN博客 …...
NR_shell运行流程简析
nr_shell 是一套开源 shell 框架,基于框架可创建终端交互功能。 为了记录终端输入指令,以及进行解析处理,nr_shell 提供了一套 cmd 结构体,具体如下:typedef struct static_cmd_function_struct {char cmd[NR_SHELL_CM…...
Prometheus部署及linux、mysql、monog、redis、RocketMQ、java_jvm监控配置
Prometheus部署及linux、mysql、monog、redis、RocketMQ、java_jvm监控配置 1.Prometheus部署1.2.Prometheus修改默认端口 2.grafana可视化页面部署3.alertmanager部署4.监控配置4.1.主机监控node-exporter4.2.监控mysql数据库mysqld_exporter4.3.监控mongod数据库mongodb_expo…...
问题排查 - TC397 CORE2 50MS/100MS任务不运行
1、问题描述 CORE2 的任务运行次数的计数值OsTask_100ms_Core2 - task_cnt[12]、OsTask_50ms_Core2 - task_cnt[16]不在累加,但是其他任务OsAlarm_1ms_Core2、OsAlarm_5ms_Core2、OsAlarm_10ms_Core2、OsAlarm_20ms_Core2 任务计数值累加正常。 如果是任务栈溢出&a…...
Spring FatJar写文件到RCE分析
背景 现在生产环境部署 spring boot 项目一般都是将其打包成一个 FatJar,即把所有依赖的第三方 jar 也打包进自身的 app.jar 中,最后以 java -jar app.jar 形式来运行整个项目。 运行时项目的 classpath 包括 app.jar 中的 BOOT-INF/classes 目录和 BO…...
百度APP iOS端磁盘优化实践(上)
01 概览 在APP的开发中,磁盘管理已成为不可忽视的部分。随着功能的复杂化和数据量的快速增长,如何高效管理磁盘空间直接关系到用户体验和APP性能。本文将结合磁盘管理的实践经验,详细介绍iOS沙盒环境下的文件存储规范,探讨业务缓…...
蓝桥杯之c++入门(一)【第一个c++程序】
目录 前言一、第⼀个C程序1.1 基础程序1.2 main函数1.3 字符串1.4 头文件1.5 cin 和 cout 初识1.6 名字空间1.7 注释 二、四道简单习题(点击跳转链接)练习1:Hello,World!练习2:打印飞机练习3:第⼆个整数练习4ÿ…...
14-6-1C++STL的list
(一)list容器的基本概念 list容器简介: 1.list是一个双向链表容器,可高效地进行插入删除元素 2.list不可以随机存取元素,所以不支持at.(pos)函数与[ ]操作符 (二)list容器头部和尾部的操作 list对象的默…...
【AI论文】Sigma:对查询、键和值进行差分缩放,以实现高效语言模型
摘要:我们推出了Sigma,这是一个专为系统领域设计的高效大型语言模型,其独特之处在于采用了包括DiffQKV注意力机制在内的新型架构,并在我们精心收集的系统领域数据上进行了预训练。DiffQKV注意力机制通过根据查询(Q&…...
InceptionV1_V2
目录 不同大小的感受野去提取特征 经典 Inception 网络的设计思路与运行流程 背景任务:图像分类(以 CIFAR-10 数据集为例) Inception 网络的设计思路 Inception 网络的运行流程 打个比方 多个损失函数的理解 1. 为什么需要多个损失函数&#…...
ORB-SLAM2源码学习:Initializer.cc⑧: Initializer::CheckRT检验三角化结果
前言 ORB-SLAM2源码学习:Initializer.cc⑦: Initializer::Triangulate特征点对的三角化_cv::svd::compute-CSDN博客 经过上面的三角化我们成功得到了三维点,但是经过三角化成功的三维点并不一定是有效的,需要筛选才能作为初始化地图点。 …...
【ArcGIS微课1000例】0141:提取多波段影像中的单个波段
文章目录 一、波段提取函数二、加载单波段导出问题描述:如下图所示,img格式的时序NDVI数据有24个波段。现在需要提取某一个波段,该怎样操作? 一、波段提取函数 首先加载多波段数据。点击【窗口】→【影像分析】。 选择需要处理的多波段影像,点击下方的【添加函数】。 在多…...
【测试人生】变更风险观测的流程逻辑设计
在线上服务变更过程中,我们希望可以通过一套实时观测机制去监测线上服务的风险,从而能够确保线上稳定性,在出问题是可以及时回滚变更。今天这篇文章,就简单讲一下变更风险观测的流程逻辑需要怎么设计。 首先需要明确变更观测的相…...
一文大白话讲清楚webpack基本使用——17——Tree Shaking
文章目录 一文大白话讲清楚webpack基本使用——17——Tree Shaking1. 建议按文章顺序从头看,一看到底,豁然开朗2. 啥叫Tree Shaking3. 什么是死代码,怎么来的3. Tree Shaking的流程3.1 标记3.2 利用Terser摇起来 4. 具体使用方式4.1 适用前提…...
ChatGPT从数据分析到内容写作建议相关的46个提示词分享!
在当今快节奏的学术环境中,研究人员面临着海量的信息和复杂的研究任务。幸运的是,随着人工智能技术的发展,像ChatGPT这样的先进工具为科研人员提供了强大的支持。今天就让我们一起探索如何利用ChatGPT提升研究效率进一步优化研究流程。 ChatG…...
PyCharm配置Python环境
1、打开PyCharm项目 可以从File-->Open-->选择你的项目路径-->OK,或者直接点击Open,找到项目路径-->OK,如图所示(点击Ok后可能有下面的弹窗,选择“Trust Project”即可,然后选择“New Window”打开项目) …...
c#配置config文件
1,引用命名空间 Configuration 及配置信息...
