LeetCode刷题总结 | 图论3—并查集

并查集理论基础

1.背景

首先要知道并查集可以解决什么问题呢?

并查集常用来解决连通性问题。大白话就是当我们需要判断两个元素是否在同一个集合里的时候,我们就要想到用并查集。

并查集主要有两个功能:

  • 将两个元素添加到一个集合中。
  • 判断两个元素在不在同一个集合

接下来围绕并查集的这两个功能来展开讲解。

2.原理讲解

从代码层面,我们如何将两个元素添加到同一个集合中呢。

可能会想到:可以把他放到同一个数组里或者set 或者 map 中,这样就表述两个元素在同一个集合。

那么问题来了,对这些元素分门别类,可不止一个集合,可能是很多集合,成百上千,那么要定义这么多个数组吗?

那可以定义一个二维数组吗?但定义一个二维数组的话,如果我们要判断两个元素是否在同一个集合里的时候 我们又能怎么办? 只能把而二维数组都遍历一遍。而且每当想添加一个元素到某集合的时候,依然需要把把二维数组组都遍历一遍,才知道要放在哪个集合里。

这仅仅是一个粗略的思路,如果沿着这个思路去实现代码,非常复杂,因为管理集合还需要很多逻辑。

那么我们来换一个思路来看看。

我们将三个元素A,B,C (分别是数字)放在同一个集合,其实就是将三个元素连通在一起,如何连通呢。

只需要用一个一维数组来表示,即:father[A] = B,father[B] = C 这样就表述 A 与 B 与 C连通了(有向连通图)。

代码如下:

// 将v,u 这条边加入并查集
void join(int u, int v) {u = find(u); // 寻找u的根v = find(v); // 寻找v的根if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回father[v] = u;
}

可能会想,这样我可以知道 A 连通 B,因为 A 是索引下标,根据 father[A]的数值就知道 A 连通 B。那怎么知道 B 连通 A呢?

我们的目的是判断这三个元素是否在同一个集合里,知道 A 连通 B 就已经足够了。

这里要讲到寻根思路,只要 A ,B,C 在同一个根下就是同一个集合。

给出A元素,就可以通过 father[A] = B,father[B] = C,找到根为 C。

给出B元素,就可以通过 father[B] = C,找到根也为为 C,说明 A 和 B 是在同一个集合里。 那么第一段代码里find函数是如何实现的呢?其实就是通过数组下标找到数组元素,一层一层寻根过程,代码如下:

// 并查集里寻根的过程
int find(int u) {if (u == father[u]) return u; // 如果根就是自己,直接返回else return find(father[u]); // 如果根不是自己,就根据数组下标一层一层向下找
}

如何表示 C 也在同一个元素里呢? 我们需要father[C] = C,即C的根也为C,这样就方便表示 A,B,C 都在同一个集合里了。

所以father数组初始化的时候要 father[i] = i,默认自己指向自己。

代码如下:

// 并查集初始化
void init() {for (int i = 0; i < n; ++i) {father[i] = i;}
}

最后我们如何判断两个元素是否在同一个集合里,如果通过 find函数 找到 两个元素属于同一个根的话,那么这两个元素就是同一个集合,代码如下:

// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {u = find(u);v = find(v);return u == v;
}

3.路径压缩

在实现 find 函数的过程中,我们知道,通过递归的方式,不断获取father数组下标对应的数值,最终找到这个集合的根。

搜索过程像是一个多叉树中从叶子到根节点的过程,如图:
在这里插入图片描述

如果这棵多叉树高度很深的话,每次find函数 去寻找根的过程就要递归很多次。

我们的目的只需要知道这些节点在同一个根下就可以,所以对这棵多叉树的构造只需要这样就可以了,如图:

在这里插入图片描述

除了根节点其他所有节点都挂载根节点下,这样我们在寻根的时候就很快,只需要一步,

如果我们想达到这样的效果,就需要 路径压缩,将非根节点的所有节点直接指向根节点。 那么在代码层面如何实现呢?

我们只需要在递归的过程中,让 father[u] 接住 递归函数 find(father[u]) 的返回结果。

因为 find 函数向上寻找根节点,father[u] 表述 u 的父节点,那么让 father[u] 直接获取 find函数 返回的根节点,这样就让节点 u 的父节点 变成根节点。

