并查集(上)

并查集简要介绍:

我们先讲并查集的一般使用场景,之后再讲并查集的具体细节以及原理。

并查集的使用一般是如下的场景:
  1. 一开始每个元素都拥有自己的集合,在自己的集合里只有这个元素自己。
  2. f i n d ( i ) find(i) find(i):查找 i i i所在集合的代表元素,代表元素来代表 i i i所在的集合。
  3. b o o l i s S a m e S e t ( a , b ) bool ~isSameSet(a,b) bool isSameSet(a,b):判断 a a a b b b在不在一个集合里。
  4. v o i d u n i o n ( a , b ) void ~union(a,b) void union(a,b) a a a所在集合所有元素 与 b b b所在集合所有元素 合并成一个集合。
  5. 各种操作单次调用的均摊时间复杂度为 O ( 1 ) O(1) O(1)

并查集的原理也比较简单,看下面原理图解就明白了:
在这里插入图片描述

​ 这张图清晰地表现了 u n i o n union union操作的原理,我们很容易分析出来,时间复杂度是 O ( 1 ) O(1) O(1),两个集合进行合并,本质上就是两个集合的代表元素进行合并,如上图,我们选了集合2代表元素d作为新集合代表,那么集合1代表元素d就要解除自环并指向d,d仍然保持自环。

在这里插入图片描述

f i n d ( i ) find(i) find(i)操作就是从元素 i i i开始沿着指针向上找,当找到自环元素时,说明找到了 i i i元素所在集合的代表元素,这个代表元素就是我们要找的结果。

i s S a m e S e t ( a , b ) isSameSet(a,b) isSameSet(a,b)操作就是找 a a a b b b分别所在集合的代表元素,如果两个元素所在集合的代表元素相同,则说明这两个元素在同一个集合里。

并查集的数组实现

在前两张图中,最明显的是我们使用了指针这个概念,我的第一反应是用链表去实现并查集,但是链表查找起来太麻烦了,参考链表的静态数组实现,我们对并查集也有对应的数组实现。

并查集数组实现方式的关键成员:

  • int father[] f a t h e r [ i ] father[i] father[i]中存储的是元素i的父元素 j j j,也就是说并查集中存在关系 i − > j i -> j i>j
  • int size[] s i z e [ i ] size[i] size[i]中存储的是元素 i i i所代表的集合的元素个数,用于 u n i o n ( ) union() union()函数中进行小挂大优化,小集合挂载到大集合下。
  • int stack[] s t a c k [ ] stack[] stack[]用于 F i n d ( i ) Find(i) Find(i)函数中,对数据进行扁平化处理。

扁平化处理原理如下图:

微信图片_20240919163600

上图中是一个比较极端的并查集,现在我们依次找 a , b , c , d , e , f , g a,b,c,d,e,f,g a,b,c,d,e,f,g 的代表元素,如果我们不做扁平化处理,只利用简单循环去做的话,需要找 7 + 6 + 5 + 4 + 3 + 2 + 1 7+6+5+4+3+2+1 7+6+5+4+3+2+1次,而做了扁平化处理的话,中间某个节点被重复遍历的次数就会减少,最优情况下,只需要找7次。这个最优情况也就是一开始就找最底层元素 a a a的代表元素。

$ Problem1 $ 并查集的实现 牛客

https://www.nowcoder.com/practice/e7ed657974934a30b2010046536a5372

描述

给定一个没有重复值的整形数组arr,初始时认为arr中每一个数各自都是一个单独的集合。请设计一种叫UnionFind的结构,并提供以下两个操作。

  1. boolean isSameSet(int a, int b): 查询a和b这两个数是否属于一个集合
  2. void union(int a, int b): 把a所在的集合与b所在的集合合并在一起,原本两个集合各自的元素以后都算作同一个集合

[要求]

如果调用isSameSetunion的总次数逼近或超过O(N),请做到单次调用isSameSetunion方法的平均时间复杂度为O(1)

输入描述:

第一行两个整数N, M。分别表示数组大小、操作次数
接下来M行,每行有一个整数opt
若opt = 1,后面有两个数x, y,表示查询(x, y)这两个数是否属于同一个集合
若opt = 2,后面有两个数x, y,表示把x, y所在的集合合并在一起

输出描述:

