在面对不同情景下采用的不同算法
朴素的迪杰斯特拉算法
适用情况:稠密图(点的个数e 边的数量v 有e≈v*v)
储存图的数据结构:邻接矩阵(一个二维数组 g[【i】【j】表示点i到点j的距离)
时间复杂度 n*n;
代码说明
dist[N] 表示起始点到1到n点的距离 若不存在则为最大值(0x3f3f3f3f)
核心是找到数组dist中最小值
每次找到最小值(如果最小值为dist【t】)时 开始遍历1到n点中
dist【j】(点1到点j的距离)与dist【t】(点1到t的距离)+g【t】【j】(表示点t到点j的距离)之间的大小然后取最小值
例题1:最短路1
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 510;
int g[N][N],dist[N];//dist表示点1到点N的距离
int n,m;
bool st[N];//来表示1到n的最短路径是否已经被确定
int cl()
{
memset(dist,0x3f,sizeof dist);//先把dist初始化
dist[1]=0;//起始点的距离到起始点的距离为0
for(int i=0;i<n;i++)//开始进行n次遍历
{
int t=-1;//t要先初始化为-1
for(int j=1;j<=n;j++)//结束只后可以在未确认的dist中找到最小值
{
if(st[j]==false&&(t==-1||dist[j]<dist[t]))
t=j;
}
st[t]=true;//此时dist[t]的值已经确定 可以标记为true
for(int j=1;j<=n;j++)
dist[j]=min(dist[j],dist[t]+g[t][j]);//开始寻找最短路径
}
if(dist[n]==0x3f3f3f3f) return -1;
else
return dist[n];
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof g);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b]=min(g[a][b],c);//可能会有重边的情况 取最小的那一条
}
cout<<cl()<<endl;
return 0;
}
堆优化的迪杰斯特拉算法:
适用情况:稀疏图(v和e接近)
储存图的数据结构:邻接表(可以看成是有许多表头的单链表)
时间复杂度:mlong(n)
邻接表的构造:
int h[N],e[N],ne[N],w[i],idx;
//h[i]是以i为顶点所能到达的所有边 是一个头结点 邻接表里面一共有n个头节点,他指向下一个节点的编号
//e[i]储存的是具体的地点值
//ne[i]指向下一个顶点的具体值
//w[i]表示指向第i个点的这条路径的长度
//idx 与单链表一样 表示用到了那个结点
memset(h,-1, sizeof h);//在单链表里面要把头结点初始化 所以在邻接表里面要把所以的头节点初始化
//memset在头文件cstring里面
void add(int a, int b, int c)//有一条从顶点a指向顶点b长度为c的路径
{
w[idx]=c;//将长度储存在w里面
e[idx]=b;//将被指的顶点存在e里面
ne[idx]=h[a];//其下一个点为头结点之前指向的点,这里面的插入都是向头节点后面插入
h[a]=idx++;//头节点指向idx 然后idx++为下一个结点做准备
}
优先队列:其实就是一个小根堆 每次返回优先队列的值时(如果未声明)返回队列里面的最大值
#include<iostream>
#include<queue>
using namespace std;
int main()
{
priority_queue<int,vector<int> > a;//大根堆,队头元素最大
priority_queue<int, vector<int>, greater<int> > b;//小根堆 ,队头元素最小
for(int i=1; i<=10; i++)
{
a.push(i);
b.push(i);
}
//返回队头元素
cout<<a.top()<<endl;
cout<<b.top()<<endl;
}
与朴素迪杰斯特拉算法不同的是 不用每次去挨个寻找为确定的dist之中的最小值是多少 只需要返回优先队列的队头元素即可
题目: 同朴素版迪杰斯特拉
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N = 150010;
typedef pair<int,int> PII;
int h[N],ne[N],e[N],idx,w[N],dist[N];//w数组用来储存路径的权
int n,m;
bool st[N];
void add(int a,int b,int c)
{
w[idx]=c;
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int djst()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
//接下来要用小根堆来寻找未被确认的dist【n】中的最小值
priority_queue<PII, vector<PII>, greater<PII>> heap;//第一个是声明
//一个PII类型的队列,第二个是声明是那vector存储的队列,第三个是声明
//小根堆
heap.push({0,1});//{n,m}表示1到m号点距离为n;
while(heap.size())//当优先队列非空
{
PII t=heap.top();//然后取其队头
heap.pop();//将队头拿出
int ver=t.second,distance=t.first;//ver为队头中点的值,distance为
//距离
if(st[ver]==true)//当ver这个点已经被确定过,即dist【ver】已经最小
continue;
st[ver]=true;
for(int i=h[ver];i!=-1;i=ne[i])//开始遍历 来优化所有经过ver的路径长度
{
int j=e[i];//表示具体的结点值
if(dist[j]>distance+w[i])
{
dist[j]=distance+w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
else
return dist[n];
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
cout<<djst()<<endl;
return 0;
}
弗洛伊德(Floyd)算法
特点:可以求出 任意两点之间的距离;
储存图的方法:邻接矩阵 二维数组
注意: 在储存图的时候当i=j时 g[i][j] =0;
时间复杂度: n^3;
思路: 如果要求点a到点b的最短距离时 在所有除了a,b点之外的点是否存在一个点p使得 g[a][b]>g[a][p]+g[p][b] 如果存在则令 g[a][b]=g[a][p]+g[p][b] ,此时由a到b的点就会经过p来使其距离减少,然后在所有除了a,b,p点之外的点中去寻找拥有与p点由相同性质的点,当把所有点遍历完了之后就可以找到由a到b的最短路径。
核心代码:
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]);
}
代码解释:
f[i, j, k]表示从i走到j的路径上除i和j点外只经过1到k的点的所有路径的最短距离。那么f[i, j, k] = min(f[i, j, k - 1), f[i, k, k - 1] + f[k, j, k - 1]。
因此在计算第k层的f[i, j]的时候必须先将第k - 1层的所有状态计算出来,所以需要把k放在最外层。
例题2;Floyd求最短路
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 210,M=1e9;
int h[N][N];
int n,m,t;
int main()
{
cin>>n>>m>>t;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i==j) h[i][j]=0;//当i==j时要特殊处理 与迪杰斯特拉不同
else h[i][j]=M;//给一个比较大的数
}
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
h[a][b]=min(h[a][b],c);
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
h[i][j]=min(h[i][k]+h[k][j],h[i][j]);
}
while(t--)
{
int x,y;
cin>>x>>y;
if(h[x][y]>M/2)//在这里不能能用h[x][y]==M来判断
cout<<"impossible"<<endl;
else
cout<<h[x][y]<<endl;
}
return 0;
}
ford算法
特点:解决负权边(即路径长度为负数)
储存图的方法;一维数组(按着边的数量储存在数组中,一般用三个一维数组)
时间复杂度:o(n*m)
说明:要用到三个一维数组
v,u,w;
如果第一条边为 4 5 7 即第一条边为由点4到点5 路径为7 的边
那么v[1]=4,u[1]=5,w[1]=7;
即数组v中储存的都是出发点
数组u中储存的都是到达点
数组w中储存的是路径长度
思想: 最外层要遍历n-1次 因为最短路中最多会由n-1条边构成
内层遍历m次即把每一跳边都遍历
寻找dist[u[i]]<dist[v[i]]+w[i]的点 使dist[u[i]]=dist[v[i]]+w[i]
步骤
0.初始化dist数组为正无穷,dist[1]=0;
1.(外重循环)循环i从1到n-1,遍历n-1次
2.(内重循环)循环j从1到m,遍历m条边,把所有边都进行松弛操作;
每次取出两点以及他们连接的边的权重(a,b,w表示a—>b的一条边);
用从起点到a的当前最短距离+权重来更新从起点到b的当前最短距离;
dist[b]=min(dist[b],dist[a]+w);
3.返回答案;
例题3有边数限制的最短路
#include<iostream>
#include<algorithm>
#include<cstring>
const int N = 10010;
int v[N],u[N],w[N],dist[N],back[N];
int n,m,k;
using namespace std;
int main()
{
cin>>n>>m>>k;
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=1;i<=m;i++)
{
cin>>v[i]>>u[i]>>w[i];
}
for(int i=1;i<=k;i++)
{
memcpy(back,dist,sizeof dist);//要使用一个back数组 防止串联
for(int j=1;j<=m;j++)
{
dist[u[j]]=min(dist[u[j]],back[v[j]]+w[j]);//这里更新时要用到上次的点即去使用back中的值
}
}
if(dist[n]>0x3f3f3f3f/2) cout<<"impossible";
else
cout<<dist[n];
}
spfa算法
说明:spfa算法是对ford算法的优化 ford算法每次都要把m个边给遍历一遍 有的遍历是无效的 大大的提高了运算时间,所有我们只用遍历那些到源点距离变小的点所连接的边即可,只有当一个点的前驱结点更新了,该节点才会得到更新;因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。
st数组是用来判断某一个点是否在队列中 记住某一个点可以重复的在队列中出现 即进去后出去后还可以再进去
spfa还可以判断是否还有负权回路
图的储存方法: 邻接表
spfa算法步骤
queue <– 1(把一号点放进去)
while queue 不为空
(1) t <– 队头
queue.pop()
(2)用 t 更新所有出边 t –> b,权值为w
queue <– b (若该点被更新过,则拿该点更新其他点)
例题4 spfa求最短路
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N = 2e5+10;
int h[N],ne[N],e[N],w[N],idx;
int dist[N];
int n,m;
bool st[N];
void add(int a,int b,int c)
{
w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int spfa()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
queue<int> q;
q.push(1);
st[1]=true;
while(q.size())
{
int t=q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
q.push(j);
st[j]=true;
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
else
return dist[n];
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
int ans=spfa();
if(ans==-1) cout<<"impossible";
else
cout<<ans<<endl;
return 0;
}