【探寻C++之旅】第十三章:红黑树
请君浏览
- 前言
- 1. 红黑树的概念
- 1.2 红黑树的规则
- 1.3 红黑树如何确保最长路径不超过最短路径的两倍?
- 1.4 红黑树的效率
- 2. 红黑树的实现
- 2.1 红黑树的结构
- 2.2 红黑树的插入
- 情况1:变色
- 情况2:单旋+变色
- 情况2:双旋+变色
- 代码演示
- 2.3 红黑树的查找
- 3. 红黑树的验证
- 4. 红黑树与AVL树
- 尾声
前言
今天,我们继续踏入追寻C++的冒险历程。之前我们讲解了最早的自平衡二叉搜索树——AVL树,那么本章我们将讲解另一类自平衡二叉搜索树——红黑树。下面让我们一起来进入红黑树树的学习。
1. 红黑树的概念
前面我们讲解了一种自平衡二叉搜索树——AVL树,它可以使自己每一个节点的左右高度差严格保证在1之间,由于它更严格平衡,树高度较低,接近于log₂n
,所以它的旋转次数很多,实现相对复杂。除了AVL树外还有另一种自平衡二叉搜索树,也是今天我们要讲解的主角——红黑树。红黑树是一种自平衡的二叉搜索树,其由来可以追溯到1972年由Russell J. R. R. Anderson 和 Robert Sedgewick 提出的研究。与AVL树引入平衡因子来控制平衡不同,红黑树通过引入**节点的颜色(红色和黑色)**来帮助维护二叉搜索树树的平衡性。
红黑树的每个节点增加⼀个存储位来表⽰节点的颜⾊,可以是红⾊或者⿊⾊。 通过对任何⼀条从根到叶⼦的路径上各个节点的颜⾊进⾏约束,红⿊树确保没有⼀条路径会⽐其他路径⻓出2倍,因⽽是接近平衡的。
1.2 红黑树的规则
那么红黑树是如何做到确保没有⼀条路径会⽐其他路径⻓出2倍的呢?这得益于红黑树特有的规则:
- 每一个节点不是红色就是黑色。
- 根节点是黑色的。
- 任意一条路径不会有连续的红色节点,也就是说一个节点如果是红色的,那么它的孩子只能是黑色的。
- 对于任意⼀个节点,从该节点到其所有叶子节点的简单路径上,必须含有数量相同的⿊⾊节点。
这些性质保证了红黑树的高度不会超过log₂n
,从而确保了良好的性能。
下面让我们来看一看一些具体的红黑树:
观察上面的红黑树我们可以其每一个节点都符合上述的规则,同时也没有任何路径比其他路径长出两倍,这就是红黑树。
说明:
《算法导论》等书籍上补充了⼀条每个叶⼦节点(NIL)都是⿊⾊的规则。他这⾥所指的叶⼦节点不是传统的意义上的叶⼦节点,⽽是我们说的空节点,有些书籍上也把NIL叫做外部节点。NIL是为了⽅便准确的标识出所有路径,《算法导论》在后续讲解实现的细节中也忽略了NIL节点,所以我们知道⼀下这个概念即可
1.3 红黑树如何确保最长路径不超过最短路径的两倍?
由规则4可知,从根到叶子节点的每条路径都有相同数量的⿊⾊节点,所以极端场景下,最短路径就就是全是⿊⾊节点的路径,假设最短路径⻓度为bh(black height)
。
由规则2和规则3可知,任意⼀条路径不会有连续的红⾊节点,所以极端场景下,最⻓的路径就是⼀⿊⼀红间隔组成,那么最⻓路径的⻓度为2*bh
。
如下图所示:
综合红⿊树的4点规则⽽⾔,理论上的全⿊最短路径和⼀⿊⼀红的最⻓路径并不是在每棵红⿊树都存在的。假设任意⼀条从根到叶子节点路径的⻓度为x,那么bh <= h <= 2*bh
。
1.4 红黑树的效率
假设N
是红⿊树树中节点数量,h
最短路径的⻓度,那么2h - 1 <= N < 22*h> - 1, 由此推出 h ≈ log2N ,也就意味着红⿊树增删查改最坏也就是⾛最⻓路径 2 * log2N ,那么时间复杂度还是O(logN)
。
红⿊树的表达相对AVL树要抽象⼀些,AVL树通过⾼度差直观的控制了平衡。红⿊树通过4条规则的颜⾊约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对⽽⾔,插⼊相同数量的节点,红⿊树的旋转次数是更少的,因为它对平衡的控制没那么严格。
2. 红黑树的实现
2.1 红黑树的结构
红黑树与AVL树的结构基本上相似,只是AVL树中的每个节点的平衡因子改为了红黑树中每个节点的颜色。
// 枚举值表⽰颜⾊
enum Colour
{RED,BLACK
};
// 这⾥我们默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{// 这⾥更新控制平衡也要加⼊parent指针pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col;RBTreeNode(const pair<K, V>& kv): _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr){}
};
template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public://...
private:Node* _root = nullptr;
}
2.2 红黑树的插入
插⼊⼀个值按⼆叉搜索树规则进⾏插⼊,插⼊后我们只需要观察是否符合红⿊树的4条规则。
为了满足规则1,在空树插⼊时,新增节点必须是⿊⾊节点。如果是⾮空树插⼊,新增节点必须红⾊节点,因为⾮空树插⼊,新增⿊⾊节点就破坏了规则4,规则4是很难维护的。
- ⾮空树插⼊后,新增节点必须红⾊节点,如果⽗亲节点是⿊⾊的,则没有违反任何规则,插⼊结束。
- ⾮空树插⼊后,新增节点必须红⾊节点,如果⽗亲节点是红⾊的,则违反规则3。进⼀步分析,c是红⾊,p是红,g必为⿊(这是因为在插入之前该树是红黑树),这三个颜⾊都固定了,关键的变化看u的情况,需要根据u分为以下⼏种情况分别处理。
说明:
下面假设我们把新增节点标识为c (cur),c的⽗亲标识为p(parent),p的⽗亲标识为g(grandfather),p的兄弟标识为u(uncle)。
例如:
// g // p u // c
情况1:变色
c为红,p为红,g为⿊,u存在且为红,则将p和u变⿊,g变红。在把g当做新的c,继续往上更新。
分析:因为p和u都是红⾊,g是⿊⾊,把p和u变⿊,左边⼦树路径各增加⼀个⿊⾊节点,g再变红,相当于保持g所在⼦树的⿊⾊节点的数量不变,同时解决了c和p连续红⾊节点的问题,需要继续往上更新是因为:g是红⾊
- 如果g的⽗亲还是红⾊,那么就还需要继续处理;
- 如果g的⽗亲是⿊⾊,则处理结束了;
- 如果g就是整棵树的根,再把g变回⿊⾊。
情况1只变⾊,不旋转。所以⽆论c是p的左还是右,p是g的左还是右,都是上⾯的变⾊处理⽅式。
情况2:单旋+变色
c为红且与p在同一条斜线上,p为红,g为⿊,u不存在或者u存在且为⿊
- u不存在,则c⼀定是新增节点。
- u存在且为⿊,则c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上来的。
分析上图中左侧u不存在可知,u不存在,所以以g节点到每一个叶子节点的黑色节点数目为1,而c若不是新插入的节点,则代表它是由情况1变色而来的,说明在以c为根节点的子树中还一定存在黑色节点,此时不符合红黑树的规则4,因此如果u不存在,那么c一定是新增节点。同理,右侧如果c是新增节点,同样不符合规则4,因此如果u存在且为黑色,那么c一定不是新增节点。
分析:p必须变⿊,才能解决连续红⾊节点的问题,但是由于u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解决问题,需要进行旋转+变⾊。
- 如果p是g的左,c是p的左,那么以g为旋转点进⾏右单旋,再把p变⿊,g变红即可。p变成课这颗树新的根,这样⼦树⿊⾊节点的数量不变,没有连续的红⾊节点了,且不需要往上更新,因为p的⽗亲无论是⿊⾊还是红⾊或者空都不违反规则。
- 如果p是g的右,c是p的右,那么以g为旋转点进⾏左单旋,再把p变⿊,g变红即可。p变成课这颗树新的根,这样⼦树⿊⾊节点的数量不变,没有连续的红⾊节点了,且不需要往上更新,因为p的⽗亲是⿊⾊还是红⾊或者空都不违反规则。
这里的旋转与AVL树中的旋转一模一样,只是不再需要更新平衡因子。
无论c是否为新增节点,都不会影响我们的单旋+变色操作,与AVL树一样,对于旋转我们需要将一些子树给抽象出来,在AVL树中对于这些抽象出来的子树我们只需要知道它们的高度即可,而在红黑树中,我们需要知道这些抽象出来的子树中黑色节点的数量hb。
例如上图,我们需要对g节点进行右单旋:将g变为p的右,d变为g的左,p成为新的根,与AVL树中的右单旋一模一样。只是在红黑树中,我们进行了右单旋后还需要进行变色:旋转完后将p变为黑色,g变为红色:
当hb==0时,也就是u不存在时,也就是c为新增节点:
至于左单旋不再详细介绍,思路与右单旋一样。
情况2:双旋+变色
c为红且与p不在同一条斜线上,p为红,g为⿊,u不存在或者u存在且为⿊
- 与单旋+变色相同,u不存在,则c⼀定是新增节点,u存在且为⿊,则c⼀定不是新增节点。
分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解决问题,需要旋转+变⾊。
- 如果p是g的左,c是p的右,那么先以p为旋转点进⾏左单旋,再以g为旋转点进⾏右单旋,再把c变⿊,g变红即可。c变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为c的⽗亲是⿊⾊还是红⾊或者空都不违反规则。
- 如果p是g的右,c是p的左,那么先以p为旋转点进⾏右单旋,再以g为旋转点进⾏左单旋,再把c变⿊,g变红即可。c变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为c的⽗亲是⿊⾊还是红⾊或者空都不违反规则。
其实我们可以发现,无论是红黑树还是AVL树,它们进行单旋还是双旋的逻辑是一样的。有了前面AVL树中的旋转基础,这里我们就不再过多解释,直接用图来带大家理解:
这里用左右双旋演示:
代码演示
下面让我们来看一看具体的插入代码:
// 旋转代码的实现跟AVL树是⼀样的,只是不需要更新平衡因⼦
bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = parent->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = parent->_left;}else{return false;}}cur = new Node(kv);// 新增结点。颜⾊给红⾊cur->_col = RED;if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){// u存在且为红 -> 变⾊再继续往上处理parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{// u存在且为⿊或不存在 -> 单旋+变⾊// g// p u// cif (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}// 双旋+变色// g// p u// celse{RoteteL(parent);RoteteR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){//u存在且为红 -> 变⾊再继续往上处理parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{// u存在且为⿊或不存在 -> 单旋+变⾊// g// u p// cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}// 双旋+变色// g// u p// celse{RoteteR(parent);RoteteL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;
}
2.3 红黑树的查找
按⼆叉搜索树逻辑实现即可,搜索效率为 O(logN)
Node* Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;} else if (cur->_kv.first > key){cur = cur->_left;} else{return cur;}} return nullptr;
}
红黑树的删除操作相对复杂,因为它需要在删除节点后维护红黑树的特性。与插入一样都需要维护节点的颜色并且还要维护树的平衡,因此这里就不再赘述,感兴趣的可以去了解一下。这里只是带大家认识一下红黑树以及体会一下其维护平衡的一些做法。
3. 红黑树的验证
那么我们如何去判断一棵树是否是红黑树呢?或者说在插入或者删除后如何检测该树是否还是红黑树。
如果只是获取最⻓路径和最短路径,检查最⻓路径不超过最短路径的2倍是不可⾏的,因为就算满⾜这个条件,红⿊树也可能颜⾊不满⾜规则,当前暂时没出问题,后续继续插⼊还是会出问题的。
所以我们还是去检查4点规则,满⾜这4点规则,⼀定能保证最⻓路径不超过最短路径的2倍。
-
规则1枚举颜⾊类型,天然实现保证了颜⾊不是⿊⾊就是红⾊。
-
规则2直接检查根即可。
-
规则3前序遍历检查,遇到红⾊结点查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲的颜⾊就⽅便多了。
-
规则4前序遍历,遍历过程中⽤形参记录跟到当前结点的
blackNum
(⿊⾊结点数量),前序遍历遇到⿊⾊结点就++blackNum
,⾛到空就计算出了⼀条路径的⿊⾊结点数量。再以任意⼀条路径⿊⾊结点数量作为参考值,依次⽐较即可。
下面让我们来看一看具体的代码实现:
bool Check(Node* root, int blackNum, const int refNum)
{if (root == nullptr){// 前序遍历⾛到空时,意味着⼀条路径⾛完了//cout << blackNum << endl;if (refNum != blackNum){cout << "存在⿊⾊结点的数量不相等的路径" << endl;return false;} return true;} // 检查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲就⽅便多了if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "存在连续的红⾊结点" << endl;return false;} if (root->_col == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);
}
bool IsBalance()
{if (_root == nullptr)return true;if (_root->_col == RED)return false;// 参考值int refNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refNum;} cur = cur->_left;} return Check(_root, 0, refNum);
}
4. 红黑树与AVL树
红黑树和AVL树都是自平衡二叉搜索树,那它们之间有什么区别呢?
特性 | AVL树 | 红黑树 |
---|---|---|
平衡条件 | 每个节点的左右子树高度差最多为1 | 每条从根到叶子的路径上,黑色节点数相同,且不允许两个连续的红色节点 |
树的高度 | 更严格平衡,树高度较低,接近于log₂n | 平衡条件较松,树高度较AVL树略高,接近于2*log₂n |
旋转次数 | 插入和删除时旋转次数较多 | 插入和删除时旋转次数较少 |
实现复杂度 | 实现相对复杂,需要维护节点的高度或平衡因子 | 实现较复杂,需要维护节点的颜色属性 |
查找效率 | 查找效率较高,因为树高度更低 | 查找效率稍低,但仍为对数时间复杂度 |
删除操作性能 | 删除较复杂,可能引起多次旋转 | 删除相对简单,旋转次数较少 |
红黑树的优点:
- 插入和删除效率较高:旋转次数较少,写操作性能较好,适合写操作较频繁的场景。
- 实现灵活:由于平衡条件较松,实现和维护相对灵活。
- 广泛应用:许多库和语言(如Linux内核、Java的TreeMap等)使用红黑树作为底层数据结构。
AVL树的优点:
- 查询性能优越:由于其严格的平衡条件,AVL树的高度最小,查询操作速度更快,适合读操作多于写操作的场景。
- 平衡维护严格:高度差保证在1以内,保证了高度的最小化。
尾声
若有纰漏或不足之处欢迎大家在评论区留言或者私信,同时也欢迎各位一起探讨学习。感谢您的观看!
相关文章:
【探寻C++之旅】第十三章:红黑树
请君浏览 前言1. 红黑树的概念1.2 红黑树的规则1.3 红黑树如何确保最长路径不超过最短路径的两倍?1.4 红黑树的效率 2. 红黑树的实现2.1 红黑树的结构2.2 红黑树的插入情况1:变色情况2:单旋变色情况2:双旋变色代码演示 2.3 红黑树…...
JavaScript 性能优化全攻略:从基础到实战
引言 在现代 Web 开发中,JavaScript 作为核心语言,其性能直接影响用户体验。无论是单页应用(SPA)还是复杂交互页面,性能优化始终是开发者关注的核心。 本文将从基础策略、最新技巧、常见误区和实战案例四个维度,系统性地解析 JavaScript 性能优化的关键方法,并提供可复…...
Kafka消息队列之 【消费者分组】 详解
消费者分组(Consumer Group)是 Kafka 提供的一种强大的消息消费机制,它允许多个消费者协同工作,共同消费一个或多个主题的消息,从而实现高吞吐量、可扩展性和容错性。 基本概念 消费者分组:一组消费者实例的集合,这些消费者实例共同订阅一个或多个主题,并通过分组来协调…...
HuggingFace与自然语言处理(从框架学习到经典项目实践)[ 01 API操作 ]
本教程适用与第一次接触huggingface与相应框架和对nlp任务感兴趣的朋友,该栏目目前更新总结如下: Tokenizer: 支持单句/双句编码,自动处理特殊符号和填充。 批量编码提升效率,适合训练数据预处理。Datasets…...
uniapp-文件查找失败:‘@dcloudio/uni-ui/lib/uni-icons/uni-icons.vue‘
uniapp-文件查找失败:‘dcloudio/uni-ui/lib/uni-icons/uni-icons.vue’ 今天在HBuilderX中使用uniapp开发微信小程序时遇到了这个问题,就是找不到uni-ui组件 当时创建项目,选择了一个中间带的底部带选项卡模板,并没有选择内置u…...
springboot+vue实现在线网盘(云盘)系统
今天教大家如何设计一个网盘(云盘)系统系统 , 基于目前主流的技术:前端vue,后端springboot。 同时还带来的项目的部署教程。 视频演示 springbootvue实现在线网盘(云盘)系统 图片演示 一. 系统概述 用过百…...
启智平台调试 qwen3 4b ms-swift
以上设置完成后,我们点击新建任务。等待服务器创建和分配资源。 资源分配完成后我们看到如下列表,看到资源running状态,后面有一个调试按钮,后面就可以进入代码调试窗体界面了。 点击任务名称 跳转 访问github失败 加速器开启…...
KAXA凯莎科技AGV通信方案如何赋能智能仓储高效运作?
AGV智慧物流系统融合了先进的自动导航技术和智能控制算法,通过激光雷达、摄像头、激光传感器等多种感知设备,实现仓库内的精准定位与自主导航。系统具备环境实时感知能力,能够动态避障,并基于任务调度智能规划最优路径,…...
【AI提示词】费曼学习法导师
提示说明 精通费曼学习法的教育专家,擅长通过知识解构与重构提升学习效能。 提示词 Role: 费曼学习法导师 Profile language: 中文description: 精通费曼学习法的教育专家,擅长通过知识解构与重构提升学习效能background: 认知科学硕士背景࿰…...
体绘制中的传输函数(transfer func)介绍
文章目录 VTK volume不透明度传输函数梯度不透明度传输函数颜色传输函数VTK volume VTK (Visualization Toolkit) 中的 Volume(体积)是一个重要的概念,特别是在处理和可视化三维数据时。以下是 VTK Volume 的一些关键概念: 定义: Volume 在 VTK 中代表一个三维数据集,通…...
Algolia - Docsearch的申请配置安装【以踩坑解决版】
👨🎓博主简介 🏅CSDN博客专家 🏅云计算领域优质创作者 🏅华为云开发者社区专家博主 🏅阿里云开发者社区专家博主 💊交流社区:运维交流社区 欢迎大家的加入!…...
【文档智能】开源的阅读顺序(Layoutreader)模型使用指南
一年前,笔者基于开源了一个阅读顺序模型(《【文档智能】符合人类阅读顺序的文档模型-LayoutReader及非官方权重开源》), PDF解析并结构化技术路线方案及思路,文档智能专栏 阅读顺序检测旨在捕获人类读者能够自然理解的…...
现在的AI应用距离通用agent差的那点儿意思
现在的AI应用距离通用Agent差的那点儿意思 引言:从"生成力"到"行动力" 当前AI应用最显著的进步体现在内容生成能力上——无论是ChatGPT的流畅对话,还是Midjourney的惊艳画作,都展示了强大的生成力。然而,正…...
LeetCode 热题 100 238. 除自身以外数组的乘积
LeetCode 热题 100 | 238. 除自身以外数组的乘积 大家好,今天我们来解决一道经典的算法问题——除自身以外数组的乘积。这道题在 LeetCode 上被标记为中等难度,要求在不使用除法的情况下,计算数组中每个元素的乘积,其中每个元素的…...
分享 2 款基于 .NET 开源的实时应用监控系统
前言 在现代软件开发和运维管理中,实时应用监控系统扮演着至关重要的角色。它们能够帮助开发者和运维人员实时监控应用程序的状态,及时发现并解决问题,从而确保应用的稳定性和可靠性。今天大姚给大家分享 2 款基于.NET 开源的实时应用监控系…...
使用pytorch保存和加载预训练的模型方法
需要使用到的函数 在 PyTorch 中,torch.save() 和 torch.load() 是用于保存和加载模型的核心函数。 torch.save() 函数 主要用途:将模型或模型的状态字典(state_dict)保存到文件中。 语法: torch.save(obj, f, pi…...
Linux/AndroidOS中进程间的通信线程间的同步 - 消息队列
本文介绍消息队列,它允许进程之间以消息的形式交换数据。数据的交换单位是整个消息。 POSIX 消息队列是引用计数的。只有当所有当前使用队列的进程都关闭了队列之后才会对队列进行标记以便删除。POSIX 消息有一个关联的优先级,并且消息之间是严格按照优…...
DNA Launcher:打造个性化安卓桌面,开启全新视觉体验
DNA Launcher是一款专为安卓手机设计的桌面美化软件,旨在为用户提供丰富多样的桌面美化选项和全新的操作逻辑。通过这款软件,用户可以轻松调整桌面布局、更换主题、添加个性化元素,打造出独一无二的手机桌面。它支持多分辨率重新布局…...
Flink SQL DataStream 融合开发模式与动态配置热加载机制实战
一、为什么需要 SQL 与 DataStream 融合开发? 在实时数仓构建中,Flink SQL 的易用性和声明式优势广受欢迎;但遇到业务逻辑复杂、需要灵活控制时,DataStream API 提供了不可替代的灵活性。 而现实中,我们常常遇到如下痛点: 场景问题解决方式多业务线、多个 Kafka Topic,…...
4.2java包装类
在 Java 里,基本数据类型不具备对象的特性,像不能调用方法、参与面向对象的操作等。为了让基本数据类型也能有对象的行为,Java 提供了对应的包装类。同时,自动拆箱和自动装箱机制让基本数据类型和包装类之间的转换更加便捷。 包装…...
在一台服务器上通过 Nginx 配置实现不同子域名访问静态文件和后端服务
一、域名解析配置 要实现通过不同子域名访问静态文件和后端服务,首先需要进行域名解析。在域名注册商或 DNS 服务商处,为你的两个子域名 blog.xxx.com 和 api.xxx.com 配置 A 记录或 CNAME 记录。将它们的 A 记录都指向你服务器的 IP 地址。例如&#x…...
C++23 views::as_rvalue (P2446R2) 深入解析
文章目录 引言C20 Ranges库回顾什么是Rangesstd::views的作用 views::as_rvalue 概述基本概念原型定义工作原理 应用场景容器元素的移动与其他视图适配器结合使用 总结 引言 在C的发展历程中,每一个新版本都会带来一系列令人期待的新特性,这些特性不仅提…...
Mockoon 使用教程
文章目录 一、简介二、模拟接口1、Get2、Post 一、简介 1、Mockoon 可以快速模拟API,无需远程部署,无需帐户,免费,跨平台且开源,适合离线环境。 2、支持get、post、put、delete等所有格式。 二、模拟接口 1、Get 左…...
15.thinkphp的上传功能
一.上传功能 1. 如果要实现上传功能,首先需要建立一个上传表单,具体如下: <form action"http://localhost/tp6/public/upload"enctype"multipart/form-data" method"post"><input type&…...
G口大带宽服务器线路怎么选
G口大带宽服务器线路选择指南 一、线路类型与特点 单线(电信/联通/移动) 优势:带宽独享、价格低、延迟稳定,适合单一运营商用户集中场景。劣势:跨运营商访问延迟高(如电信…...
低秩适应(LoRA)与量化LoRA(QLoRA)技术解析
LoRA:从线性代数到模型微调 从矩阵分解理解Lora 假设我们有一个大模型中的权重矩阵,形状为1024512(包含约52万个参数)。传统微调方法会直接更新这52万个参数,这不仅计算量大,而且存在过拟合风险。 LoRA的…...
Webug4.0靶场通关笔记22- 第27关文件包含
目录 一、文件包含 1、原理分析 2、文件包含函数 (1)include( ) (2)include_once( ) (3)require( ) (4)require_once( ) 二、第27关渗透实战 1、打开靶场 2、源码分析 3、…...
OpenCV CPU性能优化
OpenCV 在 CPU 上的性能优化涉及多个层次,从算法选择到指令级优化。以下是系统的优化方法和实践技巧: 一、基础优化策略 1. 内存访问优化 连续内存布局:优先使用 cv::Mat::isContinuous() 检查 cpp if(mat.isContinuous()) {// 可优化为单循…...
OpenCV进阶操作:图像的透视变换
文章目录 前言一、什么是透视变换?二、透视变换的过程三、OpenCV透视变换核心函数四、文档扫描校正(代码)1、预处理2、定义轮廓点的排序函数3、定义透视变换函数4、读取原图并缩放5、轮廓检测6、绘制最大轮廓7、对最大轮廓进行透视变换8、旋转…...
MySQL事务隔离机制与并发控制策略
MySQL事务隔离机制与并发控制策略 MySQL事务隔离机制与并发控制策略一、数据库并发问题全景解析二、事务隔离级别深度解析三、MySQL并发控制核心技术1. 多版本并发控制(MVCC)2. 锁机制 四、隔离级别实现差异对比五、生产环境最佳实践六、高级优化技巧七、…...
【算法学习】递归、搜索与回溯算法(二)
算法学习: https://blog.csdn.net/2301_80220607/category_12922080.html?spm1001.2014.3001.5482 前言: 在(一)中我们挑了几个经典例题,已经对递归、搜索与回溯算法进行了初步讲解,今天我们来进一步讲解…...
SpringBoot整合PDF导出功能
在实际开发中,我们经常需要将数据导出为PDF格式,以便于打印、分享或存档。SpringBoot提供了多种方式来实现PDF导出功能,下面我们将介绍其中的一些。 HTML 模板转 PDF(推荐) 通过模板引擎(如 Thymeleaf 或…...
关于MySQL 数据库故障排查指南
🛠 MySQL 数据库故障排查指南 目标:解决常见数据库问题,保障数据安全与系统稳定运行。 一、常见故障类型概览 故障类型可能原因排查/解决步骤无法连接服务未启动、端口未监听、用户权限不足 查看服务状态: systemctl status my…...
ubuntu yolov5(c++)算法部署
1.安装onnx 1.15.0 首先使用如下命令关闭 anaconda 对后续源码编译的影响; # 禁用当前 conda 环境 conda deactivate# 确保 conda 初始化脚本不会自动激活 base 环境 conda config --set auto_activate_base false# 然后重新打开终端或执行 source ~/.bashrc 1.安…...
基于Centos7的DHCP服务器搭建
一、准备实验环境: 克隆两台虚拟机 一台作服务器:DHCP Server 一台作客户端:DHCP Clinet 二、部署服务器 在网络模式为NAT下使用yum下载DHCP 需要管理员用户权限才能下载,下载好后关闭客户端,改NAT模式为仅主机模式…...
《开源先锋Apache软件基金会:历史沿革、顶级项目与行业影响》
1. Apache软件基金会概述 Apache软件基金会(Apache Software Foundation, ASF) 是全球最大的开源软件组织之一,成立于1999年,是一个非营利性机构,致力于为公共利益提供开源软件。ASF以“社区主导、共识决策”为核心原…...
Java数据结构——Queue
Queue 队列的概念队列的使用offer和poll方法add和remove方法 设计循环队列队列实现栈栈实现队列 前面所说的Stack是 先入后出的原则,那有没有 先入先出的原则的结构呢?这就是本篇博客所讲的Queue序列就是这个原则 队列的概念 只允许在一段进行插入数据…...
仓储车间安全革命:AI叉车防撞装置系统如何化解操作风险
在现代物流体系中,仓储承担着货物储存、保管、分拣和配送等重要任务。但现代仓储行业的安全现状却不容乐观,诸多痛点严重制约着其发展,其中叉车作业的安全问题尤为突出。相关数据显示,全球范围内,每年因叉车事故导致的…...
深入 FaaS 核心:函数是如何“活”起来的?
深入 FaaS 核心:函数是如何“活”起来的? 在上一篇《你好,Serverless!告别服务器运维的烦恼》中,我们认识了 Serverless 的基本概念,并知道了 FaaS (Function as a Service) 是其核心计算单元,就像一个个“随叫随到”的专业工具人。 那么,这些“工具人”到底是如何被“…...
vue2 两种路由跳转方式
第一种方式:path跳转 第二中写法:用name跳转 路由传参 动态路由传参 案例 通过${} 动态路由传参 动态路由使用params来进行接收 name 传参 总结 传的什么用什么接受...
手机上使用的记录笔记的软件推荐哪一款
在快节奏的生活中,一款好用的手机笔记软件就像随身携带的“外挂大脑”,能帮我们高效记录生活点滴、工作计划和灵感创意。今天,就来给大家详细对比一下Pendo、敬业签、MIGi日历记事本这三款热门笔记软件。 一、Pendo笔记:智能日程…...
SpringBoot 讯飞星火AI WebFlux流式接口返回 异步返回 对接AI大模型 人工智能接口返回
介绍 用于构建基于 WebFlux 的响应式 Web 应用程序。集成了 Spring WebFlux 模块,支持响应式编程模型,构建非阻塞、异步的 Web 应用。WebFlux 使用了非阻塞的异步模型,能够更好地处理高并发请求。适合需要实时数据推送的应用场景。 WebClie…...
Python学习笔记--Django的安装和简单使用(一)
一.简介 Django 是一个用于构建 Web 应用程序的高级 Python Web 框架。Django 提供了一套强大的工具和约定,使得开发者能够快速构建功能齐全且易于维护的网站。Django 遵守 BSD 版权,初次发布于 2005 年 7 月, 并于 2008 年 9 月发布了第一个正式版本 1…...
Java 17配置Jenkins
找到 Java 17 的安装路径 which java ls -l /usr/lib/jvm/ 修改 Jenkins 服务配置 sudo nano /etc/systemd/system/jenkins.service 修改为 [Unit] DescriptionJenkins Automation Server Afternetwork.target[Service] Typesimple Userjenkins Groupjenkins Environment&…...
前端面试每日三题 - Day 28
这是我为准备前端/全栈开发工程师面试整理的第28天每日三题练习: ✅ 题目1:HTTP缓存策略全景解析 核心缓存类型对比表 缓存类型验证方式响应头网络请求消耗强缓存无Cache-Control/Expires无协商缓存If-Modified-Since等ETag/Last-Modified304响应 1.强…...
B站pwn教程笔记-8
接着上次的习题刷,然后补充新的知识。这开始就接触花式栈溢出了 pwn3(ret2libc较难) 上次已经知道大致思路,现在看看怎么实现。 使用命令 ldd 可看出连接的LIBC是哪个,如下图所示。(第一行) …...
uniapp项目打包的微信小程序,设置uni-popup type=“bottom“时,底部有空隙
问题: uniapp项目打包的微信小程序,设置uni-popup type"bottom"时,底部有空隙 解决思路: 1、检查代码是否存在样式问题 2、使用微信小程序自带的调试器元素 3、查看源码定位底部是如何出现该空隙的 1、检查代码 检…...
《Zabbix Proxy分布式监控实战:从安装到配置全解析》
注意:实验所需的zabbix服务器的搭建可参考博客 zabbix 的docker安装_docker安装zabbix-CSDN博客 1.1 实验介绍 1.1.1 实验目的 本实验旨在搭建一个基于Zabbix的监控系统,通过安装和配置Zabbix Proxy、MySQL数据库以及Zabbix Agent,实现分…...
zookeeper实现分布式获取全局唯一自增ID的案例。
项目结构 所有配置写在 application.yml 文件中,代码进行了拆分,加入了相关依赖。 1. pom.xml 依赖 <dependencies><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><…...
微信小程序上传视频,解决ios上传完video组件无法播放
1.碰到问题 工单里面上传完视频video组件ios无法播放视频,安卓可以 2.原因 使用了后台接口返回的url拼域名 , 正确做法:使用wx.chooseMedia()里面的tempFilePath(本地临时文件路径 (本地路径)),上传好了详情可以使用后…...