当前位置: 首页 > article >正文

数据结构(六)——红黑树及模拟实现

目录

前言

红黑树的概念及性质

红黑树的效率

红黑树的结构

红黑树的插入

变色不旋转

单旋+变色

双旋+变色

插入代码如下所示:

红黑树的查找

红黑树的验证

红黑树代码如下所示:

小结


前言

在前面的文章我们介绍了AVL这一棵完全二叉搜索树,我们分析后发现AVL树查找的时间复杂度是O(logN),说明这是一个很优秀的数据结构,但是在底层实现我们发现,AVL树为了维持它的性质,是需要进行频繁的旋转的,这样会造成不必要的消耗,那么有没有一种数据结构既可保证它的时间复杂度为O(logN),又能避免频繁的旋转呢?这就是我们今天要介绍的红黑树,学习了红黑树之后我们将用红黑树作为底层,去封装实现一个模拟的map和set,下面让我们一起来学习吧。

参考文章:

据结构(五)——AVL树(平衡二叉搜索树)

红黑树的概念及性质

首先我们要知道什么是红黑树,与AVL树相比,红黑树的节点多了一个标志符号,这个标志符号表示节点的颜色,我们这里用枚举来表示,通过控制节点的颜色来判平衡以及判断是否需要旋转。由于红黑树的旋转不像AVL树那么频繁,所以它是一颗不完全平衡二叉搜索树。

下面我们引入路径的概念,我们把从根节点开始,一直到空节点的一条路线称为路径

它的规则如下所示:

规则一:根节点必须是黑色,其余节点的颜色不是黑色就是红色

规则二:每个红色节点的左右子树必须是红色的,即:不存在连续的红色节点

规则三:每条路径的黑色节点数量是相同的,其中我们把空节点视为黑色节点

规则四:最长路径的节点数量不超过最长路径节点数量的二倍

分析上面的规则我们发现,其中最关键也是最难维护的就是规则三,我们发现,在极端场景下,最短路径就是全为黑色节点的路径,最长路径就是红黑间隔的路径,所以维护好了规则三,规则四自然就实现了

红黑树的效率

由于有规则四的存在,我们设红黑树最短路径的节点数量是h,那么最长路径的节点数量就是2*h,我们设每条路径上黑色节点的数量是N,那么他们应该满足:2^{h}-1<=N<2^{2*h}-1,也就是说,如果我们要在红黑树当中进行增删查改,最少要走的长度是2^{h}-1,查找的次数就是log(N),最坏也就是查找2*log(N),分析下来,它的时间复杂度还是log(N)。

红黑树的结构

从上面的分析中我们知道,红黑树的大致框架与AVL树相似,只不过将平衡因子改成了用枚举表示的颜色,后续我们也是通过控制颜色来判平衡,因此红黑树的结构如下所示:

enum Colour
{BLACK,RED
};
template<class K, class V>
struct RBTreeNode
{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;
};

红黑树的插入

我们来分析红黑树的插入过程,首先由于它是一颗搜索树,所以按照搜索树的规则进行插入,更改插入结点的颜色,然后我们向上进行调整。

对于插入节点的颜色,我们发现,如果我们插入黑色,那么就在当前路径上增加了一个黑色节点,不符合每条路径上黑色节点数量相同这一规则,所以我们插入节点的颜色应该为红色。代码如下所示:

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 = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{cout << "插入值已经存在,请重新插入" << endl;return false;}}cur = new Node(kv);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;
}

当我们插入红色节点后,会出下面几种情况:

情况一:父节点为黑色,不违反红黑树的规则,那么就停止向上调整

情况二:父节点为红色,违反规则,那么就需要向上调整

