算法基础14 —— 图论入门之迪杰斯特拉算法(Dijkstra)

回顾
  • Floyed算法可以求任意两点之间的最短路径,但是Dijkstra算法只能求一个结点到另一个结点的最短路径,它是一个单源的最短路径算法
  • Floyed算法的时间复杂度为O(n^3),故一般情况下数据范围要求在100以内
Dijkstra算法描述
  • 设起点为s,dis[v]表示从s到v的最短路径长度, 如dis[3] = 6表示从起点到3号结点的最短路径为6
  • pre[v]为v的前驱节点,用来输出路径
  • 初始化:
dis[v] =(v ≠ s);//初始时路径为无穷 
dis[s] = 0;//s -> s的路径为0 
pre[s] = 0; 
  • 伪代码:
for (i = 1; i <= n ; i++)
{
    //1.在没有被访问过的点中找一个顶点u使得dis[u]是最小的
    //2.把u标记为已确定最短路径
    //3.For循环遍历:与u相连的每个未确定最短路径的顶点v
    //如果结点u的加入使得s->v的距离dis[v]变小,则更新
    if (dis[u] + w[u][v] < dis[v]) 
    {
        dis[v] = dis[u] + w[u][v];
        pre[v] = u;
    }
}

可以通过下图来理解为什么要更新dis[v] = dis[u] + w[u][v]
在这里插入图片描述

  • 算法结束:dis[v]为s到v的最短距离;pre[v]为v的前驱节点,用来输出路径。
算法的总体思想:

首先设置一个空集合,并且将所有的结点分成两类,一类是确定了最短路径的结点,另一类是未确定最短路径的结点。每次向集合中加入一个可以确定最短路的结点,然后判断会不会因为该点的加入使得某个结点到其他结点的距离变得更短,如果是,则更新最短距离。

这么多伪代码,是不是很抽象有点晕了?下面来看一个例子

Dijkstra算法举例

例:根据下图a,找出从节点1出发到各个节点的最短路径长度。
在这里插入图片描述

  • 算法开始时,作为起点的dis[1] = 0//结点1到其自身的距离为0
    结点1到其他结点的距离为无穷,即dis[i] = 0x3f3f3f3f
    在这里插入图片描述
    在这里插入图片描述

  • 第一轮循环发现dis[1] = 0最小,将结点1标记成已经确定最短路径的结点(1号结点的加入会影响1号结点到2、3和4号结点的距离)。另外还需要根据图a对所有的绿点做出修改,使得dis[2] = 2dis[3] = 4dis[4] = 7
    在这里插入图片描述
    在这里插入图片描述

  • 第二轮循环发现1号结点除外,dis[2] = 2最小,将结点2标记成已经确定最短路径的结点(2号结点的加入只会影响1号结点到3和5号结点的距离)。另外还需要根据图a对其余绿色结点做出修改,使得dis[3] = 3dis[5] = 4
    在这里插入图片描述

在这里插入图片描述

  • 第三轮循环发现除1、2号结点外,dis[3] = 3最小,将结点3标记成已经确定最短路径的结点(3号结点的加入会影响1号结点到4和5号结点的距离)。另外还需要根据图a对其余绿色结点做出修改,使得dis[4] = 4dis[5] = 4
    在这里插入图片描述
    在这里插入图片描述

  • 第四轮循环发现只有4号和5号结点还没有加入"集合",因为dis[4] = dis[5] = 4,又因为4号结点无法直接到达5号结点,故可以将4号结点直接加入,不需要对dis数组做出修改。

  • 最后只剩下一个五号结点没有加入集合,将其加入即可,从而得到1号结点到其余各个结点之间的最短距离如下图:
    在这里插入图片描述

在这里插入图片描述

看到这个粉绿粉绿的东西,突然…在这里插入图片描述
圣斗士星矢!仙女座登场!!
在这里插入图片描述

迪杰斯特拉代码模板
//迪杰斯特拉算法模板
//求源点s到其余各点的最短路径
void dijkstra(int s)
{
    memset(vis,0,sizeof vis);//1表示已经确定最短路径的点,0表示还未确定最短路径的点

    //初始时将s到所有点的路径设置为无穷
    memset(dis,0x3f,sizeof dis);

    dis[s] = 0;//结点s到s的距离为0
    while (1)//直到将所有的点都设置为已经访问,结束循环
    {
        int u = 0,min = INF;
        for (int j = 1;j <= n;j++)//用j遍历dis数组,每次遍历都会确定一个最短路的结点
        {
            if (!vis[j] && min > dis[j])//当前结点未被访问过
            {
                u = j;//u保存确定的最短路结点
                min = dis[j];//保存当前dis数组中的最短路径
            }
        }
        if (u == 0) return;//所有的点都被访问过,结束循环
        vis[u] = 1;//将其设置为已经访问过的点
        for (int v = 1;v <= n;v++)//最后的循环用来更新dis数组
            if (dis[v] > dis[u] + w[u][v])//将u结点加入之后路径变短
                dis[v] = dis[u] + w[u][v];
    }
}
Dijkstra算法总结

