图论算法

Posted izayoimiku

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论算法相关的知识,希望对你有一定的参考价值。

(Floyd)算法

例题(一本通P1342)

(O(n^3))

设状态(f[k][i][j]):从i到j通过前k个点中的若干个的最短路径和
对于第k个中转点 :

走:(f[k-1][i][k]+f[k-1][k][j])

不走:(f[k-1][i][j])

显然,可以压缩到二维

#include <bits/stdc++.h>
using namespace std;
struct node
{
	int x,y;
} V[10010];
double adm[1010][1010];
double floyd[1010][1010];
int n,m;
int s,t;

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>V[i].x>>V[i].y;
	cin>>m;
	for(int i=0;i<=n;i++)
	{
		for(int j=0;j<=n;j++)
		{
			if(i==j) 
			{
				adm[i][j]=0;//到自己的距离为0 
			}
			else adm[i][j]=0x3f3f3f3f;//初始化 
		}
	}
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		adm[a][b]=adm[b][a]=min(sqrt((V[a].x-V[b].x)*(V[a].x-V[b].x)+(V[a].y-V[b].y)*(V[a].y-V[b].y)),adm[a][b]);//松弛 
	}
	cin>>s>>t;
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				if(i!=j&&i!=k&&k!=j) 
				{
					adm[i][j]=min(adm[i][j],adm[i][k]+adm[k][j]); //状转方程 
				}
			}
		}
	}
	cout<<fixed<<setprecision(2)<<adm[s][t];
	return 0;
}

(dijkstra)算法(不适用负权图)

基本思想:

1. 将图上的初始点看作一个集合S,其它点看作另一个集合

2. 根据初始点,求出其它点到初始点的距离d[i] (若相邻,则d[i]为边权值;若不相邻,则d[i]为无限大)

3. 选取最小的d[i](记为d[x]),并将此d[i]边对应的点(记为x)加入集合S

(实际上,加入集合的这个点的d[x]值就是它到初始点的最短距离)

4. 再根据x,更新跟 x 相邻点 y 的d[y]值:d[y] = min{ d[y], d[x] + 边权值w[x][y] },因为可能把距离调小,所以这个更新操作叫做松弛操作。

(仔细想想,为啥只更新跟x相邻点的d[y],而不是更新所有跟集合 s 相邻点的 d 值? 因为第三步只更新并确定了x点到初始点的最短距离,集合内其它点是之前加入的,也经历过第 4 步,所以与 x 没有相邻的点的 d 值是已经更新过的了,不会受到影响)

5. 重复3,4两步,直到目标点也加入了集合,此时目标点所对应的d[i]即为最短路径长度。
原文链接

时间复杂度(O(n^2))

#include <bits/stdc++.h>
using namespace std;
const long long int INF=(1<<31)-1;
int edg[2020][2020];//邻接矩阵
int dis[2020];//记录距离
int vis[2020];//集合标记
int n /*点*/,m /*边*/; 
int sta/*起点*/,end/*终点*/;

int main()
{
	cin>>n>>m>>sta;
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	memset(edg,0x3f,sizeof(edg));
	for(int i=1;i<=m;i++)
	{
		int x/*起点*/,y/*终点*/,z/*边权*/;
		cin>>x>>y>>z;
		edg[x][y]=min(z,edg[x][y]);//有向图写法 
	}
	dis[sta]=0;
	for(int i=1;i<=n;i++)
	{
		int k=-1;
		for(int j=1;j<=n;j++)
		{
			if(vis[j]==0&&(k==-1||dis[k]>dis[j]))//找到最短的dis 
				k=j;
		}
		vis[k]=1;//加入集合(即打上标记 
		for(int j=1;j<=n;j++)
		{
			if(!vis[j])
				dis[j]=min(dis[j],dis[k]+edg[k][j]);
		}//更新相邻点的dis(未相邻的点的 edg 值为无穷大,不会更新 
	}
	for(int i=1;i<=n;i++)
		if(dis[i]>=0x3f3f3f3f/2)
			cout<<INF<<" ";//无解处理 
		else cout<<dis[i]<<" ";
	return 0;
}

堆优化版(dijkstra)

直接使用堆(优先队列)来找最短dis的点

同时使用邻接表(vector或链式前向星实现)降低空间消耗

单次时间复杂度(O(nlogn))

#include <bits/stdc++.h>
using namespace std;
const long long int INF=(1<<31)-1;
int head[1000010];//表头目录 
int ver[1000010];//右节点目录 
int edge[1000010];//边权值 
int nxt[1000010];//第一个与它相连的点的下标 
int tot;//邻接表节点个数 
int vis[1000010];//集合标记
int dis[1000010];//距离记录 
int n /*点*/,m /*边*/; 
int sta/*起点*/;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;/*第一维距离,第二维编号*/
inline void add(int x,int y,int z)//建邻接表 
{
	ver[++tot]=y;
	edge[tot]=z;
	nxt[tot]=head[x];//下一节点 
	head[x]=tot;
}
int main()
{
	cin>>n>>m>>sta;
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		cin>>x>>y>>z;
		add(x,y,z);
	}
	dis[sta]=0;
	q.push(make_pair(0,sta));//首元素入队列 
	while(!q.empty())
	{
		int x=q.top().second;//利用优先队列(堆)直接找到最短dis的点 
		q.pop();
		if(vis[x]) continue;
		vis[x]=1;//加入集合 
		for(int i=head[x];i;i=nxt[i])//链表遍历 
		{
			int y=ver[i],z=edge[i];
			if(dis[y]>dis[x]+z)
			{
				dis[y]=dis[x]+z;
				q.push(make_pair(dis[y],y));//更新元素入队 
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		cout<<dis[i]<<" ";
	}
	return 0;
}

未完待续......

------------------------------------------------------------------------------少女祈祷中......

以上是关于图论算法的主要内容,如果未能解决你的问题,请参考以下文章

Matlab:数模05-图论模型(Floyd算法)

Prims算法:图论

图论:Tarjan算法

ACM算法与竞赛协会第二次培训-图论-代码与题目汇总

ACM算法与竞赛协会第二次培训-图论-代码与题目汇总

ACM算法与竞赛协会第二次培训-图论-代码与题目汇总