接下来我们重点分析情况二。由于不能有两个连续的红色节点,所以我们应当把父节点变成黑色,但是将父节点变成黑色后我们就在当前路径上凭空增加了一个黑色节点,这样破坏了黑色节点数量相同这一规则,那么我们就需要调整其他路径上黑色节点的数量,如果是这样的话,是不符合我们的预期的,我们再仔细观察红黑树的结构,我们把父节点的父节点定义为grandfather(祖父节点),那么祖父节点的另一个子节点称为uncle(叔节点),我们发现,无论什么情况,只要涉及到需要向上调整,祖父节点总是黑色的,此时,如果叔节点存在且为红色的话,我们就只需要将父节点和叔节点颜色变为黑色,再将祖父节点变为红色,这样就解决了这个问题;如果叔节点不存在或者存在且为空节点,那么此时我们就需要做特殊处理。

通过上述分析,我们总结一下红黑树的插入过程:

第一步:插入节点。按照二叉搜索树的规则插入节点,链接父节点,将插入节点的颜色改成红色,如果是根节点,就变为黑色。

第二步:向上调整黑色节点的数量,让其满足红黑树的规则。

接下来我们逐步来分析向上调整。

根据上面的分析我们知道,我们可以根据uncle节点的有无以及颜色,可以分为下面两种情况:

情况一:uncle节点存在且颜色为红色

情况二:uncle节点存在且为黑色或者uncle不存在

如下图所示:

对于情况一,我们只需要将父节点和uncle节点颜色变为黑色,然后再将祖父节点颜色变为红色,然后向上更新parent节点和grandfather节点,因此我们需要一个循环,循环结束的条件是:parent为空或者parent的颜色为黑色。

对于情况二,我们发现这种情况又需要分为两种场景,一种是插入节点是parent的左节点,另一种是插入节点是parent的右节点,这两种情景无论我们怎么变色都没有办法满足红黑树的规则,为此我们需要寻找一种新的解决办法。

回到红黑树的定义,我们知道,红黑树是一颗不完全平衡二叉搜索树,从前面的AVL树我们知道,要成为一颗平衡树,我们要做的就是旋转,而旋转分为左旋和右旋,那么我们尝试将AVL树的旋转带入红黑树当中,那么对于parent与cur都位于同一侧节点的这种情况,我们使用单旋,对于parent与cur位于不同一侧节点的这种情况,我们使用双旋。

综上所述,红黑树的插入可以分为以下三种不同的情况:

变色不旋转

条件:grandfather为黑,parent与cur为红色,uncle存在且为红。

解决办法:parent与cur变为黑色,grandfather变为红色,然后再将grandfather当成新的cur,继续向上更新。

分析:由于cur与parent都是红色,所以我们应该把parent变为黑色,这样就在当前节点凭空增加了一个黑色节点,从而导致黑色节点失衡;由于uncle是红色,为了让黑色节点数量保持平衡,所以我们需要把uncle也变成黑色,然后让grandfather变为红色。

单旋+变色

条件:grandfather为黑色,parent与cur为红色,uncle不存在或者uncle存在且为黑,并且parent与cur位于同一侧

解决办法:以grandfather节点为旋转点,如果位于左侧则右旋;如果位于右侧则左旋,旋转结束后让parent变为黑色,grandfather变成红色

分析:由于uncle是黑色节点,如果我们直接按照第一种情况进行变色,那么就会导致uncle这条路径上始终少一个节点,这样导致节点失衡,所以我们要先进行旋转,旋转结束后parent成为了cur与grandfather的父节点,为了让黑色节点数量保持平衡,我们需要让parent变成黑色,让grandfather变成红色

双旋+变色

条件:grandfather为黑色,parent与cur为红色,uncle不存在或者uncle存在且为黑,并且parent与cur位于不同侧

解决办法:首先以parent为旋转点进行一次旋转,然后再以grandfather为旋转点进行一次旋转,旋转结束后让cur变成黑色,让grandfather变成红色。

分析:由于uncle是黑节点,cur与parent位于相反的两侧,如果我们旋转一次,结果还是这种情况,所以我们需要进行两次旋转,旋转结束后,为了保持黑色节点数量的平衡,我们需要将cur变成黑色,让grandfather变成红色

