图的复习(萌新之人所写)

本文详细介绍了Floyd算法在处理带权和不带权图中的应用,包括核心步骤、邻接矩阵的使用,以及并查集在图联通问题中的运用。还讨论了Kruskal算法和Dijkstra算法的基本概念及其在求解最短路径问题中的角色。
摘要由CSDN通过智能技术生成

floyd算法

该算法有一个核心步骤就是使用三个for循环将全部有向(或者无向)图各个点之间的最短距离储存在一个二维数组里面,所使用的数据结构是一个邻接矩阵。可以处理带权值的也可以处理没有带权值的图,没有带权值的图往往是使用一个bool类型的邻接矩阵。

核心代码
这是储存带权图

for(int k=1;k<=n;k++)
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(a[i][k]<inf&&a[k][j]<inf&&a[i][j]>a[i][k]+a[k][j])
			{
				a[i][j]=a[i][k]+a[k][j];
			}
		}
	}
}

这是不带权并且使用bool类型来装

for(int k=1;k<=n;k++)
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(a[i][k]&&a[k][j])
			{
				a[i][j]=true;
			}
		}
	}
}

例题:刻入光盘

 这一题就是需要使用不带权和使用bool类型类型来判断两点是否已经联通,也就是上面提供的第二个模板,如果i->k联通且k->j联通,那么i->j就联通,那么此时就已经将i->j标记为true,也就是已经联通的意思。
  这里还需值得学习的一点就是,这里也使用了并查集的一点思想,就是利用一个fa数组将各个点之间的关系链接起来,如果fa数组所存的数字就是自己本身的下标,那么就代表着是最开始借出去光碟的人,那么ans(答案)就需要加加。这个可以通过一个for循环来检查一遍

#include<iostream>
#define N 220
using namespace std;
int n;
int a[N][N];
int fa[440];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int x;
		while(cin>>x&&x)
		{
			a[i][x]=1;
		}
	}
	for(int i=1;i<=440-10;i++)
	{
		fa[i]=i;
	}
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				if(a[i][k]&&a[k][j])
				a[i][j]=1;
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(a[i][j])
			{
				fa[j]=fa[i];
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		if(fa[i]==i)
		{
			ans++;
		}
	}
	cout<<ans<<endl;
	return 0;
}

P6464 [传智杯 #2 决赛] 传送门

这一题就是第一个带权图

我们看一下这一题的数据大小,只有100所以很小,所以我们直接使用暴力解法。每一边都尝试使其为0(就是重新使权值为0)然后在这个新的带权图重新走一边floyd算法重新计算全源最短路径

这里值得注意的是我们可以分别重新以i和j走一遍floyd这样就可以使得时间复杂度变为n的4次方

代码如下:

#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
const int N=150;
int n,m,a[N][N],a1[N][N];
void inback()
{
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)a1[i][j]=a[i][j];
}
int ans=2e9;

int main()
{
	cin>>n>>m;
	memset(a,0x3f,sizeof(a));
	for(int i=1;i<=m;i++){
		int x,y,z;
		cin>>x>>y>>z;
		a[x][y]=a[y][x]=z;
	}
	for(int k=1;k<=n;k++) 
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
	inback();
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++){
		a1[i][j]=a1[j][i]=0;
		for(int k1=1;k1<=n;k1++)
		for(int k2=1;k2<=n;k2++){
			a1[k1][k2]=min(a1[k1][k2],a1[k1][i]+a1[i][k2]);
		}
		
		for(int k1=1;k1<=n;k1++)
		for(int k2=1;k2<=n;k2++){
			a1[k1][k2]=min(a1[k1][k2],a1[k1][j]+a1[j][k2]);
		}
		int tmp=0;
		for(int k=1;k<=n;k++)
		for(int t=1;t<k;t++){
			tmp+=a1[k][t];
		}
		
		ans=min(ans,tmp);
		inback();
	}
	cout<<ans<<endl;
	return 0;
}

并查集数据结构 和 Kruskal算法

并查集就是使用一个一维数组通过子节点的下标指向父节点来使其成为一颗树,其核心就是构建find函数和Union函数。

并查集是一种数据结构,对这种数据和结构来说最常用结合的算法就是kruskal算法

对于kruskal算法来说:就是一种采取最优解的方法,常常使用排序,所以在这里我们需要知道定义结构体算法和运算符重载

