一、图的知识框架
图的相关概念非常多,本篇只着重讲解下图所包含的内容。有了以下的内容作为基础,就已经能够学习Dijkstra算法。
二、基础知识
1、图的定义
关联一下之前学过的单链表和树,单链表是一对一的关系,树是一对多的关系,而图是多对多的关系,每个顶点都可以和其他多个顶点相关联。
图的严格定义:
注:
2、有向图 和 无向图 和 自环
图中所有边都没有方向的图是无向图
图中所有边都有方向的图是有向图
- 无向图是特殊的有向图
3、无权图 和 带权图
- 无权图可看作所有边权值都为1的带权图
4、稠密图 和 稀疏图
设一个图中:点数为n,边数为m
稠密图:m与n^2是一个级别
稀疏图:m与n是一个级别
如:题目给出测试点的范围是:1<=n<=500
1<=m<=100000
这显然是一个稠密图。
5、邻接矩阵存图
讲了这么多概念,那一个图体现在代码里面是什么样子?邻接矩阵故名思意,用一个矩阵存图。
- 对于一个有n个顶点的无权图,创建整型数组
g[n][n]
,若第 i 个点到第 j 个点之间存在边,则令g[i-1][j-1]
的值为1,反之,值为0。用0和1表示两点之间是否有边。 - 对于有n个定点的带权图,同样使用
g[n][n]
来存储,若第 i 个点到第 j 个点之间存在权值为weight
的边,则令g[i-1][j-1]
的值为weight
,若不存在边,则赋为 +∞ - 邻接矩阵适合存储稠密图。因为矩阵的大小只与图的点数有关,与边数无关,只要点数固定,即使图再怎么复杂也能够用同一个矩阵存储,不需要耗费额外的内存空间。
三、Dijkstra(最短路径)算法
1、原理
Dijkstra算法是基于贪心的思想,每一步都找当前情况下的最优解,那么最后找出来的就是整体的最优解。Djkstra算法的严格证明不需要大家掌握,只要会用它解决问题即可。所以本文讲解的重点是Dijkstra算法的步骤。
2、朴素版Dijkstra算法的适用情况
- 求单源最短路的情况,即任意一点到源点之间的最短路
- 所有边权都是正数
- 稠密图
3、算法步骤
以n个点的有向有权图为例:
- 初始化所有点到源点的距离:开始只有源点的距离确定,为0,其他点的距离初始化为正无穷
- 迭代n次
- 每次确定一个点到起点的最短路。
确定哪个点呢?是当前还没确定最短路径的点中,到起点距离最短的点。这个距离一定就是该点到源点的最短路,这里就是贪心的思想。
- 确定这个点的最短距离之后,只用这个点 来更新其他点到源点的最短距离。
一次迭代结束。
- 经过n次迭代,就可以确定n个点到源点的最短路。那么源点到终点的最短路也就确定了。
下面看一个例子:
4、代码实现
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数n 和 m
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出 −1。
数据范围
1≤n≤500,
1≤m≤10^5,
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#define N 510
#define INF 0x3f3f3f3f
//这里涉及到无穷大为什么取0x3f3f3f3f的问题,可以查看下面的博客
//https://blog.csdn.net/m0_51551385/article/details/121522095
int n,m;//n为点数,m为边数
int g[N][N];//g为存储图的邻接矩阵
int dist[N];//dist[i]为点i到源点的距离
int st[N];//st[i]为1表示该点的最短距离已经确定,为0表示该点的最短距离还未确定
int dijkstra()
{
memset( dist , 0x3f , sizeof(dist) );//初始化距离,对应Dijkstra算法的第一步
dist[1] = 0;//源点到自己的最短距离为0
for( int i = 0 ; i < n ; i++ )//对应第二步,进行n次迭代,每次确定一个点的最短路径
{ //用t表示当前选择的要确定最短路的点,t取值要求:不能是已经确定最短路的点
int t = -1;//t = -1表示开始还没选择任何点
for( int j = 1 ; j <= n ; j++ )//遍历所有点,找到符合条件的点
{
if( st[j] == 0 && (t == -1 || dist[t] > dist[j]) )//st[j] == 0确保t的最短路径还没有被确定
t = j; //t == -1保证t 一定 是第一个遍历到的最短路没有确定的点
} //dist[t] > dist[j]保证 t 是当前 到源点距离最短的点
//t的最短距离已经确定,st数组标记为1
st[t] = 1;
//用t更新其他点到源点的最短距离
for( int j = 1 ; j <= n ; j++ )
{
dist[j] = fmin( dist[j] , dist[t] + g[t][j] );
}
}
if( dist[n] == INF )
return -1;
else
return dist[n];
}
int main()
{
scanf("%d %d",&n,&m);//输入点数和边数
memset( g , 0x3f , sizeof(g) );//初始化邻接矩阵
for( int i = 1 ; i <= n ; i++ )//每个点到自己距离为0
{ //因为输入的点的编号从 1 开始,所以为了后续操作方便,第0行 和 第0列舍弃,直接从第一行第一列开始存
g[i][i] = 0;
}
while( m-- )//输入m条边
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
g[a][b] = fmin( g[a][b] , c);//可能有重边,所以只保留距离最短的边即可
//g[b][a] = g[a][b];如果是无向图的话采用这种方式来建图
}
int t = dijkstra();
if( t == -1 )
printf("-1");
else
printf("%d",t);
return 0;
}
5、时间复杂度:O(n^2)
四、作业
利用最短路径Dijkstra算法解决实际问题,校园中的最短路问题。
说明:
- 起点(源点)和终点的选取是随意的,只要能用Dijkstra算法输出他们的最短路即可。
- 不要求手动输入,直接在main函数中建图即可。
五、拓展
下图中列举了所有求最短路径的情况,有兴趣的可以自行学习。