Part 1:什么是图??
A:图是指由m个点,n条边组成的数据结构,分为有向图和无向图两种,其中有向图是指"1-->3" 而无向图是"1--3",在存图是会有不同(有向图只需存一遍,而无向图需要反向存一遍(a[i][j]=a[j][i]=val;
))
Part 2:如何存图??
A:有三种方式可以用来存图,分别是邻接矩阵(数组模拟),邻接表(数组模拟),Vector存图;
代码也很简单:
还有重头戏 利用STL中的Vector进行存图
Part 3:最短路问题
图已经存好了,接下来就是利用c++语言来求解问题了.
其中最经典的问题当然就是如何求最短路了;
-
Floyd 求多源最短路;
-
Dijkstra 求单源最短路;
-
队列优化后的Bellman-Ford(天朝oiers称作SPFA)
三种代码按照顺序一一介绍,
首先是 Floyd:
这是一种O(n^3)的算法,原理:对每一个中间节点k做松弛(寻找更短路径);
但它适合算多源最短路径,即任意两点间的距离。
但spfa,迪杰斯特拉就只能算一个点到其他任一点的最短路径。
k不单单是枚举,还是一个状态变量,找i和j之间通过编号不超过k(k从1到n)的节点的最短路径(一定要注意,这里是当前最短路径,
k之前的已经变成最短路了,对于每一个k,我们都进行了n^2的充分枚举(ij),已保证当前已经满足对从1到k的节点最优,
那么当k枚举完所有点,那么一定是最优的了
for(k=1;k<=n;k++) //中转节点
for(i=1;i<=n;i++) //第二层循环
for(j=1;j<=n;j++) //第三层循环
if(e[i][j]>e[i][k]+e[k][j] )//如果直接到达比通过k这个中转接点到达的距离短
e[i][j]=e[i][k]+e[k][j];//那么就更新松弛
Dijkstra算法,
Dijkstra算法基于贪心的思想,每次从dis[ ]数组中取出一个dis[ ]值最小的节点x,把vis[x]标记为true,同时用这个点的所有连边去更新与x相连的点y的dis[ ]值
其中,更新的条件是这样的:(dis[y]=min(dis[y],dis[x]+x->y)),也就是y的更新后最短路值=min(当前y的最短路值,x的最短路值+x->y的边权)
每次松弛操作,使得1->x,1->y,x->y这三条边满足三角形不等式,扫完x点的所有边之后,此时,没有被访问过且dis值最小的点的最短路就已经被确定了
重复取出dis[ ]值最小节点的操作,更新y的值,直到vis[ ]全部为true,也就是所有节点都被访问过。此时,dis数组中dis[i]就是1->i的最短路
Part 2:优化Dijkstra算法
还记得吗?前面我们提到了在松弛之前,有一个取出dis数组中最小值的操作,对吧?
这个操作的复杂度是O(n)O(n)的,再加上用来更新的for循环,整体复杂度就变成了O(nO(n22))的
显然,O(nO(n22))的复杂度还不够快,那么考虑怎么优化?
很容易想到数据结构对吧?这里我们可以使用一个小根堆来实时维护dis中的最小值和这个最小值所对应的编号,取得最小值所在节点编号的复杂度降低到了O(logn)O(logn)
这样做,使得整体复杂度降低到了O((m+n)logn)O((m+n)logn)(其中m是边数,n是点数)
问题又来了,手写小根堆优化会导致码量的暴增,而且容易出错,我们迫切的需要一种简洁,易实现的代码
当然不,STL中queue头文件为我们提供了priority_queue这样一个大根堆,我们想想,可不可以利用这个大根堆呢?
想要利用priority_queue,首先要解决两个问题:
1、我们需要存下两个变量,一个是dis数组中的值,当做第一关键字入堆,还有这个dis值对应的节点编号,这需要开一个结构体
开结构体又导致另一个问题——priority-queue不知道以谁为关键字入堆排序
2、把优先队列的大根堆形式变成小根堆
“做到这些,需要一些奇技淫巧” ——By 一扶苏一 2020.6.28
解决方法一:重载‘<’运算符
我们重载‘<’号运算符,让priority_queue一dis值为第一关键字的同时,把原来的大根堆变成小根堆
代码如下:
struct node{
int sp,num;
bool friend operator < (node a,node b){
return a.sp>b.sp;
priority_queue<node>q;
}
}
这里我们重载了‘<’运算符(PS:对于所有C++STL,需要比较大小的,只要求我们重载‘<’运算符,因为通过‘<’号的定义,可以推至所有别的符号的定义,比如,这里我把‘<’重载为返回sp大的,那么‘>’自然就是返回sp小的),一次性解决了上面的两个问题
解决方法二:C++内置pair二元组
我们可以使用C++内置的二元组pair来解决关键字的问题,因为pair默认以第一维为第一关键字
(PS:pair以第二维为第二关键字,但是这里第二关键字我们并不关心,因为不会影响到dijkstra的结果)
C++内置pair基本用法如下代码:
另外,把大根堆变成小根堆,我们可以在入堆的时候把dis值取反(只是在堆中是相反数,dis值保持原样),这样我们就用pair解决了这两个问题 ----引用自同机房大佬顾昕麟的blog
所以最后优化后的代码如下