插入代码如下所示:

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 = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{cout << "插入值已经存在,请重新插入" << endl;return false;}}cur = new Node(kv);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;
}
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;if (subRL)subRL->_parent = parent;Node* grandfather = parent->_parent;parent->_parent = subR;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (grandfather->_left == parent){grandfather->_left = subR;}else{grandfather->_right = subR;}subR->_parent = grandfather;}}
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;if (subLR)subLR->_parent = parent;Node* grandfather = parent->_parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (grandfather->_left == parent){grandfather->_left = subL;}else{grandfather->_right = subL;}subL->_parent = grandfather;}
}

旋转逻辑在这篇文章介绍:

数据结构(五)——AVL树(平衡二叉搜索树)

红黑树的查找

红黑树的查找与二叉搜索树相同,都是通过遍历比较节点值与目标值的大小知道找到目标值所在的节点然后返回或者直到遍历完这颗树为止,其代码如下所示:

Node* Find(const pair<K, V>& kv)
{Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return cur;}}cout << "目标值不存在" << endl;return nullptr;
}

红黑树的验证

前面我们验证红黑树时使用的方法是验证平衡因子以及左右子树高度差,实际上我们是按照AVL树的规则进行验证的,那么对于红黑树而言我们要通过它的规则进行验证,检查最长路径不超过最短路径的二倍吗?这样显然是不现实的,那么我们是通过什么方法去验证呢?我们还是通过递归去验证,不同的是我们引入了一个新的参数——参考值,这个值是用来记录从根节点到空节点这一整条路径中黑色节点的数量的,我们用这个值作为参考去比较其他路径上黑色节点的数量是否与这个值相同。其代码如下所示:

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);
}

红黑树代码如下所示:

enum Colour
{BLACK,RED
};
template<class K, class V>
struct RBTreeNode
{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: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 = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{cout << "插入值已经存在,请重新插入" << endl;return false;}}cur = new Node(kv);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;if (subRL)subRL->_parent = parent;Node* grandfather = parent->_parent;parent->_parent = subR;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (grandfather->_left == parent){grandfather->_left = subR;}else{grandfather->_right = subR;}subR->_parent = grandfather;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;if (subLR)subLR->_parent = parent;Node* grandfather = parent->_parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (grandfather->_left == parent){grandfather->_left = subL;}else{grandfather->_right = subL;}subL->_parent = grandfather;}}Node* Find(const pair<K, V>& kv){Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return cur;}}cout << "目标值不存在" << endl;return nullptr;}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);}
private:Node* _root = nullptr;
};

小结

本篇文章我们着重介绍了红黑树的概念以及插入逻辑的实现,红黑树作为一颗不完全平衡二叉搜索树有着独特的地方,它是通过节点的颜色来判断是否平衡以及是否需要旋转操作,它省去了频繁旋转所带来的消耗,从而更加追求效率,总的来说,红黑树是一颗非常优秀的数据结构

以上就是本篇博客的主要内容,如果对您有所帮助的话,记得点赞、评论、关注加转发,您的支持就是我创作的最大动力

相关文章:

数据结构(六)——红黑树及模拟实现

目录 前言 红黑树的概念及性质 红黑树的效率 红黑树的结构 红黑树的插入 变色不旋转 单旋变色 双旋变色 插入代码如下所示&#xff1a; 红黑树的查找 红黑树的验证 红黑树代码如下所示&#xff1a; 小结 前言 在前面的文章我们介绍了AVL这一棵完全二叉搜索树&…...

【家政平台开发(48)】家政平台安全“攻防战”:渗透测试全解析

本【家政平台开发】专栏聚焦家政平台从 0 到 1 的全流程打造。从前期需求分析,剖析家政行业现状、挖掘用户需求与梳理功能要点,到系统设计阶段的架构选型、数据库构建,再到开发阶段各模块逐一实现。涵盖移动与 PC 端设计、接口开发及性能优化,测试阶段多维度保障平台质量,…...

