问题描述: n个顶点,m条边,每条边有一个cost。 从m条边中选择若干条边使得n个顶点构成一颗树,要求cost的总和花费最大(最小)。
解决方案: kruskar算法生成最小生成树。关键点在于判断边的加入是否会引入环,环的检查使用并查集来实现,复杂度是O(lgN)。
算法的思想是贪心, 需要将输入的边按照cost从小到大排序,边的选择就是按照cost进行选择的。
#include<iostream>
#include<algorithm>
#include<fstream>
using namespace std;
//#define DEBUG
struct edge
{
int u;
int v;
int w;
};
bool cmp(const edge &a, const edge &b)
{
return a.w > b.w;
}
static struct edge edges[20000];
static int p[1001];
static int num[1001];
int findset(int x) /* 压缩路径 */
{
if (x != p[x])
p[x] = findset(p[x]);
return p[x];
}
int unionset(int s1, int s2) /* 按照数量合并 */
{
if (s1 == s2) return -1;
if (num[s1] > num[s2])
{
p[s2] = s1; num[s2] = 0;
}
else
{
p[s1] = s2; num[s1] = 0;
}
return 0;
}
int main()
{
#ifdef DEBUG
fstream cin("G:\\book\\algorithms\\acm\\Debug\\dat.txt");
#endif
int n, m;
cin >> n >> m;
int i;
for (i = 0; i < m; i++)
{
cin >> edges[i].u >> edges[i].v >> edges[i].w;
}
sort(edges, edges + m, cmp);/* 降序排列 贪心*/
for (i = 1; i <= n; i++)
{
p[i] = i; num[i] = 1;
}
int cost = 0;
int trees = n;
for (i = 0; i < m; i++)
{
int pu, pv;
pu = findset(edges[i].u);
pv = findset(edges[i].v);
if (pv != pu) /* the edge connect different trees */
{
unionset(pv,pu);
cost += edges[i].w;
trees--;
}
}
if (trees > 1)
cout << -1 << "\n";
else
cout << cost << "\n";
return 0;
}
使用最小生成树算法,理论分析见算法导论的23.2小节。
基本的算法思想是贪心算法。对输入的边按照权重进行排序后依次处理,根据排序的方式决定是最大生成树还是最小生成树。
成树的过程中防止生成环, 采用并查集的方式进行检测。可以看出使用并查集后算法的过程就很简单了。复杂度是和边的数目有关的,
是否能够成树,即是否最终能够得到一个联通图,采用变量进行检测即可。
Kruskal算法对边进行操作,初始状态是n棵树构成的森林,通过边将各个树联通,构成一个无环的联通图