1.单源最短路问题
1.dijkstra算法
1.稠密图(朴素dijkstra)(适用于邻接矩阵)
思路:迭代n次,每次找到距离离起点最近的点,起点置为0,并且用距离最近的点更新其他点。
题目:
给定一个 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。
#include<iostream>
#include<cstring>
using namespace std;
const int N=510;
int g[N][N];//map数组
int dist[N];//存每个点到原点距离
bool st[N];//st数组用于标记是否用过
int n;
int m;
int dijk()
{
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
for(int i=0;i<n;i++)//迭代n次
{
int t=-1;//第一次初始化为-1,利于初始化dist
for(int j=1;j<=n;j++)//找到最小的点并且未被标记
{
if(st[j]==0&&(t==-1||dist[j]<dist[t]))
{
t=j;
}
}
st[t]=1;//标记
for(int j=1;j<=n;j++)//更新每个点
{
dist[j]=min(dist[j],dist[t]+g[t][j]);
}
}
if(dist[n]==0x3f3f3f3f){return -1;}
return dist[n];
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof(g));
while(m--)
{
int a,b,w;
cin>>a>>b>>w;
g[a][b]=min(g[a][b],w);//防止重边与自环
}
cout<<dijk();
}
2.优化版dijkstra(适用于稀疏图)(邻接表存储)
思路:利用小根堆进行优化
给定一个 nn 个点 mm 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。
请你求出 11 号点到 nn 号点的最短距离,如果无法从 11 号点走到 nn 号点,则输出 −1−1。
输入格式
第一行包含整数 nn 和 mm。
接下来 mm 行每行包含三个整数 x,y,zx,y,z,表示存在一条从点 xx 到点 yy 的有向边,边长为 zz。
输出格式
输出一个整数,表示 11 号点到 nn 号点的最短距离。
如果路径不存在,则输出 −1−1。
数据范围
1≤n,m≤1.5×1051≤n,m≤1.5×105,
图中涉及边长均不小于 00,且不超过 1000010000。
数据保证:如果最短路存在,则最短路的长度不超过 109109。
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 100010; // 把N改为150010就能ac
// 稀疏图用邻接表来存
int h[N], e[N], ne[N], idx;
int w[N]; // 用来存权重
int dist[N];
bool st[N]; // 如果为true说明这个点的最短路径已经确定
int n, m;
void add(int x, int y, int c)
{
//利用索引快速表示邻接表
w[idx] = c;
e[idx] = y;
ne[idx] = h[x];
h[x] = idx++;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap; // 定义一个小根堆
// 一个变量要是距离,另一个存入点号
heap.push({ 0, 1 }); // 这个顺序不能倒,pair排序根据first,再根据second,所以是0,1
while(heap.size())
{
PII k = heap.top(); // 取不在集合S中距离最短的点
heap.pop();
int ver = k.second, distance = k.first;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i]; // i只是个下标,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()
{
memset(h, -1, sizeof(h));
scanf("%d%d", &n, &m);
while (m--)
{
int x, y, c;
scanf("%d%d%d", &x, &y, &c);
add(x, y, c);
}
cout << dijkstra() << endl;
return 0;
}
2.bellman-ford算法,适用于负权边并且有边数限制
思路::假设 1 号点到 n 号点是可达的,每一个点同时向指向的方向出发,更新相邻的点的最短距离,通过循环 n-1 次操作,若图中不存在负环,则 1 号点一定会到达 n 号点,若图中存在负环,则在 n-1 次松弛后一定还会更新
步骤:
for n次
for 所有边 a,b,w (松弛操作)
dist[b] = min(dist[b],back[a] + w)
给定一个 nn 个点 mm 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你求出从 11 号点到 nn 号点的最多经过 kk 条边的最短距离,如果无法从 11 号点走到 nn 号点,输出
impossible
。注意:图中可能 存在负权回路 。
输入格式
第一行包含三个整数 n,m,kn,m,k。
接下来 mm 行,每行包含三个整数 x,y,zx,y,z,表示存在一条从点 xx 到点 yy 的有向边,边长为 zz。
点的编号为 1∼n1∼n。
输出格式
输出一个整数,表示从 11 号点到 nn 号点的最多经过 kk 条边的最短距离。
如果不存在满足条件的路径,则输出
impossible
。数据范围
1≤n,k≤5001≤n,k≤500,
1≤m≤100001≤m≤10000,
1≤x,y≤n1≤x,y≤n,
任意边长的绝对值不超过 1000010000。#include<iostream> #include<cstring> using namespace std; const int N=510; const int M=10010; //bellman吊的一点在于存边的形式可以任意,结构体也能存 struct edge { int a; int b; int w; }e[M]; int dist[N]; int back[N]; int n,m,k; int bell() { memset(dist,0x3f,sizeof(dist));//初始化dist,除一号点以外,其他置为正无穷 dist[1]=0;//一号点置为0 for(int i=0;i<k;i++)//k次,边数限制 { memcpy(back,dist,sizeof(dist));//拷贝一下,防止发生串联。 for(int j=0;j<m;j++) { int a=e[j].a; int b=e[j].b; int w=e[j].w; dist[b]=min(dist[b],back[a]+w);//松弛操作 } } return dist[n]; } int main() { cin>>n>>m>>k; for(int i=0;i<m;i++) { int a,b,w; scanf("%d%d%d",&a,&b,&w); e[i]={a,b,w}; } int des=bell(); //要在main内判断,否则会出现-1,但是不是impossible、的情况 if(des>(0x3f3f3f3f/2))puts("impossible"); else { printf("%d",bell()); } return 0; }
3.spfa算法,bellman的优化,用bfs进行优化
思路:Bellman_ford算法会遍历所有的边,但是有很多的边遍历了其实没有什么意义,我们只用遍历那些到源点距离变小的点所连接的边即可,只有当一个点的前驱结点更新了,该节点才会得到更新;因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。
题目同bellman算法
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=1e5+10;
#define fi first
#define se second
typedef pair<int,int> PII;//到源点的距离,下标号
int h[N],e[N],w[N],ne[N],idx=0;
int dist[N];//各点到源点的距离
bool st[N];
int n,m;
void add(int a,int b,int c){
e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
int spfa(){
queue<PII> q;
memset(dist,0x3f,sizeof dist);
dist[1]=0;
q.push({0,1});
st[1]=true;
while(q.size()){
PII p=q.front();
q.pop();
int t=p.se;
st[t]=false;//从队列中取出来之后该节点st被标记为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];
if(!st[j]){//当前已经加入队列的结点,无需再次加入队列,标记一下
st[j]=true;
q.push({dist[j],j});
}
}
}
}
return dist[n];
}
int main(){
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
int des=spfa();
if(des>(0x3f3f3f3f/2))puts("impossible");
else
{
printf("%d",bell());
}
return 0;
}
2.多源汇最短路问题
1.Floyd算法
思路:3次循环,最好记,但是基于动态规划的算法,原理就先算了
模板如下:
void floyd() {
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}