代码如下,注意看注释,路径压缩就一行代码:

// 并查集里寻根的过程
int find(int u) {if (u == father[u]) return u;else return father[u] = find(father[u]); // 路径压缩
}

以上代码在C++中,可以用三元表达式来精简一下,代码如下:

int find(int u) {return u == father[u] ? u : father[u] = find(father[u]);
}

4.代码模板

那么此时并查集的模板就出来了, 整体模板C++代码如下:

int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好
vector<int> father = vector<int> (n, 0); // C++里的一种数组结构// 并查集初始化
void init() {for (int i = 0; i < n; ++i) {father[i] = i;}
}
// 并查集里寻根的过程
int find(int u) {return u == father[u] ? u : father[u] = find(father[u]); // 路径压缩
}// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {u = find(u);v = find(v);return u == v;
}// 将v->u 这条边加入并查集
void join(int u, int v) {u = find(u); // 寻找u的根v = find(v); // 寻找v的根if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回father[v] = u;
}

通过模板,我们可以知道,并查集主要有三个功能。

  • 寻找根节点,函数:find(int u),也就是判断这个节点的祖先节点是哪个
  • 将两个节点接入到同一个集合,函数:join(int u, int v),将两个节点连在同一个根节点上
  • 判断两个节点是否在同一个集合,函数:isSame(int u, int v),就是判断两个节点是不是同一个根节点

5.常见误区

模板中的 join 函数里的这段代码:

u = find(u); // 寻找u的根
v = find(v); // 寻找v的根
if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回

与 isSame 函数的实现是不是重复了? 如果抽象一下呢,代码如下:

// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {u = find(u);v = find(v);return u == v;
}
// 将v->u 这条边加入并查集
void join(int u, int v) {if (isSame) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回father[v] = u;
}

这样写可以吗? 好像看出去没问题,而且代码更精简了。

其实这么写是有问题的,在join函数中 我们需要寻找 u 和 v 的根,然后再进行连线在一起,而不是直接 用 u 和 v 连线在一起。

举一个例子:

join(1, 2);
join(3, 2);

此时构成的图是这样的:
在这里插入图片描述

此时问 1,3是否在同一个集合,我们调用 join(1, 2); join(3, 2); 很明显本意要表示 1,3是在同一个集合。

但我们来看一下代码逻辑,当我们调用 isSame(1, 3)的时候,find(1) 返回的是1,find(3)返回的是3。 return 1 == 3 返回的是false,代码告诉我们 1 和 3 不在同一个集合,这明显不符合我们的预期,所以问题出在哪里?

问题出在我们精简的代码上,即 join 函数 一定要先 通过find函数寻根再进行关联。

如果find函数是这么实现,再来看一下逻辑过程。

void join(int u, int v) {u = find(u); // 寻找u的根v = find(v); // 寻找v的根if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回father[v] = u;
}

分别将 这两对元素加入集合。

join(1, 2);
join(3, 2);
当执行join(3, 2)的时候,会先通过find函数寻找 3的根为3,2的根为1 (第一个join(1, 2),将2的根设置为1),所以最后是将1 指向 3。

构成的图是这样的:

在这里插入图片描述

因为在join函数里,我们有find函数进行寻根的过程,这样就保证元素 1,2,3在这个有向图里是强连通的。

此时我们在调用 isSame(1, 3)的时候,find(1) 返回的是3,find(3) 返回的也是3,return 3 == 3 返回的是true,即告诉我们 元素 1 和 元素3 是 在同一个集合里的。

6.复杂度分析

空间复杂度: O(n) ,申请一个father数组。

关于时间复杂度,如果想精确表达出来需要繁琐的数学证明,就不在本篇讲解范围内了,大家感兴趣可以自己去深入研究。

这里做一个简单的分析思路。

路径压缩后的并查集时间复杂度在O(logn)与O(1)之间,且随着查询或者合并操作的增加,时间复杂度会越来越趋于O(1)。

在第一次查询的时候,相当于是n叉树上从叶子节点到根节点的查询过程,时间复杂度是logn,但路径压缩后,后面的查询操作都是O(1),而 join 函数 和 isSame函数 里涉及的查询操作也是一样的过程。

