最短路径(Dijkstra、Bellman-Ford和SPFA算法)
前言
提醒一下:最短路径一般用于有向网(就是有方向和权值的图),而最小生成树一般是用于无向网。
本来是想分开写,不过想到这样大家看起来比较麻烦。就写在一起了,这样可以很好的进行对比。
图的存储方式
常用的无非就
- 邻接矩阵
- 邻接表
- 链式前向星
邻接矩阵
这个可以说是最好理解的了,分为以下几种情况
- 无权值得图,初始化为0,有边想连(有关系)就赋值为1
- 有权值图(网),初始化为 INF(一个大的常数),有边想连(有关系)就赋值为 该权值
邻接表
可以说邻接表的建图就很花里胡哨了,如果不理解它的意思,肯定被每一个人都不一样的邻接表给弄糊涂的。为了让大家看下边的代码不乱,特意说明一下。
链表建立
- 对于链表,就是一个一维数组,其中存放表头节点,每一个表头节点都连着一个链表,是由其相邻的顶点组成。
利用vector
基于链表中的特性,可以用一个vector数组来代替链表
- 如果是不带权值的
vector<int>edge[N];
int main()
{
int V, E;
cin >> V >> E;
for (int i = 0; i < E; ++i)
{
int u,v;
cin >> u >> v;
edge[u].push_back(v);
/*
如果是无向图
edge[u].push_back(v);
edge[v].push_back(u);
*/
}
// 遍历,通过顶点来遍历,
for (int i = 1; i <= V; ++i)
{
for (int j = 0; j < edge[i].size(); ++j)
cout << edge[i][j] << " ";
}
}
- 如果有权值的话,有两个地方改就好
#define pi pair<int,int>
vector<pi>edge[N];
- 存的时候,
for (int i = 0; i < E; ++i)
{
int u,v,w;
cin >> u >> v>>w;
edge[u].push_back(make_pair(v,w));
/*
如果是无向图
edge[u].push_back(make_pair(v,w));
edge[v].push_back(make_pair(u,w));
*/
}
结构体
结构体的话就想到与用边来作为一个头来建立
//从顶点u到顶点v的权值为w的边
struct edge
{
int u, v, w;
};
edge es[N];//储存所有边
int main()
{
int V, E;
cin >> V >> E;
for (int i = 1; i <= E; ++i)
{
int u, v,w;
cin >> u >> v>>w;
es[i] = edge{
u, v, w };
/*
如果是无向图
es[i] = edge{ u, v, w };
es[i] = edge{ v, u, w };
*/
}
// 遍历,通过边来遍历,
for (int i = 1; i <= E; ++i)
{
edge e = es[i];
cout << e.u << " " << e.v << " " << e.w << endl;
}
}
相信说到这,就可以慢慢悟了把
核心思路
本来想放在后头,不过怕大家一路看过去直接迷了。就放在前边,可以带着这个去看.
Dijkstra算法
// dis[k] 中 dis[k] 表示源点到 顶点k的距离(直接到k)
//dis[mark] + g[mark][k] 先到当前距离短的点mark,通过这个点转到k
dis[k] = min(dis[k], dis[mark] + g[mark][k]); //取最小的距离
Bellman-Ford和SPFA算法
if (dis[e.v] > dis[e.u] + e.w)
dis[e.v] = dis[e.u] + e.w;
其实大家理解和这两个是一个道理。都可以通过下面这段话理解。
可以想一下有三个点,1,2,3.刚开始1到3距离为6,1到2是3,2到3是2.以1为源点,因为到顶点2的距离小,所以将2加入。这个时候在更新距离时,之前没有加入2的时候1到3只有1–>3这个路径可以走,而现在2加入后,有新的路径了1–>2–>3,我们在算一下长度,哇才是5,比直接走到3小,好那么我们就改变路径。这就是上面代码的意义。
Dijkstra算法
图解
由于本博主画的图太,借了点图
----- S是已计算出最短路径的顶点的集合
----- U是未计算出最短路径的顶点的集合
----- C(3)表示顶点C到起点D的最短距离为3
- 选择顶点D
S={D(0)}
U={A(∞), B(∞), C(3), E(4), F(∞), G(∞)}
- 选取顶点C
S={D(0), C(3)}
U={A(∞), B(13), E(4), F(9), G(∞)}
就这样当作一个引子
基本思想
把带权图中的所有顶点V分成两个集合S和T,S中存放已经确定最短路径的顶点,初始时,S中仅包含一个源点; T=V−S,存储待确定最短路径的顶点,初始时,T中包含除源点外的顶点,此时各顶点的当前最短路径长度为源点到各顶点的弧上的权值。开始操作时,从T中选取当前最短路径长度最小的一个顶点 Vi 加入S,然后修改T中剩余顶点的当前最短路径长度,修改的原是:当 Vi 的最短路径长度与 Vi 到T中的顶点之间的权值之和小于该顶点的当前路径长度时,用前者替换后者。重复上述过程,直到S中包含所有的顶点
为止。
简单来说,将点分为两个集合,将源点放入一个集合中,其余的点放入另一个集合。(实现算法不需要用到集合,可以用一个bool 数组来记录,为true就表明就加入到了源点所在的集合中)。然后遍历不在源点集合的点,找到距离源点最近的点。将这个点加入到源点所在的集合中。然后以这个点为中转点更新源点到其余点的距离。
求解步骤
- 将有向网用邻接矩阵储存,没有相连的边用一个较大的数表示。
- 找到距离源点最近的点,将其加入到源点所在的集合中。
- 以这个点为中转点,更新源点到其余点的距离。
细节解释
变量的意义
int g[N][N];//邻接矩阵存图,g[i][j] 表示 顶点i带顶点j的距离
int dis[N];//表示 源点src到 i的最短距离
bool vis[N];//是否在集合中
int stc;//源点
int n, m;//n 顶点数,m边数