前言
单源最短路指的是在一张有向图中从起点 a a a 走到重点 b b b 的最小花费。
Dijkstra算法
简介
该算法是从起始点开始,采用贪心方法,每次找到最近且还没被访问过的点作为起点并从该点向其他还没访问过的点查询。
详解
开始将除了初始点外的所有点设为无穷大,且图如下图所示。不妨令 a i a_i ai 表示从起始点到 i i i 的最小花费,可得:
i i i | 1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 |
---|---|---|---|---|
a i a_i ai | 0 0 0 | + ∞ +\infty +∞ | + ∞ +\infty +∞ | + ∞ +\infty +∞ |
首次遍历,从 1 1 1 开始依次走向其它 3 3 3 个点,更新 a i a_i ai。
i i i | 1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 |
---|---|---|---|---|
a i a_i ai | 0 0 0 | 2 2 2 | 5 5 5 | 4 4 4 |
接着找到 2 2 2 为没遍历过最小的,更新
i i i | 1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 |
---|---|---|---|---|
a i a_i ai | 0 0 0 | 2 2 2 | 4 4 4 | 3 3 3 |
总结
可以发现,我们每次的工作如下:
- 找到一个未被标记的最小点 m i n v minv minv 并将其用 v v v 打上标记。
- 从 m i n v minv minv 出发,更新所有的 k k t o kk_{to} kkto,也就是上表所示的 a i a_i ai。
#include<bits/stdc++.h>
using namespace std;
struct edge{
int to,w;
}e[1000005];
int head[200005],en,nex[1000005];
bool v[200005];
void add(int x,int y,int ww){
nex[++en] = head[x];
head[x] = en;
e[en].to = y;
e[en].w = ww;
return;
}
int kk[200005];
int main() {
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i = 1;i <= m;i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
for(int i = 1;i <= n;i++) {
kk[i] = pow(2,31) - 1;
}
kk[s] = 0;
for(int i = 1;i <= n;i++) {
int minn = pow(2,31) - 1,minv;
for(int j = 1;j <= n;j++) {
if(!v[j] && minn > kk[j]) {
minn = kk[j];
minv = j;
}
}
v[minv] = true;
for(int j = head[minv];j;j = nex[j]){
if(!v[e[j].to]) {
kk[e[j].to] = min(kk[e[j].to],kk[minv] + e[j].w);
}
}
}
for(int i = 1;i <= n;i++) {
printf("%d ",kk[i]);
}
}
优化
上面算法复杂度为 O ( N 2 ) O(N^2) O(N2),考虑采用优先队列。推荐大家先阅读一下两篇文章:CSDN-C++——优先级队列(priority_queue)、 【C++】结构体使用及运算符重载。
对于每次更新 k k i kk_i kki,考虑将 i , k i i,k_i i,ki 赋值为一个结构体,并用 k k i kk_i kki 降序排列,每次取出最上面一个,时间复杂度 O ( N l o g N ) O(N log N) O(NlogN)
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,s;
int head[100005],nex[400005],cnt = 0,a[100005];
bool vis[100005];
struct edge{
int to,w;
}node[400005];
void add(int x,int y,int z) {
nex[++cnt] = head[x];
head[x] = cnt;
node[cnt].to = y;
node[cnt].w = z;
}
struct point{
int id,w;
friend bool operator <(point x,point y) {
return x.w > y.w;
}
};
priority_queue<point>p;
signed main() {
scanf("%lld %lld %lld",&n,&m,&s);
for(int i = 1;i <= n;i++) a[i] = 2147483647;
a[s] = 0;
point b;
b.id = s,b.w = 0;
p.push(b);
for(int i = 1;i <= m;i++) {
int x,y,z;
scanf("%lld %lld %lld",&x,&y,&z);
add(x,y,z);
}
while(!p.empty()) {
point now = p.top();
p.pop();
if(vis[now.id]) continue;
vis[now.id] = 1;
for(int i = head[now.id];i;i = nex[i]) {
if(a[node[i].to] > a[now.id] + node[i].w) {
a[node[i].to] = a[now.id] + node[i].w;
point c;
c.id = node[i].to;
c.w = a[now.id] + node[i].w;
p.push(c);
}
}
}
for(int i = 1;i <= n;i++) printf("%lld ",a[i]);
return 0;
}
例题
P1027 [NOIP2001 提高组] Car 的旅行路线
这题操作较为复杂,尽管是四个源点,操作方法却和上文相同。注意矩形的边不一定与坐标轴平行,需要用勾股定理判断出直角边后在进行操作。
#include<bits/stdc++.h>
#define int long long
using namespace std;
double dis(double x_1,double y_1,double x_2,double y_2) {
return (x_1 - x_2) * (x_1 - x_2) + (y_1 - y_2) * (y_1 - y_2);
}
int head[405],nex[500005],cnt;
double x[405],y[405],T[405];
struct edge{
int to;
double w;
}node[500005];
double t;
int s,a,b;
void add(int p1,int p2,double w) {
nex[++cnt] = head[p1];
head[p1] = cnt;
node[cnt].to = p2;
node[cnt].w = w;
}
struct point{
int id;
double w;
friend bool operator <(point x_1,point y_1) {
return x_1.w > y_1.w;
}
};
priority_queue<point>p;
double ans[405];
signed main() {
ios::sync_with_stdio(false);//取消输入输出流和stdio流的同步
cin.tie(0);//解除cin和cout的绑定
int n;
cin>>n;
while(n--) {
//预处理
cnt = 0;
for(int i = 0;i <= 400;i++) ans[i] = 10000000.0,head[i] = 0,x[i] = 0,y[i] = 0,T[i] = 0;
for(int i = 0;i <= 500000;i++) nex[i] = 0,node[i].to = 0,node[i].w = 0;
//输入
cin>>s>>t>>a>>b;
for(int i = 0;i < s;i++) {
cin>>x[i * 4 + 1]>>y[i * 4 + 1];
cin>>x[i * 4 + 2]>>y[i * 4 + 2];
cin>>x[i * 4 + 3]>>y[i * 4 + 3];
cin>>T[i];
double dis1 = dis(x[i * 4 + 2],y[i * 4 + 2],x[i * 4 + 3],y[i * 4 + 3]);
double dis2 = dis(x[i * 4 + 1],y[i * 4 + 1],x[i * 4 + 3],y[i * 4 + 3]);
double dis3 = dis(x[i * 4 + 1],y[i * 4 + 1],x[i * 4 + 2],y[i * 4 + 2]);
// cout<<dis1<<" "<<dis2<<" "<<dis3<<endl;
if(dis1 + dis2 == dis3) {
x[i * 4] = x[i * 4 + 1] + x[i * 4 + 2] - x[i * 4 + 3];
y[i * 4] = y[i * 4 + 1] + y[i * 4 + 2] - y[i * 4 + 3];
}
if(dis1 + dis3 == dis2) {
x[i * 4] = x[i * 4 + 1] + x[i * 4 + 3] - x[i * 4 + 2];
y[i * 4] = y[i * 4 + 1] + y[i * 4 + 3] - y[i * 4 + 2];
}
if(dis2 + dis3 == dis1) {
x[i * 4] = x[i * 4 + 2] + x[i * 4 + 3] - x[i * 4 + 1];
y[i * 4] = y[i * 4 + 2] + y[i * 4 + 3] - y[i * 4 + 1];
}
}
for(int i = 0;i <= s * 4 - 2;i++) {
// cout<<x[i]<<" "<<y[i]<<endl;
for(int j = i + 1;j <= s * 4 - 1;j++) {
double d = sqrt(dis(x[i],y[i],x[j],y[j]));
//printf("%lld %lld %.1lf\n",i,j,d);
if(i / 4 == j / 4) add(i,j,d * T[i / 4]),add(j,i,d * T[i / 4]);
else add(i,j,d * t),add(j,i,d * t);
}
}
point start;
start.w = 0;
for(int i = 4 * (a - 1);i < 4 * a;i++) start.id = i,p.push(start);
//dij
while(!p.empty()) {
start = p.top();
p.pop();
if(ans[start.id] != 10000000.0) continue;
ans[start.id] = start.w;
//printf("%lld %.1lf:\n",start.id,ans[start.id]);
for(int i = head[start.id];i;i = nex[i]) {
//printf("%lld %.1lf %.1lf\n",node[i].to,node[i].w,ans[node[i].to]);
if(ans[node[i].to] > ans[start.id] + node[i].w) {
point new_point;
new_point.id = node[i].to;
new_point.w = ans[start.id] + node[i].w;
p.push(new_point);
//printf("__________\n");
}
}
}
double min_ans = 10000000.0;
for(int i = 4 * b - 4;i < 4 * b;i++) {
if(min_ans > ans[i]) min_ans = ans[i];
}
printf("%.1lf\n",min_ans);
}
return 0;
}