【算法】【最短路】SPFA

SPFA

首先是bfs写法的spfa。构建一个队列,选择一个点位源点,并将源点入队。不断从队列中取出一个节点,并沿着相邻的边进行松弛,将松弛后的边连着的节点入队。将取出的节点出队。
某个节点出队后还能再入队。因为某次松弛后的结果不一定是最短的结果,可能还能够通过某条路径进行松弛。
一个节点最多入队N-1次(节点数目-1),也就是说当某个节点入队N次说明图中存在负权回路。原因,,,不知道。有待补充

原理

在网上找了些spfa代码,大多数给了我冗杂的感觉。看了其思想后我照着写了一个,保存一下方便以后寻找不足并改进。
spfa是bellman-ford的改进,bellman-ford的想法是把每个边都松弛一下,算法复杂度是V^2,大部分的时间浪费在了查找新的点s和更新当前点的最短距离d。我对于spfa的理解就是bfs或dfs的变形。先用邻接表保存某个点的相邻点(节点参数 : 节点编号,两点距离),这部分复杂度为o(E),然后用队列保存被松弛的相邻节点。
如果某个点进入队列V次,说明图中存在负权回路
代码:
bfs法 这个是有向图

#define N 500
#define INF 0x3f3f3f3f
#define mem(arr,num) memset(arr,num,sizeof(arr))
/***************************************/


int V, E;
struct edge{
	int v, w;
};
int d[N];
int num[N];
bool used[N];
vector <edge> e[N];
bool spfa(int s){
	fill(d, d + V + 1, INF);
	mem(used, 0);
	d[s] = 0;
	num[s] = 1;
	queue <int> que;
	que.push(s);
	while (!que.empty()){
		int x = que.front(); que.pop();
		for (int i = 0; i < e[x].size(); i++){
			int next = e[x][i].v;
			if (d[next]>d[x] + e[x][i].w)
			{
				d[next] = e[x][i].w + d[x];
				if (!used[next])
				{
					num[next]++;
					used[next] = true;
					que.push(next);
				}
				if (num[next] == V)
					return false;
			}
		}
		used[x] = false;
	}
	return true;
}
int main()
{
	cin >> V >> E;
	for (int i = 1; i <= E; i++){
		int u; edge ed;
		cin >> u;
		cin >> ed.v >> ed.w;
		e[u].push_back(ed);
	}
	if (spfa(1)){
		cout << d[3] << endl;
	}
	else cout << "NO" << endl;
}


然后是dfs类似的 dfs判断负权回路快

#define N 500
#define INF 0x3f3f3f3f
/***************************************/


int V, E;
struct edge{
	int v, w;
};
int d[N];
int num[N];
vector <edge> e[N];
bool spfa(int s){
	for (int i = 0; i < e[s].size(); i++){
		int v = e[s][i].v;
		if (d[v]>d[s] + e[s][i].w){
			d[v] = d[s] + e[s][i].w;
			num[v]++;
			if (num[v] == V)
				return true;
		}
		if (spfa(v)){
			return true;
		}
	}
	return false;
}
int main()
{
	cin >> V >> E;
	for (int i = 1; i <= E; i++){
		int u; edge ed;
		cin >> u ;
		cin >> ed.v >> ed.w;
		e[u].push_back(ed);
	}
	fill(d, d + V + 1, INF);
	d[1] = 0;
	num[1] = 1;
	if (!spfa(1)){
		cout << d[3] << endl;
	}
	else cout << "NO" << endl;
}

例题

POJ - 3159Candies差分约束+spfa

During the kindergarten days, flymouse was the monitor of his class. Occasionally the head-teacher brought the kids of flymouse’s class a large bag of candies and had flymouse distribute them. All the kids loved candies very much and often compared the numbers of candies they got with others. A kid A could had the idea that though it might be the case that another kid B was better than him in some aspect and therefore had a reason for deserving more candies than he did, he should never get a certain number of candies fewer than B did no matter how many candies he actually got, otherwise he would feel dissatisfied and go to the head-teacher to complain about flymouse’s biased distribution.
求编号1与编号n的最大差是多少。
数据量较大,直接用队列会TLE,可以采用数组模拟队列。
用邻接表。