Python爬虫-爬取全球股市涨跌幅和涨跌额数据

前言 本文是该专栏的第52篇,后面会持续分享python爬虫干货知识,记得关注。 本文中,笔者将基于Python爬虫,实现批量采集全球股市行情(亚洲,美洲,欧非,其他等)的各股市“涨跌幅”以及“涨跌额”数据。 具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。…...

解决 Vue 中 input 输入框被赋值后,无法再修改和编辑的问题

目录 需求&#xff1a; 出现 BUG&#xff1a; Bug 代码复现 解决问题&#xff1a; 解决方法1&#xff1a; 解决方法2 关于 $set() 的补充&#xff1a; 需求&#xff1a; 前段时间&#xff0c;接到了一个需求&#xff1a;在选择框中选中某个下拉菜单时&#xff0c;对应的…...

【差分隐私相关概念】瑞丽差分隐私(RDP)-瑞丽散度约束了贝叶斯因子后验变化

分步解释和答案&#xff1a; 在Rnyi差分隐私&#xff08;RDP&#xff09;框架中&#xff0c;通过贝叶斯因子和Rnyi散度的关系可以推导出关于后验变化的概率保证。以下是关键步骤的详细解释&#xff1a; 1. 贝叶斯因子的定义与分解 设相邻数据集 D D D 和 D ′ D D′&#x…...

vue3 onMounted 使用方法和注意事项

基础用法 / 语法糖写法 <script> import { onMounted } from vue;// 选项式 API 写法 export default {setup() {onMounted(() > {console.log(组件已挂载);});} } </script><script setup> onMounted(() > {console.log(组件已挂载); }); </scrip…...

Dockerfile 文件常见命令及其作用

Dockerfile 文件包含一系列命令语句&#xff0c;用于定义 Docker 镜像的内容、配置和构建过程。以下是一些常见的命令及其作用&#xff1a; FROM&#xff1a;指定基础镜像&#xff0c;后续的操作都将基于该镜像进行。例如&#xff0c;FROM python:3.9-slim-buster 表示使用 Pyt…...

前端快速入门——JavaScript函数、DOM

1.JavaScript函数 函数是一段可重复使用的代码块&#xff0c;它接受输入(参数)、执行特定任务&#xff0c;并返回输出。 <scricpt>function add(a,b){return ab;}let cadd(5,10);console.log(c); </script>2.JavaScript事件 JavaScript绑定事件的方法&#xff1…...

shell 编程之循环语句

目录 一、for 循环语句 二、while 循环语句 三、until 循环语句 四、总结扩展 1. 循环对比 2. 调试技巧 3. 易混淆点解析 4. 进阶技巧 一、for 循环语句 1. 基础概念 含义&#xff1a; 用于 遍历一个已知的列表&#xff0c;逐个执行同一组命令 核心作用&#xff1a…...

10【模块学习】LCD1602(二):6路温度显示+实时时钟

项目&#xff1a;6路温度显示实时时钟 1、6路温度显示①TempMenu.c文件的代码②TempMenu.h文件的代码③main.c文件的代码④Timer.c文件的代码⑤Delay.c文件的代码⑥Key.c文件的代码 2、实时时钟显示①BeiJingTime.c文件的代码②BeiJingTime.h文件的代码③main.c文件的代码如下④…...

Linux基础14

一、搭建LAMP平台 安装包&#xff1a;mariadb-server、php、php-mysqlnd、php-xml、php-json 搭建平台步骤&#xff1a; ​ php步骤&#xff1a; ​ 创建网页&#xff1a;index.php ​ 网页内编写php语言&#xff1a; > ​ eg&#xff1a;<?p…...

PDF处理控件Aspose.PDF指南:使用 C# 从 PDF 文档中删除页面

