必备前置知识 :算法学习------并查集-CSDN博客
一、基本定义
1. 生成树(Spanning Tree)
对于一个连通无向图,它的生成树是指:
-
包含原图的所有顶点(n个)
-
只有n-1条边
-
仍然保持连通性
-
没有环(acyclic)
2. 最小生成树(Minimum Spanning Tree, MST)
当图中的边有权重(成本、长度、代价)时,在所有的生成树中,边权重之和最小的那个生成树就是最小生成树。
二、核心特性
1. 无环性(Acyclic)
最小生成树中不能有环,因为如果有环,就可以去掉环上的一条边而仍然保持连通,从而减小总权重。
2. 连通性(Connected)
必须连接所有顶点,不能有孤立的顶点或连通分量。
3. 最少边数
恰好有n-1条边(n为顶点数):
-
少于n-1条边:无法连通所有顶点
-
多于n-1条边:必然形成环
三、Kruskal算法
Kruskal算法是一种贪心算法 ,通过并查集 高效判断连通性,适合解决稀疏图的最小生成树问题。其核心是"按边选择,避免成环",时间复杂度主要由排序决定,为O(m log m)。
四、贪心策略
每次选择权值最小 的边,如果加入这条边不会形成环,则将其加入生成树中。
将图中所有边按权值从小到大排序
初始化并查集father数组,每个节点指向自己
依次检查排序后的每条边(x,y,z):
a. 如果加入这条边不会形成环,即x和y不在同一个集合,即find(x) ≠ find(y)
b. 计数器cnt+1,总权重ans+z,合并x和y所在的集合(Union)
c. 如果cnt=n-1,算法结束;否则说明图不连通
五、代码实现
1.头文件与命名空间
cpp
#include<iostream>
#include<algorithm>//sort
#include<vector>
#include<stack>
using namespace std;
2.全局变量定义
cpp
const int N = 2e5 + 10;
// 并查集数组,存储每个节点的父节点
int father[N];
int n, m; // n:节点数,m:边数
3.数据结构定义
cpp
struct edge // 边结构体
{
int x;
int y;
int z;
};
// 存储所有边的动态数组
vector <struct edge> edges;
4.比较函数
cpp
bool cmp(struct edge a, struct edge b)
{
// 按边权重从小到大排序(Kruskal算法核心)
return a.z < b.z;
}
5.并查集操作函数
cpp
//1.初始化并查集
void build()
{
// 每个节点的父节点初始化为自身
for (int i = 1;i <= n;i++)
father[i] = i;
}
//2.查找节点i的根节点
int find(int i)
{
stack <int> st;// 使用栈记录查找路径
while (father[i] != i)// 当i不是根节点时
{
st.push(i);// 将当前节点压入栈
i = father[i];// 向上查找父节点
}
while (!st.empty())// 路径压缩优化
{
// 将路径上所有节点的父节点设为根节点
father[st.top()] = i;
st.pop();// 弹出栈顶
}
return i; // 返回根节点
}
//3.合并两个集合
bool Union(int x, int y)
{
//保存第一次find()的结果
//避免重复调用可能带来的问题
int fx = find(x); // 查找x的根节点
int fy = find(y); // 查找y的根节点
if (fx != fy) // 如果根节点不同
{
father[fx] = fy; // 合并两个集合
return true; // 返回true表示合并成功
}
return false; // 返回false表示已在同一集合
}
6.主函数
cpp
int main()
{
cin >> n >> m; // 输入节点数和边数
build();// 初始化并查集(必须在知道n之后)
for (int i = 1;i <= m;i++)
{
int x = 0, y = 0, z = 0;
cin >> x >> y >> z;// 输入一条边的信息
edges.push_back({ x,y,z });// 将边加入数组
}
// 按边权重升序排序
sort(edges.begin(), edges.end(), cmp);
int cnt = 0;// 记录已选边数(最小生成树需要n-1条边)
int ans = 0;// 记录最小生成树总权重
for (auto e : edges) // 遍历所有排序后的边
{
// 如果边的两端点不在同一连通块
if (Union(e.x, e.y))
{
cnt++; // 选择这条边
ans += e.z;// 累加权重
}
// 已选够n-1条边(最小生成树完成)
if (cnt == n - 1)
break;// 提前结束循环
}
if (cnt == n - 1) // 如果成功构建最小生成树
cout << ans << endl;// 输出总权重
else // 如果图不连通
cout << "impossible" << endl;// 输出不可能
return 0;
}
7.所有代码
cpp
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;
const int N = 2e5 + 10;
int father[N];
int n, m;
struct edge
{
int x;
int y;
int z;
};
vector <struct edge> edges;
bool cmp(struct edge a, struct edge b)
{
return a.z < b.z;
}
void build()
{
for (int i = 1;i <= n;i++)
father[i] = i;
}
int find(int i)
{
stack <int> st;
while (father[i] != i)
{
st.push(i);
i = father[i];
}
while (!st.empty())
{
father[st.top()] = i;
st.pop();
}
return i;
}
bool Union(int x, int y)
{
//保存第一次find()的结果
//避免重复调用可能带来的问题
int fx = find(x);
int fy = find(y);
if (fx != fy)
{
father[fx] = fy;
return true;
}
return false;
}
int main()
{
cin >> n >> m;
//要在输入n之后调用build
build();
for (int i = 1;i <= m;i++)
{
int x = 0, y = 0, z = 0;
cin >> x >> y >> z;
edges.push_back({ x,y,z });
}
sort(edges.begin(), edges.end(), cmp);
int cnt = 0;
int ans = 0;
for (auto e : edges)
{
if (Union(e.x, e.y))
{
cnt++;
ans += e.z;
}
if (cnt == n - 1)
break;
}
if (cnt == n - 1)
cout << ans << endl;
else
cout << "impossible" << endl;
return 0;
}