本文节点的编号从0开始计算
思路
dijkstra伪算法(未优化)
源点到自己的距离为0,其余点到源点的距离为无穷大
记录当前节点now为源点s
循环下面的步骤V次,V是结点的个数
now标记已访问
遍历now的出度(以now为起点出发的边)
如果连向的节点(姑且记为to)未被访问,就“松弛”它
松弛就是用“源点到to的距离”和“先到now再从now走到to的距离”的较小值更新前者
伪代码 dis[to] = min( dis[to], dis[now] + cost[now][to] )
按顺序遍历所有 “未被访问” 的节点
找出离源点最近的那个,记录为pos
now = pos
这个复杂度是O(N^2)
C++实现
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
using LL = long long;
const int inf = 0x3f3f3f3f;
const int MAX_E = 1e5+5; // 最大边数
const int MAX_V = 1e4+5; // 最大节点数
int V, E; // 点、边
struct edge{ int to, w; };
vector<edge> G[MAX_E];
bool vis[MAX_V];
int dis[MAX_V]; // 到源点的距离
void input();
void Build(int x, int y, int w);
void dijkstra(int s);
int main()
{
input();
dijkstra(0);
return 0;
}
void Build(int x, int y, int w = 1)
{
edge e; e.to = y; e.w = w;
G[x].push_back(e);
}
void input()
{
int a,b,w;
edge e;
cin >> V >> E;
for (int i = 0; i < E; i++)
{
cin >> a >> b >> w; // 带权
//cin >> a >> b; // 不带权
Build(a, b, w);
Build(b, a, w); // 无向
}
}
void dijkstra(int s)
{
memset(dis, inf, sizeof(vis)); dis[s] = 0;
memset(vis, false, sizeof(vis));
int now = s;
for (int i = 0; i < V; i++) // 循环V次,i没有特殊意义
{
vis[now] = true;
for (int j = 0; j < G[now].size(); j++) // 遍历now的出度
{
if (!vis[G[now][j].to]) // 如果第j条出度连向的节点未被访问
{
dis[G[now][j].to] = min( dis[G[now][j].to], dis[now] + G[now][j].w); // 松弛
}
}
int Min = inf, pos = 0; // 找出未被访问的节点中距离最短的那个,这一步可以堆优化,即用优先队列避免遍历V次
for (int i = 0; i < V; i++) // 如果结点从1开始,遍历[1,V]
{
if (!vis[i] && dis[i] < Min)
{
Min = dis[i];
pos = i;
}
}
now = pos;
}
for (int i = 0; i < V; i++) printf("%d->%d=%d\n", s, i, dis[i]);
}
借用书上的例图,并将它看作是有向图,当然只要把input函数里边的第二个Build注释掉就是有向图了。
输入
5 8
0 2 10
0 4 30
0 5 100
1 2 5
2 3 50
3 5 10
4 3 20
4 5 60
输出
0->0=0
0->1=15
0->2=10
0->3=50
0->4=30
堆优化
原理是优化“寻找未被访问的节点中离源点最近的那个”这一步。本来是遍历,所以复杂度是O(N*N),如果改用优先队列存储这些节点,就可以将复杂度改进为O(NlogN)
参考了https://www.cnblogs.com/Renyi-Fan/p/7508098.html
堆优化伪算法
首先定义新的数据类型node,存储点编号和点到源点的距离,也可以用pair代替
然后定义仿函数,为优先队列提供自定义的出队优先级
源点入队(入队的是node对象,含编号和距离)
当队列非空,循环下面步骤
记录队首元素的点编号和距离 min_i, min_d
出队
如果min_i 被访问过,跳过这轮循环
标记min_i已访问
遍历min_i的出度
记录第i条边连向的结点to,以及这条边的权w
如果to已被访问,跳过此轮循环
松弛
node(to, dis[to]) 入队
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
using LL = long long;
using PII = pair<int, int>; // first记录点编号,second记录边权
const int inf = 0x3f3f3f3f;
const int MAX_E = 1e5+5; // 最大边数
const int MAX_V = 1e4+5; // 最大节点数
int V, E; // 点、边
struct edge{ int to, w; };
struct cmp
{
bool operator () (const PII & P1, const PII &P2) const { return P1.second > P2.second; } // 距离短的优先
};
vector<edge> G[MAX_E];
bool vis[MAX_V];
int dis[MAX_V]; // 到源点的距离
void input();
void Build(int x, int y, int w);
void dijkstra(int s);
void dijkstra_2(int s); // 堆优化版本
int main()
{
input();
dijkstra(0);
cout << endl;
dijkstra_2(0);
return 0;
}
void Build(int x, int y, int w = 1)
{
edge e; e.to = y; e.w = w;
G[x].push_back(e);
}
void input()
{
int a,b,w;
edge e;
cin >> V >> E;
for (int i = 0; i < E; i++)
{
cin >> a >> b >> w; // 带权
//cin >> a >> b; // 不带权
Build(a, b, w);
Build(b, a, w); // 无向
}
}
void dijkstra(int s) // 未优化
{
memset(dis, inf, sizeof(vis)); dis[s] = 0;
memset(vis, false, sizeof(vis));
int now = s;
for (int i = 0; i < V; i++) // 循环V次,i没有特殊意义
{
vis[now] = true;
for (int j = 0; j < G[now].size(); j++) // 遍历now的出度
{
if (!vis[G[now][j].to]) // 如果第j条出度连向的节点未被访问
{
dis[G[now][j].to] = min( dis[G[now][j].to], dis[now] + G[now][j].w); // 松弛
}
}
int Min = inf, pos = 0; // 找出未被访问的节点中距离最短的那个,这一步可以堆优化,即用优先队列避免遍历V次
for (int i = 0; i < V; i++) // 如果结点从1开始,遍历[1,V]
{
if (!vis[i] && dis[i] < Min)
{
Min = dis[i];
pos = i;
}
}
now = pos;
}
for (int i = 0; i < V; i++) printf("%d->%d=%d\n", s, i, dis[i]);
}
struct node
{
int i; // 节点编号
int d; // 到源点的距离
node(int ii = -1, int dd = inf) : i(ii), d(dd){}
};
struct Cmp // 仿函数
{
bool operator()(const node & n1, const node & n2) { return n1.d > n2.d; } // 距离短的优先出队
};
priority_queue<node, vector<node>, Cmp> Q;
void dijkstra_2(int s) // 堆优化
{
node n;
int min_i, min_d; // 存距离源点最近的点编号和距离
memset(dis, inf, sizeof(dis));
memset(vis, false, sizeof(vis));
dis[s] = 0;
Q.push(node(s, dis[s]));
while (!Q.empty())
{
n = Q.top();
Q.pop();
min_i = n.i;
min_d = n.d;
if (vis[min_i]) continue; // 如果 min_i 已被访问,不重复处理
vis[min_i] = true;
for (int i = 0; i < G[min_i].size(); i++) // 遍历 min_i 的边
{
int to = G[min_i][i].to; // min_i的第i条边连向的节点
int w = G[min_i][i].w; // 边权
if (!vis[to])
{
dis[to] = min( dis[to], dis[min_i] + w ); // 松弛
Q.push(node(to, dis[to]));
}
}
}
for (int i = 0; i < V; i++) printf("%d->%d=%d\n", s, i, dis[i]);
}
附一组测试数据
6 10
0 1 5
0 3 7
1 2 4
2 0 8
2 5 9
3 2 5
3 5 6
4 3 5
5 4 1
5 0 3
输出
0->0=0
0->1=5
0->2=8
0->3=7
0->4=4
0->5=3
0->0=0
0->1=5
0->2=8
0->3=7
0->4=4
0->5=3