需要从 PDF 文档中删除特定页面&#xff1f;本快速指南将向您展示如何仅用几行代码删除不需要的页面。无论您是清理报告、跳过空白页&#xff0c;还是在共享前自定义文档&#xff0c;C# 都能让 PDF 操作变得简单高效。学习如何以编程方式从 PDF 文档中选择和删除特定页面&#…...

如何在不同版本的 Elasticsearch 之间以及集群之间迁移数据

作者&#xff1a;来自 Elastic Kofi Bartlett 当你想要升级一个 Elasticsearch 集群时&#xff0c;有时候创建一个新的独立集群并将数据从旧集群迁移到新集群会更容易一些。这让用户能够在不冒任何停机或数据丢失风险的情况下&#xff0c;在新集群上使用所有应用程序测试其所有…...

Vue3生命周期钩子详解

Vue 3 的生命周期钩子函数允许开发者在组件不同阶段执行特定逻辑。与 Vue 2 相比&#xff0c;Vue 3 在 Composition API 中引入了新名称&#xff0c;并废弃了部分钩子。以下是详细说明&#xff1a; 一、Vue 3 生命周期阶段与钩子函数 1. 组件创建阶段 setup() 替代 Vue 2 的 b…...

Day08【基于预训练模型分词器实现交互型文本匹配】

基于预训练模型分词器实现交互型文本匹配 目标数据准备参数配置数据处理模型构建主程序测试与评估总结 目标 本文基于预训练模型bert分词器BertTokenizer&#xff0c;将输入的文本以文本对的形式&#xff0c;送入到分词器中得到文本对的词嵌入向量&#xff0c;之后经过若干网络…...

npm和npx的作用和区别

npx 和 npm 是 Node.js 生态系统中两个常用的工具&#xff0c;它们有不同的作用和使用场景。 1. npm&#xff08;Node Package Manager&#xff09; 作用&#xff1a; npm 是 Node.js 的包管理工具&#xff0c;主要用于&#xff1a; 安装、卸载、更新项目依赖&#xff08;包&a…...

mysql按条件三表并联查询

下面为你呈现一个 MySQL 按条件三表并联查询的示例。假定有三个表&#xff1a;students、courses 和 enrollments&#xff0c;它们的结构和关联如下&#xff1a; students 表&#xff1a;包含学生的基本信息&#xff0c;有 student_id 和 student_name 等字段。courses 表&…...

C++学习之金融类安全传输平台项目git

目录 1.知识点概述 2.版本控制工具作用 3.git和SVN 4.git介绍 5.git安装 6.工作区 暂存区 版本库概念 7.本地文件添加到暂存区和提交到版本库 8.文件的修改和还原 9.查看提交的历史版本信息 10.版本差异比较 11.删除文件 12.本地版本管理设置忽略目录 13.远程git仓…...

CCF CSP 第36次(2024.12)(1_移动_C++)

CCF CSP 第36次&#xff08;2024.12&#xff09;&#xff08;1_移动_C&#xff09; 解题思路&#xff1a;思路一&#xff1a; 代码实现代码实现&#xff08;思路一&#xff09;&#xff1a; 时间限制&#xff1a; 1.0 秒 空间限制&#xff1a; 512 MiB 原题链接 解题思路&…...

7.thinkphp的路由

一&#xff0e;路由简介 1. 路由的作用就是让URL地址更加的规范和优雅&#xff0c;或者说更加简洁&#xff1b; 2. 设置路由对URL的检测、验证等一系列操作提供了极大的便利性&#xff1b; 3. 路由是默认开启的&#xff0c;如果想要关闭路由&#xff0c;在config/app.php配置…...

Browser-use 是连接你的AI代理与浏览器的最简单方式

