Dijkstra算法:用来解决单源最短路问题。给定图G和起点s,通过算法得到S到达其他每个顶点的最短距离。
基本思想:对图G(V, E)设置集合S,存放已被访问的顶点,然后每次从集合V-S中选择与起点s的最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中介点,优化起点s与所有从u能到达的顶点v之间的最短距离。这样的操作执行n次(n为顶点个数),直到集合S已包含所有顶点。
有向非负带权如图所示:
为了更加方便理解dijkstra算法,我们先把所有的有向边全部删掉,从源点开始,红色表示未被访问的点,白色表示已经被访问过的点,设v0到v0的路径为0,v0到所有点的距离为无穷大
如下图所示:从v0开始,我们将与v0相连的边全找出来,如下图所示,找到所有边中权值最小的边,并且,将与该条边相连的点标记为已经访问,此时,该条边就是v0到v1的最短路径。(可证明,若改条边不是v0到v1的最短路径,那么一定会有一条路径使得v0到v1经过中介点m,使得v0到v1路径最短,那么v0到v1的直接距离一定会比v0到中介点的距离大,这与我们找到的v0到v1是与v0相连路径中最短路径冲突)
此时,我们从最新标记已经访问过的点v1出发,找出所有与v1相连的,且以v1为出发点的有向边,并且找到所有有向边中的最短路径,此时我们发现,由v0到v3的直接路径为4,而v0经过v1为中介点到v3的距离为3,此时我们更新v0到v3的最短路径
此时,我们发现未被访问过的点,中v0到v3的距离是最短的,将v3更新为已经访问,此时,我们将以v3为出发点,与v3相连的有向边全部找出
此时,我们发现由v0直接到v4的路径比v0经过v3到v4的路径要短,因此,我们无需更新v0到v4的最短路径
v0经过v3到v2的距离由无穷大变为5,更新v0到v2的最短路径
我们发现由由v0出发,且与v0距离最小的点是v4,把v4设为已经访问,且找出以v4为出发点,与v4相连的有向边,我们发现v0到v5经过v4所需的路径为7,此时更新v0到v5的最短距离
由v0出发到未被访问的点且路径最短,此时找到v2,将该点变为已经访问,且找到以v2为出发点的有向边,此时更新v5的距离
找到未被访问的点,使得v0到该点的距离是所有未被访问点中路径最短的,找到v5,标记v5为已经访问
此时已经全部更新完成。
Dijkstra算法的策略:
设置集合S存放已被访问的顶点,然后执行n次下面的两个步骤(n为顶点个数)
1)每次从集合V-S中选择与起点s的最短距离最小的一个顶点(记为u),访问并加入集合S。
2)之后,令顶点u为中介点,优化起点s与所有从u能到达的顶点v之间的最短距离。
Dijkstra算法的具体实现:
1)集合S可以用一个bool型数组vis[]来实现,即当vis[i] == true时表示顶点Vi已被访问,当vis[i] == false时表示顶点Vi未被访问。
2)令int型数组d[]表示起点s到达顶点Vi的最短距离,初始时除了起点s的d[s]赋为0,其余顶点都赋为一个很大的数来表示inf,即不可达。
Dijkstra算法的伪代码
Dijkstra(G, d[], s)
{
初始化;
for(循环n次)
{
u = 使d[u]最小的还未访问的顶点的标号;
记u已被访问;
for(从u出发能到达的所有顶点v)
{
if(v未被访问 && 以u为中介点使s到顶点v的最短距离d[v]更优)
{
优化d[v];
}
}
}
}
定义MAXV为最大顶点数、INF为一个很大的数字
const int MAXV = 1000; //最大顶点数
const int INF = 1000000000; //设INF为一个很大的数
int n, G[MAXV][MAXV]; //n为顶点数,MAXV为最大顶点数
int d[MAXV]; //起点到达各点的最短路径长度
bool vis[MAXV] = {false}; //标记数组,vis[i]==true表示已访问。初值均为false
void Dijkstra(int s) //s为起点
{
fill(d, d + MAXV, INF); //fill函数将整个d数组赋为INF
d[s] = 0; //起点s到达自身的距离为0
for(int i = 0; i < n; i++) //循环n次
{
int u = -1, MIN = INF; //u使d[u]最小,MIN存放该最小的d[u]
for(int j = 0; j < n; j++) //找到未访问的顶点中d[]最小的
{
if(vis[j] == false && d[j] < MIN)
{
u = j;
MIN = d[j];
}
}
//找不到小于INF的d[u],说明剩下的顶点和起点s不连通
if(u == -1)
return;
vis[u] = true; //标记u为已访问
for(int v = 0; v < n; v++)
{
//如果v未访问 && u能到达v && 以u为中介点可以使d[v]更优
if(vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v])
{
d[v] = d[u] + G[u][v]; //优化d[v]
}
}
}
}
struct Node
{
int v, dis; //v为边的目标顶点,dis为边权
}
vector<Node> Adj[MAXV]; //图G,Adj[u]存放从顶点u出发可以到达的所有顶点
int n; //n为顶点数,图G使用邻接表实现,MAXV为最大顶点数
int d[MAXV]; //起点到达各点的最短路径长度
bool vis[MAXV] = {false}; //标记数组,vis[i]==true表示已访问。初值均为false
void Dijkstra(int s) //s为起点
{
fill(d, d + MAXV, INF); //fill函数将整个d数组赋为INF
d[s] = 0; //起点s到达自身的距离为0
for(int i = 0; i < n; i++) //循环n次
{
int u = -1, MIN = INF; //u使d[u]最小,MIN存放该最小的d[u]
for(int j = 0; j < n; j++) //找到未访问的顶点中d[]最小的
{
if(vis[j] == false && d[j] < MIN)
{
u = j;
MIN = d[j];
}
}
//找不到小于INF的d[u],说明剩下的顶点和起点s不连通
if(u == -1)
return;
vis[u] = true; //标记u为已访问
//只有下面这个for与邻接矩阵的写法不同
for(int j = 0; j < Adj[u].size(); j++)
{
int v = Adj[u][j].v; //通过邻接表直接获得u能到达的顶点v
if(vis[v] == false && d[u] + Adj[u][j].dis < d[v])
{
//如果v未访问 && 以u为中介点可以使d[v]更优
d[v] = d[u] + Adj[u][j].dis; //优化d[v]
}
}
}
}
从起点V0到达其他所有顶点都必须是最短距离,即将上述过程实现
#include
#include
using namespace std;
const int MAXV =1000; //最大顶点数
const int INF = 100000000; //设INF为一个很大的数
int n, m, s, G[MAXV][MAXV]; //n为顶点数,m为边数,s为起点
int d[MAXV]; //起点到达各点的最短路径长度
bool vis[MAXV] = {false}; //标记数组,vis[i]==true表示已访问。初值均为false
void Dijkstra(int s) //s为起点
{
fill(d, d + MAXV, INF); //fill函数将整个d数组赋为INF
d[s] = 0; //起点s到达自身的距离为0
for(int i = 0; i < n; i++) //循环n次
{
int u = -1, MIN = INF; //u使得d[u]最小,MIN存放该最小的d[u]
for(int j = 0; j < n; j++) //找到未访问的顶点中d[]最小的
{
if(vis[j] == false && d[j] < MIN)
{
u = j;
MIN = d[j];
}
}
//找不到小于INF的d[u],说明剩下的顶点和起点s不连通
if(u == -1)
return;
vis[u] = true; //标记u为已访问
for(int v = 0; v < n; v++)
{
//如果v未访问 && u能到达v && 以u为中介点可以使d[v]更优
if(vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v])
{
d[v] = d[u] + G[u][v]; //优化d[v]
}
}
}
}
int main()
{
int u, v, w;
scanf("%d%d%d", &n, &m, &s); //顶点个数、边数、起点编号
fill(G[0], G[0] + MAXV * MAXV, INF); //初始化图G
for(int i = 0; i < m; i++)
{
scanf("%d%d%d", &u, &v, &w); //输入u,v以及u->v的边权
G[u][v] = w;
}
Dijkstra(s); //Dijkstra算法入口
for(int i = 0; i < n; i++)
{
printf("%d ", d[i]); //输出所有顶点的最短距离
}
return 0;
}
//输入数据
6 8 0 //6个顶点,8条边,起点为0号。以下8行为8条边
0 1 1 //边0->1的边权为1,下同
0 3 4
0 4 4
1 3 2
2 5 1
3 2 2
3 4 3
4 5 3
0 1 5 3 4 6 //输出结果
题目若是无向边,则把无向边当成两条指向相反的有向边。
最短路径的求法
Dijkstra算法伪代码
if(v未访问 && 以u为中介点可以使起点s到顶点v的最短距离d[v]更优)
{
优化d[v];
}
以u为中介点可以使起点s到顶点v的最短距离d[v]更优:使d[v]变得更小的方案是让u作为从s到v最短路径上v的前一个结点(s->…->u->v)。
启发:把这个信息记录下来,设置数组pre[],令pre[v]表示从起点s到顶点v的最短路径上v的前一个顶点(前驱结点)的编号。当伪代码中的条件成立时,就可以将u赋给pre[v],最终就能把最短路径上每一个顶点的前驱结点记录下来。
if(v未访问 && 以u为中介点可以使起点s到顶点v的最短距离d[v]更优)
{
优化d[v];
令v的前驱为u;
}
邻接矩阵
int n, G[MAXV][MAXV]; //n为顶点数,MAXV为最大顶点数
int d[MAXV]; //起点到达各点的最短路径长度
int pre[MAXV]; //pre[v]表示从起点到达顶点v的最短路径上v的前一个顶点
bool vis[MAXV] = {false}; //标记数组,vis[i]==true表示访问。初值均为false
void Dijkstra(int s) //s为起点
{
fill(d, d + MAXV, INF); //fill函数将整个d数组赋为INF
for(int i = 0; i < n; i++)
{
pre[i] = i; //初始状态设每个点的前驱为自身
}
d[s] = 0; //起点s到达自身的距离为0
for(int i = 0; i < n; i++) //循环n次
{
int u = -1, MIN = INF; //u使d[u]最小,MIN存放该最小的d[u]
for(int j = 0; j < n; j++) //找到未访问的顶点中d[]最小的
{
if(vis[j] == false && d[j] < MIN)
{
u = j;
MIN = d[j];
}
}
//找不到小于INF的d[u],说明剩下的顶点和起点s不连通
if(u == -1)
return;
vis[u] = true; //标记u为已访问
for(int v - 0; v < n; v++)
{
//如果v未访问 && u能到达v && 以u为中介点可以使d[v]更优
if(vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v])
{
d[v] = d[u] + G[u][v]; //优化d[v]
pre[v] = u; //记录v的前驱顶点是u
}
}
}
}
pre[4] = 3;
pre[3] = 2;
pre[2] = 1;
pre[1] = 1;
当想要知道从起点V1到达V4的最短路径,就需要先从pre[4]得到V4的前驱顶点是V3,然后从pre[3]得到V3的前驱顶点是V2,再从pre[2]得到V2的前驱结点是V1
递归:用pre[]的信息寻找前驱,直至到达起点V1后从递归深处开始输出。
void DFS(int s, int v) //s为起点编号,v为当前访问的顶点编号(从终点开始递归)
{
if(v == s) //如果当前已经到达起点s,则输出起点并返回
{
printf("%d\n", s);
return;
}
DFS(s, pre[v]); //递归访问v的前驱顶点pre[v]
printf("%d\n", v); //从最深处return回来之后,输出每一层的顶点号
}
碰到有两种及以上可以达到最短距离的路径,题目就会给出一个第二标尺(第一标尺是距离),要求在所有最短路径中选择第二标尺最优的一条路径。第二标尺常见的是一下 3 种出题方法或其组合,及解决其对应的解决方案:
只需要增加一个数组来存放新增的边权或点权或最短路径条数,然后在Dijkstra算法中修改**优化d[v]**的那个步骤即可,其他不改动。
for(int v = 0; v < n; v++)
{
//如果v未访问 && u能到达v
if(vis[v] == false && G[u][v] != INF)
{
if(d[u] + G[u][v] < d[v]) //以u为中介点可以使d[v]更优
{
d[v] = d[u] + G[u][v];
c[v] = c[u] + cost[u][v];
}
else if(d[u] + G[u][v] == d[v] && c[u] + cost[u][v] < c[u])
{
c[v] = c[u] + cost[u][v]; //最短距离相同时看能否使c[v]更优
}
}
}
for(int v = 0; v < n; v++)
{
//如果v未访问 && u能到达v
if(vis[v] == false && G[u][v] != INF)
{
if(d[u] + G[u][v] < d[v]) //以u为中介点可以使d[v]更优
{
d[v] = d[u] + G[u][v];
w[v] = w[u] + weight[v];
}
else if(d[u] + G[u][v] == d[v] && w[u] + weight[v] > w[v])
{
w[v] = w[u] + weight[v]; //最短距离相同时看能否使w[v]更优
}
}
}
for(int v = 0; v < n; v++)
{
//如果v未访问 && u能到达v
if(vis[v] == false && G[u][v] != INF)
{
if(d[u] + G[u][v] < d[v]) //以u为中介点可以使d[v]更优
{
d[v] = d[u] + G[u][v];
num[v] = num[u];
}
else if(d[u] + G[u][v] == d[v])
{
num[v] += num[u]; //最短距离相同时累加num
}
}
}