在做搜索题目中,出现图论中的最短路径问题(用了迪杰斯特拉算法)。这里以算法笔记中的讲解为基础,复习迪杰斯特拉算法基本模板
算法笔记P386
有 N 个城市(编号为0~N - 1)、M 条道路(无向边),并给出M条道路的距离属性和花费属性。现给定起点 S 和终点 D,求从起点到终点的最短路径、最短距离以及花费。注意:如果有多条最短路径,则选择花费最小的那条
输入格式:
第一行输入四个整数 N、M、S、D
接下来M 行,每行4个数字,分别是M 条道路的两个端点,以及距离和花费
输出格式:
输出从起点到终点的最短路径,以及距离和花费
输入样例
4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20
输出样例
0 2 3 3 40
DijkstraDijkstraDijkstra方法
#include<bits/stdc++.h>
using namespace std;
const int INF = 0X3fffffff;
const int N = 600;
int n,m,st,ed;
int g[N][N], cost[N][N]; //分别用来存储距离和花费
int dis[N], c[N], pre[N]; //一个用来保存距离,一个用来保存最小花费
bool vis[N]; //用来标识每个节点是否访问过
void Dijkstra(int st)
{
//先初始化dis和c两个数组.首先已选择节点只有起点一个
for(int i = 0;i < n;i ++){
if(g[st][i] != INF){
dis[i] = g[st][i];
c[i] = cost[st][i];
}
}
dis[st] = 0; c[st] = 0; vis[st] = true;//初始化
//遍历剩下的n - 1个节点
for(int i = 0;i < n - 1;i ++){
int u = -1, MIN = INF;
//找到距离最近的一个节点
for(int j = 0;j < n;j ++){
if(!vis[j] && dis[j] < MIN){
MIN = dis[j];
u = j;
}
}
if(u == -1) return;
vis[u] = true;
for(int v = 0;v < n;v ++){
if(!vis[v] && g[u][v] != INF){
//如果该节点没有被访问过且当前中间点到目标节点存在路径
if(dis[u] + g[u][v] < dis[v]){
dis[v] = dis[u] + g[u][v];
c[v] = c[u] + cost[u][v];
pre[v] = u;
}
else if(dis[u] + g[u][v] == dis[v]){
if(c[u] + cost[u][v] < c[v]){
c[v] = c[u] + cost[u][v]; //只更新花费
pre[v] = u;
}
}
}
}
}
}
void DFS(int ed)
{
stack<int> path;
int node = ed;
while(node != st){
path.push(node);
node = pre[node];
}
path.push(node);
while(!path.empty()){
node = path.top();
path.pop();
cout<<node<<" ";
}
return;
}
int main()
{
fill(g[0], g[0] + N*N, INF);
fill(cost[0], cost[0] + N*N, INF);
cin>>n>>m>>st>>ed;
for(int i = 0;i < m;i ++){
int u,v;
cin>>u>>v;
cin>>g[u][v]>>cost[u][v];
g[v][u] = g[u][v];
cost[v][u] = cost[u][v];
}
Dijkstra(st);
DFS(ed); //打印路径
cout<<dis[ed]<<" "<<c[ed];
return 0;
}
Dijkstra+DFSDijkstra + DFSDijkstra+DFS方法
整体的算法逻辑和上述方法相似;
不过将上游任务和下游任务分开了;此时上游任务就是纯粹的、最核心的最短距离问题, 用DijkstraDijkstraDijkstra算法计算源点各点的最短距离并且保存其最短路径(前驱节点方法)
注意DFS的不同处理,这里在保留最短路径之后,按照Pre数组构造一棵路径树,用DFS的方法遍历找出最小花费,如果花费得到更新,则保存此时的路径
#include<bits/stdc++.h>
using namespace std;
const int INF = 0X3fffffff;
const int N = 600;
int n,m,st,ed;
int g[N][N], cost[N][N];
int dis[N];
bool vis[N];
//以下是和上面方法不同的全局变量
int minCost = INF;
vector<int> pre[N];
vector<int> tempPath, path; //用来存储临时路径和最优路径
//此时的dijkstra算法是纯粹的最短路径和最短距离算法,只用于找到原点到每个点的最短距离并且保存相应的最短路径
void Dijkstra(int st)
{
fill(dis, dis + N, INF);
for(int i = 0;i < n;i ++){
if(g[st][i] != INF){
dis[i] = g[st][i];
pre[i].push_back(st);
}
}
vis[st] = true; dis[st] = 0;
for(int i = 0;i < n - 1;i ++){
int u = -1, MIN = INF;
//找到单趟次最小的节点
for(int j = 0;j < n;j ++){
if(!vis[j] && dis[j] < MIN){
u = j;
MIN = dis[j];
}
}
if(u == -1) return;
vis[u] = true;
for(int v = 0;v < n;v ++){
if(!vis[v] && g[u][v] != INF){
if(dis[u] + g[u][v] < dis[v]){
dis[v] = dis[u] + g[u][v];
pre[v].clear();
pre[v].push_back(u);
}
else if(dis[u] + g[u][v] == dis[v]){
pre[v].push_back(u);
}
}
}
}
}
//相当于根据我的pre容器构建一颗路径数组,数组的叶子节点即为路径的起点,进行递归路径遍历以找到符合要求的路径
void DFS(int v)
{
//因为我保留的是前驱节点,所以这时是在向前做递归
if(v == st){
//递归边界,此时形成了一条从st 走向 ed 的完整的道路,这是开始统计花费
tempPath.push_back(v);
int tempCost = 0;
for(int i = tempPath.size() - 1;i > 0;i --){
//从起点向终点遍历
int now = tempPath[i];
int next = tempPath[i - 1];
tempCost += cost[now][next];
}
if(tempCost < minCost){
minCost = tempCost;
//并且保存路径
path = tempPath;
}
//易错点,此时找到一条完整路径并且计算花费,注意可能不止存在一条路径
tempPath.pop_back();
return;
}
tempPath.push_back(v);
for(int i = 0;i < pre[v].size();i ++){ //此时保存着在最短路径的前提下,v的可能的不同的前驱节点
DFS(pre[v][i]);
}
tempPath.pop_back(); //DFS的基本格式,需要回退
}
int main()
{
scanf("%d%d%d%d", &n, &m, &st, &ed);
//初始化
fill(g[0], g[0] + N * N, INF);
fill(cost[0], cost[0] + N * N, INF);
for(int i = 0;i < m;i ++){
int u,v;
scanf("%d%d", &u, &v);
scanf("%d%d", &g[u][v], &cost[u][v]);
g[v][u] = g[u][v]; cost[v][u] = cost[u][v];
}
Dijkstra(st);
DFS(ed); //保存路径的前提下用于获取最优路径(也即花费最下的路径)
for(int i = path.size() - 1;i >= 0;i --)
printf("%d ", path[i]);
printf("%d %d", dis[ed], minCost);
return 0;
}