并查集例题

并查集例题1:1971 寻找图中是否存在路径(easy)

有一个具有 n 个顶点的 双向 图,其中每个顶点标记从 0 到 n - 1(包含 0 和 n - 1)。图中的边用一个二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点对由 最多一条 边连接,并且没有顶点存在与自身相连的边。

请你确定是否存在从顶点 source 开始,到顶点 destination 结束的 有效路径 。

给你数组 edges 和整数 n、source 和 destination,如果从 source 到 destination 存在 有效路径 ,则返回 true,否则返回 false 。

思路:标准并查集问题,利用edges和join函数创建并查集再判断source和destination根节点是否相同即可,注意最开始不要忘了进行init()初始化

代码实现:

class Solution {
private:int n = 200005; // 节点数量 20000vector<int> father = vector<int> (n, 0); // C++里的一种数组结构// 并查集初始化void init() {for (int i = 0; i < n; ++i) { father[i] = i;}}// 并查集里寻根的过程int find(int u) {return u == father[u] ? u : father[u] = find(father[u]);}// 判断 u 和 v是否找到同一个根bool isSame(int u, int v) {u = find(u);v = find(v);return u == v;}// 将v->u 这条边加入并查集void join(int u, int v) {u = find(u); // 寻找u的根v = find(v); // 寻找v的根if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回father[v] = u;}public:bool validPath(int n, vector<vector<int>>& edges, int source, int destination) {init();for (int i = 0; i < edges.size(); i++) {join(edges[i][0], edges[i][1]);}return isSame(source, destination);}
};

并查集例题2:684 冗余连接(medium)

树可以看成是一个连通且 无环 的 无向 图。

给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。

请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的那个。

思路:简单思考一下发现还是套并查集模板就行,注意不要忘了初始化。

题目说是无向图,返回一条可以删去的边,使得结果图是一个有着N个节点的树(即:只有一个根节点)。如果有多个答案,则返回二维数组中最后出现的边。

那么我们就可以从前向后遍历每一条边(因为优先让前面的边连上,如果题目中说:如果有多个答案,则返回二维数组中最前出现的边。 那我们就要 从后向前遍历每一条边了)

  • 边的两个节点如果不在同一个集合,就加入集合(即:同一个根节点)。

  • 如果边的两个节点已经出现在同一个集合里,说明着边的两个节点已经连在一起了,再加入这条边一定就出现环了,那么返回这条边。

代码实现1:

class Solution {
private:int n = 1005; // 节点数量3 到 1000vector<int> father = vector<int> (n, 0); // C++里的一种数组结构// 并查集初始化void init() {for (int i = 0; i < n; ++i) {father[i] = i;}}// 并查集里寻根的过程int find(int u) {return u == father[u] ? u : father[u] = find(father[u]);}// 判断 u 和 v是否找到同一个根bool isSame(int u, int v) {u = find(u);v = find(v);return u == v;}// 将v->u 这条边加入并查集void join(int u, int v) {u = find(u); // 寻找u的根v = find(v); // 寻找v的根if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回father[v] = u;
}
public:vector<int> findRedundantConnection(vector<vector<int>>& edges) {init();for (int i = 0; i < edges.size(); i++) {if (isSame(edges[i][0], edges[i][1])) return edges[i];else join(edges[i][0], edges[i][1]);}return {};}
};

代码实现2:

class Solution {
public:int sum;vector<int> father;void init(){father.resize(sum + 1);for(int i = 0; i < sum; i++){father[i] = i;}}int find(int u){return father[u] == u? u: father[u] = find(father[u]);}bool isSame(int u, int v){u = find(u);v = find(v);return u == v;}void join(int u, int v){u = find(u);v = find(v);if(u == v) return;father[v] = u;}vector<int> findRedundantConnection(vector<vector<int>>& edges) {sum = edges.size();init();for(int i = 0; i < sum; i++){if(isSame(edges[i][0], edges[i][1]))return edges[i];join(edges[i][0], edges[i][1]);}return {};}
};

并查集例题3:685 冗余连接 II(hard)

在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。

输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。

结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。

返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

思路:这道题目是hard题,相较于单纯的套并查集模板其实要考虑的还蛮多的,需要进行分情况讨论,但总体思路还是建立在并查集的基础上的

