数据结构--图(下) 最小生成树、拓扑排序

最小生成树问题

由一个图生成的一个树,无回路,e=v-1 ,这颗树包含母图的全部顶点,且只有v-1条边,边的权重之和最小。这样的树就叫最小生成树。
计算生成树主要有两个算法,一个是Prim算法,一个是Kruskal算法,两个算法都是贪心的思想,这里介绍效率较高的Kruskal算法。

Kruskal

算法步骤:

  1. 每次从所有边中获取权值最小的边
  2. 如果这条边的点不会构成环则采用这条边
  3. 重复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
*/


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值