全网最全!PAT甲组1003.Emergency (优先队列实现迪杰斯特拉算法,Bellman算法,SPFA)思路与注意点--补充《算法笔记》

A1003

题目链接

个人思路

Dijkstra典型的贪心思想。
本题在Dijkstra算法上增加点权和统计最短路径条数,除了正常实现算法之外,尝试使用优先队列求解,以提高运行效率
但好像优先队列时间还没有正常实现快,不明白怎么回事,但学习一下优先队列实现的思路还是好的

效率上看:SPFA>优先队列>正常>Bellman(但在本题上优先队列速度略慢于正常Dijkstra)

正常实现时间

在这里插入图片描述

优先队列实现时间

在这里插入图片描述

Bellman实现时间

在这里插入图片描述

SPFA实现时间

在这里插入图片描述

正常实现代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 505;
const int inf = 0x3fffffff;
int N, M, C1, C2;
int team[maxn];//点权,各点救援队数目 
int pathCnt[maxn];//最短路径个数 
int G[maxn][maxn];//无向有权图 
int dist[maxn], t[maxn];
bool vis[maxn];
int main(int argc, char *argv[]) {
	scanf("%d%d%d%d", &N, &M, &C1, &C2);
	for(int i = 0; i < N; ++i)
	{
		scanf("%d", &team[i]);
		dist[i] = inf;
	}
	for(int i = 0; i < M; ++i)
	{
		int c1, c2, l;
		scanf("%d%d%d", &c1, &c2, &l);
		G[c1][c2] = G[c2][c1] = l;
	}
	//Dijkstra
	dist[C1] = 0;//初始化 
	t[C1] = team[C1];
	pathCnt[C1] = 1;
	for(int i = 0; i < N; ++i)
	{
		int u, udist = inf;
		for(int j = 0; j < N; ++j)//寻找当前最短路径 
		{
			if(!vis[j])
			{
				if(dist[j] < udist)
				{
					u = j;
					udist = dist[j];
				}
			}
		}
		vis[u] = true;
		for(int v = 0; v < N; ++v)
		{
			if(!vis[v] && G[u][v])
			{
				if(udist + G[u][v] < dist[v])//更新 
				{
					dist[v] = udist + G[u][v];//更新最短距离 
					t[v] = t[u] + team[v];//更新点权
					pathCnt[v] = pathCnt[u];//更新路径数目 
				}
				else if(udist + G[u][v] == dist[v])
				{
					if(t[u] + team[v] > t[v])
						t[v] = t[u] + team[v];
					pathCnt[v] += pathCnt[u];
				}
			}
		}
	}
	printf("%d %d", pathCnt[C2], t[C2]);
	return 0;
}

优先队列+Dijkstra

优先队列在Dijkstra中的作用,在O(1)的时间内得到dist[]中的最短距离的点,减少顺序循环遍历,以O(N)的时间在dist[]数组找最短距离的点。但中间对优先队列的插入删除同样会消耗O(logN)的时间
鉴于优先队列只是提供最短距离的点,因此不能用优先队列取代dist[]数组,因为无法找到相应顶点进行最短距离的更新操作。除此之外根据题意仍要有记录点权的数组和记录最短路径条数的数组。
若当前距离等于最短距离时,只需更新点权和最短路径条数,无需再将节点入队,否则只是重复访问该节点。

