更好的阅读体验请前往:Paxton的小破站
一、最小生成树
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。——百度百科
最小生成树简单来说就是用总数最短的边来将所有的点连通,并且不能存在环结构。
最小生成树有两种算法,即kruskal(克鲁斯卡尔)算法或prim(普里姆)算法,本文介绍kruskal(克鲁斯卡尔)算法。
如上图,这是一个无向图,我们要做的第一步就是将他们的每一条边的长度以及他们连通的两个点取出并单独排序,如下图
排序完之后我们便按照这个顺序往图上加边,每次加边时需要判断是否成环,若成环则舍弃该边,直到选用的边数=点数-1,则最小生成树构建完成
若边全部选用完也无法相等,则无法构建最小生成树
判断是否成环
当两个点具有共同祖先时,连接将导致成环,判断方法如下
int x = find(l[i].a), y = find(l[i].b);//存储两条边的祖先
if (x == y)//两条边有共同祖先则会成环,不予考虑
continue;
例题一(Networking POJ1287)
原题链接:https://vjudge.net/contest/478160#problem/E
1、题干
您被分配在广域内的某些点之间设计网络连接。在该区域中为您提供了一组点,以及可能连接成对点的电缆的一组可能路线。对于两点之间的每条可能路线,将为您提供在该路线上连接各点所需的电缆长度。请注意,两个给定点之间可能存在许多可能的路线。假定给定的可能路线(直接或间接)连接区域中的每两个点。
您的任务是设计该区域的网络,以使每两个点之间都具有连接(直接或间接)(即,所有点都是互连的,但不一定是通过直接电缆),并且总长度为使用的电缆极少。
2、输入格式
输入文件由许多数据集组成。每个数据集定义一个所需的网络。集合的第一行包含两个整数:第一行定义给定点的数量P,第二行定义点之间的给定路线的数量R。以下R线定义了点之间的给定路线,每条给出三个整数:前两个数字标识点,第三个给出路线的长度。数字用空格分隔。仅给出一个数字P = 0的数据集表示输入的结尾。数据集用空行分隔。
最大点数为50。给定路线的最大长度为100。可能的路线数是无限的。用1到P(含)之间的整数标识节点。两个点i和j之间的路线可以指定为i-j或j-i。
3、输出格式
对于每个数据集,在单独的一行上打印一个数字,以给出用于整个设计网络的电缆的总长度。
4、样例
sample input
1 0
2 3
1 2 37
2 1 17
1 2 68
3 7
1 2 19
2 3 11
3 1 7
1 3 5
2 3 89
3 1 91
1 2 32
5 7
1 2 5
2 3 7
2 4 8
4 5 11
3 5 10
1 5 6
4 2 12
0
sample output
0
17
16
26
例题一题解
1、分析
典型的最小生成树模板题,直接套用模板即可,注意单独输入0为结束输入
2、代码
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
int fa[5005];
void init(int n) //并查集初始化函数
{
for (int i = 1; i <= n; ++i)
fa[i] = i;
return;
}
int find(int x) //并查集找祖先函数
{
return (x == fa[x] ? x : fa[x] = find(fa[x]));
}
struct line //结构体
{
int a, b, len;
} l[100000];
bool cmd(line a, line b) //结构体排序
{
return a.len < b.len;
}
int main()
{
int n, m, sum = 0, num = 0; // sum边长度和 num已经使用的边数量
while (cin >> n) // n个点 m条边
{
if (n == 0)
return 0;
cin >> m;
sum = 0, num = 0;
memset(fa, 0, sizeof(fa));
memset(l, 0, sizeof(l));
for (int i = 1; i <= m; ++i)
{
cin >> l[i].a >> l[i].b >> l[i].len; //输入边链接的两点和长度
}
sort(l + 1, l + 1 + m, cmd); //将边按照长度由小到大排序
init(n); //初始化并查集
for (int i = 1; i <= m; ++i) //遍历边
{
int x = find(l[i].a), y = find(l[i].b); //存储两条边的祖先
if (x == y) //两条边有共同祖先则会成环,不予考虑
continue;
sum += l[i].len; //加入长度总和
fa[x] = y; //将x的父亲设置为y
if (++num == n - 1) //边的数量为点数量-1,最小生成树构建完成
{
cout << sum << endl;
break;
}
}
if(n==1)
cout << 0 << endl;
}
return 0;
}
例题二(Built? AT2643)
原题链接:https://vjudge.net/contest/478160#problem/F
1、题干
平面上有 N 个城市。第 i 个城市的坐标为 (xi,yi)。同一个坐标上可能有多个城市。在坐标为 (a,b)(a,b) 的城市和坐标为 (c,d)(c,d) 的城市间建造一条道路需要 min(|a-c|,|b-d|)円。只能在城市与城市间建造道路。 要使任意两个城市之间有直接或间接道路相连,最少需要多少円?
2、输入格式
输入按照以下格式
N
x1 y1
x2 y2
.
.
.
xn yn
3、输出格式
请输出使任意两城市间有直接或间接道路连接所需最少钱数。
4、数据范围
2≤N≤10^5
0≤xi,yi≤10^9
输入全为整数
5、样例
sample input 1
3
1 5
3 9
7 8
sample output 1
3
sample input 2
6
8 3
4 9
12 19
18 1
13 5
7 6
sample output 2
8
sample input 3
3
1 5
3 9
7 8
sample output 3
3
sample input 4
6
8 3
4 9
12 19
18 1
13 5
7 6
sample output 4
8
例题二题解
1、分析
将样例在坐标轴上表示之后,我们可以简单推出,当所有点以x方向的大小排序时,x坐标最近的每两个点连线生成的总长度最小,y轴同理
以此为规律分别将x方向和y方向的边全部存储,构建最小生成树
2、代码
#include <algorithm>
#include <iostream>
#include <string.h>
using namespace std;
int fa[100005];
void init(int n) //并查集初始化函数
{
for (int i = 1; i <= n; ++i)
fa[i] = i;
return;
}
int find(int x) //并查集找祖先函数
{
return (x == fa[x] ? x : fa[x] = find(fa[x]));
}
void unionn(int i, int j)//并查集链接函数
{
int i_fa = find(i);
int j_fa = find(j);
fa[i_fa] = j_fa;
return;
}
struct ed
{
int from, to, len;//存储边的来,去,长度
} edge[200005];
struct city
{
int x, y, id;//存储点的x,y坐标和编号
};
city ci1[100005], ci2[100005];//ci1存储按x坐标小到大排序的城市编号,ci2则按y
bool cmd1(city a, city b)//结构体排序
{
return a.x < b.x;
}
bool cmd2(city a, city b)//同上
{
return a.y < b.y;
}
bool cmd3(ed a, ed b)//将所有边由小到大排序
{
return a.len < b.len;
}
int main()
{
int n;
cin >> n;
init(n);
for (int i = 1; i <= n; ++i)
{
cin >> ci1[i].x >> ci1[i].y;//将城市的xy坐标分开存储
ci2[i].x = ci1[i].x;
ci2[i].y = ci1[i].y;
ci1[i].id = i;
ci2[i].id = i;
}
sort(ci1 + 1, ci1 + 1 + n, cmd1);//按x排序
sort(ci2 + 1, ci2 + 1 + n, cmd2);//按y排序
int m = 0;
for (int i = 1; i <= n - 1; ++i)//每个城市按两个坐标轴方向分别有x方向最近城市和y方向最近城市,分别建边
{
edge[++m].from = ci1[i].id, edge[m].to = ci1[i + 1].id, edge[m].len = abs(ci1[i].x - ci1[i + 1].x);
edge[++m].from = ci2[i].id, edge[m].to = ci2[i + 1].id, edge[m].len = abs(ci2[i].y - ci2[i + 1].y);
}//分别建边后所有边均存储于edge
sort(edge + 1, edge + 1 + 2 * (n - 1), cmd3);//----以下套用最小生成树模板
int sum = 0, num = 0;
for (int i = 1; i <= 2 * (n - 1); ++i) //构建最小生成树
{
int a = find(edge[i].from), b = find(edge[i].to);
if (a == b)
continue;
sum += edge[i].len;
fa[a] = b;
if (++num == n - 1)
{
cout << sum;
return 0;
}
}
}