简述:对于一个有编号为1~n的n个结点的有向图(权值为正),对于求点1到点n的最短路径。
初始假设除第一个结点外,所有结点到结点1无路径(也就是每个点都与结点1不连通,可以设边(1,i)的权值为正无穷大来实现);
第二步循环n次(每次确定一个最短路径点),寻找没有确定到结点1最短路径的点的这个集合中,权值最小的为下一个确定为最短路径的点,然后根据这个点的权值,更新未确定最短路径的点权值
最后如果n的权值仍为无穷大,那么说明n与1不连通;
否则输出点n的到1的距离;
模板:
不能存在负权值
int g[N][N]; // 存储每条边
int dist[N]; // 存储1号点到每个点的最短距离
bool st[N]; // 存储每个点的最短路是否已经确定
// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n - 1; i ++ )
{
int t = -1; // 在还未确定最短路的点中,寻找距离最小的点
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
// 用t更新其他点的距离
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
st[t] = true;
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
模板题:
当数据量很小时,可以用最简单的朴素dijistra
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出 −1。
数据范围
1≤n≤500,
1≤m≤105,
图中涉及边长均不超过10000。
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
代码及详解:
//n较小,用朴素版dijistra即可
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=510;
int n,m;
int e[N][N],d[N],st[N];//d[N]表示到1的距离,st[N]表示是否确定了到1的最短路
// /时间复杂度为n*n
int dijkstra(){
//初始化
memset(d,0x3f,sizeof d);//表示所有点都无法到达1
d[1]=0;//一到自己的距离为0
//每次寻找当前没确定最短路径过且到d[]最小的点
//寻找n次,每次确定一个最短路径
for(int j=0;j<n;j++){
int t=0;
for(int i=1;i<=n;i++){
if(!st[i]&&d[t]>d[i]) t=i;
}
st[t]=true;
for(int i=1;i<=n;i++){//邻接矩阵的边
if(!e[t][i]) continue;
d[i]=min(d[i],e[t][i]+d[t]);//被确定入最短路径集合的点距离也会更新
}
}
if(d[n]==0x3f3f3f3f) return -1;//memset按字节赋值,一个字节3f,int为四个字节,所以有四个3f
else return d[n];
}
int main(){
cin>>n>>m;
for(int k=0;k<m;k++){//如果用邻接矩阵存边,要特殊处理重边
int i,j,w;
scanf("%d%d%d",&i,&j,&w);
if(!e[i][j]){
e[i][j]=w;
continue;
}
e[i][j]=min(e[i][j],w);//会有重边,所以只考虑最短的那个
}
cout<<dijkstra()<<endl;
return 0;
}
当数据范围很大时,需要用小根堆优化:
//n较大,要用堆优化
#include <iostream>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int,int> PII;
const int N=150010,M=N;
int n,m;
int h[N],e[M],ne[M],w[M],idex;
int d[N];
int st[N];
void add(int a,int b,int c){
ne[idex]=h[a],e[idex]=b,w[idex]=c,h[a]=idex++;
}
//时间复杂度n
int dijistra(){
memset(d,0x3f,sizeof d);
priority_queue<PII,vector<PII>,greater<PII>> heap;
d[1]=0;
heap.push({0,1});
//m*logn
while(heap.size()){
PII temp=heap.top();
heap.pop();
int t=temp.second,dist=temp.first;
if(st[t]) continue;
st[t]=true;
for(int j=h[t];j!=-1;j=ne[j]){//每条边都加入堆,堆里最多m个元素
d[e[j]]=min(d[e[j]],d[t]+w[j]);
heap.push({d[e[j]],e[j]});//堆里插入数据时logn级别
}
}
return (d[n]==0x3f3f3f3f)?-1:d[n];
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
printf("%d\n",dijistra());
return 0;
}
好题:最短距离
关键:增设一个源点
有 N 个村庄,编号 1 到 N。
村庄之间有 M 条无向道路,第 i 条道路连接村庄 ai 和村庄 bi,长度是 ci。
所有村庄都是连通的。
共有 K 个村庄有商店,第 j 个有商店的村庄编号是 xj。
然后给出 Q 个询问,第 k 个询问给出一个村庄的编号 yk,问该村庄距离最近的商店有多远?
输入格式
第一行包含两个整数 N,M。
接下来 M 行,每行包含三个整数 ai,bi,ci,表示第 i 条道路连接村庄 ai 和村庄 bi,长度是 ci。
再一行包含整数 K。
接下来 K 行,每行包含一个整数 xj,表示第 j 个有商店的村庄编号是 xj。
再一行包含整数 Q。
接下来 Q 行,每行包含一个整数 yk,表示询问编号为 yk 的村庄与其距离最近的商店之间的距离。
输出格式
对于每个询问,输出该询问的结果。
数据范围
2≤N≤105,
N−1≤M≤min(N(N−1)2,105),
1≤Q≤105,
1≤K≤N,
1≤ci≤10000
输入样例:
7 7
1 2 5
1 4 3
2 3 2
2 5 1
3 6 7
5 6 8
6 7 6
3
7
5
4
7
1
2
3
4
5
6
7
输出样例:
3
1
3
0
0
6
0
代码:
//用堆优化dijistra也会超时
//可以设置一个源点有k条边指向k个超市,可以优化成只用一次dijistra算法
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
typedef pair<int,int> PII;
const int N=1e5+10,M=3*N;
//d[]不会爆int
int h[N],ne[M],e[M],idex;//邻接表
int n,m;
int w[M];//权值数组
int d[N];//到某点的距离
bool st[N];//是否确定最短路
void add(int a,int b,int c){
ne[idex]=h[a],e[idex]=b,w[idex]=c,h[a]=idex++;
}
void dijistra(){
memset(d,0x3f,sizeof d);
d[0]=0;
priority_queue<PII,vector<PII>,greater<PII>> heap;
heap.push({0,0});
while(heap.size()){
PII temp=heap.top();
heap.pop();
int t=temp.second,dist=temp.first;
if(st[t]) continue;
st[t]=true;
for(int j=h[t];j!=-1;j=ne[j]){
d[e[j]]=min(d[e[j]],d[t]+w[j]);
heap.push({d[e[j]],e[j]});
}
}
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
int k;
cin>>k;
while(k--){
int s;
scanf("%d",&s);
add(0,s,0);//添加超市与源点的边
}
dijistra();
int Q;
cin>>Q;
while(Q--){
int q;
scanf("%d",&q);
printf("%d\n",d[q]);
}
return 0;
}