优先队列实现代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 505;
const int inf = 0x3fffffff;
int N, M, C1, C2;
int team[maxn];//点权,各点救援队数目 
int pathCnt[maxn];//最短路径个数 
int G[maxn][maxn];//无向有权图 
int dist[maxn], t[maxn];//最短距离dist,最大队员数目t 
bool vis[maxn];
struct Node{
	int index;//顶点 
	int dist;//最短距离 
	Node(){}
	Node(int id, int dd)
	{
		index = id;
		dist = dd;
	}
	friend bool operator < (Node n1, Node n2)//重载运算符,在优先队列中排序 
	{
		if(n1.dist != n2.dist)
			return n1.dist > n2.dist;
		else
			return n1.index > n2.index;
	}
}node;
priority_queue<Node> q;//优先队列便于查找最短距离 
int main(int argc, char *argv[]) {
	scanf("%d%d%d%d", &N, &M, &C1, &C2);
	for(int i = 0; i < N; ++i)
	{
		scanf("%d", &team[i]);
		dist[i] = inf;
	}
	for(int i = 0; i < M; ++i)
	{
		int c1, c2, l;
		scanf("%d%d%d", &c1, &c2, &l);
		G[c1][c2] = G[c2][c1] = l;
	}	
	//初始化 
	dist[C1] = 0;
	t[C1] = team[C1];
	pathCnt[C1] = 1;
	node = Node(C1, dist[C1]);
	q.push(node);
	//Dijkstra 
	while(!q.empty())
	{
		Node now = q.top();
		q.pop();//删除队首元素 
		int u = now.index;
		int udist = now.dist;
		if(vis[u])//若在 udist + G[u][v] == dist[v]条件中q.push(node),队列中元素会重复 
			continue;
		vis[u] = true;
		for(int v = 0; v < N; ++v)
		{
			if(!vis[v] && G[u][v])
			{
				if(udist + G[u][v] < dist[v])
				{
					t[v] = t[u] + team[v];
					pathCnt[v] = pathCnt[u];
					dist[v] = udist + G[u][v];
					node = Node(v, dist[v]);
					q.push(node);//更新后的节点入队 
				}
				else if(udist + G[u][v] == dist[v])
				{
					t[v] = max(t[v], t[u] + team[v]);
					pathCnt[v] += pathCnt[u];
					/*node = Node(v, dist[v]);//加上后需要在循环外判断是否被访问 
					q.push(node);//无需此入队操作,只会造成重复访问*/
				}
			}
		}
	}
	printf("%d %d", pathCnt[C2], t[C2]);
	return 0;
}

Bellman算法

主要是练习一下Bellman算法,实现起来与Dijkstra算法类似,与Dijkstra算法不同之处:

  • Bellman是对边进行遍历的,Dijkstra是对顶点进行遍历;
  • 在处理num[]数组时,由于Bellman需要对顶点重复访问(不能用vis[])因此当dist[u] + vdist == dist[v]时需要重新计算num[v]
  • 时间复杂度较高

Bellman算法实现

#include <bits/stdc++.h>
using namespace std;
const int maxn = 505;
const int inf = 0x3fffffff;
int N, M, C1, C2;
struct Node{
	int v;//边的终点 
	int dist;//边权
	Node(){}
	Node(int vv, int dd){
		v = vv;
		dist = dd;
	} 
}node; 
vector<Node> G[maxn];
set<int> pre[maxn];//前驱结点集合 
int weight[maxn];
int dist[maxn], w[maxn], num[maxn];
void Bellman()
{
	for(int i = 0; i < N - 1; ++i)//做N-1次遍历 
	{
		for(int u = 0; u < N; ++u)//遍历图中每条边 
		{
			for(int j = 0; j < G[u].size(); ++j)
			{
				int v = G[u][j].v;
				int vdist = G[u][j].dist;
				if(dist[u] + vdist < dist[v])
				{
					dist[v] = dist[u] + vdist;
					w[v] = w[u] + weight[v];
					num[v] = num[u];
					pre[v].clear();
					pre[v].insert(u);
				}
				else if(dist[u] + vdist == dist[v])
				{
					pre[v].insert(u);
					w[v] = max(w[v], w[u] + weight[v]);
					num[v] = 0;//重新统计num[v] 
					for(set<int>::iterator it = pre[v].begin(); it != pre[v].end(); ++it)
					{
						num[v] += num[*it];
					}
				}
			}
		}
	}
	
}
int main(int argc, char *argv[]) {
	scanf("%d%d%d%d", &N, &M, & C1, &C2);
	for(int i = 0; i < N; ++i)
	{
		scanf("%d", &weight[i]);
		dist[i] = inf;
	}
	for(int i = 0; i < M; ++i)
	{
		int c1, c2, l;
		scanf("%d%d%d", &c1, &c2, &l);
		node = Node(c2, l);
		G[c1].push_back(node);//构造无向有权图 
		node = Node(c1, l);
		G[c2].push_back(node);
	}
	dist[C1] = 0;
	w[C1] = weight[C1];
	num[C1] = 1;
	Bellman();
	printf("%d %d", num[C2], w[C2]);
	return 0;
}

