最短路算法

本文介绍了Dijkstra算法的原理、适用情况、步骤及伪代码实现,包括邻接表和邻接矩阵两种方法。接着讨论了Dijkstra算法无法处理负权边的原因,并展示了Bellman-Ford算法如何解决这一问题。此外,还介绍了SPFA算法的思路和过程,以及Floyd算法的初始化和应用。这些算法都是寻找图中节点间最短路径的关键工具。

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

知识体系

朴素Dijkstra算法 

介绍:

Dijkstra算法是基于贪心的思想,每一步都找当前情况下的最优解,那么最后找出来的就是整体的最优解。

适用情况:

1.边权为正数

2.单源最短路

3.稠密图(n为点数,m为边数,n^2于m是一个级别,也就是边特别多)

图解步骤:

<1>用一个 dist 数组保存源点到其余各个节点的距离,dist[i] 表示源点到节点 i 的距离。初始时,dist 数组的各个元素为无穷大。
用一个状态数组 state 记录是否找到了源点到该节点的最短距离,state[i] 如果为真,则表示找到了源点到节点 i 的最短距离,state[i] 如果为假,则表示源点到节点 i 的最短距离还没有找到。初始时,state 各个元素为假。

<2>源点到源点的距离为 0。即dist[1] = 0。

<3>遍历 dist 数组,找到一个节点,这个节点是:没有确定最短路径的节点中距离源点最近的点。假设该节点编号为 i。此时就找到了源点到该节点的最短距离,state[i] 置为 1。

<4>遍历 i 所有可以到达的节点 j,如果 dist[j] 大于 dist[i] 加上 i -> j 的距离,即 dist[j] > dist[i] + w[i][j](w[i][j] 为 i -> j 的距离) ,则更新 dist[j] = dist[i] + w[i][j]。

<5>重复 3 4 步骤,直到所有节点的状态都被置为 1。

<6>此时 dist 数组中,就保存了源点到其余各个节点的最短距离。

伪代码实现:

int dist[n],state[n];
dist[1] = 0, state[1] = 1;
for(i:1 ~ n)
{
    t <- 没有确定最短路径的节点中距离源点最近的点;
    state[t] = 1;
    更新 dist;
}

例题:

邻接表做法 :

#include<iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 510, M = 100010;

int h[N], e[M], ne[M], w[M], idx;//邻接表存储图
int state[N];//state 记录是否找到了源点到该节点的最短距离
int dist[N];//dist 数组保存源点到其余各个节点的距离
int n, m;//图的节点个数和边数

