一、最小生成树
- 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。
- 在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边,而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集且为无循环图,使得
的 w(T) 最小,则此 T 为 G 的最小生成树。 - 最小生成树其实是最小权重生成树的简称。
二、Kruskal算法(克鲁斯卡尔算法)
构造过程
假设连通网N = (V, E),将N中的边按权值从小到大的顺序排列。
- 初始状态为只有n个顶点而无边的非连通图T = (V, {}),图中每个顶点自成一个连通分量。
- 在E中选择权值最小的边,若改变依附的顶点落在T中不同的连通分量上(即不形成回路),则将此边加入到T中,否则舍去此边而选择下一条权值最小的边。
- 重复第二步,直至T中所有顶点都在同一连通分量上为止。
算法步骤
- 将数组Edge中的元素按权值从小到大排序。
- 依次查看数组Edge中的边,循环执行以下操作:
- 依次取出数组Edge中的一条边(U1, U2);
- 用并查集数组vset找到U1、U2的父节点;
- 如果父节点不等,则修改U2的父节点为U1的父节点,记录权值和,记录已合并的边数;
- 当已记录的边数和等于 顶点数-1 时,退出循环;
四、举一个栗子(洛谷P3366)
Description
给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz
。
Input
- 第一行包含两个整数 N,M,表示该图共有 N 个结点和 M 条无向边;
- 接下来 M 行每行包含三个整数 Xi,Yi,Zi,表示有一条长度为 Zi的无向边连接结点 Xi 、Yi;
Sample Input
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
Sample Output
7
More Info
数据规模:
对于 20% 的数据,N≤5,M≤20;
对于 40% 的数据,N≤50,M≤2500;
对于 70% 的数据,N≤500,M≤104;
对于 100% 的数据:1≤N≤5000,1≤M≤2×105;
Code
详见注释
#include <iostream>
#include <algorithm>
#define Max 200003
using namespace std;
typedef struct Edge { //定义边的结构体
int head, tail;
int lowcost;
};
typedef struct Graph { //定义图
int vex, arc;
Edge edge[Max];
};
int vset[Max], sum=0, n=0; //并查集数组、总权值、最小生成树边数
bool cmp(Edge x, Edge y) //自定义排序规则
{
return x.lowcost < y.lowcost;
}
int find(int x) //找到父亲结点
{
if (vset[x] == x) return x;
else return find(vset[x]);
}
void Kruskal(Graph &G) //克鲁斯卡尔算法
{
sort(G.edge, G.edge+G.arc, cmp); //STL快排模板
for (int i = 0; i < G.arc; i++) //遍历边
{
int x, y;
x = find(G.edge[i].head);
y = find(G.edge[i].tail); //找到顶点的父亲节点
if (x != y) { //如果不在一个连通图里
//cout << G.edge[i].head << " -> " << G.edge[i].tail << endl;
sum += G.edge[i].lowcost;
vset[y] = x; //并入连通串
if (++n == G.vex - 1) break;//如果满了n-1条边,则结束循环
}
}
cout << sum; //输出总权
}
void putin(Graph &G) //输入函数
{
cin >> G.vex >> G.arc;
for (int i = 0; i < G.arc; i++)
cin >> G.edge[i].head >> G.edge[i].tail >> G.edge[i].lowcost;
for (int i = 1; i <= G.vex; i++) //初始化并查集数组
vset[i] = i;
}
int main()
{
Graph G;
putin(G);
Kruskal(G);
return 0;
}
蒟蒻一只,欢迎指正