#include<queue>
#include<stdio.h>
#include<string>
#include<iostream>
#include<map>
#include<limits>
#include<math.h>

using namespace std;
#define N 150000+2
#define LL long long int
#define pow(a) ((a)*(a))
#define INF 0x3f3f3f3f
#define mem(arr,a) memset(arr,a,sizeof(arr))
struct edge{
	int to, cost, next;
};
edge es[N];
int headlist[N];
int d[N];
int vis[N];
int n, m;
int Q[N];
void spfa(){
	for (int i = 1; i <= n; i++){
		d[i] = INF;
		vis[i] = 0;
	}
	mem(Q, -1);
	int top = 0;
	d[1] = 0;
	Q[top] = 1;
	while (top >= 0){
		int u = Q[top]; top--; vis[u] = 0;
		for (int i = headlist[u]; i != -1; i = es[i].next){
			if (d[es[i].to] > d[u] + es[i].cost){
				d[es[i].to] = d[u] + es[i].cost;
				if (!vis[es[i].to]){
					Q[++top] = es[i].to;
					vis[es[i].to] = 1;
				}
			}
		}
	}
}
int main(){
	cin >> n >> m;
	mem(headlist, -1);
	for (int i = 0; i < m; i++){
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		es[i].to = b;
		es[i].cost = c;
		es[i].next = headlist[a];
		headlist[a] = i;
	}
	spfa();
	cout << d[n] << endl;
}

POJ - 3169Layout差分约束+spfa

Like everyone else, cows like to stand close to their friends when queuing for feed. FJ has N (2 <= N <= 1,000) cows numbered 1…N standing along a straight line waiting for feed. The cows are standing in the same order as they are numbered, and since they can be rather pushy, it is possible that two or more cows can line up at exactly the same location (that is, if we think of each cow as being located at some coordinate on a number line, then it is possible for two or more cows to share the same coordinate).
在进行差分约束时把b-a<=m可以看做从a指向b做一条权值为m的边。建图时要求所有点之间的关系都是b-a<=m的形式。遇到b-a>=m时可转化成a-b<=-m。
以上两种关系加上一个a[i+1]-a[i]>=0的关系,可以建图。
边值可能为负,这里用spfa求解。

#include<queue>
#include<stdio.h>
#include<string>
#include<iostream>
#include<map>
#include<limits>
#include<math.h>

using namespace std;
#define N 20000+5
#define LL long long int
#define pow(a) ((a)*(a))
#define INF 0x3f3f3f3f
#define mem(arr,a) memset(arr,a,sizeof(arr))
int n, a, b;
int d[N];
int edgelist[N];
int vis[N];
int num[N];
int Q[N];
struct edge{
	int v, w;
	int next;
};
edge es[N];
int top = -1;
void spfa(){
	mem(d, INF);
	d[1] = 0;
	Q[++top] = 1;
	while (top != -1){
		int x = Q[top]; top--; vis[x] = 0;
		for (int i = edgelist[x]; i != -1; i = es[i].next){
			edge e = es[i];
			if (d[e.v] > d[x] + e.w){
				d[e.v] = d[x] + e.w;
				if (!vis[e.v]){
					vis[e.v] = 1;
					Q[++top] = e.v;
					num[e.v]++;
					if (num[e.v] == n){
						cout << -1 << endl;
						return;
					}
				}
			}
		}
	}
	if (d[n] == INF)cout << -2 << endl;
	else cout << d[n] << endl;
}
void add(int from, int to, int dis,int i){
	es[i].v = to;
	es[i].w = dis;
	es[i].next = edgelist[from];
	edgelist[from] = i;
}
int main(){
	cin >> n >> a >> b;
	mem(edgelist, -1);
	int k = 1;
	for (int i = 0; i < a; i++){
		int from, to, MAX;
		scanf("%d%d%d", &from, &to, &MAX);
		if (from>to)swap(from, to);
		add(from, to, MAX, k);
		k++;
	}
	for (int i = 0; i < b; i++){
		int from, to, MIN;
		scanf("%d%d%d", &from, &to, &MIN);
		if (from>to)swap(from, to);
		add(to, from, -MIN, k);
		k++;
	}
	for (int i = 0; i < n - 1; i++){
		add(i+1, i, 0, k);
		k++;
	}
	es;
	spfa();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值