最短路:可以分为单起点终点最短路和多起点多终点最短路。它包含了很多算法Dijkstra,floyd等等。
当图中,边权全都是正数,一般采用Dijkstra。算法步骤如下:
1.选择当前距离起点,最短的点t。点t的最短路就已经确定。
2.标记点t。
3.用t更新其余所有点的最短路。
可以看出,每一次确定一个点,有n个点就需要迭代n次。
朴素dijkstra
朴素Dijkstra
它的时间复杂度只和点有关,适用于稠密图(边较多)。一般而言,如果m与n^2是一个级别可以认为是稠密图。
//时间复杂度O(n^2),n是点数
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 510;
int n,m;
//g是邻接矩阵用来存储稠密图,dis存储每个点到起点的距离
int g[maxn][maxn],dis[maxn];
bool st[maxn]; //st用来标记每个点是否已经确定最短路
int dijkstra(){
for(int i = 1;i<=n;i++) //循环n次处理n个点
{
int t = -1;
for(int j = 1;j<=n;j++)
{
if(!st[j] && (t == -1 || dis[t] > dis[j]))
{
t = j; //找到最近点t
}
}
st[t] = true; //标记访问
for(int j = 1;j<=n;j++)
{
dis[j] = min(dis[t] + g[t][j],dis[j]); //更新所有点距离
}
}
if(dis[n] == 0x3f3f3f3f) return -1; //起点与终点不连通
else return dis[n];
}
int main(){
cin>>n>>m;
memset(dis,0x3f,sizeof(dis));//初始化dis为无穷
memset(g,0x3f,sizeof(g));//初始化邻接矩阵为无穷
dis[1] = 0;
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
g[a][b] = min(g[a][b],c);//处理平行边
}
cout << dijkstra() <<endl;
return 0;
}
堆优化Dijkstra
适用于稀疏图(边较少)。dijsktra算法第一步可以用最小堆优化,降为O(1)。但是,在第3步时,需要修改堆内元素,而STL不支持这种操作。因此,我们直接将更新后的点插入堆中,这会产生冗余但是影响不大。
//时间复杂度O(m*logn) m是边数
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<map>
using namespace std;
const int maxn = 2 * 1e5 + 10;
int n,m,dis[maxn];
int head[maxn],val[maxn],ne[maxn],w[maxn],idx; //数组模拟邻接表效率高
bool st[maxn];
typedef pair<int,int> PII; //每个点既要存储距离,也要存储编号
priority_queue<PII,vector<PII>,greater<PII>> h;//定义小根堆
void add(int a,int b,int c){ //w用来存储边权
val[idx] = b,ne[idx] = head[a],w[idx] = c,head[a] = idx++;
}
int dijkstra(){
memset(dis,0x3f,sizeof(dis));
dis[1] = 0;
h.push({0,1});
while(h.size())
{
auto t = h.top(); //找到最小
h.pop();
int d= t.first, tag = t.second;
if(st[tag]) continue;
st[tag] = true;//标记访问
for(int i = head[tag];i != -1;i = ne[i])
{
int u = val[i];
if(dis[u] > d + w[i])
{
dis[u] = d + w[i];//更新最短距离
h.push({dis[u],u}); //加入堆
}
}
}
if(dis[n] == 0x3f3f3f3f) return -1;
else return dis[n];
}
int main(){
cin>>n>>m;
memset(head,-1,sizeof(head));
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
cout << dijkstra()<< endl;
return 0;
}
存在负权边,可以采用bellman-ford,spfa,Floyd。
bellman-ford
时间复杂度O(m*n),Bellman_ford
/*
memeset(dis,0x3f,sizeof dis);
dis[1] = 0;
for 遍历1-k次 //第k次遍历表示,不超过k条边的最短路
memcpy(backup,dis,sizeof dis); //backup保留上次更新结果,因为dis是动态更新的
for 遍历所有边 e = {a,b,c}; //如果a到b右边,尝试利用(1->a距离与a->b距离)更新(1->b)
dist[e.b] = min(dist[e.b],backup[e.a] + e.c);
*/
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 510,maxm=10010;
int n,m,k;
int dis[maxm],backup[maxm];
struct Edge{
int a,b,w;
}edges[maxm];
int bellman_ford(){
memset(dis,0x3f,sizeof(dis));
dis[1] = 0;
for(int i=1;i<=k;i++){
memcpy(backup,dis,sizeof(dis));
for(int j=0;j<m;j++){
int a = edges[j].a,b = edges[j].b,w = edges[j].w;
dis[b] = min(dis[b],backup[a]+w);
}
}
}
int main(){
cin>>n>>m>>k;
for(int i=0;i<m;i++){
int a,b,w;
scanf("%d%d%d",&a,&b,&w);
edges[i].a = a,edges[i].b = b,edges[i].w = w;
}
bellman_ford();
if(dis[n]>= 0x3f3f3f3f/2) puts("impossible");
else printf("%d\n",dis[n]);
return 0;
}
spfa
对bellman_ford算法进行优化,在更新最短路时,只有在a更新过后,b才有可能变小。因此,利用队列对更新过程进行优化。
//时间复杂度一般是O(m) 最坏是O(n*m)
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
queue<int> q; //q用来存储,被更新的点
const int maxn = 1e5 + 10;
int n,m,dis[maxn];
int head[maxn],val[maxn],ne[maxn],w[maxn],idx;
bool st[maxn];
void add(int a,int b,int c){
val[idx] = b,w[idx] = c,ne[idx] = head[a],head[a] = idx++;
}
int spfa(){
memset(dis,0x3f,sizeof(dis));
dis[1] = 0;
q.push(1);
st[1] = true;
while(q.size()){
int u = q.front();
q.pop();
st[u] = false;
for(int i = head[u];i != -1;i = ne[i]){ //更新队首的所有邻边,因为队首上一轮被更新。
int j = val[i];
if(dis[j]>dis[u] + w[i]){
dis[j] = dis[u] + w[i];
if(st[j] == false){
st[j] = true;
q.push(j);
}
}
}
}
if(dis[n] > 0x3f3f3f3f/2) return -1;
else return dis[n];
}
int main(){
cin>>n>>m;
memset(head,-1,sizeof(head));
for(int i=0;i<m;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
spfa();
if(dis[n] > 0x3f3f3f3f/2) puts("impossible");
else cout<<dis[n];
return 0;
}
SPFA判断负环
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int maxn = 2010,maxm = 1e5 + 10;
int n,m,dis[maxn],cnt[maxn];//增加数组cnt,表示从点1到点n所用的边数
int head[maxn],val[maxm],ne[maxm],w[maxm],idx;
bool st[maxn];
queue<int> q;
void add(int a,int b,int c){
val[idx] = b,w[idx]=c,ne[idx]=head[a],head[a]=idx++;
}
int spfa(){
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
for(int i=1;i<=n;i++){
q.push(i);
st[i] = true;
}
while(q.size()){
int u = q.front();
q.pop();
st[u] = false;
for(int i=head[u];i != -1;i=ne[i]){
int j =val[i];
if(dis[j]>dis[u]+w[i]){
dis[j]=dis[u] + w[i];
cnt[j]=cnt[u] + 1;
if(cnt[j]>=n) return true; //边数超过n,一定存在负环。
if(st[j]==false){
q.push(j);
st[j]=true;
}
}
}
}
return false;
}
int main(){
cin>>n>>m;
memset(head,-1,sizeof(head));
for(int i=0;i<m;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
bool t = spfa();
if(t) puts("Yes");
else puts("No");
return 0;
}
以上算法都是用于单起点,单终点最短路
Floyd
适用于多起点,多终点最短路。
//时间复杂度一般是O(n^3)
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=210,inf=0x3f3f3f3f;
int g[maxn][maxn];
int n,m,q;
void floyd(){ //floyd算法
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
g[i][j] = min(g[i][j],g[i][k]+g[k][j]); //利用点k更新所有边
}
}
}
}
int main(){
cin>>n>>m>>q;
for(int i= 1;i<=n;i++)
for(int j = 1;j<=n;j++){
if(i==j) g[i][j] = 0;
else g[i][j] = inf;
}
for(int i=0;i<m;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
g[a][b] = min(g[a][b],c);
}
floyd();
while(q--){
int a,b;
scanf("%d%d",&a,&b);
if(g[a][b] > inf/2 ) puts("impossible");
else printf("%d\n",g[a][b]);
}
return 0;
}