首先重点读懂题目中的这句

该图由一个有着N个节点 (节点值不重复1, 2, ..., N) 的树及一条附加的边构成。
附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。

这说明题目中的图原本是是一棵树,只不过在不增加节点的情况下多加了一条边!

还有

若有多个答案,返回最后出现在给定二维数组的答案。

这说明在两条边都可以删除的情况下,要删顺序靠后的!

因为是有向图,所以本题不一定成环,还有可能是某一个节点出现了两个父节点(也即该节点的入度为2),所以要分情况:

  • 情形1:入度为2
  • 情形2:出现有向环

情形1(入度为2):

首先先计算节点的入度,edges[i][1] 表示的节点都是 箭头指向的节点,即这个节点有一个入度!。

所以,统计入度的代码如下:

int inDegree[N] = {0}; // 记录节点入度
n = edges.size(); // 边的数量
for (int i = 0; i < n; i++) {inDegree[edges[i][1]]++; // 统计入度
}

入度为2的情况,一定是删除指向入度为2的节点的两条边其中的一条,如果删了一条,判断这个图是一个树,那么这条边就是答案,同时注意要从后向前遍历,因为如果两条边删哪一条都可以成为树,就删最后那一条

代码如下:

vector<int> vec; // 记录入度为2的边(如果有的话就两条边)
// 找入度为2的节点所对应的边,注意要倒序,因为优先返回最后出现在二维数组中的答案
for (int i = n - 1; i >= 0; i--) {if (inDegree[edges[i][1]] == 2) {vec.push_back(i);}
}// 处理情况1
// 如果有入度为2的节点,那么一定是两条边里删一个,看删哪个可以构成树
if (vec.size() > 0) {if (isTreeAfterRemoveEdge(edges, vec[0])) {return edges[vec[0]];} else {return edges[vec[1]];}
}

情况2(形成有向环):

明确没有入度为2的情况,那么一定有向环,找到构成环的边就是要删除的边。

可以定义一个函数,代码如下:

// 在有向图里找到删除的那条边,使其变成树,返回值就是要删除的边
vector<int> getRemoveEdge(const vector<vector<int>>& edges)

综合上面的叙述我们要实现两个最为关键的函数:

isTreeAfterRemoveEdge() 判断删一个边之后是不是树了
getRemoveEdge() 确定图中一定有了有向环,那么要找到需要删除的那条边

此时应该是用到并查集了,因为如果两个点所在的边在添加图之前如果就可以在并查集里找到了相同的根,那么这条边添加上之后 这个图一定不是树了

代码实现:

