Dijkstra算法适用于边权值为正的情况,可用于计算正权图上的单元最短路。
其伪代码如下:
设d[v0] = 0, 其他d[i] = INF
循环n次{
在所有未标号的结点中,选取d值最小的结点x
给结点x加上永久标号
对于从x出发的所有边,执行松弛操作。
}
//松弛操作的伪代码如下:
RELAX(u,v,w)
if(u.d + w(u,v) < v.d){
v.d = w.d + w(u,v);
pre[v] = u;
}
Dijkstra算法代码:
/* Dijkstra 单源最短路算法 */ #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 100; //定义顶点数 const int INF = 65535; //定义最大权值 int v[maxn], w[maxn][maxn];//v为标号,w是边,点自动按顺序编号 int d[maxn]; //d用于最短路长度的记录 int pre[maxn]; //用于记录路径, prev[i]为i的前一点的下标 /* 0到其他点的最短路 */ void dijkstra(int n, int v0){ memset(v, 0, sizeof v); //如果是其他结点,只要更改i==?就可以 for (int i = 0; i < n; ++i){ d[i] = (i == v0 ? 0 : INF); } //如果上面初始化为0的d,那么此处可以从1开始 for (int i = 0; i < n; ++i){ int x, m = INF; //遍历找最小的边 for (int y = 0; y < n; ++y){ if (!v[y] && d[y] <= m){ m = d[x = y]; } }//for(y) v[x] = 1; //永久标号 for (int y = 0; y < n; ++y){ //更新最短路径 //若无须记录路径只需要下面的一行 //d[y] = min(d[y], d[x] + w[x][y]); //记录路径代码 if (d[x] + w[x][y] < d[y]){ d[y] = d[x] + w[x][y]; pre[y] = x; } } }//for(i) } int main() { int n, m; //点数和边数 scanf("%d%d", &n, &m); //点自动按数组编号 int u1, v1, w1; for (int i = 0; i < n; ++i){ for (int j = 0; j < n; ++j){ w[i][j] = INF; } } for (int i = 0; i < m; ++i){ scanf("%d%d%d", &u1, &v1, &w1); w[u1][v1] = w1; w[v1][u1] = w1; //无向图 } dijkstra(n, 0); for (int i = 0; i < n; ++i){ printf("%3d", d[i]); } printf(" "); //打印0-8的最短路径走过的点 注意是逆序的 int k = 8; while (k){ printf("%3d", k); k = pre[k]; } printf("%3d", k); printf(" "); return 0; } /* 测试数据 9 16 0 1 1 0 2 5 1 2 3 1 3 7 1 4 5 2 4 1 2 5 7 3 4 2 3 6 3 4 5 3 4 6 6 4 7 9 5 7 5 6 7 2 6 8 7 7 8 4 结果: 数组d 0 1 4 7 5 8 10 12 16 0~8路径反序: 8 7 6 3 4 2 1 0 */
其实Dijkstra是可以优化的,优化后的Dijkstra适用于系数图(m << n*n)
优化的Dijkstra(以点为存储结构):
/* 改进的Dijkstra --- 以点为存储结构 */ #include <cstdio> #include <vector> #include <cstring> #include <queue> using namespace std; const int INF = 65535; const int maxn = 100; /* 定义结点类型 用于存储图结构 */ struct node{ int x; //终点坐标 int w; //边的权值 node(){} node(int a, int b) :x(a), w(b){} }; /* 用于优化的结点类型 */ struct HeapNode{ int x, d; //终点的坐标及其维护的最短路径 HeapNode(){} HeapNode(int a, int b) :x(a), d(b){} bool operator<(const HeapNode& rhs) const{ return d > rhs.d; } }; //Vertex[i]表示以i为起点的点集 Vertex[i][j]为其某一个终点(共有Vertex[i].size()-1个终点) vector<node> Vertex[maxn]; int dis[maxn], n;//dis维护最短路的值, n为结点数 bool visit[maxn]; //求最短路时用于标记的数组 int pre[maxn]; //记录路径 /* 求v0到其他点的最短路 */ void Dijkstra(int v0){ for (int i = 0; i < n; ++i){ dis[i] = INF; } dis[v0] = 0; memset(visit, 0, sizeof visit); priority_queue<HeapNode> q; q.push(HeapNode(v0, 0)); while (!q.empty()){ HeapNode x = q.top(); q.pop(); //取得最小的路径的点 //此处应该判别x是否已经访问!已经加入永久标号的点,到达其更长的路径可能还在队列中 //但使用这个点已经不可能更新最短路径表,因为已经比其更小的路径已经用来更新过, //故应该设置一个visit数组标记是否访问过! if (visit[x.x]){ continue; //如果已经是永久标号 结束 } //从点x出发,更新和其相连的端点的最短路 //x.x表示点的编号 Vertex[x.x]表示以x.x为起点集合 for (int i = 0; i < Vertex[x.x].size(); ++i){ node y = Vertex[x.x][i]; //y是终点 x是起点 //若经过点x可使得路径更小 ---> 更新 if (dis[x.x] + y.w < dis[y.x]){ dis[y.x] = dis[x.x] + y.w; pre[y.x] = x.x; //将更新的更小的路径入队 //此时队列中可能存在相同的点但比其更长的路径,这也是为什么需要上面的visit数组 q.push(HeapNode(y.x, dis[y.x])); } } } } int main() { int m; int u, v, w; scanf("%d%d", &n, &m); for (int i = 0; i < m; ++i){ scanf("%d%d%d", &u, &v, &w); Vertex[u].push_back(node(v, w)); Vertex[v].push_back(node(u, w)); } Dijkstra(0); for (int i = 0; i < n; ++i){ printf("%3d", dis[i]); } printf(" "); int k = 8; while (k){ printf("%3d", k); k = pre[k]; } printf("%3d ", k); return 0; } /* 测试数据 9 16 0 1 1 0 2 5 1 2 3 1 3 7 1 4 5 2 4 1 2 5 7 3 4 2 3 6 3 4 5 3 4 6 6 4 7 9 5 7 5 6 7 2 6 8 7 7 8 4 结果: 数组d 0 1 4 7 5 8 10 12 16 0~8路径反序: 8 7 6 3 4 2 1 0 */
优化的Dijkstra(以边为存储结构):
/* 改进的Dijkstra算法 --- 以边为存储结构 */ #include <cstdio> #include <cstring> #include <vector> #include <queue> using namespace std; const int maxn = 100; const int INF = 65535; /* 边结构 */ struct Edge{ int from, to, dist;//from,to,dist分别为边起点、终点和权值 Edge(int u, int v, int d) :from(u), to(v), dist(d){} }; /* 此节点为优先队列的元素 相当于(i,d[i])二元组 */ struct HeapNode{ int d, u; //u记录最短路径,u为起点 bool operator<(const HeapNode& rhs) const{ return d > rhs.d; //小的优先 } HeapNode(int a, int b) :d(a), u(b){} }; /* 定义最短路算法结构体 由于边是用vector存储,且自动编号,似乎只适用于有向图 */ struct Dij{ int n, m; vector<Edge> edges; //边集---保存边信息(自动编号为0~m-1) vector<int> G[maxn]; //按点记录边 G[i]表示以i为起点的集合 //G[i][j]表示以i为起点的集合中其中一条边的在edges中的序号 bool visit[maxn]; //是否已经是永久标号 int d[maxn]; //s到各点的距离 s是起点 ---> 记录最短路 int pre[maxn]; //记录路径,pre[i]表示以i为终点的边的编号 /* n为顶点数 */ void init(int n){ this->n = n; for (int i = 0; i < n; ++i){ G[i].clear(); //以i为起点的集合清空 }//for(i) edges.clear(); //清空边集 } void AddEdge(int from, int to, int dist){ edges.push_back(Edge(from, to, dist)); //加入边集 m = edges.size(); //m-1即为当前加入的边的序号 G[from].push_back(m - 1); //无向图 加上反向边 有向图可省去下面三行 edges.push_back(Edge(to, from, dist)); m = edges.size(); G[to].push_back(m - 1); } void dijkstra(int s){ priority_queue<HeapNode> Q; for (int i = 0; i < n; ++i){ d[i] = INF; }//for(i) d[s] = 0; memset(visit, 0, sizeof visit); Q.push(HeapNode(0,s)); //0,s分别为距离和起点 while (!Q.empty()){ HeapNode x = Q.top(); Q.pop(); //优先小地出栈,即先找到最小的d int u = x.u; //if (visit[u]) // continue; visit[u] = true; for (int i = 0; i < G[u].size(); ++i){ Edge& e = edges[G[u][i]]; //取得以u为起点的边e if (d[u] + e.dist < d[e.to]){ //经过点u和e到达e.to能更小 d[e.to] = d[u] + e.dist; //更新最小路径 pre[e.to] = G[u][i]; //记录路径 Q.push(HeapNode(d[e.to], e.to) ); } }//for(i) } } }; int main() { Dij dij; int n, m; //点和边的数目 scanf("%d%d", &n, &m); dij.init(n); int a, b, c; for (int i = 0; i < m; ++i){ scanf("%d%d%d", &a, &b, &c); dij.AddEdge(a, b, c); } dij.dijkstra(0); for (int i = 0; i < dij.n; ++i){ printf("%3d", dij.d[i]); } printf(" "); //pre记录的是路径 int k = 8; while (k){ printf("%3d", k); k = dij.edges[dij.pre[k]].from; //取得前一个结点, } printf("%3d ", k); return 0; } /* 测试数据 9 16 0 1 1 0 2 5 1 2 3 1 3 7 1 4 5 2 4 1 2 5 7 3 4 2 3 6 3 4 5 3 4 6 6 4 7 9 5 7 5 6 7 2 6 8 7 7 8 4 结果: 数组d 0 1 4 7 5 8 10 12 16 0~8路径反序: 8 7 6 3 4 2 1 0 */