【CCF-CSP】 201609-4 交通规划 C++ Dijkstra


一、题目

在这里插入图片描述

原题目链接

二、解题

1.题目

实现了一个解决交通规划问题的算法,主要思路是使用最小生成树的算法,并且使用了 Dijkstra 算法,具体步骤如下:

  1. 定义边结构体 Edge,以存储每一条边的起点、终点和权值。
  2. 构建邻接表,将每一条边的信息存储到 edges 向量中,同时在 G[u] 和 G[v] 列表中记录前向星,表示 u 和 v 之间有一条边。
  3. 初始化 visited,表示节点 u 是否被访问过;初始化 d,表示起点到每一个节点的最短距离;初始化 priority_queue,使用 Node 结构体存放当前扩展的节点 u 的编号,从起点 1 开始扩展。将起点的距离设为 0,并加入 priority_queue 中。
  4. 使用 priority_queue 建立小根堆,每次取出当前最小距离的节点,将其标记为已访问,统计其对应的边的权值,更新到总长度中。
  5. 遍历与节点 u 相邻的节点v,如果 v 未被访问,那么对其进行松弛操作,即使用更新后的距离 d[v]=d[u]+e.w 来更新节点v的最短距离,并将节点v入优先队列,表示 v 可能成为下一个待拓展的节点。
  6. 重复步骤 4 和步骤 5 直到 priority_queue 为空。

最终,程序会输出最小生成树的总长度,即为交通规划问题的解。

2.代码

dev c++ 5.11

#include<iostream>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;
const int N=10010;
const int M=100010;
int n,m;

struct Edge{
	int f,t,w;
	Edge(int f,int t,int w):f(f),t(t),w(w){
	}
};
vector<Edge> edges;
vector<int> G[M];
int visited[N];
int d[N];
struct Node{
	int u,d,pre;
	Node(int u=0,int d=0,int pre=0):u(u),d(d),pre(pre){}
	bool operator < (const Node a) const {
		if(d==a.d) return pre>a.pre;
		return d>a.d;
	}
};
int solve(){
	memset(visited,0,sizeof(visited));
	for(int i=1;i<=n;i++) d[i]=1<<30;
	d[1]=0;
	priority_queue<Node> pq;
	pq.push(Node(1,0,0));
	int ans=0;
	while(!pq.empty()){
		Node node=pq.top();
		pq.pop();
		int u=node.u;
		if(visited[u]) continue;
		visited[u]=1;
		ans+=node.pre;
		for(int i=0;i<G[u].size();i++){
			Edge e=edges[G[u][i]];
			int v=e.t;
			if(d[v]>=d[u]+e.w){
				d[v]=d[u]+e.w;
				pq.push(Node(v,d[v],e.w));
			}
		}
	}
	return ans;
}

int main(){
	cin>>n>>m;
	for(int i=0;i<m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		edges.push_back(Edge(u,v,w));
		edges.push_back(Edge(v,u,w));
		int k=edges.size();
		G[u].push_back(k-2);
		G[v].push_back(k-1);   
	}
	cout<<solve();
	return 0;
}



3.提交结果

在这里插入图片描述

总结

1.解释

  1. 存储边的信息
	cin>>n>>m;
	for(int i=0;i<m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		edges.push_back(Edge(u,v,w));
		edges.push_back(Edge(v,u,w));
		int k=edges.size();
		G[u].push_back(k-2);
		G[v].push_back(k-1);   
	}

其中G[u]表示从节点u出发有哪些边,这里完全忽略边的方向,将u作为起点和终点都加入到边列表中。这里的k表示边列表中边的编号,因此k-2和k-1分别表示这条边在边列表中的起点和终点的编号。通过这种方式,将边的信息添加到邻接表中,方便后面的遍历。

举例说明:

当n=4,m=5,(u,v,w)={(1,2,4),(1,3,5),(2,3,2),(2,4,3),(3,4,2)}时,程序的运行如下所示:

a. 在edges列表中添加边的信息,其中Edge(f,t,w)表示边的起点、终点和权值

