一、简介:
Dijkstra算法算是贪心思想实现的,首先把起点到所有点的距离存下来找个最短的,然后松弛一次再找出最短的,所谓的松弛操作就是,遍历一遍看通过刚刚找到的距离最短的点作为中转站会不会更近,如果更近了就更新距离,这样把所有的点找遍之后就存下了起点到其他所有点的最短距离。
二、步骤:
一个包含9条边6个点的有向图如下:
将图用二维数组进行存储:
此时再用一个dis数组存储算法目前阶段节点1到各个节点的最短的距离。(算法是从终点向起点倒推的,所以存储的是目前阶段各个节点到节点6最短距离):
每个点松弛计算,直至推出最后结果:
三、算法代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#pragma warning(disable:4996)
#define MAX 10
#define Inf 0x3f3f3f3f//memset赋值无穷大的好选择,笔记有解释
using namespace std;//使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespace std,这样才能正确使用cout
int map[MAX][MAX];
int vis[MAX], dis[MAX];
int n, m;//n个点,m条边
void Init()//初始化二维数组
{
memset(map, Inf, sizeof(map));
for (int i = 1; i <= n; i++)
{
map[i][i] = 0;
}
}
void Getmap()
{
int u, v, w;
printf("请依次输入欲计算的图信息,化为二维数组后的行数、列数以及权值信息,空格分开,回车换组\n");
for (int t = 1; t <= m; t++)
{
printf("第%d组数据:\n", t);
scanf("%d%d%d", &u, &v, &w);
if (map[u][v]>w)
{
map[u][v] = w;
map[v][u] = w;
}
}
}
void Dijkstra(int u)
{
memset(vis, 0, sizeof(vis));
for (int t = 1; t <= u; t++)
{
dis[t] = map[u][t];
}
vis[u] = 1;
printf("最短路径为:%d\n",u);
for (int t = 1; t<u; t++)
{
int minn = Inf, temp;
for (int i = 1; i <= u; i++)
{
if (!vis[i] && dis[i]<minn)
{
minn = dis[i];
temp = i;
}
}
vis[temp] = 1;
printf("<-%d", temp);//temp记录当前最短路径节点
for (int i = 1; i <= u; i++)
{
if (map[temp][i] + dis[temp]<dis[i])
{
dis[i] = map[temp][i] + dis[temp];
}
}
}
}
int main()
{
printf("请输入边数和点数,空格分开:\n");
scanf("%d%d", &m, &n);
Init();
Getmap();
Dijkstra(n);
printf("\n最短路径长为%d\n", dis[1]);
return 0;
}
四、结果:
五、笔记:
1.C语言指针的提醒。指针指向存储单元地址,故*a中a存储的是指针指向的地址,故若b=2,要赋值2给*a,便可“*a=b”、“*a=2”或者“a=&b”。但在声明变量时,只能“int *a=&b”,这是因为声明时“int *”实则为一体,所以需要将存储b的单元地址赋值给a,这需要注意。其次还要注意的便是数组的特殊性,声明一个数组b[n],其中b其实存储的是存储这个数组空间的首地址,和&b[n]和&b[0]值相同,故赋值给指针*a除了直接“*a=b[n]”外,还可以直接“a=b”或者“a=&b[n]”或者“a=&b[0]”。
2.为什么函数有全局变量依旧要传参入函数,是因为当我们此函数调用不多时,还分配个全局变量给这个函数其实非常浪费空间,故尽量设置形参传入是节省空间的好办法。
3.为什么使用0x3f3f3f3f作为无穷大数INF进行赋值?在算法竞赛中,我们常常需要用到一个“无穷大”的值,于是很多程序猿将INF设为0x7fffffff。这是32-bit int的最大值。如果这个无穷大只用于一般的比较(比如求最小值时min变量的初值),那么0x7fffffff确实是一个完美的选择。但是更多情况下,0x7fffffff并不是一个好的选择,比如在最短路径算法中,我们使用松弛操作:
if (d[u]+w[u][v]<d[v]) d[v]=d[u]+w[u][v];
如果u,v之间没有边,那么w[u][v]=INF,如果我们的INF取0x7fffffff,那么d[u]+w[u][v]会溢出而变成负数,我们的松弛操作便出错了!准确来说,0x7fffffff不能满足“无穷大加一个有穷的数依然是无穷大”这个条件,它会变成了一个很小的负数。
0x3f3f3f3f的十进制是1061109567,是10^9级别的(和0x7fffffff一个数量级),而一般场合下的数据都是小于10^9的,所以它可以作为无穷大使用而不致出现数据大于无穷大的情形。
同时如果我们想要将某个数组清零,我们通常会使用memset(a,0,sizeof(a)),方便又高效,但是当我们想将某个数组全部赋值为无穷大时,就不能使用memset函数而得自己写循环了,因为memset是按字节操作的,它能够对数组清零是因为0的每个字节都是0(一般我们只有赋值为-1和0的时候才使用它)。现在好了,如果我们将无穷大设为0x3f3f3f3f,那么奇迹就发生了,0x3f3f3f3f的每个字节都是0x3f!所以要把一段内存全部置为无穷大,我们只需要memset(a,0x3f,sizeof(a))。
4.关于运算符优先级,如下图,另所有一元操作符优先级都高于&&等二元操作符:
5.main函数里定义的变量并非全局变量,是局部变量,只能在main函数里使用,main函数里的调用函数都无法使用,因为调用函数是在main以外定义的。
六、版权声明:
本文节选或转载cnblogs博主「black_hole6」的原创文章,依作者要求以下附上原文出处链接。
原文链接:https://www.cnblogs.com/Staceyacm/p/10782094.html
本文节选或转载CSDN博主「jiange_zh」的原创文章,遵循 CC 4.0 BY-SA 版权协议,以下附上原文出处链接。
原文链接:https://blog.csdn.net/jiange_zh/article/details/50198097