数据结构和算法(10):B-树
B-树:大数据
现代电子计算机发展速度空前,就存储能力而言,情况似乎也是如此:如今容量以TB计的硬盘也不过数百元,内存的常规容量也已达到GB量级。
然而从实际应用的需求来看,问题规模的膨胀却远远快于存储能力的增长。
在同等成本下,存储器的容量越大(小)则访问速度越慢(快)。
实践证明,分级存储是行之有效的方法。在由内存与外存(磁盘)组成的二级存储系统中,数据全集往往存放于外存中,计算过程中则可将内存作为外存的高速缓存,存放最常用数据项的复本。借助高效的调度算法,如此便可将内存的“高速度”与外存的“大容量”结合起来。
两个相邻存储级别之间的数据传输,统称I/O操作。(各级存储器的访问速度相差悬殊,故应尽可能地减少I/O操作)
以内存与磁盘为例,其单次访问延迟大致分别在纳秒(ns)和毫秒(ms)级别,相差5至6个数量级。对内存而言的一秒/一天,相当于磁盘的一星期/两千年。
多路搜索树(二叉搜索树与四路搜索树):将通常的二叉搜索树,改造为多路搜索树——在中序遍历的意义下,这也是一种等价变换。
以两层为间隔,将各节点与其左、右孩子合并为“大节点”:原节点及其孩子的共三个关键码予以保留;孩子节点原有的四个分支也予以保留并按中序遍历次序排列;节点到左、右孩子的分支转化为“大节点”内部的搜索,在图中表示为水平分支。如此改造之后,每个“大节点”拥有四个分支,故称作四路搜索树。
B-树:结构
所谓 m m m 阶 B-树,即 m m m 路平衡搜索树( m ≥ 2 m \geq 2 m≥2)
所有外部节点的深度统一相等。同时,每个内部节点都存有不超过m - 1
个关键码,以及用以指示对应分支的不超过 m
个引用。
具体地,存有 n ≤ m − 1 n \leq m - 1 n≤m−1 个关键码: K 1 < K 2 < K 3 < K 4 < . . . < K n K_1 < K_2 <K_3 < K_4 < ... < K_n K1<K2<K3<K4<...<Kn 的内部节点,同时还配有 n + 1 ≤ m n+1 \leq m n+1≤m 个引用: A 0 < A 1 < A 2 < A 3 < A 4 < . . . < A n A_0 < A_1 <A_2<A_3 < A_4 <...<A_n A0<A1<A2<A3<A4<...<An。
反过来,各内部节点的分支数也不能太少。具体地,除根以外的所有内部节点,都应满足: n + 1 ≥ [ m / 2 ] n+1 \geq [m /2] n+1≥[m/2],而在非空的 B-树中,根节点应满足: n + 1 ≥ 2 n+1 \geq 2 n+1≥2。
由于各节点的分支数介于 [ m / 2 ] [m/2] [m/2] 至 m m m 之间,故 m m m 阶B-树也称作 ( m / 2 m/2 m/2, m)-树。
所有叶节点的深度统一相等。
计算B-树高度时,还需要计入其最底层的外部节点:树高 h = 外部节点的深度
实现
B-树节点
#include "../vector/vector.h"
#define BTNodePosi(T) BTNode<T>* //B-树节点位置template <typename T> struct BTNode { //B-树节点模板类
// 成员(为简化描述起见统一开放,读者可根据需要迕一步封装)BTNodePosi(T) parent; //父节点Vector<T> key; //关键码向量Vector<BTNodePosi(T)> child; //孩子向量(其长度总比key多一)
// 构造函数(注意:BTNode叧能作为根节点创建,而且初始时有0个关键码和1个空孩子指针)BTNode() { parent = NULL; child.insert ( 0, NULL ); }BTNode ( T e, BTNodePosi(T) lc = NULL, BTNodePosi(T) rc = NULL ) {parent = NULL; //作为根节点,而且初始时key.insert ( 0, e ); //叧有一个关键码,以及child.insert ( 0, lc ); child.insert ( 1, rc ); //两个孩子if ( lc ) lc->parent = this; if ( rc ) rc->parent = this;}
};
B-树
#include“BTNode.h” //引入B-树节点类template <typename T> class BTree [ //B-树模板类
protected:int _size;//存放的关键码总数int _order; //B-树的阶次,至少为3--创建时指定,一般不能修改BTNodePosi(T)_root; //根节点BTNodePosi(T)_hot; //BTree::search()最后访问的非空( 除非树空)的节点位置void solveOverflow ( BTNodePosi(T) ); //因插入而上溢之后的分裂处理void solveUnderflow ( BTNodePosi(T) ); //因删除而下溢之后的合并处理
public:BTree ( int order = 3 ): _order ( order ),_size ( ) //构造函数:默认为最低的3阶{_root = new BTNode<T>();}~BTree(){ if ( _root ) release ( _root );} //析构函数:释放所有节点int const order(){ return _order; } //阶次int const size(){ return _size; } //规模BTNodePosi(T) & root() { return_root;} //树根bool empty() const { return !_root;} //判空BTNodePosi(T) search ( const T& e ); //查找bool insert ( const T& e ); //插入bool remove ( const T& e ); //删除
}; //BTree
B-树:查找
只载入必需的节点,尽可能减少 I/O 操作。
可以将大数据集组织为 B-树并存放于外存。对于活跃的B-树,其根节点会常驻于内存;此外,任何时刻通常只有另一节点(称作当前节点)留驻于内存。
过程:
先以根节点作为当前节点,然后再逐层深入。若在当前节点(所包含的一组关键码)中能够找到目标关键码,则成功返回。否则(在当前节点中查找“失败”),则必可在当前节点中确定某一个引用(“失败”位置),并通过它转至逻辑上处于下一层的另一节点。若该节点不是外部节点,则将其载入内存,并更新为当前节点,然后继续重复上述过程。
实现
template<typename T> BTNodePosi(T) BTree<T>::search ( const T& e ) //在B-树中查找关键码eBTNodePosi(T) v =_root; _hot = NULL; //从根节点出发while ( v ){ //逐层查找Rank r = v->key.search ( e ); //在当前节点中,找到不大于e的最大关键码if ( ( 0 <= r ) && ( e == v->key[r] ) ) return v; //成功:在当前节点中命中目标关键码_hot = v; v = v->hild[r + 1]; //否则,转入对应子树(_hot指向其父)--需做I/0,最费时间}//这里在向量内是二分查找,但对通常的_order可直接顺序查找return NULL; //失败:最终抵达外部节点
}
成功时返回目标关键码所在的节点,上层调用过程可在该节点内进一步查找以确定准确的命中位置;失败时返回对应外部节点,其父节点则由变量_hot
指代。
性能分析
B-树查找操作所需的时间消耗于两个方面:1.将某一节点载入内存;2.在内存中对当前节点进行查找。
对于高度为h
的B-树,外存访问不超过O(h - 1)
次.
若存有N
个关键码的m
阶B-树高度为h
,则必有: log m ( N + 1 ) ≤ h ≤ l o g [ m / 2 ] [ ( N + 1 ) / 2 ] + 1 \log_m(N+1) \leq h \leq log_{[m/2]}[(N+1)/2]+1 logm(N+1)≤h≤log[m/2][(N+1)/2]+1
也就是说,存有N
个关键码的m
阶B-树的高度 h = Θ ( log m N ) h = \Theta (\log_m N) h=Θ(logmN)。
每次查找过程共需访问 O ( log m N ) O(\log_m N) O(logmN) 个节点,相应地需要做 O ( log m N ) O(\log_m N) O(logmN) 次外存读取操作。由此可知,对存有 N N N 个关键码的 m m m 阶B-树的每次查找操作,耗时不超过 O ( l o g m N ) O(log_m N) O(logmN)。
尽管没有渐进意义上的改进,但相对而言极其耗时的I/O操作的次数,却已大致缩减为原先的1/log2 m
。
B-树:插入
关键码插入
template <typename T> boo BTree<T>::insert ( const T& e ){ //将关键码e插入B树中BTNodePosi(T) v = search ( e ); if ( v )return false; //确认目标节点不存在Rank r = _hot->key.search ( e ); //在节点_hot的有序关键码向量中查找合适的插入位置_hot->key.insert ( r + 1,e ); //将新关键码插至对应的位置_hot->child.insert ( r + 2,NULL ); //创建一个空子树指针_size++;//更新全树规模solveOverflow (_hot); //如有必要,需做分裂return true; //插入成功
}
为在B-树中插入一个新的关键码 e
,首先调用search(e)
在树中查找该关键码。若查找成功,则按照“禁止重复关键码”的约定不予插入,操作即告完成并返回false
。
查找过程必然终止于某一外部节点v
,且其父节点由变量_hot
指示。当然,此时的_hot
必然指向某一叶节点(可能同时也是根节点)。接下来,在该节点中再次查找目标关键码e
。尽管这次查找注定失败,却可以确定e
在其中的正确插入位置r
。最后,只需将e
插至这一位置。
至此,_hot
所指的节点中增加了一个关键码。若该节点内关键码的总数依然合法(即不超过m - 1
个),则插入操作随即完成。否则,称该节点发生了一次上溢(overflow),此时需要通过适当的处理,使该节点以及整树重新满足B-树的条件。
上溢与分裂
一般地,刚发生上溢的节点,应恰好含有 m m m 个关键码。若取 s = [ m / 2 ] s = [m/2] s=[m/2],则它们依次为: { k 0 , . . . , k s − 1 ; k s ; k s + 1 , . . . , k m − 1 } \{ k_0,...,k_{s-1};k_s;k_{s+1},...,k_{m-1} \} {k0,...,ks−1;ks;ks+1,...,km−1}。可见,以 k s k_s ks 为界,可将该节点分前、后两个子节点,且二者大致等长。于是,可令关键码 k s k_s ks上升一层,归入其父节点(若存在)中的适当位置,并分别以这两个子节点作为其左、右孩子。这一过程,称作节点的分裂。
实现
template<typename T> //关键码插入后若节点上溢,则做节点分裂处理
void BTree<T>::solveOverflow ( BTNodePosi(T) v ) {if (_order >= v->child.size() ) return; //递归基:当前节点并未上溢Rank s = _order / 2; //轴点(此时应有_order = key.size() = child.size() - 1)BTNodePosi(T) u = new BTNode<T>();//注意:新节点已有一个空孩子for ( Rank j= 0;j< _order - s - 1; j++ ) {//v右侧_order-s-1个孩子及关键码分裂为右侧节点uu->child.insert ( j,v->child.remove ( s +1));//逐个移动效率低u->key.insert ( j,v->key.remove ( s +1));//此策略可改进}u->child[_order - - 1] = V->child.remove ( s + );//移动v最靠右的孩子if ( u->child[e] ) //若u的孩子们非空,则for ( Rank j= ;j< _order - s; j++ ) //令它们的父节点统一u->child[j]->parent = u; //指向uBTNodePosi(T) p = v->parent; //v当前的父节点pif ( !p )[_root = p = new BTNode<T>(); p->child[0] = v; v->parent = p;} //若p空则创建之Rank r = 1 + p->key.search ( v->key[0] ); //p中指向u的指针的秩p->key.insert ( r,v->key.remove ( s ) ); //轴点关键码上升p->child.insert (r + 1,u );u->parent = p;//新节点u与父节点p互联solveOverflow ( p ); //上升一层,如有必要则继续分裂——至多递归O(logn)层
}
上溢持续传播至根的情况:原树根分裂之后,新创建的树根仅含单关键码。由此也可看出,就B-树节点分支数的下限要求而言,树根节点的确应该作为例外。
复杂度
若将B-树的阶次m
视作为常数,则关键码的移动和复制操作所需的时间都可以忽略。至于solveOverflow()算法,其每一递归实例均只需常数时间,递归层数不超过B-树高度。由此可知,对于存有N
个关键码的m
阶B-树,每次插入操作都可在O(logm N)
时间内完成。
B-树:删除
template <typename T> bool BTree<T>::remove ( const T& e ) { //从BTree树中删除关键码eBTNodePosi(T) v = search ( e ); if ( !v ) return false; //确认目标关键码存在Rank r = v->key.search ( e );//确定目标关键码在节点v中的秩(由上,肯定合法)if ( v->child[0] ) { //若v非叶子,则e的后继必属于某叶节点BTNodePosi(T) u = v->child[r+1]; //在右子树中一直向左,即可while ( u->child[0] ) u = u->child[0]; //找出e的后继v->key[r] = u->key[e]; v = u; r = 0; //并与之交换位置} //至此,v必然位于最底层,且其中第r个关键码就是待删除者v->key. remove ( r ); v->child.remove ( r + 1 ); _size--; //删除e,以及其下两个外部节点之一solveUnderflow ( v );// 如有必要,需做旋转或合并
return true;
为从 B-树中删除关键码 e
,也首先需要调用 search(e)
查找 e
所属的节点。倘若查找失败,则说明关键码 e
尚不存在,删除操作即告完成:否则按照代码的出口约定,目标关键码所在的节点必由返回的位置v
指示。此时,通过顺序查找,即可进一步确定e
在节点v
中的秩r
。
不妨假定v
是叶节点——否则,e
的直接前驱(后继)在其左(右)子树中必然存在,而且可在O(height(v))
时间内确定它们的位置,其中height(v)
为节点v
的高度。此处不妨选用直接后继。于是,e
的直接后继关键码所属的节点u
必为叶节点,且该关键码就是其中的最小者u[0]
。既然如此,只要令e
与u[0]
互换位置,即可确保待删除的关键码e
所属的节点v
是叶节点。
接下来可直接将e
(及其左侧的外部空节点)从v
中删去。如此,节点v
中所含的关键码以及(空)分支将分别减少一个。若该节点所含关键码的总数依然合法(即不少于[m/2] - 1
),则删除操作随即完成。否则,称该节点发生了下溢,并需要通过适当的处置,使该节点以及整树重新满足 B-树的条件。
下溢与合并
在m
阶B-树中,刚发生下溢的节点V
必恰好包含[m/2] - 2
个关键码和[m/2] - 1
个分支。
1.V
的左兄弟 L
存在,且至少包含 [m/2]
个关键码
将
y
从节点P
转移至节点V
中(作为最小关键码),再将x
从L
转移至P
中(取代原关键码y
)
2.V
的右兄弟 R
存在,且至少包含 [m/2]
个
3.V
的左、右兄弟 L
和R
或者不存在,或者其包含的关键码均不足 [m/2]
个
从父节点P
中抽出介于L
和V
之间的关键码y
,并通过该关键码将节点L
和V
“粘接”成一个节点——这一过程称作节点的合并。
在经如此合并而得新节点中,关键码总数应为:([m/2] - 1) + 1 + ([m/2] - 2) = 2*[m/2] - 2 <= m - 1
故原节点V
的下溢缺陷得以修复,而且同时也不致于反过来引发上溢。
修复之后仍可能导致祖父节点以及更高层节点的下溢——这种现象称作下溢的传递。特别地,当下溢传递至根节点且其中不再含有任何关键码时,即可将其删除并代之以其唯一的孩子节点,全树高度也随之下降一层。
与上溢传递类似地,每经过一次下溢修复,新下溢节点的高度都必然上升一层。整个下溢修复的过程中至多需做O(log m N)
次节点合并操作。
复杂度
与插入操作同理,在存有N
个关键码的m
阶 B-树中的每次关键码删除操作,都可以在O(logm N)
时间内完成。另外同样地,因某一关键码的删除而导致 O ( l o g m N ) \mathcal O(log m N) O(logmN) 次合并操作的情况也极为罕见,单次删除操作过程中平均只需做常数次节点的合并。
相关文章:

数据结构和算法(10):B-树
B-树:大数据 现代电子计算机发展速度空前,就存储能力而言,情况似乎也是如此:如今容量以TB计的硬盘也不过数百元,内存的常规容量也已达到GB量级。 然而从实际应用的需求来看,问题规模的膨胀却远远快于存储能…...

VR会议:远程带看功能,专为沉浸式云洽谈而生
随着科技的不断发展,VR技术已经成为当今市场上较为热门的新型技术之一了,而VR会议远程带看功能,更是为用户提供更加真实、自然的沉浸式体验。 随着5G技术的发展,传统的图文、视频这种展示形式已经无法满足消费者对信息真实性的需求…...

实验室管理系统LIMS
在数字化浪潮中,越来越多的企业开始有数字化转型的意识。对于实验室而言,数字化转型是指运用新一代数字技术,促进实验室业务、生产、研发、管理、服务、供应链等方面的转型与升级,实现实验室业务“人、机、料、法、环”的多维度发…...

开源ERP和CRM套件Dolibarr
什么是 Dolibarr ? Dolibarr ERP & CRM 是一个现代软件包,用于管理您组织的活动(联系人、供应商、发票、订单、库存、议程…)。它是开源软件(用 PHP 编写),专为中小型企业、基金会和自由职业…...

视频号双11激励政策,快来看一看
双十一即将来临,不少平台都公布了自己的双十一政策。这篇文章,我们来看看视频号推出的激励政策,看有哪些需要准备的。...

Maven最新版本安装及配置
Maven是一个Java项目管理和构建工具,它可以定义项目结构、项目依赖,并使用统一的方式进行自动化构建,是Java项目不可缺少的工具。 本章我们详细介绍如何使用Maven。 一、Maven是什么? 如果每一个项目都自己搞一套配置…...
探索ClickHouse——使用MaterializedPostgreSQL同步PostgreSQL数据库
安装PostgreSQL sudo apt install postgresql修改配置 sudo vim /etc/postgresql/14/main/postgresql.conf 解开并修改wal_level 的配置项 wal_level logical 重启服务 /etc/init.d/postgresql restartRestarting postgresql (via systemctl): postgresql.service AUTHENTI…...

《向量数据库指南》——向量数据库 有必要走向专业化吗?
向量数据库 有必要走向专业化吗? 向量数据库系统的诞生,来源于具体业务需求——想要高效处理海量的向量数据,就需要更细分、更专业的数据基础设施,为向量构建专门的数据库处理系统。 但这种路径是必须的吗? 从产品层面讲,如果传统数据库厂商不单独研发向量数据库,那么…...

你必须知道的数据查询途径!!
在当今信息爆炸的时代,我们每天都会面临海量的数据和信息。如何在这些繁杂的信息中快速、准确地找到自己需要的内容,也是当代一个非常重要的技能。下面,我将介绍几种你必须知道的企业数据信息查找途径。 1. 搜索引擎 搜索引擎是我们日常中…...

火焰原子吸收光谱法、容量法和电感耦合等离子体发射光谱法
声明 本文是学习GB-T 1871.5-2022 磷矿石和磷精矿中氧化镁含量的测定 火焰原子吸收光谱法、容量法和电感耦合等离子体发射光谱法. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件描述了在磷矿石和磷精矿中测定氧化镁含量的火焰原子吸收…...
亚马逊云科技 2023 柏林峰会主题演讲总结
欢迎来到我们的亚马逊云科技2023柏林峰会主题演讲全面总结!在这篇文章中,我们将深入探讨在活动期间分享的主要公告、亮点和故事。通过这里的视频格式,展示了亚马逊云科技技术如何转化为商业和行业。 每年,亚马逊云科技峰会都会汇…...

CentOS Stream9 安装远程桌面服务 Xrdp
1. 安装 XRDP 若服务器本身没有桌面则首先需要安装本地桌面: yum -y groups install "GNOME Desktop" startx配置源: dnf install epel-release安装 xrdp dnf install xrdp 2. 配置 Xrdp Xrdp 配置文件位于 /etc/xrdp 目录中。对于常规 X…...

实施运维01
一.运维实施工程师所具备的知识 1.运维工程师,实施工程师是啥? 运维工程师负责服务的稳定性,确保服务无间断的为客户提供服务. 实施工程师负责工程的实施工作,负责现场培训,一般都要出差,哪里有项目就去…...
MySQL大表直接复制文件的copy方式
看腻了就来听听视频演示吧:https://www.bilibili.com/video/BV1Bp4y1F7kd/ MyISAM引擎可单独将 *.MYD和 *.MYI 拷贝到远程服务器上InnoDB引擎受限于版本(MySQL5.5)无法直接拷贝.ibd文件,因为在ibdata1文件保存有表的字典信息&…...

Redis-集群
Redis-集群 主从复制和哨兵只能在主节点进行写数据,从节点读取数据,因此本质上,是进行了读写的分离,每个节点都保存了所有的数据,并不能实现一个很好的分布式效果。 1.哈希求余算法 假设有N台主机,对每台…...

使用CrawlSpider爬取全站数据。
CrawpSpider和Spider的区别 CrawlSpider使用基于规则的方式来定义如何跟踪链接和提取数据。它支持定义规则来自动跟踪链接,并可以根据链接的特征来确定如何爬取和提取数据。CrawlSpider可以对多个页面进行同样的操作,所以可以爬取全站的数据。CrawlSpid…...

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)
目录 LockSupport与线程中断 线程中断机制 什么是中断机制? 与中断相关的3个API 如何停止中断运行中的线程? 当前线程的中断标识为true,是不是线程就会立刻停止? 如何理解静态方法Thread.interrupted() LockSupport是什么…...

Springboot学习笔记——3
Springboot学习笔记——3 一、热部署1.1、手动启动热部署1.2、自动启动热部署1.3、热部署范围配置1.4、关闭热部署 二、配置高级2.1、第三方bean属性绑定2.2、松散绑定2.3、常用计量单位应用2.4、bean属性校验2.5、进制数据转换规则 三、测试3.1、加载测试专用属性3.2、加载测试…...

jupyter 切换虚拟环境
当前只有两个环kernel 我已经创建了很多虚拟环境,如何在notebook中使用这些虚拟环境呢?请看下面 比如说我要添加nlp 这个虚拟环境到notebook中 1. 切换到nlp环境 2. 安装如下模块 pip install ipykernel 3. 执行如下命令 python -m ipykernel install …...

如何在Apache和Resin环境中实现HTTP到HTTPS的自动跳转:一次全面的探讨与实践
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...

MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】,分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...

【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...