Dijkstra算法的时间复杂度为O(n^2),它是用来计算从一个点到其他所有点的最短路径的算法,是一种单源最短路径算法。也就是说,只能计算起点只有一个的情况。**它不能处理存在负边权的情况。**那么,为什么迪杰斯特拉算法不能处理负的边权呢?举个例子:
在这里插入图片描述
在上图中,结点2到结点3的权值为-4,显然从起点1到3的最短路径是-2,即1 → 2 → 3,但是dijskstra在第二轮循环开始时会找到当前dis[i]最小的点3,并标记它为白点。这时的dis[3] = 1,然而1却不是从起点到点3的最短路径。因为3已被标记为白点,最短路径值dis[3]不会再被修改了,所以我们在边权存在负数的情况下得到了错误的答案。

  • 第一次循环
    在这里插入图片描述
  • 第二次循环
    在这里插入图片描述

例题以及AC代码
Acwing 849 Dijkstra求最短路 I

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

int n,m;
const int N = 510;
const int INF = 0x3f3f3f3f;
int dis[N];//表示源点到其余各点的最短距离
int w[N][N];//w[i][j]表示i到j的路径
int vis[N];//vis[i]=0表示当前结点没被访问过,否则已经被访问过

//迪杰斯特拉算法模板
//求源点s到其余各点的最短路径
void dijkstra(int s)
{
    memset(vis,0,sizeof vis);//1表示已经确定最短路径的点,0表示还未确定最短路径的点
    
    //初始时将s到所有点的路径设置为无穷
    memset(dis,0x3f,sizeof dis);
    
    dis[s] = 0;//结点s到s的距离为0
    while (1)//直到将所有的点都设置为已经访问,结束循环
    {
        int u = 0,min = INF;
        for (int j = 1;j <= n;j++)//用j遍历dis数组,每次遍历都会确定一个最短路的结点
        {
            if (!vis[j] && min > dis[j])//当前结点未被访问过
            {
                u = j;//u保存确定的最短路结点
                min = dis[j];//保存当前dis数组中的最短路径
            }
        }
        if (u == 0) return;//所有的点都被访问过,结束循环
        vis[u] = 1;//将其设置为已经访问过的点
        for (int v = 1;v <= n;v++)//最后的循环用来更新dis数组
            if (dis[v] > dis[u] + w[u][v])//将u结点加入之后路径变短
                dis[v] = dis[u] + w[u][v];
    }
}

int main()
{
    memset(w,0x3f,sizeof w);
    scanf("%d%d",&n,&m);
    for (int i = 0;i < m;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        w[x][y] = min(w[x][y],z);//有重边的情况,取最小值
    }
    //(dij可以省略)for (int i = 1;i <= n;i++) w[i][i] = 0;
    dijkstra(1);
    if (dis[n] > INF / 2) printf("-1\n");
    else printf("%d\n",dis[n]);
    return 0;
}
迪杰斯特拉(Dijkstra)算法是一种寻找图中两点间最短路径的经典算法,适用于无向图和有向图,特别是当边权表示距离、费用或其他成本时。算法的基本思想是从起点开始逐步探索邻接节点,并始终选择当前已访问节点到未访问节点中代价最小的一条边作为下一步的前进方向。 在MATLAB中实现迪杰斯特拉算法通常涉及以下几个步骤: 1. **初始化**:创建一个二维数组或矩阵来存储各顶点之间的距离,将所有初始值设置为无穷大,除了起点到自身的距离设置为0;创建一个布尔型数组记录哪些节点已经被处理过。 2. **选取最小距离节点**:从未被处理过的节点中选出距离起点最近的一个节点作为当前节点。 3. **更新距离**:对于当前节点的所有相邻节点,如果从起点通过当前节点到相邻节点的距离比之前记录的距离更小,则更新这个距离。 4. **标记已处理节点**:将当前节点标记为已经处理过。 5. **重复步骤2至4**,直到所有节点都被处理或找到目标节点。 MATLAB代码示例: ```matlab function [shortestPaths, processedNodes] = dijkstra(graphMatrix, startNode) % graphMatrix 是一个邻接矩阵,其中非零元素表示两个节点间的距离。 % startNode 是起始节点的位置。 % shortestPaths 和 processedNodes 分别返回最短路径矩阵和处理节点状态。 n = size(graphMatrix, 1); visited = false(n, 1); % 初始化未访问节点标志位 distances = inf(1, n); % 初始距离设为无穷大 distances(startNode) = 0; % 起始节点距离设为0 for i = 1:n-1 current = find(~visited & (distances == min(distances(~visited))), 1); visited(current) = true; for j = 1:n if ~visited(j) && graphMatrix(current, j) ~= 0 newDist = distances(current) + graphMatrix(current, j); if newDist < distances(j) distances(j) = newDist; end end end end shortestPaths = distances; processedNodes = visited; ``` **相关问题**: 1. **如何优化迪杰斯特拉算法**?在大数据集上运行时,可以考虑使用优先队列来加速查找下一个最短路径候选节点的过程。 2. **迪杰斯特拉算法与贝尔曼-福特算法的区别是什么**?贝尔曼-福特算法可以在存在负权重边的情况下求解最短路径,而迪杰斯特拉算法不支持负权重边。 3. **如何将迪杰斯特拉算法应用到实际问题中**?比如网络路由优化、地图导航系统中的路径规划等场景,都可以利用此算法来找到从源点到所有其他点的最短路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值