点击查看代码
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#pragma warning(disable:4996)
//迪杰斯特拉算法伪代码
dijkstra(G, d[], s) { //图G,d[]是起点与各点之间的最短路径,s是起点
初始化:将d[]初始为一个很大的值,表示起点与与各点之间不相连。起点s与自身距离为0,即d[s]=0;
for (循环n次) { //n个顶点循环n次
u = 能使d[u]最小,且还未被访问的顶点编号;
设置u已被访问;
for (枚举u的所有邻接点v) {
if (如果v未被访问,且以u为中转点能使s与v的最短距离d[v]更优) {
更新最优的d[v];
}
}
}
}
/*
注意:
1、迪杰斯特拉算法只能处理边权都是非负整数的情况,如果边权出现负数则要使用SPFA算法
2、以下是有向图的迪杰斯特拉算法。如果题目给出的是无向图,可以把无向图理解为两条反方向的边,
(1)在邻接矩阵中,对于u和v之间的无向边,在输入时分别对G[u][v]和G[v][u]添加这条边即可
(2)在邻接表中,对于u和v之间的无向边,在u的邻接表adj[u]中添加v,在v的邻接表adj[v]中添加u即可
*/
const int maxn = 1010; //最多1000个结点
const int INF = 1000000000; //无穷大表示两个顶点之间不相连
//1、邻接矩阵版迪杰斯特拉算法
//时间复杂度:外层循环O(n),内层循环(寻找最小的d[u]需要O(n),枚举u的邻接点需要O(n)),总共为O(n^2)
int n, G[maxn][maxn]; //顶点个数,邻接矩阵
int d[maxn]; //起点与各点之间的最短距离distance
int pre[maxn]; //pre[v]=k,表示从起点到顶点v的最短路径上,v的前驱顶点是k
bool vis[maxn] = { false }; //记录顶点是否已被访问,vis[i]=true表示顶点i已被访问
//迪杰斯特拉算法扩展
//(1)使用边权筛选最短路径,如选择边权之和最小的最短路径
int cost[maxn][maxn]; //存储边权,cost[u][v]存储边u->v的边权
int c[maxn]; //存储最短路径中的最小边权和,c[u]存储起点s到顶点u的最小边权和(初始时c[s]=0,其余为INF无穷大)
//(2)使用点权筛选最短路径,如选择点权之和最大的最短路径
int weight[maxn]; //存储点权,weight[s]存储顶点s的点权
int w[maxn]; //存储最短路径中的最大点权和,w[u]存储起点s到顶点u的最大点权和(初始时w[s]=weight[s],其余为0)
//(3)输出所有最短路径
int num[maxn]; //存储最短路径条数,num[u]存储从起点s到顶点u的最短路径条数(初始时num[s]=1,其余为0)
void Dijkstra(int s) { //起点编号为s
fill(d, d + maxn, INF); //将d[]初始为INF,表示起点与各点都不相连
fill(c, c + maxn, INF); //将c[]初始为INF,表示起点到各点的最小边权和都为无穷大
//fill(w, w + maxn, 0); //将w[]初始为0,表示起点到各点的最大点权和都为0
//fill(num, num + maxn, 0); //将num[]初始为0,表示起点到各点的最短路径条数为0
d[s] = 0; //起点s与自己的最短距离为0
c[s] = 0; //起点s与自己的最小边权之和为0
//w[s] = weight[s]; //起点s与自己的最大点权之和为weight[s]
//num[s] = 1; //起点s到自己的最短路径条数为1
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++) { //枚举所有顶点
if (vis[j] == false && d[j] < MIN) { //查找在未访问的顶点中,d[j]最小的那个顶点
u = j; //用u记录j
MIN = d[j]; //MIN记录最短距离d[j]
}
}
if (u == -1) return; //如果u等于-1,则表示剩下的顶点都已被访问,或它们和起点s都不相连,可以结束算法
vis[u] = true; //否则设置u已被访问
for (int v = 0; v < n; v++) { //枚举所有顶点,查找u的邻接点v
if (vis[v] == false && G[u][v] != INF) { //如果v未被访问,且u和v之间相连
if (d[u] + G[u][v] < d[v]) { //如果以u为中转点能使s与v的最短距离d[v]更小,则发现最短路径
pre[v] = u; //记录顶点v的前驱顶点是u
d[v] = d[u] + G[u][v]; //更新s与v的最短距离d[v]
c[v] = c[u] + cost[u][v]; //更新s与v的最小边权和c[v]
//w[v] = w[u] + weight[v]; //更新s与v的最大点权和w[v]
//num[v] = num[u]; //因为是以u为中转点发现当前最短路径,所以点v可以选择的最短路径条数与点u相同
}
else if (d[u] + G[i][v] == d[v] && c[u] + cost[u][v] < c[v]) { //如果有多条最短路径距离相同
c[v] = c[u] + cost[u][v]; //如果当前最短路径能使边权之和c[v]更小,则更新c[v]
//else if (d[u] + G[i][v] == d[v] && w[u] + weight[v] > w[v]) { //如果有多条最短路径距离相同
//w[v] = w[u] + weight[v]; //如果当前最短路径能使点权之和w[v]更大,则更新w[v]
//else if (d[u] + G[i][v] == d[v]) { //如果有多条最短路径距离相同
//num[v] += num[u]; //因为以u为中转点可以有多条最短路径,所以点v可以选择的最短路径条数是在点u的基础上叠加点v自己可以选择的条数
}
}
}
}
}
//2、邻接表版迪杰斯特拉算法
//时间复杂度:外层循环O(n),内层循环(寻找最小的d[u]需要O(n),枚举u的邻接点需要O(adj[u].size()),总共为O(n^2+E)
//如果使用STL的优先队列存储d[u](内部会自动排序,可以设队首元素最小),在查找最小d[u]过程中可以降低时间复杂度,变为O(nlogn+E)
struct Node {
int v, dis; //v为边的目标顶点,dis是边权
};
vector<Node> adj[maxn]; //邻接表
int n, d[maxn]; //顶点数,起点与各点之间的最短距离distance
bool vis[maxn] = { false }; //记录顶点是否已被访问,vis[i]=true表示顶点i已被访问
int pre[maxn]; //存储最短路径上所有顶点编号,pre[v]=k,表示从起点到顶点v的最短路径上,v的前驱顶点是k
void Dijkstra(int s) { //起点编号为s
fill(d, d + maxn, INF); //将d[]初始为INF,表示起点与各点都不相连
d[s] = 0; //起点与自身距离为0
for (int i = 0; i < n; i++) { //n个顶点循环n次
int u = -1, MIN = INF; //u存储d[u]最小的顶点,MIN存放最短距离d[u]
for (int j = 0; j < n; j++) { //枚举所有顶点
if (vis[j] == false && d[j] < MIN) { //找到在未访问的顶点中d[j]最小的那个顶点
u = j; //用u记录j
MIN = d[j]; //MIN记录最短距离d[j]
}
}
if (u == -1) return; //如果u等于-1,则表示剩下的顶点都已被访问,或它们和起点s都不相连,可以结束算法
vis[u] = true; //否则设置u已被访问
//邻接表版本只有以下部分与邻接矩阵不同,因为邻接表可以直接获得u的邻接点,不需要枚举所有顶点
for (int j = 0; j < adj[u].size(); j++) { //枚举u的所有邻接点
int v = adj[u][j].v; //为了方便书写,将邻接点adj[u][j]的编号存入v中
//如果v未被访问,且以u为中转点能使s与v的最短距离d[v]更小
if (vis[v] == false && d[u] + adj[u][j].dis < d[v]) {
d[v] = d[u] + adj[u][j].dis; //更新s与v之间的最短距离d[v]
pre[v] = u; //记录顶点v的前驱顶点u
}
}
}
}
//使用DFS遍历pre[],不断回溯前驱顶点,逐个输出最短路径上的每个顶点
void DFS(int s, int v) { //s为起点编号,v为当前访问的顶点
if (v == s) { //当回溯到起点s时才开始输出,先输出起点s
printf("%d\n", s);
return; //结束递归返回上一层
}
DFS(s, pre[v]); //递归访问v的前驱顶点pre[v]
printf("%d\n", v); //从下一层递归结束回来后,再输出当前访问的顶点v
}