edges[0]=Edge(1,2,4)
edges[1]=Edge(2,1,4)
edges[2]=Edge(1,3,5)
edges[3]=Edge(3,1,5)
edges[4]=Edge(2,3,2)
edges[5]=Edge(3,2,2)
edges[6]=Edge(2,4,3)
edges[7]=Edge(4,2,3)
edges[8]=Edge(3,4,2)
edges[9]=Edge(4,3,2)

b. 遍历边列表中的每一条边,将其添加到邻接表中

for(int i=0;i<m;i++){
int u,v,w;
cin>>u>>v>>w;
edges.push_back(Edge(u,v,w));
edges.push_back(Edge(v,u,w));
int k=edges.size();
G[u].push_back(k-2);
G[v].push_back(k-1);
}

G[1]={0,2}
G[2]={1,4,6}
G[3]={3,5,8}
G[4]={7,9}

其中G[1]={0,2}表示从节点1出发有边0和边2,即节点1到节点2和节点3的两条边。

这么做的原因是因为为了方便遍历边,不需要每次遍历整个edges数组,而只需要遍历每个点对应的G数组即可找到其连接的边。同时由于是无向图,每一条边需要在两个端点对应的G数组中都添加一次。

  1. solve()函数
    函数 solve() 的作用是寻找给定图的最小生成树并返回最小权值和。

具体而言,solve() 函数使用了一个优先队列实现 Prim 算法。首先初始化 visited 数组和 d 数组,visited 表示节点是否被访问过,初始为 0(false);d 表示每个节点到达生成树的最小距离,初始为 ∞。然后将第一个节点 1 插入优先队列 pq,优先队列按照解除距离的大小排序。初始化 MST 总权值 ans 为 0。

循环遍历优先队列,取出队首元素 u,并将 visited[u] 设为 1。然后遍历与 u 相邻的节点 v,并更新 d[v] 和 pq。如果 d[v] ≥ d[u]+e.w,其中 e.w 为 u 与 v 之间的权值,则更新 d[v]=d[u]+e.w 和插入优先队列节点 pq.push(Node(v,d[v],e.w))。同时,将边的权值 e.w 加入 MST 总权值 ans 中。

最后,循环结束时,返回 MST 总权值 ans,就是这个图的最小生成树的权值和。举例说明:
在迭代过程中,首先取出队首元素 Node(1,0,0),visited[1]=1,MST 总权值 ans 更新为 0+0=0,并遍历与 1 相邻的节点 2 和 3。对于 v=2,d[v]=∞,循环体中更新为 d[v]=d[u]+e.w=0+4=4,并将 Node(2,4,4) 加入优先队列 pq;对于 v=3,d[v]=∞,循环体中更新为 d[v]=d[u]+e.w=0+5=5,并将 Node(3,5,5) 加入优先队列。优先队列中的元素为 {(2,4,4),(3,5,5)}。

接下来,优先队列元素中取出 Node(2,4,4),visited[2]=1,MST 总权值 ans 更新为 0+4=4,并遍历与 2 相邻的节点 1 和 3。对于 v=1,d[v]=0,visited[1]=1,循环体中更新为不操作;对于 v=3,d[v]=∞,循环体中更新为 d[v]=d[u]+e.w=4+2=6,并将 Node(3,6,3) 加入优先队列。优先队列中的元素为 {(3,5,5),(3,6,3)}。

然后,优先队列元素中取出 Node(3,5,5),visited[3]=1,MST 总权值 ans 更新为 4+5=9,并遍历与 3 相邻的节点 1 和 2。对于 v=1,d[v]=0,visited[1]=1,循环体中更新为不操作;对于 v=2,d[v]=4,visited[2]=1,循环体中更新为不操作。优先队列元素中只剩下 Node(3,6,3)。

最后,优先队列元素中取出 Node(3,6,3),visited[3]=1,MST 总权值 ans 更新为 9+3=12,并遍历与 3 相邻的节点 4。对于 v=4,d[v]=∞,循环体中更新为 d[v]=d[u]+e.w=6+2=8,并将 Node(4,8,2) 加入优先队列。

由于优先队列中已经没有元素,循环结束。求得该图的最小生成树总权值为 12,对应的边集为 {(1,2,4),(2,3,2),(3,4,2)}。

因此,程序输出结果为 12。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值