SPFA算法

SPFA对Bellman算法优化之处在于一个递进关系:只有当某个顶点dist[u]值改变时,从它出发的边的邻接点v的dist[v]值才有可能改变。
SPFA算法的性能优于堆优化的Dijkstra算法
通过SPFA算法找出最短路径,存入pre[]。通过对pre[]进行dfs,计算得到相应的最大点权值和最短路径条数

SPFA算法实现

#include <bits/stdc++.h>
using namespace std;
const int maxn = 505;
const int inf = 0x3fffffff;
int N, M, C1, C2;
struct Node{
	int v;//边的终点 
	int dist;//边权
	Node(){}
	Node(int vv, int dd){
		v = vv;
		dist = dd;
	} 
}node; 
vector<Node> G[maxn];
vector<int> pre[maxn];//前驱结点集合 
int weight[maxn];
int dist[maxn], w[maxn], num[maxn];

bool inq[maxn];
int inqcnt[maxn];//记录入队次数 
void SPFA()
{
	queue<int> q;
	q.push(C1);
	inq[C1] = true;
	inqcnt[C1]++;
	while(!q.empty())
	{
		int u = q.front();
		q.pop();
		inq[u] = false;//设置u不在队列中
		for(int i = 0; i < G[u].size(); ++i)
		{
			int v = G[u][i].v;
			int vdist = G[u][i].dist;
			if(dist[u] + vdist < dist[v])
			{
				dist[v] = dist[u] + vdist;
			//	w[v] = w[u] + weight[v];//在SPFA过程中不能准确计算w[],num[]
			//	num[v] = num[u];
				pre[v].clear();
				pre[v].push_back(u);
				if(!inq[v])
				{
					q.push(v);
					inq[v] = true;
					inqcnt[v]++;
				}
			}
			else if(dist[u] + vdist == dist[v])
			{
				//w[v] = max(w[v], w[u] + weight[v]);
				pre[v].push_back(u);
			/*	num[v] = 0;
				for(vector<int>::iterator it = pre[v].begin(); it != pre[v].end(); ++it)
					num[v] += num[*it]; */
			}
		} 
	}
}
int pathcnt = 0, maxWeight = -1;
vector<int> tempPath;
void dfs(int v)
{
	if(v == C1)
	{
		tempPath.push_back(v);
		int curWeight = 0;
		pathcnt++;
		for(int i = tempPath.size() - 1; i >= 0; i--)
		{
			int id = tempPath[i];
			curWeight += weight[id];
		}
		if(curWeight > maxWeight)
			maxWeight = curWeight;
		tempPath.pop_back();
		return;
	}
	tempPath.push_back(v);
	for(int i = 0; i < pre[v].size(); ++i)
	{
		dfs(pre[v][i]);
	}
	tempPath.pop_back();
} 
int main(int argc, char *argv[]) {
	scanf("%d%d%d%d", &N, &M, & C1, &C2);
	for(int i = 0; i < N; ++i)
	{
		scanf("%d", &weight[i]);
		dist[i] = inf;
	}
	for(int i = 0; i < M; ++i)
	{
		int c1, c2, l;
		scanf("%d%d%d", &c1, &c2, &l);
		node = Node(c2, l);
		G[c1].push_back(node);//构造无向有权图 
		node = Node(c1, l);
		G[c2].push_back(node);
	}
	dist[C1] = 0;
	w[C1] = weight[C1];
	num[C1] = 1;
	SPFA();
	dfs(C2);
	printf("%d %d", pathcnt, maxWeight);
	return 0;
}

SPFA过程中的问题

在SPFA过程中,无法对w[],num[]直接计算,必须要通过pre[]集合dfs,计算出来相应的值
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值