void add(int a, int b, int c)//插入边
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void Dijkstra()
{
    memset(dist, 0x3f, sizeof(dist));//dist 数组的各个元素为无穷大
    dist[1] = 0;//源点到源点的距离为置为 0
    for (int i = 0; i < n; i++)
    {
        int t = -1;
        for (int j = 1; j <= n; j++)//遍历 dist 数组,找到没有确定最短路径的节点中距离源点最近的点t
        {
            if (!state[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        }

        state[t] = 1;//state[i] 置为 1。

        for (int j = h[t]; j != -1; j = ne[j])//遍历 t 所有可以到达的节点 i
        {
            int i = e[j];
            dist[i] = min(dist[i], dist[t] + w[j]);//更新 dist[j]
        }


    }
}

int main()
{
    memset(h, -1, sizeof(h));//邻接表初始化

    cin >> n >> m;
    while (m--)//读入 m 条边
    {
        int a, b, w;
        cin >> a >> b >> w;
        add(a, b, w);
    }

    Dijkstra();
    if (dist[n] != 0x3f3f3f3f)//如果dist[n]被更新了,则存在路径
        cout << dist[n];
    else
        cout << "-1";
}

邻接矩阵做法:

#include<bits/stdc++.h>
using namespace std;

int dist[510];
int g[510][510];
int st[510];
int n,m;

int dijkstra()
{
    memset(dist,0x3f,sizeof(dist));
    dist[1]=0;
    
    for(int j=1;j<=n;j++)
    {
        int t=0;// dist[0]=0x3f3f3f3f
        for(int i=1;i<=n;i++)
        {
            if(st[i]==0&&dist[t]>dist[i])//找到1~n里没有确定的点中的最短路径点
            t=i;
        }
        st[t]=1;
        for(int i=1;i<=n;i++)
        dist[i]=min(dist[i],dist[t]+g[t][i]);
    }
    
    if(dist[n]==0x3f3f3f3f) return -1;  //如果第n个点路径为无穷大即不存在最低路径
    return dist[n];
}



int main()
{
    memset(g,0x3f,sizeof(g));
    
    cin>>n>>m;
    while(m--)
    {
        int a,b,z;
        cin>>a>>b>>z;
        g[a][b]=min(g[a][b],z);
    }
    
    cout<<dijkstra();
    return 0;
}

堆优化的Dijkstra

板子:

const int N = 2e5 + 10;
int key[N * 2], h[N * 2], ne[N * 2], idx, vue[N* 2], dist[N * 2], flag[N * 2];
void add(int u, int v, int x) {
	key[idx] = v;
	vue[idx] = x;
	ne[idx] = h[u];
	h[u] = idx++;
}

int dijkstra(int start, int n)//求start到n的最短路
{
    memset(dist, 0x3f, sizeof dist);//距离初始化为无穷大
    dist[start] = 0;
    std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::greater<std::pair<int, int>>> heap;//小根堆
    heap.push({0, start});//插入距离和节点编号
 
    while (len(heap))
    {
        auto t = heap.top();//取距离源点最近的点
        heap.pop();
 
        int ver = t.second,distance = t.first;//ver:节点编号,distance:源点距离ver 的距离
 
        if (flag[ver]) continue;//如果距离已经确定,则跳过该点
        flag[ver] = 1;
 
        for (int i = h[ver]; i != -1; i = ne[i])//更新ver所指向的节点距离
        {
            int j = key[i];
            if (dist[j] > dist[ver] + vue[i])
            {
                dist[j] = distance+ vue[i];
                heap.push({dist[j], j});//距离变小,则入堆
            }
        }
    }
 
    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

例题:

#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>//堆的头文件

using namespace std;

typedef pair<int, int> PII;//堆里存储距离和节点编号

const int N = 1e6 + 10;

int n, m;//节点数量和边数
int h[N], w[N], e[N], ne[N], idx;//邻接表存储图
int dist[N];//存储距离
bool st[N];//存储状态

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);//距离初始化为无穷大
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;//小根堆
    heap.push({0, 1});//插入距离和节点编号

    while (heap.size())
    {
        auto t = heap.top();//取距离源点最近的点
        heap.pop();

        int ver = t.second,distance = t.first;//ver:节点编号,distance:源点距离ver 的距离

        if (st[ver]) continue;//如果距离已经确定,则跳过该点
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])//更新ver所指向的节点距离
        {
            int j = e[i];
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = distance+ w[i];
                heap.push({dist[j], j});//距离变小,则入堆
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main()
{
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    cout << dijkstra() << endl;

    return 0;
}

 Bellman-frod

为什么Dijkstra不能解决含有负权边的问题?:

会发生串联

例题

#include <iostream>
#include <algorithm>
#include <cstring>


using namespace std;

const int N = 510,M = 10010;

struct Edge
{
    int a;//起点
    int b;//终点
    int w;//边的权重;
}e[M];
int dist[N];//存储当前点到起点的距离;
int back[N];//作为备份存储;
int n,m,k;//n个点,m条边,k为最大限制边数;

void bellman_ford()
{
    memset(dist ,0x3f, sizeof dist);//对距离数组初始化,所有边都为无穷大;
    dist[1] = 0;//起点到起点的距离为0;

    for(int i = 0;i < k;i++)//最大限制是k条边以内;
    {
        memcpy(back,dist,sizeof dist); //使用back 先将dist中的数据拷贝,防止出现串联更新;

        for(int j = 0;j < m; j++ )
        {
            int a = e[j].a;
            int b = e[j].b;
            int c = e[j].w;

            dist[b] = min(dist[b], back[a]+c);//每一条边都要比较更新,但是不会一直更新下去发生串联,而dijkstra算法则是一直会更新下去
        }
    }
}

int main()
{
    scanf("%d %d %d",&n,&m,&k);
    for(int i=0 ;i<m; i++)
    {
        int a,b,w;
        scanf("%d %d %d",&a,&b,&w);
        e[i] = {a,b,w};
    }
    bellman_ford(); 

    if(dist[n] > 0x3f3f3f/2) printf("impossible");
    else printf("%d",dist[n]);

    return 0;


}

spfa

求最短路:

思路过程:

1.建立一个队列,初始时队列里只有起始点。

2.再建立一个数组记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。

3.再建立一个数组,标记点是否在队列中。

4.队头不断出队,计算始点起点经过队头到其他点的距离是否变短,如果变短且被点不在队列中,则把该点加入到队尾。

5.重复执行直到队列为空。

6.在保存最短路径的数组中,就得到了最短路径

图解过程:

给定一个有向图,如下,求A~E的最短路。

源点A首先入队,然后A出队,计算出到BC的距离会变短,更新距离数组,BC没在队列中,BC入队

B出队,计算出到D的距离变短,更新距离数组,D没在队列中,D入队。然后C出队,无点可更新。

D出队,计算出到E的距离变短,更新距离数组,E没在队列中,E入队。

E出队,此时队列为空,源点到所有点的最短路已被找到,A->E的最短路即为8

例题

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N = 100010;
int h[N], e[N], w[N], ne[N], idx;//邻接表,存储图
int st[N];//标记顶点是不是在队列中
int dist[N];//保存最短路径的值
int q[N], hh, tt = -1;//队列

void add(int a, int b, int c){//图中添加边和边的端点
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void spfa(){
    q[++tt] = 1;//从1号顶点开始松弛,1号顶点入队
    dist[1] = 0;//1号到1号的距离为 0
    st[1] = 1;//1号顶点在队列中
    while(tt >= hh){//不断进行松弛
        int a = q[hh++];//取对头记作a,进行松弛
        st[a] = 0;//取完队头后,a不在队列中了
        for(int i = h[a]; i != -1; i = ne[i])//遍历所有和a相连的点
        {
            int b = e[i], c = w[i];//获得和a相连的点和边
            if(dist[b] > dist[a] + c){//如果可以距离变得更短,则更新距离

                dist[b] = dist[a] + c;//更新距离

                if(!st[b]){//如果没在队列中
                    q[++tt] = b;//入队
                    st[b] = 1;//打标记
                }
            }
        }
    }
}
int main(){
    memset(h, -1, sizeof h);//初始化邻接表
    memset(dist, 0x3f, sizeof dist);//初始化距离
    int n, m;//保存点的数量和边的数量
    cin >> n >> m;
    for(int i = 0; i < m; i++){//读入每条边和边的端点
        int a, b, w;
        cin >> a >> b >> w;
        add(a, b, w);//加入到邻接表
    }
    spfa();
    if(dist[n] == 0x3f3f3f3f )//如果到n点的距离是无穷,则不能到达 
        cout << "impossible";
    else cout << dist[n];//否则能到达,输出距离
    return 0;
}

练习:E-小红勇闯地下城_牛客周赛 Round 33 (nowcoder.com)

Floyd

初始化:
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) d[i][j] = 0;
            else d[i][j] = INF;

// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值