class Solution {
private:static const int N = 1010; // 如题:二维数组大小的在3到1000范围内int father[N];int n; // 边的数量// 并查集初始化void init() {for (int i = 1; i <= n; ++i) {father[i] = i;}}// 并查集里寻根的过程int find(int u) {return u == father[u] ? u : father[u] = find(father[u]);}// 将v->u 这条边加入并查集void join(int u, int v) {u = find(u);v = find(v);if (u == v) return ;father[v] = u;}// 判断 u 和 v是否找到同一个根bool same(int u, int v) {u = find(u);v = find(v);return u == v;}// 在有向图里找到删除的那条边,使其变成树vector<int> getRemoveEdge(const vector<vector<int>>& edges) {init(); // 初始化并查集for (int i = 0; i < n; i++) { // 遍历所有的边if (same(edges[i][0], edges[i][1])) { // 构成有向环了,就是要删除的边return edges[i];}join(edges[i][0], edges[i][1]);}return {};}// 删一条边之后判断是不是树bool isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int deleteEdge) {init(); // 初始化并查集for (int i = 0; i < n; i++) {if (i == deleteEdge) continue;if (same(edges[i][0], edges[i][1])) { // 构成有向环了,一定不是树return false;}join(edges[i][0], edges[i][1]);}return true;}
public:vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {int inDegree[N] = {0}; // 记录节点入度n = edges.size(); // 边的数量for (int i = 0; i < n; i++) {inDegree[edges[i][1]]++; // 统计入度}vector<int> vec; // 记录入度为2的边(如果有的话就两条边)// 找入度为2的节点所对应的边,注意要倒序,因为优先返回最后出现在二维数组中的答案for (int i = n - 1; i >= 0; i--) {if (inDegree[edges[i][1]] == 2) {vec.push_back(i);}}// 处理情况1// 如果有入度为2的节点,那么一定是两条边里删一个,看删哪个可以构成树if (vec.size() > 0) {if (isTreeAfterRemoveEdge(edges, vec[0])) {return edges[vec[0]];} else {return edges[vec[1]];}}// 处理情况2// 明确没有入度为2的情况,那么一定有有向环,找到构成环的边返回就可以了return getRemoveEdge(edges);}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/824114.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安装GPT 学术优化 (GPT Academic)@FreeBSD

GPT 学术优化 (GPT Academic)是一个非常棒的项目 可以帮助我们完成中科院的一些日常工作。 官网&#xff1a;GitHub - binary-husky/gpt_academic: 为GPT/GLM等LLM大语言模型提供实用化交互接口&#xff0c;特别优化论文阅读/润色/写作体验&#xff0c;模块化设计&#xff0c;…

【Linux系列】Ctrl + R 的使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

EelasticSearch的docker安装-----》es客户端使用!!!

1.Docker安装 docker run -d --name es7 -e ES_JAVA_POTS"-Xms256m -Xmx256m" -e "discovery.typesingle-node" -v /opt/es7/data/:/usr/share/elasticsearch/data -p 9200:9200 -p 9300:9300 elasticsearch:7.14.02.客户端UI工具&#xff0c;Edge浏览器…

Linux(磁盘管理与文件系统)

目录 1. 磁盘基础 1.1 磁盘结构 1.2 MBR 1.3 磁盘分区结构 2. 文件系统类型 2.1 XFS文件系统 2.2 SWAP 2.3 fdisk命令 2.4 创建新硬盘 3.创建文件系统 3.1 mkfs 3.2 挂载、卸载文件系统 3.3 查看磁盘使用情况 1. 磁盘基础 1.1 磁盘结构 磁盘的物理结构 盘片:硬…

35. UE5 RPG制作火球术技能

接下来&#xff0c;我们将制作技能了&#xff0c;总算迈进了一大步。首先回顾一下之前是如何实现技能触发的&#xff0c;然后再进入正题。 如果想实现我之前的触发方式的&#xff0c;请看此栏目的31-33篇文章&#xff0c;讲解了实现逻辑&#xff0c;这里总结一下&#xff1a; …

微服务拆分:打造高性能、高扩展的未来架构

目录 一、微服务介绍 二、主链路规划 2.1 业务完整性 2.2 转化率重因子 2.3 流量端占比 2.4 现金水库 三、如何识别主链路 3.1 导流端 3.2 转化端 3.3 漏斗中部&#xff1a;订单转化 3.4 漏斗底部&#xff1a;下单 四、总结 一、微服务介绍 单体应用将所有的功能都…

微服务架构与Dubbo

一、微服务架构 微服务架构是一种架构概念&#xff0c;旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。 分布式系统式若干独立系统的集合&#xff0c;但是用户使用起来好像是在使用一套系统。 和微服务对应的是单体式开发&#xff0c;即所有的功能打包在一个WAR…

搭建Zookeeper完全分布式集群(CentOS 9 )

ZooKeeper是一个开源的分布式协调服务&#xff0c;它为分布式应用提供了高效且可靠的分布式协调服务&#xff0c;并且是分布式应用保证数据一致性的解决方案。该项目由雅虎公司创建&#xff0c;是Google Chubby的开源实现。 分布式应用可以基于ZooKeeper实现诸如数据发布/订阅…

Vue的生命周期的详解

Vue的生命周期是每个使用Vue框架的前端人员都需要掌握的知识&#xff0c;以此作为记录。 Vue的生命周期就是vue实例从创建到销毁的全过程&#xff0c;也就是new Vue() 开始就是vue生命周期的开始。Vue 实例有⼀个完整的⽣命周期&#xff0c;也就是从开始创建、初始化数据、编译…

算法|最大堆、最小堆和堆排序的实现(JavaScript)

一些概念 堆&#xff1a;特殊的完全二叉树&#xff0c;具有特定性质的完全二叉树。大根堆&#xff1a;父节点 > 子节点小根堆&#xff1a;父节点 < 子节点 二叉堆也属于完全二叉树&#xff0c;所以可以用数组表示。 若下标从1开始&#xff0c;左节点为 2*i &#xff0…

树莓派4B+Debian(Raspbian)+开机自启动Python文件

非系统全面的教程&#xff0c;只是记录操作过程中的问题和解决方案。 说明1&#xff1a;树莓派的不同版本&#xff0c;要查看CPU的位数、内存大小&#xff1b;从而确定安装的raspbian的对应版本 若是对应64位的OS&#xff0c;安装成了32位的OS&#xff0c;可能会对系统中安装…

5、JVM-G1详解

G1收集器 -XX:UseG1GC G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征. G1将Java堆划分为多个大小相等的独立区域&#xff08;Region&#xff09;&#xff0c;JVM目标…

代码学习记录25---单调栈

随想录日记part45 t i m e &#xff1a; time&#xff1a; time&#xff1a; 2024.04.17 主要内容&#xff1a;今天开始要学习单调栈的相关知识了&#xff0c;今天的内容主要涉及&#xff1a;每日温度 &#xff1b;下一个更大元素 I 739. 每日温度 496.下一个更大元素 I Topic…

书生·浦语大模型全链路开源体系-第4课

书生浦语大模型全链路开源体系-第4课 书生浦语大模型全链路开源体系-第4课相关资源XTuner 微调 LLMXTuner 微调小助手认知环境安装前期准备启动微调模型格式转换模型合并微调结果验证 将认知助手上传至OpenXLab将认知助手应用部署到OpenXLab使用XTuner微调多模态LLM前期准备启动…

Mac电脑上有什么好玩的格斗游戏 《真人快打1》可以在苹果电脑上玩吗

你是不是喜欢玩格斗游戏&#xff1f;你是不是想在你的Mac电脑上体验一些刺激和激烈的对战&#xff1f;在这篇文章中&#xff0c;我们将介绍Mac电脑上有什么好玩的格斗游戏&#xff0c;以及《真人快打1》可以在苹果电脑上玩吗。 一、Mac电脑上有什么好玩的格斗游戏 格斗游戏是…

命令模式

命令模式&#xff1a;将一个请求封装为一个对象&#xff0c;从而使你可用不同的请求对客户进行参数化&#xff1b;对请求排队或记录请求日志&#xff0c;以及支持可撤销的操作。 命令模式的好处&#xff1a; 1、它能较容易地设计一个命令队列&#xff1b; 2、在需要的情况下&a…

gpt-6有望成为通用工具

OpenAI CEO山姆奥特曼&#xff08;Sam Altman&#xff09;在最新的博客访谈中&#xff0c;提到gpt-6有望成为通用工具。 奥特曼还认为&#xff0c;目前的模型不够聪明&#xff0c;“使用GPT-2进行科学研究曾被认为是不切实际的想法。而如今&#xff0c;虽然人们使用GPT-4进行科…

获取公募基金净值【数据分析系列博文】

摘要 从指定网址获取公募基金净值数据&#xff0c;快速解析并存储数据。 &#xff08;该博文针对自由学习者获取数据&#xff1b;而在投顾、基金、证券等公司&#xff0c;通常有Wind、聚源、通联等厂商采购的数据&#xff09; 导入所需的库&#xff1a;代码导入了一些常用的库…

OpenCV从入门到精通实战(八)——基于dlib的人脸关键点定位

本文使用Python库dlib和OpenCV来实现面部特征点的检测和标注。 下面是代码的主要步骤和相关的代码片段&#xff1a; 步骤一&#xff1a;导入必要的库和设置参数 首先&#xff0c;代码导入了必要的Python库&#xff0c;并通过argparse设置了输入图像和面部标记预测器的参数。…

ns3.36以后的版本中_ns3命令的原理_CMAKE的使用以及一些例子

本文主要来自于ns3的官方文档&#xff1a;4.3. Working with CMake — Manual&#xff0c;不过只包含以下部分&#xff1a; 4.3. 使用CMake 4.3.1. 配置项目 4.3.1.1. 使用ns3配置项目 4.3.1.2. 使用CMake配置项目 4.3.2. 手动刷新CMake缓存 4.3.3. 建设项目 4.3.3.1. 使用ns3…