【题目链接】
ybt 1348:【例4-9】城市公交网建设问题
输出时要求对于每条边,小的顶点写在前,大的顶点写在后。
多条边之间,第一个顶点小的排在前面。如果第一个顶点相同,则第二个顶点小的排在前面。
【题目考点】
1. 图论:最小生成树
- Prim算法
- Prim算法堆优化
- Kruskal算法
【解题思路】
该题难点是要保存最小生成树的所有边,对于不同的求最小生成树的算法,有不同的方法来完成。
解法1:Prim算法
Prim算法是加点法。每次在未选择的顶点中选择到已选择顶点边权值最小的顶点,将其加入最小生成树中。设选择的新顶点为u,这个新的顶点u与最小生成树中已选择的顶点from[u]
相连。from[u]
可以理解为:u顶点是从最小生成树中的哪个顶点连出来的。
因此(u, from[u])就是最小生成树中的一条边。
为了输出时有序,可以把表示最小生成树中边的所有数对都加到一个set中,即可得到有序序列。
关于set的比较规则:
set默认使用less<pair<int, int>>比较规则,仿函数类模板less的实现大体为:template<class T> struct Less { bool operator () (T a, T b) { return a < b; } }
此时T类型为
pair<int, int>
,那么代码中的a < b
就是两个pair<int, int>
对象之间的比较。
而pair类型已经重载了g各种比较运算符,比较规则是先比较第一个元素,如果第一个元素相等,再比较第二个元素。其大体实现为:template<class T1, class T2> struct pair { T1 first; T2 second; bool operator < (pair<T1, T2> b) { if(first == b.first) return second < b.second; else return first < b.first; } }
该比较规则就是我们预想的输出数对的比较规则:
- 如果第一个数字不同,第一个数字小的数对排在前面。
- 如果第一个数字相同,第二个数字小的数对排在前面
因此,在使用set时不用特意设其他的比较规则,使用默认的仿函数类
less<pair<int, int>>
即可。
解法2:Prim算法堆优化
求最小生成树使用Prim算法堆优化方法。
注意优先队列中要保存包含到达顶点及权值的Edge类型的对象。
保存边的处理方法与解法1相同。
解法3:Kruskal算法
Kruskal算法是加边法,把获得最小生成树过程中选择的边都加入到一个容器(set或vector)中进行排序,按规则输出该容器中的所有边即可。
【题解代码】
解法1:Prim算法 邻接矩阵 使用set排序
#include<bits/stdc++.h>
using namespace std;
#define N 105
struct Edge
{
int v, w;
Edge(){}
Edge(int a, int b):v(a),w(b){}
};
int n, m, dis[N], from[N];//from[i]到顶点i的一条边是最小生成树中的边
bool vis[N];
vector<Edge> edge[N];
set<pair<int, int>> tree;//保存生成树的所有边
void prim()
{
memset(dis, 0x3f, sizeof(dis));
dis[1] = 0;
for(int k = 1; k <= n; ++k)
{
int u = 0;
for(int i = 1; i <= n; ++i)
if(vis[i] == false && (u == 0 || dis[i] < dis[u]))
u = i;
vis[u] = true;
if(from[u] != 0)
tree.insert(make_pair(min(from[u], u), max(from[u], u)));//from[u]和u的较小值为pair的first,较大值为second
for(Edge e : edge[u])
{
int v = e.v, w = e.w;
if(vis[v] == false && dis[v] > w)
{
dis[v] = w;
from[v] = u;
}
}
}
}
int main()
{
int f, t, w;
cin >> n >> m;
for(int i = 1; i <= m; ++i)
{
cin >> f >> t >> w;
edge[f].push_back(Edge(t, w));
edge[t].push_back(Edge(f, w));
}
prim();
for(pair<int, int> p : tree)
cout << p.first << " " << p.second << endl;
return 0;
}
解法2:Prim算法堆优化 邻接表 使用vector及sort完成排序
#include<bits/stdc++.h>
using namespace std;
#define N 105
struct Edge
{
int v, w;
Edge(){}
Edge(int a, int b):v(a),w(b){}
bool operator < (const Edge &b) const
{
return b.w < w;
}
};
int n, m, dis[N], from[N];//from[i]到顶点i的一条边是最小生成树中的边
bool vis[N];
vector<Edge> edge[N];
priority_queue<Edge> pq;
set<pair<int, int>> tree;//保存生成树的所有边
void prim()//prim算法堆优化
{
memset(dis, 0x3f, sizeof(dis));
dis[1] = 0;
pq.push(Edge(1, 0));
int visNum = 0;
while(pq.empty() == false)
{
int u = pq.top().v;
pq.pop();
if(vis[u])
continue;
vis[u] = true;
if(from[u] != 0)
tree.insert(make_pair(min(from[u], u), max(from[u], u)));
if(++visNum == n)
break;
for(Edge e : edge[u])
{
int v = e.v, w = e.w;
if(vis[v] == false && dis[v] > w)
{
dis[v] = w;
from[v] = u;
pq.push(e);
}
}
}
}
int main()
{
int f, t, w;
cin >> n >> m;
for(int i = 1; i <= m; ++i)
{
cin >> f >> t >> w;
edge[f].push_back(Edge(t, w));
edge[t].push_back(Edge(f, w));
}
prim();
for(pair<int, int> p : tree)
cout << p.first << " " << p.second << endl;
return 0;
}
解法3:Kruskal算法 保存所有边 使用vector及sort完成排序
#include<bits/stdc++.h>
using namespace std;
#define N 105
struct Edge
{
int f, t, w;
Edge(){}
Edge(int a, int b, int c):f(a),t(b),w(c){}
};
int n, m, fa[N], edgeNum;
vector<Edge> edges, tree;//edges:所有的边 tree:最小生成树的所有边
void initFa(int n)
{
for(int i = 1; i <= n; ++i)
fa[i] = i;
}
int find(int x)
{
if(x == fa[x])
return x;
else
return fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
fa[find(x)] = find(y);
}
bool cmp_edges(Edge a, Edge b)
{
return a.w < b.w;
}
bool cmp_tree(Edge a, Edge b)
{
if(a.f == b.f)
return a.t < b.t;
else
return a.f < b.f;
}
void kruskal()
{
sort(edges.begin(), edges.end(), cmp_edges);//按照权值从小到大排序
for(Edge e :edges)
{
int f = e.f, t = e.t;
if(find(f) != find(t))
{
tree.push_back(e);
merge(f, t);
if(++edgeNum == n-1)
break;
}
}
}
int main()
{
int f, t, w;
cin >> n >> m;
initFa(n);
for(int i = 1; i <= m; ++i)
{
cin >> f >> t >> w;
if(f > t)//强行让小的编号在前,大的编号在后
swap(f, t);
edges.push_back(Edge(f, t, w));//加入的边保证f<t
}
kruskal();
sort(tree.begin(), tree.end(), cmp_tree);//按照边的f从小到大,如f相同,按t从小到大排序。
for(Edge e : tree)
cout << e.f << " " << e.t << endl;
return 0;
}