图 最短路 Dijkstra 迪杰斯特拉算法

本文探讨了一种解决单源最短路径问题的算法,通过Dijkstra方法,利用d数组存储起点到各节点的距离,并通过巧妙的数据结构和更新规则,对比动态规划,展示了其高效性和空间优化。讨论了内存限制和代码实现的优化技巧,包括数组大小调整和关键判断的解释。

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

这个算法适合于解决单源最短路径问题,即规定了一个起点,然后求这个起点到各个点的最短路,感觉和动态规划有点像,而且他这个数组设置的也很有特色,和动态规划也有异曲同工之妙。

设置d[i] 表示从起点到i这个位置所花费的最小路程。

#include <bits/stdc++.h>
using namespace std;
#define MAXN 0x3fffffff 
int mp[500][500];
int main()
{
	int N,M;
	cin >> N >> M;//表示n个范围 M个数据 E为终结点 0为起始点 
	for (int i = 0;i<N;i++)
	for (int j = 0;j<N;j++)
	mp[i][j] = MAXN;
	while(M--)
	{
		int a,b,c;
		cin >> a >> b >> c;
		mp[a][b] = c;//从a到b需要花费c代价 
	} 
	 int d[500];//表示从0开始到此点的最小距离 
	 int vis[500];
	 for (int i = 0;i<N;i++) d[i] = MAXN;
	 memset (vis,0,sizeof(vis));
	 d[0] = 0;
	 for (int i = 0;i<N;i++)//相当于每一层跑一遍下面操作 
	 {
	 	int u ,min;
	 	u = -1 , min = MAXN;
	 	for (int j = 0;j<N;j++)//寻找数组中最小值 
	 	{
			 if  (!vis[j] && d[j]<min)
			 {
			 	u = j;
			 	min = d[i];
			 }
		 }
		 //cout << u << " " << min << endl;
		 if (u==-1) break;
		 vis[u] = 1;//标记已经访问 
		for(int v = 0;v<N;v++)
		{
			if (!vis[v] && mp[u][v] != MAXN && d[u] + mp[u][v] < d[v])
			{
				d[v] = d[u] + mp[u][v];
			}
		  }  
	 }
	for (int i = 0;i<N;i++)
	{
		cout <<i<<" " <<  d[i] << endl;
	}
	return 0;
 } 
 /*
 6 8
 0 1 1
 0 3 4
 0 4 4
 1 3 2
 2 5 1
 3 2 2
 3 4 3
 4 5 3
 */

而下面的更新数据就显得更有意思了,如果mp【u】【v】存在,说明u到v是存在相通的,而加上

d【u】相当于是起点到u的最小距离 ,最后再给出一个判断 ,就可以把起点到v的最小距离求出来了,但是这个最小不一定是最终的,只是目前基础上比较优秀的,当他被标记为已经访问过,才能保证他就是最优的了。

 这份伪代码的话,可以很好的描述上面的思路。

很简单的一个题,用上面的思路就可以写出来。

#include <iostream>
using namespace std;
#define MAXN 0x3ffff
int mp[20005][20005];
int d[20005];
int n,m;
bool vis[20005];
void Dij(int s)
{ 
    for (int i = 0;i<=n;i++) d[i]=MAXN;
    d[s] = 0;
    for (int i = 1;i<=n;i++)
    {
        int u = 0,min = MAXN;
        for (int j = 1;j<=n;j++)//只是寻找最小值
        {
            if (!vis[j] && d[j]<min)
            {
                u = j;
                min = d[j];
            }
        }
        if (u==0) break;
        vis[u]=true;
        for (int v = 1;v<=n;v++)
        {
            if (!vis[v] && d[u] + mp[u][v] < d[v])
            {
                d[v] = d[u]+mp[u][v];
            }
        }
    }
}
int main()
{
   cin >> n >> m;
    for (int i = 0;i<=n;i++)
        for (int j = 0;j<=n;j++)
            mp[i][j] = MAXN;
    while (m--)
    {
        int u,v,l;
        scanf ("%d%d%d",&u,&v,&l);
        mp[u][v] = l;
    }
    Dij(1);
    for (int i = 2;i<=n;i++)
    {
        cout << d[i] << endl;
    }
    return 0;
}

很显然,写的是对的,但是占用内存太多了,爆栈了,所以还得修改,二维数组不能开的这么随意。内存最多开到1e8,也就是10000*10000,所以二维数组方法行不通了。

#include<stdio.h>
int main()
{
	int n,m,t,i,k,check,dis[20005],u[200005],v[200005],w[200005];
	int inf=99999999;
	scanf("%d %d",&n,&m);
	for(i=0;i<m;i++)
	{
		scanf("%d %d %d",&u[i],&v[i],&w[i]);
	}
	for(i=1;i<=n;i++)
	dis[i]=inf;
	dis[1]=0;
	for(k=1;k<=n-1;k++)
	{
		check=0;
		for(i=0;i<m;i++)
		{
			if(dis[v[i]]>dis[u[i]]+w[i]&&dis[u[i]]<inf)
			{
				check=1;
				dis[v[i]]=dis[u[i]]+w[i];
			}
		}
		if(check==0)
		break;
	}
	for(i=2;i<=n;i++)
	{
		printf("%d\n",dis[i]);
	}
	return 0;
}

这个方法就非常的妙,牛逼多了呀。

特别是那个关键代码处,存储的是起点,终点,权值,而根据d【i】数组的含义可以得出

d【终点】= d【起点】+权值 。然后遍历更新,很妙哇。非常好,简洁而且省空间。因为每个路径都试一遍,所以会存在最小值的,O(n*n) -> O(n*m) 时间方面也很优秀,真的不错。

#include <iostream>
using namespace std;
#define MAXN 0x3ffffff
int A[200005],B[200005],C[200005];
int d[200005];
int n,m;
void Dij(int s)
{ 
    for (int i = 1;i<=n;i++) d[i]=MAXN;
    d[s] = 0;
    for (int i = 1;i<=n;i++)
    {
        int f = 0;
        for (int j = 0;j<m;j++)
        {
            if (d[B[j]]>d[A[j]]+C[j])
            {
                f = 1;
                d[B[j]] = d[A[j]] + C[j];
            }
        }
        if (f==0) break;
    }
}
int main()
{
   cin >> n >> m;
    for (int i = 0;i<m;i++)
    {
        int u,v,l;
        scanf ("%d%d%d",&u,&v,&l);
        A[i] = u;
        B[i] = v;
        C[i] = l;
    }
    Dij(1);
    for (int i = 2;i<=n;i++)
    {
        cout << d[i] << endl;
    }
    return 0;
}

 自己ac的代码,有几个很需要注意的点,首先就是数组的大小问题,必须以m为标准,所以要开到2e6 而n范围只有2E5。

实际上还有几个地方不太理解的,就是循环里面的判断句,为什么不是小于号,按常识,不应该求最短,只有小于才可以换值吗,为什么恰恰相反,取大于还是对的。得好好思考一番。

哎,脑子一下短路了,是没错的,就是如果后面的值比前面的要小的话,就赋值给前面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值