图论——最小生成树
Posted 牧空
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论——最小生成树相关的知识,希望对你有一定的参考价值。
Kruskal算法
原理
任何选择一些点属于集合A,剩余的点属于集合B,最小生成树必定存在一条权值最小的边,并且这条边的两个顶点分别属于集合A和集合B(即连通两个集合)
算法步骤
虽然在原理中是在两个点集中中边,但是通过点集去找边需要反复遍历某个点集中所有点的边,时间复杂度会很高,这里从边开始考虑,遍历所有边,自然也会涉及到所有的点,使用并查集可以判断边的两个点是否在同一个集合内
- 初始化所有顶点属于孤立的集合,即G为森林,每个节点为一棵树
- 按照边权递增顺序遍历所有边,若遍历到的边的两个顶点仍分属不同的集合(该边即为连通这两个集合的边中权值最小的那条边),则确定改变为最小生成树上的一条边,并将该边两个顶点分属的集合合并
- 遍历完所有边后,若原图连通,则被选取的边和所有顶点构成最小生成树;若原图不连通,最小生成树不存在
例题
描述
省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。现得到城镇道路统计表,表中列出了任意两城镇间修建道路的费用,以及该道路是否已经修通的状态。现请你编写程序,计算出全省畅通需要的最低成本。
输入描述:
测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( 1< N < 100 );随后的 N(N-1)/2 行对应村庄间道路的成本及修建状态,每行给4个正整数,分别是两个村庄的编号(从1编号到N),此两村庄间道路的成本,以及修建状态:1表示已建,0表示未建。 当N为0时输入结束。
输出描述:
每个测试用例的输出占一行,输出全省畅通需要的最低成本。
//来自王道机试
#include <iostream>
#include <cstdio>
#include <algorithm>
#define MAXN 100
using namespace std;
struct Edge
{
int from;
int to;
int length;
bool operator<(const Edge &e) const
{
return length < e.length;
}
};
Edge edge[MAXN * MAXN]; //边集
int father[MAXN];
int height[MAXN];
void Initial(int n)
{
for (int i = 0; i <= n; i++)
{
father[i] = i;
height[i] = 0;
}
}
int Find(int x)
{
if (x != father[x])
{
father[x] = Find(father[x]);
}
return father[x];
}
void Union(int x, int y)
{
x = Find(x);
y = Find(y);
if (x != y)
{
if (height[x] < height[y]) //x<-z y --> x<-z x<-y
{
father[x] = y;
}
else if (height[x] > height[y]) //x y<-z --> y<-z y<-x
{
father[y] = x;
}
else
{
father[y] = x;
height[x]++;
}
}
return;
}
int Kruskal(int n, int edgeNumber)
{
Initial(n);
sort(edge, edge + edgeNumber);
int sum = 0;
for (int i = 0; i < edgeNumber; i++)
{
Edge current = edge[i];
if (Find(current.from) != Find(current.to))
{
Union(current.from, current.to);
sum += current.length;
}
}
return sum;
}
int main()
{
int n;
while (scanf("%d", &n) != EOF) //顶点数
{
if (n == 0)
break;
int edgeNumber = n * (n - 1) / 2;
for (int i = 0; i < edgeNumber; i++)
{
int status = 0;
scanf("%d%d%d%d", &edge[i].from, &edge[i].to, &edge[i].length, &status);
//已建成的路不可能拆掉,且必然会算在最短生成树中,所以将长度置为0,表示消耗为0
if (status == 1)
edge[i].length = 0;
}
int cost = Kruskal(n, edgeNumber);
printf("%d\\n", cost);
}
return 0;
}
Prim算法
原理
贪心,持续寻找相邻可达的最近节点
算法步骤
- 用两个集合A{},B{}分别表示找到的点集,和未找到的点集;
- 我们以A中的点为起点a,在B中找一个点为终点b(可达的,不可达的距离为无穷大),这两个点构成的边(a,b)的权值是其余边中最小的,将找到的b置入集合A
- 重复上述步骤2,直至B中的点集为空,A中的点集为满
例题
描述
某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。
输入描述:
测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。 当N为0时,输入结束,该用例不被处理。
输出描述:
对每个测试用例,在1行里输出最小的公路总长度。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#define MAXN 101
using namespace std;
/**
* Prim算法
* */
vector<vector<int>> Graph; //邻接矩阵
// n为节点个数
int Prim(int n)
{
int lowCost[MAXN]; //lowCost[j] 存储节点j与当前已选节点集合相连的最小权值,如果lowCost[j]==0,说明j节点在已选择的点集中
int closest[MAXN]; //closest[j] 存储lowCost[j]对应的连接点
int newVertex = 0; //newVertex:最新加入集合的点
int sum = 0;
// 使用第一个节点作为起始节点
for (int i = 0; i < n; i++)
{
closest[i] = 0;
lowCost[i] = Graph[0][i];
}
// 找接下来的n-1个点
for (int i = 1; i < n; i++)
{
// 在lowCost中寻找权重最小的边,并记录该节点
int min = INT32_MAX, newVertex = -1;
for (int j = 0; j < n; j++)
{
if (lowCost[j] != 0 && lowCost[j] < min)
{
min = lowCost[j];
newVertex = j;
}
}
// 将该点置入已选择的点集中,并累计边的权重
lowCost[newVertex] = 0;
sum += min;
// 根据新点集更新lowCost,只要遍历index所连接的点并与已有的lowCost比较
for (int j = 0; j < n; j++)
{
// 未选中的点且新加入的点到其距离小于已记录的最小值
if (lowCost[j] != 0 && Graph[newVertex][j] < lowCost[j])
{
lowCost[j] = Graph[newVertex][j];
closest[j] = newVertex; //同时也更新点的连接对象
}
}
}
return sum;
}
int main()
{
int n;
while (scanf("%d", &n) != EOF) //顶点数
{
if (n == 0)
break;
int edgeNumber = n * (n - 1) / 2;
Graph.clear();
Graph.resize(n);
for (int i = 0; i < n; i++)
Graph[i].resize(n);
for (int i = 0; i < edgeNumber; i++)
{
int x, y, len;
scanf("%d%d%d", &x, &y, &len);
Graph[x - 1][y - 1] = Graph[y - 1][x - 1] = len;
}
int cost = Prim(n);
printf("%d\\n", cost);
}
return 0;
}
以上是关于图论——最小生成树的主要内容,如果未能解决你的问题,请参考以下文章