思路分析
克鲁斯卡尔算法的基本思路,是在联通图已经建立的基础上,通过一次次地选择某条边来连接两个个顶点,直到所有顶点都被连接到一起为止, 因此又被称为”加点法“。
具体步骤如下:
1. 将图中的所有边按照权值从小到大进行排序。//本代码利用优先队列实现
2. 从权值最小的边开始,依次选择边,并检查是否会形成环路。//”检验是否形成回路“是代码实现的难点,下面会细讲~
3. 如果选中的边不会形成环路,则将其加入最小生成树中。//本代码用一个新的邻接矩阵存储
4. 继续选择下一条权值较小的边,直到所有顶点都被连接为止。
//ps:最早将所有边存于优先队列中,每条边只在上述过程走一次, 每取出一条边,就将其从队列中删除,当队列为空时,则理论上最小生成树也形成了(如果生成树可以形成的话)。
自定义结构体
typedef struct { // 定义结构体Node
int u, v; // 边的起点和终点
int wt; // 边的权值
}Node;
关于”判断是否形成环路“的阐释
在实现过程中,通常会使用并查集来判断是否形成环路。如果选中的边的两个顶点属于同一个集合,则说明会形成环路,此时应该舍弃该边。否则,将两个顶点合并到同一个集合中,并将该边加入最小生成树中。相关代码节选如下:
if (rem[t_node.u] != rem[t_node.v])
// 可以理解成rem用来存储上级,若rem值想等,则已经与同一结点连接,借此判断是否形成环
{
//如果不成环,则加入,并更新rem
for (int i = 1; i <= n; i++) // 更新并查集
{
rem[i] = rem[i] == rem[t_node.v] ? rem[t_node.u] : rem[i];
}
}
建立最小生成树的代码如下:
int rem[size] = { 0 }; // 定义并初始化并查集数组rem
for (int i = 0; i < size; i++) rem[i] = i; // 初始化并查集
while (!pq.empty()) // 当优先队列不为空时
{
t_node = pq.top(); pq.pop(); // 取出权值最小的边
if (rem[t_node.u] != rem[t_node.v]) // 判断是否形成环
{
graph[t_node.u][t_node.v] = t_node.wt; // 更新邻接矩阵
graph[t_node.v][t_node.u] = t_node.wt;
for (int i = 1; i <= n; i++) // 更新并查集
{
rem[i] = rem[i] == rem[t_node.v] ? rem[t_node.u] : rem[i];
}
}
}
完整代码如下:
#include<bits/stdc++.h> // 包含标准库头文件
using namespace std; // 使用标准命名空间
#define max 1000000 // 定义最大值常量
#define size 100 // 定义数组大小常量
//set<int>s; // 注释掉的代码
typedef struct { // 定义结构体Node
int u, v; // 边的起点和终点
int wt; // 边的权值
}Node;
struct cmp { // 定义比较器结构体cmp
bool operator()(const Node& a, const Node& b) // 重载()操作符
{
return a.wt > b.wt; // 按权值从小到大排序
}
};
priority_queue<Node, vector<Node>, cmp>pq; // 定义优先队列pq
int main() // 主函数
{
int n, m; // 定义顶点数和边数变量
cin >> n >> m; // 输入顶点数和边数
Node t_node; // 定义边的结构体变量t_node
for (int i = 0; i < m; i++) // 循环读入边的起点、终点和权值
{
cin >> t_node.u >> t_node.v >> t_node.wt;
pq.push(t_node); // 将边加入优先队列
}
int graph[size][size] = { 0 }; // 定义邻接矩阵graph并初始化为0
for (int i = 0; i < size; i++) { // 初始化邻接矩阵为0
for (int j = 0; j < size; j++) graph[i][j] = 0;
}
//kruskal
int rem[size] = { 0 }; // 定义并初始化并查集数组rem
for (int i = 0; i < size; i++) rem[i] = i; // 初始化并查集
while (!pq.empty()) // 当优先队列不为空时
{
t_node = pq.top(); pq.pop(); // 取出权值最小的边
if (rem[t_node.u] != rem[t_node.v]) // 判断是否形成环
{
graph[t_node.u][t_node.v] = t_node.wt; // 更新邻接矩阵
graph[t_node.v][t_node.u] = t_node.wt;
for (int i = 1; i <= n; i++) // 更新并查集
{
rem[i] = rem[i] == rem[t_node.v] ? rem[t_node.u] : rem[i];
}
}
}
//最小生成树以邻接矩阵的方式呈现
for (int i = 1; i <= n; i++) // 遍历输出最小生成树的邻接矩阵
{
for (int j = 1; j <= n; j++)
{
cout << graph[i][j] << " "; // 输出邻接矩阵元素
}
cout << endl; // 换行
}
}