对此我们还需要知道最小生成树使k>=n-1 这常常是最小生成树的退出条件

代码如下:

村村通

 这一题的主要思路就是其实就是使用并查集来解决,如果fa数组中所存放的值就是自己本身,那么就代表这个为一个整体。有多少个整体就需要修多少条路

代码如下:

#include<iostream>
#include<cstdio>
using namespace std;
const int N=1100;
int fa[N];
int n,m;
void ini()
{
	for(int i=1;i<=n;i++)
	{
		fa[i]=i;
	}
}
int cha(int x){
	return fa[x]==x?x:fa[x]=cha(fa[x]);
}
void Union(int x,int y)
{
	x=cha(x);
	y=cha(y);
	if(x==y)return ;
	fa[x]=y;
}
int main()
{
	
	while(cin>>n>>m&&n)
	{
		ini();
		for(int i=1;i<=m;i++){
			int x,y;
			cin>>x>>y;
			Union(x,y);
		}
		int ans=0;
		for(int i=1;i<=n;i++)
		{
			if(fa[i]==i)
			ans++;	
		}
		cout<<ans-1<<endl;
	}
	return 0;
}

最小生成树

 如果有n个点,那么就有n-1条边,所以这就是我们循环的退出条件。  既然我们要使用到kruskal算法,那么我们就要将最小的边利用排序先放在前面。然后再使用并查集Union将其联合。

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
const int N=2e5+100;

int fa[N];

struct node 
{
	int x,y,w;
}e[N];

bool cmp(node x,node y){
	return x.w<y.w;
}

int cha(int x){
	return fa[x]==x?x:fa[x]=cha(fa[x]);
}

void Union(int x,int y){
	x=cha(x);y=cha(y);
	if(x==y)return ;
	fa[x]=y;
}

int main()
{
	cin>>n>>m;
	
	for(int i=1;i<=m;i++){
		int x,y,z;
		cin>>e[i].x>>e[i].y>>e[i].w;
	}
	
	sort(e+1,e+1+m,cmp);
	
	for(int i=1;i<=N-100;i++){
		fa[i]=i;
	}	
	int ans=0;
	int tmp=0;
	for(int i=1;i<=m;i++)
	{
		if(cha(e[i].x)!=cha(e[i].y)){
			Union(e[i].x,e[i].y);
			tmp++;
			ans+=e[i].w;
			if(tmp>=n-1){
				cout<<ans<<endl;
				return 0;
			}	
		}		
	}
	cout<<"orz"<<endl;
	return 0;
}

dijstra算法

dijstra算法又称单源最短路径算法,是指定某地到某地的求最短距离的算法,这里需要掌握的是dijstra算法的两个核心步骤,以及一个关于空间复杂度的优化(链式前向星),和时间复杂度的优化(堆优化,优先队列)

【模板】单源最短路径(标准版)

#include<iostream>
#include<queue>
using namespace std;
const int maxN = 100010;
const int maxM = 500010;
int n, m, s, cnt;
struct node
{
	int id;
	int dis;
	bool operator< (const node& x) const {
		return x.dis < dis;
	}
};
priority_queue<node> q;
struct EDGE
{
	int to;
	int w;
	int next;
}edge[maxM];
int head[maxM];
bool vis[maxM];
int ans[maxM];
void add(int u, int v, int w)
{
	cnt++;
	edge[cnt].to = v;
	edge[cnt].w = w;
	edge[cnt].next = head[u];
	head[u] = cnt;
}
int main()
{
	cin >> n >> m >> s;
	for (int i = 1; i <= m; i++)
	{
		int u, v, w;
		cin >> u >> v >> w;
		add(u, v, w);
	}
	for (int i = 1; i <= maxM; i++)
	{
		ans[i] = 0x7fffffff;
	}
	
	ans[s]=0;
	q.push(node{s,0});
	while(!q.empty())
	{
		node tmp=q.top();
		q.pop();
		int k=tmp.id;
		if(vis[k])
		continue;
		vis[k]=true;
		for(int i=head[k];i!=0;i=edge[i].next)
		{
			int to=edge[i].to;
			if(!vis[to]&&ans[to]>ans[k]+edge[i].w)
			{
				ans[to]=ans[k]+edge[i].w;
				q.push(node{to,ans[to]});
			}
		}
	}
	
	for (int i = 1; i <= n; i++)
	{
		cout << ans[i] << " ";
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白色的风扇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值