对于每个opt = 1的操作,若为真则输出"Yes",否则输出"No"

示例1

输入:

4 5
1 1 2
2 2 3
2 1 3
1 1 1
1 2 3

输出:

No
Yes
Yes

说明:

每次2操作后的集合为
({1}, {2}, {3}, {4})
({1}, {2, 3}, {4})
({1, 2, 3}, {4})

套用并查集模板即可,解决代码如下:

#include<cstdio>
#include <iostream>
#include <vector>
using namespace std;int MAXN = 1000000;
vector<int> father(MAXN);
vector<int> setSize(MAXN);
vector<int> stack(MAXN);  // 利用栈进行扁平化处理void build(int N) {for (int i = 0; i < N; i++) {father[i] = i;setSize[i] = 1;}
}int Find(int i) {int Size = 0;while (father[i] != i) {stack[Size++] = i;i = father[i];}// 沿途节点收集完毕,找到代表元素,同时也是栈中所有元素的代表元素while (Size > 0) {father[stack[--Size]] = i;}return i;
}void Union(int x, int y) {int fx = Find(x);int fy = Find(y);if (fx != fy) {if (setSize[fx] > setSize[fy]) {setSize[fx] += setSize[fy];father[fy] = fx;} else {setSize[fy] += setSize[fx];father[fx] = fy;}}
}bool isSameSet(int x, int y) {return Find(x) == Find(y);
}int main() {int N, M;scanf("%d", &N);scanf("%d", &M);build(N);vector<vector<int>> Edge(M, vector<int>(3));for (int i = 0; i < M; i++) {int opt, x, y;scanf("%d", &opt);scanf("%d", &x);scanf("%d", &y);Edge[i][0] = opt;Edge[i][1] = x;Edge[i][2] = y;}for (int i = 0; i < M; i++) {if(Edge[i][0] == 1){if(isSameSet(Edge[i][1],Edge[i][2])){printf("Yes\n");}else{printf("No\n");}}if(Edge[i][0] == 2){Union(Edge[i][1], Edge[i][2]);}}return 0;
}

并查集的小挂大操作在一些场景中是不必要的,对于不进行小挂大操作,我们有另一种实现并查集的方式,用下面这个例子来给你详细说明。

P r o b l e m 2 Problem2 Problem2 【模板】并查集 洛谷P3367

如题,现在有一个并查集,你需要完成合并和查询操作。

第一行包含两个整数 N , M N,M N,M ,表示共有 N N N 个元素和 M M M 个操作。

接下来 M M M 行,每行包含三个整数 Z i , X i , Y i Z_i,X_i,Y_i Zi,Xi,Yi

Z i = 1 Z_i=1 Zi=1 时,将 X i X_i Xi Y i Y_i Yi 所在的集合合并。

Z i = 2 Z_i=2 Zi=2 时,输出 X i X_i Xi Y i Y_i Yi 是否在同一集合内,是的输出 Y ;否则输出 N

输出格式

对于每一个 Z i = 2 Z_i=2 Zi=2 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N

输入:

4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4

输出

N
Y
N
Y

提示

对于 30 % 30\% 30% 的数据, N ≤ 10 N \le 10 N10 M ≤ 20 M \le 20 M20

对于 70 % 70\% 70% 的数据, N ≤ 100 N \le 100 N100 M ≤ 1 0 3 M \le 10^3 M103

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 4 1\le N \le 10^4 1N104 1 ≤ M ≤ 2 × 1 0 5 1\le M \le 2\times 10^5 1M2×105 1 ≤ X i , Y i ≤ N 1 \le X_i, Y_i \le N 1Xi,YiN Z i ∈ { 1 , 2 } Z_i \in \{ 1, 2 \} Zi{1,2}