AI MCP 系列 AgentGPT-01-入门介绍 Browser-use 是连接你的AI代理与浏览器的最简单方式 AI MCP(大模型上下文)-01-入门介绍 AI MCP(大模型上下文)-02-awesome-mcp-servers 精选的 MCP 服务器 AI MCP(大模型上下文)-03-open webui 介绍 是一个可扩展、功能丰富且用户友好的…...

(五)机器学习---决策树和随机森林

在分类问题中还有一个常用算法&#xff1a;就是决策树。本文将会对决策树和随机森林进行介绍。 目录 一.决策树的基本原理 &#xff08;1&#xff09;决策树 &#xff08;2&#xff09;决策树的构建过程 &#xff08;3&#xff09;决策树特征选择 &#xff08;4&#xff0…...

【项目管理】第16章 项目采购管理-- 知识点整理

项目管理-相关文档&#xff0c;希望互相学习&#xff0c;共同进步 风123456789&#xff5e;-CSDN博客 &#xff08;一&#xff09;知识总览 项目管理知识域 知识点&#xff1a; &#xff08;项目管理概论、立项管理、十大知识域、配置与变更管理、绩效域&#xff09; 对应&…...

2025年4月15日 百度一面 面经

目录 1. 代理相关 从静态代理到动态代理 2. cglib可以代理被final修饰的类吗,为什么 3. JVM 体系结构 4. 垃圾回收算法 5. 什么是注解 如何使用 底层原理 6. synchronized和reentrantlock 7. 讲一下你项目中 redis的分布式锁 与java自带的锁有啥区别 8. post 请求和 ge…...

从图像“看出动作”

&#x1f4d8; 第一部分&#xff1a;运动估计&#xff08;Motion Estimation&#xff09; &#x1f9e0; 什么是运动估计&#xff1f; 简单说&#xff1a; &#x1f449; 给你一段视频&#xff0c;计算机要“看懂”里面什么东西动了、往哪动了、有多快。 比如&#xff1a; 一…...

鸿蒙案例---生肖抽卡

案例源码&#xff1a; Zodiac_cards: 鸿蒙生肖抽奖卡片 效果演示 初始布局 1. Badge 角标组件 此处为语雀内容卡片&#xff0c;点击链接查看&#xff1a;https://www.yuque.com/kevin-nzthp/lvl039/rccg0o4pkp3v6nua 2. Grid 布局 // 定义接口 interface ImageCount {url:…...

达梦数据库-学习-18-ODBC数据源配置(Linux)

一、环境信息 名称值CPU12th Gen Intel(R) Core(TM) i7-12700H操作系统CentOS Linux release 7.9.2009 (Core)内存4G逻辑核数2DM版本1 DM Database Server 64 V8 2 DB Version: 0x7000c 3 03134284194-20240703-234060-20108 4 Msg Versi…...

Conda 入门指令教程

Conda 入门指令教程 Conda 是一个强大的包和环境管理工具&#xff0c;广泛应用于数据科学和机器学习项目中。本文将介绍 Conda 的常用指令&#xff0c;帮助你快速上手。 1. Conda 基础操作 查看 Conda 版本 conda --version显示当前安装的 Conda 版本。 更新 Conda conda…...

宿舍管理系统(servlet+jsp)

宿舍管理系统(servletjsp) 宿舍管理系统是一个用于管理学生宿舍信息的平台&#xff0c;支持超级管理员、教师端和学生端三种用户角色登录。系统功能包括宿舍管理员管理、学生管理、宿舍楼管理、缺勤记录、添加宿舍房间、心理咨询留言板、修改密码和退出系统等模块。宿舍管理员…...

驱动-兼容不同设备-container_of

驱动兼容不同类型设备 在 Linux 驱动开发中&#xff0c;container_of 宏常被用来实现一个驱动兼容多种不同设备的架构。这种设计模式在 Linux 内核中非常常见&#xff0c;特别 是在设备驱动模型中。linux内核的主要开发语言是C&#xff0c;但是现在内核的框架使用了非常多的面向…...