最小生成树问题
由一个图生成的一个树,无回路,e=v-1 ,这颗树包含母图的全部顶点,且只有v-1条边,边的权重之和最小。这样的树就叫最小生成树。
计算生成树主要有两个算法,一个是Prim算法,一个是Kruskal算法,两个算法都是贪心的思想,这里介绍效率较高的Kruskal算法。
Kruskal
算法步骤:
- 每次从所有边中获取权值最小的边
- 如果这条边的点不会构成环则采用这条边
- 重复1 2 知道边的数量为v-1即可
我们先用sort给所有边的权值排个序,然后一次判断,判断的时候用前面讲的并查集去判环即可。
这里以上面这个图为例:
#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#include <set>
#define Max 100
using namespace std ;
typedef struct edg{
int u , v ,w ;
edg(int u ,int v ,int w): u(u),v(v),w(w){}
}Edg;
vector <Edg> edgs ;
int a[Max];
int v ,e;
bool cmp(Edg &a1 , Edg &a2)//自定义排序 从小到大
{
return a1.w < a2.w;
}
int find (int x) //寻找并压缩路径
{
return a[x] == x ? x : a[x] = find (a[x]);
}
int Kruskal()
{
int ans = 0 ; //记录权值
int count = 0 ;//记录收入边的个数 最后判断是否有可生成最小生成树 count != v-1
sort(edgs.begin(),edgs.end() ,cmp); //将边进行排序
for (int i = 1 ;i<=v ;i++) //初始化并查集
a[i] = i ;
for (int i=0;i<edgs.size(); i++) // 遍历权值从小到大 的边
{
edg &e = edgs[i];
int from =e.u;
int to = e.v;
if (find(from) != find(to)) // 如果取这条边不会构成环则收入
{
count ++ ; //边+1
ans +=e.w ;//加上权值
a[find(from)] = find(to); // 建立两个点的联系
}
}
if (count != v-1) return -1;
else
return ans ;
}
int main ()
{
cin >>v>>e;
for (int i=0;i<e;i++)
{
int u ,v ,w ;
cin >> u >> v>>w ;
edgs.push_back(edg(u,v,w));
}
int item = Kruskal();
if (item ==-1)
cout <<"不存在最小生成树 ";
else
cout <<"最小生成树权值为:"<<item ;
return 0;
}
/*
7 12
1 2 2
1 3 4
1 4 1
2 4 3
2 5 10
3 4 2
3 6 5
4 5 7
4 6 8
4 7 4
5 7 6
6 7 1
*/
拓扑排序
如果图中从V到W有一条有向路径,则V一定排在W之前。满足此条件的顶点序列称为一个拓扑序。获得一个拓扑序的过程就是拓扑排序。AOV(网络)如果有合理的拓扑序,则必定是有向无环图。因此,拓扑图和并查集一样可以判断是否有环。
举一个计算机排课的例子:
左边是每个课程的指向关系,比如如果要学数据结构的前提要先学程序设计基础。这样就生成了又边的一个图。一个结点被其他结点指向的个数成为入度,每次先出来的一定是入度为0的结点,例如1 2 8 4 ,当出来一个结点后,其他结点的入度数可能会发生改变。
下面是出队顺序, 顺序不唯一!
1 2 8 4
3 13 9 5
7 6
11 12 10 15
14
#include <iostream>
#include <vector>
#include <string.h>
#include <queue>
#define Max 100
using namespace std ;
int main ()
{
int rudu[Max];
vector <int> Map[Max]; //储存图
memset(rudu,0,sizeof (rudu)) ; //入度初始化为0
int m,n; //m 个点 n 个关系
cin >> m >> n;
for (int i=0;i<n ;i++)
{
int u , v ;
cin >> u >> v ;
rudu[v] ++ ;//v点的入度加一
Map[u].push_back(v);// 保存u指向的点,便于弹出时更新指向点的入度
}
queue<int> q ;// 记录入读为0的点,依次弹出
for (int i=1;i<=m;i++)
{
if (rudu[i]==0)
q.push(i);
}
int count = 0;
while (!q.empty())
{
int out = q.front() ;q.pop();
cout << out <<" ";
count ++ ;
for (int i=0;i<Map[out].size();i++)
{
rudu[Map[out][i]] -=1;
if (rudu[Map[out][i]] == 0)
q.push(Map[out][i]);
}
}
if (count < m)
cout <<"存在环"<<endl;
}
/*
15 14
6 15
10 14
2 13
7 12
7 11
9 11
7 10
9 10
8 9
3 7
5 6
4 5
1 3
2 3
*/
习题: 公路村村通
上面最小生成树的代码稍微修改即可!
#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#include <set>
#define Max 10000
using namespace std ;
typedef struct edg{
int u , v ,w ;
edg(int u ,int v ,int w): u(u),v(v),w(w){}
}Edg;
vector <Edg> edgs ;
int a[Max];
int v ,e;
bool cmp(Edg &a1 , Edg &a2)//自定义排序 从小到大
{
return a1.w < a2.w;
}
int find (int x) //寻找并压缩路径
{
return a[x] == x ? x : a[x] = find (a[x]);
}
int Kruskal()
{
int ans = 0 ; //记录权值
int count = 0 ;//记录收入边的个数 最后判断是否有可生成最小生成树 count != v-1
sort(edgs.begin(),edgs.end() ,cmp); //将边进行排序
for (int i = 1 ;i<=v ;i++) //初始化并查集
a[i] = i ;
for (int i=0;i<edgs.size(); i++) // 遍历权值从小到大 的边
{
edg &e = edgs[i];
int from =e.u;
int to = e.v;
if (find(from) != find(to)) // 如果取这条边不会构成环则收入
{
count ++ ; //边+1
ans +=e.w ;//加上权值
a[find(from)] = find(to); // 建立两个点的联系
}
}
if (count != v-1) return -1;
else
return ans ;
}
int main ()
{
cin >>v>>e;
for (int i=0;i<e;i++)
{
int u ,v ,w ;
cin >> u >> v>>w ;
edgs.push_back(edg(u,v,w));
}
int item = Kruskal();
if (item ==-1)
cout <<"-1";
else
cout <<item ;
return 0;
}
/*
7 12
1 2 2
1 3 4
1 4 1
2 4 3
2 5 10
3 4 2
3 6 5
4 5 7
4 6 8
4 7 4
5 7 6
6 7 1
*/