图论最短路径问题-Dijkstra算法

本文介绍了Dijkstra算法及其在寻找最短路径中的应用,包括代码示例,以及与深度优先搜索(DFS)的结合使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在做搜索题目中,出现图论中的最短路径问题(用了迪杰斯特拉算法)。这里以算法笔记中的讲解为基础,复习迪杰斯特拉算法基本模板

算法笔记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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值