专题 dijkstra

算法

算法简介

d i j k s t r a dijkstra dijkstra是单源最短路径的有效算法,解决的是有向图中最短路径问题。dijkstra主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

算法原理

  1. d i j k s t r a dijkstra dijkstra本质上的思想是贪心,它只适用于不含负权边的图
  2. 我们把点分成两类,一类是已经确定最短路径的点(即已经访问过/未标记占领的点),另一类是未确定最短路径的点(即还未访问过的点/未标记占领的点)。
  3. 初始化将源点到各个节点i的最短路径 d i s [ i ] dis[i] dis[i]设为最大值,同时将源点s的最短路径 d i s [ s ] dis[s] dis[s]设为 0 0 0,并将其放入节点集合 S S S中。
  4. 在节点集合 S S S中未确定最短路径的点里找到 d i s dis dis最小的点 u u u,并标记占领。
  5. 遍历u的所有出边 ( u , v , w ) (u,v,w) (u,v,w) d i s [ v ] > d i s [ u ] + w dis[v] > dis[u] + w dis[v]>dis[u]+w,则令 d i s [ v ] = d i s [ u ] + w dis[v] = dis[u] + w dis[v]=dis[u]+w,并将节点v放入节点集合S中。
  6. 重复 4 4 4 5 5 5步操作,直至所以点都被标记占领。

说明

1. 为什么dijkstra是正确的?
当所有边长都是非负数的时候,全局最小值不可能再被其他节点更新。所以在上述算法原理第 4 4 4步中找出的未标记占领的点 u u u必然满足 u : d i s [ u ] u:dis[u] u:dis[u]已经是源点到 u u u的最短路径。我们不断选择全局最小值进行标记和拓展,最终可以得到起点到每个节点的最短路径的长度。

下面用以下这个例子来简单说明:
将源点 s s s设为 1 1 1。开始时我们把 d i s [ s ] dis[s] dis[s]初始化为 0 0 0,其余点初始化为极大值 I N F INF INF
在这里插入图片描述

i12345
disINFINFINFINFINF
usedfalsefalsefalsefalsefalse

第一轮循环找到 d i s dis dis值最小的点 1 1 1,将1标记,对所有与1相连的未标记的点的dis值进行维护,使得 d i s [ 2 ] = 2 dis[2]=2 dis[2]=2, d i s [ 3 ] = 4 dis[3]=4 dis[3]=4, d i s [ 4 ] = 7 dis[4]=7 dis[4]=7
在这里插入图片描述

i12345
dis0247INF
usedtruefalsefalsefalsefalse

第二轮循环找到 d i s dis dis值最小的点 2 2 2,将 2 2 2标记,对所有与 2 2 2相连的未标记点的dis值进行维护,使得 d i s [ 3 ] = 3 dis[3]=3 dis[3]=3 d i s [ 5 ] = 4 dis[5]=4 dis[5]=4
在这里插入图片描述

i12345
dis02374
usedtruetruefalsefalsefalse

第三轮循环找到 d i s dis dis值最小的点 3 3 3,将 3 3 3标记,对所有与 2 2 2相连的未标记的点的 d i s dis dis值进行维护,使得 d i s [ 4 ] = 5 dis[4]=5 dis[4]=5
在这里插入图片描述

i12345
dis02354
usedtruetruetruefalsefalse

接下来两轮循环分别将 5 5 5, 4 4 4标记,算法结束,已求出所有点的最短路径。
在这里插入图片描述

i12345
dis02354
usedtruetruetruetruetrue

算法时间复杂度为 O ( n 2 ) O(n^2) O(n2)
2. dijkstra为什么不能处理有负权边的情况?
d i j k s t r a dijkstra dijkstra本质上的思想是贪心。在运行过程中维持的关键信息是一组节点集合 S S S,从源节点 s s s到该集合中每个节点之间的最短路径已经被找到。算法重复从节点集合 S S S中选择最短路径估计最小的节点 u u u,将 u u u加入到集合 S S S,然后对所有从 u u u出发的边进行松弛操作。
当把一个节点选入集合 S S S时,即意味着已经找到了从源点到这个点的最短路径,但若存在负权边,就与这个前提矛盾,可能会出现得出的距离加上负权后比已经得到 S S S中的最短路径还短。

下面用以下这个例子来简单说明:
d i j k s t r a dijkstra dijkstra求得 d i s [ 1 ] [ 2 ] = 3 dis[1][2]=3 dis[1][2]=3,事实上 d [ 1 ] [ 2 ] = 2 d[1][2]=2 d[1][2]=2,就是通过了 1 → 3 → 2 1\rightarrow3\rightarrow2 132使得路径减小。
在这里插入图片描述

代码实现

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

const int MAXN=10005;
const int MAXM=500005;
int n,m,s;
int dis[MAXN];
bool used[MAXN];

int size=0;
int head[MAXN];
struct E
{
    int u;
    int v;
    int w;
    int next;
}edge[MAXM];

inline int read()
{
    int X=0; bool flag=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
    while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
    if(flag) return X;
    return ~(X-1);
}

inline void add(int u,int v,int w)
{
    edge[size].u=u;
    edge[size].v=v;
    edge[size].w=w;
    edge[size].next=head[u];
    head[u]=size++;
}

struct node
{
	int dis;	
	int pos;
	bool operator <(const node &x) const
	{
		return x.dis<dis;//inportant
	}
};

priority_queue <node> q;

inline void dijkstra()
{
    dis[s]=0;
    q.push((node){0,s});
    while(!q.empty())
    {
        node tmp=q.top(); q.pop();
        int u=tmp.pos, d=tmp.dis;
        if(used[u]) continue;
        used[u]=1;
        for(int i=head[u];~i;i=edge[i].next)
        {
            int v=edge[i].v;
            if(dis[v]>dis[u]+edge[i].w)
            {
                dis[v]=dis[u]+edge[i].w;
                if(!used[v])
                {
                    q.push((node){dis[v],v});
                }
            }
        }
    }
}

int main()
{
	freopen("input.txt","r",stdin);
	memset(head,-1,sizeof(head));
    n=read();m=read(); s=read();
	for(int i=1;i<=n;++i) dis[i]=0x7fffffff;
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read(),w=read();
		add(u,v,w);
	}
	dijkstra();
	for(int i=1;i<=n;i++) 
	    printf("%d ",dis[i]);
	return 0;
}

经典例题

蒟蒻学艺不精,还望大佬指点。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值