#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;int MAXN = 1000000;
vector<int> father(MAXN);void build(int N) {for (int i = 1; i <= N; i++) {father[i] = i;}
}int Find(int i) {// 利用堆栈进行扁平化处理if (i != father[i]) {father[i] = Find(father[i]);}return father[i];
}void Union(int x, int y) { father[Find(x)] = Find(y); }bool isSameSet(int x, int y) { return Find(x) == Find(y); }int main() {int N, M;scanf("%d", &N);scanf("%d", &M);build(N);vector<vector<int>> Edge(M, vector<int>(3));for (int i = 0; i < M; i++) {int opt, x, y;scanf("%d", &opt);scanf("%d", &x);scanf("%d", &y);Edge[i][0] = opt;Edge[i][1] = x;Edge[i][2] = y;}for (int i = 0; i < M; i++) {if (Edge[i][0] == 2) {if (isSameSet(Edge[i][1], Edge[i][2])) {printf("Y\n");} else {printf("N\n");}}if (Edge[i][0] == 1) {Union(Edge[i][1], Edge[i][2]);}}return 0;
}
P r o b l e m 3 Problem3 Problem3 情侣牵手LeetCode765

n 对情侣坐在连续排列的 2n 个座位上,想要牵到对方的手。

人和座位由一个整数数组 row 表示,其中 row[i] 是坐在第 i 个座位上的人的 ID。情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2n-2, 2n-1)

返回 最少交换座位的次数,以便每对情侣可以并肩坐在一起每次交换可选择任意两人,让他们站起来交换座位。

示例 1:

输入: row = [0,2,1,3]
输出: 1
解释: 只需要交换row[1]和row[2]的位置即可。

示例 2:

输入: row = [3,2,0,1]
输出: 0
解释: 无需交换座位,所有的情侣都已经可以手牵手了。

提示:

  • 2n == row.length
  • 2 <= n <= 30
  • n 是偶数
  • 0 <= row[i] < 2n
  • row 中所有元素均无重复

问题分析:

乍一眼看去,这道题好像和并查集扯不上关系,这种一眼看过去并不能快速反应出来需要使用哪种算法的题需要我们仔细地去分析,去挖掘其中的数学关系。

我们先枚举几个简单的例子来看看题目具体情况:

如果只有1对情侣在2个座位上,那么他们一定是并肩坐在一起的,

如果2对情侣坐在4个座位上(没有任何一对情侣坐在一起),那么我们只需要交换1次。

在这里插入图片描述

如果3对情侣坐在6个座位上,那么我们只需要交换2次。

在这里插入图片描述

由于座位一直是 2 n 2n 2n个, n n n对情侣最终要并肩坐在一起,我们就可以形象地表示成 最终n对情侣都坐在各自的沙发上。

现在继续往下分析,如果 n n n对情侣分别坐在 n n n个沙发上(并且没有任何一对情侣坐在同一张沙发上),我们需要交换几次呢?

我们从第一张沙发开始分析,要想让第一张沙发中的两个人 a , b a,b a,b坐上一对情侣,可以让第一张沙发中的其中一人(假设让b)和 a a a的男(女)朋友进行交换,这样第一张沙发就安排好了,现在安排第二张沙发,继续同样的操作,一直这样下去做 n − 2 n-2 n2次操作,最终只剩下最后两张沙发,最后两张沙发只要再做一次交换操作就好了。

所以我们可以得出结论:

【结论1】如果有 K K K对情侣混坐在一起,那么我们至少需要 K − 1 K-1 K1次交换操作。

所以现在所有情侣的情况如下:

N 1 N_1 N1对情侣混坐在一起, N 2 N_2 N2对情侣混坐在一起, … , N r N_r Nr对情况混坐在一起,

​ 剩余对情侣都是配对好了的。

按照结论1,我们最终的结果是 N 1 + N 2 + N 3 + N 4 + . . . + N r − r N_1 + N_2 + N_3 + N_4 + ... + N_r - r N1+N2+N3+N4+...+Nrr N 1 N_1 N1对情况混坐在一起我们将其看成集合 A 1 A_1 A1 N 2 N_2 N2对情况混坐在一起我们将其看成集合 A 2 A_2 A2, …, N r N_r Nr对情侣混坐在一起我们将其看成集合 A r A_r Ar。最朴素的想法是求出这 r r r个集合分别的元素个数,之后累加再 − r -r r

注意,这里每个集合的元素个数是情侣对数,现在就剩下一个问题了,怎么判断 N N N对情侣混坐在一起?

为了简单化,我们把一对一对情侣进行编号:第0对:0,1;第1对:2,3…;第n对, 2 ∗ n 2*n 2n 2 ∗ n + 1 2*n + 1 2n+1。所以 i i i会出现两次,情侣两人各出现一次。现在我将原来的编号转化为情侣对数编号并将混在一起的情侣进行合并,原理如下图:

合并结束之后,我们就可以利用并查集来找那些集合中元素大于1个的集合。

for(int i = 0;i < n;i++){if(father[i] == i){if(size[i] > 1){result += size[i] - 1;}}
}

但是利用循环去找太浪费时间了,我们继续分析,这个 N 1 + N 2 + N 3 + . . . + N r N_1 + N_2 + N_3 + ... + N_r N1+N2+N3+...+Nr加起来是没有坐在一起的情侣的总对数,剩下的情侣都是坐在一起的,我们记坐在一起的情侣有 N s N_s Ns对,那么
N 1 + N 2 + N 3 + . . . + N r + N s = 情侣总对数 n N_1 + N_2 + N_3 + ... + N_r + N_s = 情侣总对数n N1+N2+N3+...+Nr+Ns=情侣总对数n
N s N_s Ns对情侣都是单独坐好的,在并查集中表现为 N s N_s Ns个元素个数为1的集合,所以
N 1 + N 2 + N 3 + . . . + N r − r = N 1 + N 2 + N 3 + . . . . + N r + N s − r − N s = n − ( r + N s ) = n − s e t s N_1 + N_2 + N_3 +... + N_r - r \\ = N_1 + N_2 + N_3 + .... + N_r + N_s - r -N_s\\ = n - (r + N_s)\\ = n - sets N1+N2+N3+...+Nrr=N1+N2+N3+....+Nr+NsrNs=n(r+Ns)=nsets
s e t s sets sets指的是并查集中的集合个数。

最终处理方法:

先对并查集初始化,有 n n n对情侣嘛,一开始都未入座时,情侣肯定是两两一对的,所以有n个集合。

void build(int n){for(int i = 0 ; i < n;i++){ //情侣编号从0开始的father[i] = i;}sets = n;
}
最终解决代码:
class Solution {
public:std::vector<int> father; // 不直接初始化大小int sets;                // 表示并查集中集合个数// 构造函数Solution() {father.resize(30); // 可以在构造函数中调整大小}void build(int N) {for (int i = 0; i < N; i++) {father[i] = i;}sets = N;}int find(int x) {if (x != father[x]) {father[x] = find(father[x]);}return father[x];}void Union(int x,int y) { // 一张沙发上的两人不是情侣,则说明混在一起,将集合合并int fx = find(x);int fy = find(y);if (fx != fy) {father[fx] = fy;sets--;}}int minSwapsCouples(vector<int>& row) {int N = row.size() / 2;build(N);for (int i = 0; i < N; i++) {Union(row[2*i] / 2, row[2*i + 1] / 2);}return N - sets;}
};

这道题的问题分析写得不是很好,同学们可以去看左程云老师的【并查集(上)】课程视频,看完视频后再看我的解析,应该就能明白我在讲什么了。

P r o b l e m 4 Problem4 Problem4 相似字符串组 LeetCode839

如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等,那么称 XY 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。

例如,"tars""rats" 是相似的 (交换 02 的位置); "rats""arts" 也是相似的,但是 "star" 不与 "tars""rats",或 "arts" 相似。

总之,它们通过相似性形成了两个关联组:{"tars", "rats", "arts"}{"star"}。注意,"tars""arts" 是在同一组中,即使它们并不相似。形式上,对每个组而言,要确定一个单词在组中,只需要这个词和该组中至少一个单词相似。

给你一个字符串列表 strs。列表中的每个字符串都是 strs 中其它所有字符串的一个字母异位词。请问 strs 中有多少个相似字符串组?

示例 1:

输入:strs = ["tars","rats","arts","star"]
输出:2

示例 2:

输入:strs = ["omv","ovm"]
输出:1

问题分析:

这道题和** P r o b l e m 3 Problem3 Problem3 情侣牵手问题**很相似,都是先判断两个元素是否有一定关系,如果有关系,则将两个元素所在集合合并,如果没有关系,则不合并。

P r o b l e m 3 Problem3 Problem3中,同一张沙发上的两个人如果不是一对情侣,那么就将两个人分别所在的集合合并,表示 编号 A 编号A 编号A情侣两人和 编号 B 编号B 编号B情侣两人混坐在一起。如果是一对情侣,我们也做出了 将两人分别所在集合合并 操作,一对情侣中两人的情侣编号相同(假设为 K K K), K K K K K K合并之后还是 K K K,相当于 K K K所在集合中只有 K K K,类似于** K K K与其他集合不进行合并**。

回到 P r o b l e m 4 Problem4 Problem4,和 P r o b l e m 3 Problem3 Problem3一样,我们在初始状态把各个字符串都放在自己单独的集合里,如果两个字符串相似,再将两个字符串所在集合合并。有一点不同的是,如果字符串 s t r i n g A stringA stringA单独所在集合最终与集合 s e t A setA setA合并,只需要和 s e t A setA setA中的其中一个字符串相似即可,所以我们需要遍历集合 s e t A setA setA。所以我们可以按照数组顺序来合并集合。

解决代码:
int MAXN = 3000;
vector<int> father;
int sets;
Solution() { father.resize(MAXN); }
void build(int N) {for (int i = 0; i < N; i++) {father[i] = i;}sets = N;
}int find(int x) {if (x != father[x]) {father[x] = find(father[x]);}return father[x];
}void Union(int x, int y) {int fx = find(x);int fy = find(y);if (fx != fy) {father[fx] = fy;sets--;}
}// 判断两个字符串是否相似,两处字符不同则为相似
bool isSimilar(string a, string b) {int length = a.size();int diff = 0;for (int i = 0; i < length && diff < 3; i++) {if (a[i] != b[i]) {diff++;}}return diff == 2 || diff == 0;
}int numSimilarGroups(vector<string>& strs) {int N = strs.size();build(N);// 对每个字符串进行遍历,并与排在它前面的字符串进行比较for(int i = 1; i < N; i++){for(int j = 0; j < i; j++){if(isSimilar(strs[i],strs[j])){Union(i,j);}}}return sets;
}
P r o b l e m 5 Problem5 Problem5 岛屿数量 LeetCode200

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]
]
输出:1

