一、题目
二、解题
1.题目
实现了一个解决交通规划问题的算法,主要思路是使用最小生成树的算法,并且使用了 Dijkstra 算法,具体步骤如下:
- 定义边结构体 Edge,以存储每一条边的起点、终点和权值。
- 构建邻接表,将每一条边的信息存储到 edges 向量中,同时在 G[u] 和 G[v] 列表中记录前向星,表示 u 和 v 之间有一条边。
- 初始化 visited,表示节点 u 是否被访问过;初始化 d,表示起点到每一个节点的最短距离;初始化 priority_queue,使用 Node 结构体存放当前扩展的节点 u 的编号,从起点 1 开始扩展。将起点的距离设为 0,并加入 priority_queue 中。
- 使用 priority_queue 建立小根堆,每次取出当前最小距离的节点,将其标记为已访问,统计其对应的边的权值,更新到总长度中。
- 遍历与节点 u 相邻的节点v,如果 v 未被访问,那么对其进行松弛操作,即使用更新后的距离 d[v]=d[u]+e.w 来更新节点v的最短距离,并将节点v入优先队列,表示 v 可能成为下一个待拓展的节点。
- 重复步骤 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.解释
- 存储边的信息
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数组中都添加一次。
- 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。