问题分析:

这道题和前面几题一样,都是用并查集一直合并集合,最终拿到最后所剩的集合数,也就是岛屿的数量。

值得一提的是,我们对每个由单个字符串构成的集合的遍历是有顺序的,在第二题中,我们是对数组从左到右逐个遍历,只有按照顺序来,遍历过的字符串的最终集合才能确定下来。这道题同样如此,只有按照行从上到下,列从左到右的遍历顺序来,遍历过的节点才能确定最终在哪片岛屿中。

举个反例,如果不按照顺序来遍历,那要求某个节点究竟在哪片岛屿中,就要以此节点为中心,向上下左右四个方向搜索陆地节点并合并集合。这时又会出现一个问题,看下面这张图:

在这里插入图片描述

红色节点1最先会被划分到蓝色集合中,之后又和下面橙色节点1合并,划分到橙色集合中,但其实正确结果是红色节点1被划分到蓝色集合,并且下方的橙色节点1最终也是要被划分到蓝色集合的。为什么会出现这种结果呢?

究其原因是橙色节点1还是处于最开始(每个节点独自构成一个集合)的初始状态,也就是说橙色节点的最终归属集合还没有确定好,要想得到并查集的最终结果,我们在某一节点向四周搜索时必须搜索已经有归属集合的节点。

因为我们对数组遍历是从行从上到下,列从左到右的,所以遍历某个节点时只需要搜索其左边的节点和上边的节点。

解决代码:

最终代码如下:

int MAXN = 100001;
vector<int> father;
int sets;
Solution() { father.resize(MAXN); }
void build(vector<vector<char>>& grid) {int rows = grid.size();int columns = grid[0].size();sets = 0;for (int i = 0; i < rows; i++) {for (int j = 0; j < columns; j++) {if (grid[i][j] == '1') {int index = Index(i, j, columns);father[index] = index;sets++;}}}
}int Find(int i) {// 利用堆栈进行扁平化处理if (i != father[i]) {father[i] = Find(father[i]);}return father[i];
}void Union(int x1, int y1, int x2, int y2, int columns) {int index1 = Index(x1, y1, columns);int index2 = Index(x2, y2, columns);int f1 = Find(index1);int f2 = Find(index2);if (f1 != f2) {father[f1] = f2;sets--;}
}int Index(int i, int j, int columns) { // 将二维坐标映射到一维return i * columns + j;
}int numIslands(vector<vector<char>>& grid) {int rows = grid.size();int columns = grid[0].size();build(grid);for (int i = 0; i < rows; i++) {for (int j = 0; j < columns; j++) {if (grid[i][j] == '1') {// 尝试能否与左边节点所在集合合并if (j > 0 && grid[i][j - 1] == '1') {// 左边节点存在并且是陆地,则可以考虑合并Union(i, j, i, j - 1, columns);}// 尝试能否与上边节点所在集合合并if (i > 0 && grid[i - 1][j] == '1') {// 右边节点存在并且是陆地,则可以考虑合并Union(i, j, i - 1, j, columns);}}}}return sets;
}

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构之算法复杂度

目录 前言 一、复杂度的概念 二、时间复杂度 三、大O的渐进表示法 四、空间复杂度 五、常见复杂度对比 总结 前言 本文主要讲述数据结构中的算法复杂度 一、复杂度的概念 算法在编写成可执行程序后&#xff0c;运行时需要耗费时间资源和空间(内存)资源。因此衡量一个算法的好坏…

python源代码编译exe 防止反编译的问题

1&#xff09;使用pyinstaller 打包为exe, 记住是版本是5.*&#xff0c;我用的是5.13.2 &#xff0c;不能是6.* 这是第一步。 pyinstaller -F -i d:\whs.ico packer.py -w 2&#xff09;使用pyarmor 再次加密,我使用的版本是8.3.11&#xff0c;不是7.*&#xff0c;这是第二步…

摩托车骑行行为检测系统源码分享

摩托车骑行行为检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Comput…

Cursor Rules 让 Cursor AI 代码生成更智能、更高效,效率再次飞升!

最近,AI 代码生成工具越来越火,比如 Cursor AI 编辑器。很多开发者已经开始使用它来自动生成代码,以提高工作效率。不过你有没有发现,有时候 AI 自动生成的代码并不总是符合最佳实践?比如变量命名不够规范、代码风格不统一,或者生成的代码逻辑不够清晰。这些问题有时让人…

c# 线程等待变量的值符合条件

在C#中&#xff0c;如果你想让一个线程等待直到某个变量的值满足特定条件&#xff0c;你可以使用ManualResetEvent或者AutoResetEvent来实现线程间的同步。以下是使用AutoResetEvent实现的一个简单例子&#xff1a; 在这个例子中&#xff0c;同时实现了如何让static函数访问非…

闲鱼ip地址在哪就是人在哪吗

在数字化时代&#xff0c;IP地址作为网络设备的唯一标识&#xff0c;常被用于追踪用户的地理位置。然而&#xff0c;对于闲鱼这样的二手交易平台&#xff0c;用户的IP地址是否真实反映了其所在地&#xff0c;却是一个值得深入探讨的问题。本文将围绕这一话题展开&#xff0c;带…

单卡3090 选用lora微调ChatGLM3-6B

环境配置 Python 3.10.12 transformers 4.36.2 torch 2.0.1 下载demo代码 在官方网址https://github.com/THUDM/ChatGLM3/blob/main/finetune_demo 下载demo代码cd 进入文件夹 pip install -r requirements.txt 安装一些包 基本知识 SFT 全量微调: 4张显卡平均分配&#…

昂科烧录器支持ST意法半导体的电可擦除可编程存储器M95128-DFDW

芯片烧录行业领导者-昂科技术近日发布最新的烧录软件更新及新增支持的芯片型号列表&#xff0c;其中ST意法半导体的电可擦除可编程存储器M95128-DFDW已经被昂科的通用烧录平台AP8000所支持。 M95128-DFDW是电可擦除可编程存储器&#xff08;EEPROM&#xff09;通过SPI总线进行…

springcloud微服务实战<1>

单机结构 我只需要一台服务器完成我项目的部署&#xff08;单体应用&#xff09;&#xff0c;开发部署简单 他就会有单点问题&#xff0c; 因为此时只有一台机器&#xff0c;一旦这个机器挂了&#xff0c;我用户就没有办法使用应用的服务了 这个就是单点问题针对我们的项目进…

Qt/C++ 多线程同步机制详解及应用

在多线程编程中&#xff0c;线程之间共享资源可能会导致数据竞争和不一致的问题。因此&#xff0c;采用同步机制确保线程安全至关重要。在Qt/C中&#xff0c;常见的同步机制有&#xff1a;互斥锁&#xff08;QMutex、std::mutex&#xff09;、信号量&#xff08;QSemaphore&…

多模态大模型MiniCPM-V技术学习

目前性价比最高的多模态模型 Minicpm-V-2.6参数8B&#xff0c;int4版本推理显存仅7GB&#xff0c;并且在幻觉数据集上效果好于其他模型&#xff0c;测试下来效果非常好&#xff0c;官方演示里面还给出了手机上端侧运行的图片和视频推理示例 p.s.Qwen2-VL和Minicpm-V-2.6头对头…

【操作系统】02.深入理解操作系统

一、操作系统的定位 任何计算机系统都包含一个基本的程序集合&#xff0c;称为操作系统(OS)。笼统的理解&#xff0c;操作系统包括操作系统内核和其他程序。 由上述的宏观图其实我们就知道&#xff1a;操作系统是一款进行软硬件资源管理的软件。 二、设计操作系统的目的 操…

众数信科AI智能体政务服务解决方案——寻知智能笔录系统

政务服务解决方案 寻知智能笔录方案 融合民警口供录入与笔录生成需求 2分钟内生成笔录并提醒错漏 助办案人员二次询问 提升笔录质量和效率 寻知智能笔录系统 众数信科AI智能体 产品亮点 分析、理解行业知识和校验规则 AI实时提醒用户文书需注意部分 全文校验格式、内…

C一语言—动态内存管理

目录 一、为什么要有动态内存管理 二、malloc和free &#xff08;2.1&#xff09;malloc &#xff08;2.2&#xff09;free 三、calloc和realloc &#xff08;3.1&#xff09;calloc &#xff08;3.2&#xff09;realloc 四、常见的动态内存的错误&#xff08;举例均为错…

springboot每次都需要重设密码?明明在springboot的配置中设置了密码

第一步&#xff1a;查看当前的密码是什么&#xff1f; 打开redis-cli.exe&#xff0c;输入config get requirepass&#xff0c;查看当前的密码是什么&#xff1f; 接着&#xff0c;修改redis的配置文件&#xff0c;找到redis的安装目录&#xff0c;找到相关的conf文件&#x…

Amazon Bedrock 模型微调实践(二):数据准备篇

本博客内容翻译自作者于 2024 年 9 月在亚马逊云科技开发者社区发表的同名博客&#xff1a; “Mastering Amazon Bedrock Custom Models Fine-tuning (Part 2): Data Preparation for Fine-tuning” 亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、…

Unity3D入门(一) : 第一个Unity3D项目,实现矩形自动旋转,并导出到Android运行

1. Unity3D介绍 Unity3D是虚拟现实行业中&#xff0c;使用率较高的一款软件。 它有着强大的功能&#xff0c;是让玩家轻松创建三维视频游戏、建筑可视化、实时三维动画等互动内容的多平台、综合型 虚拟现实开发工具。是一个全面整合的专业引擎。 2. Unity安装 官网 : Unity…

1042 Shuffling Machine,1050 String Subtractio

1042 Shuffling Machine 普通模拟即可&#xff0c;注意每一次交换牌的时候需要更新start数组&#xff08;当前卡牌的顺序&#xff09;&#xff0c;并且清空ans数组&#xff08;交换后的卡牌顺序&#xff09; #include<bits/stdc.h> using namespace std; const int N 5…

hal 正点原子 exti外部中断

1.这个是 f4/f7/h7 用于配置外部中断的寄存器 需要先使能时钟 2.这个是f1用于配置外部中断的配置器&#xff0c;也是需要先配置时钟&#xff0c;但是区别在于除了f1 &#xff0c;别的系列都用的SYSCFG 3.这个是外部中断线io和怎么exti对应的 4.这两张图 都是exti和io的对应关系…

QFramework v1.0 使用指南 更新篇:20240919. 新增 BindableDictionary

虽然笔者目前还不知道 BindableDictionary 能用在什么使用场景下&#xff0c;但是还是应童鞋的要求实现了 BindableDictionary。 基本使用如下: using System.Linq; using UnityEngine;namespace QFramework.Example {public class BindableDictionaryExample : MonoBehaviou…