最短路的定义可以说是很好理解了,就是一个点到另一个点的最短距离。
最短路又分为两种,单源最短路和多源汇最短路。
其实定义上没有什么好理解的,这里就引入两个概念,然后。。。背模板就好了吧(我觉的,应该是这样QwQ)
这里先简单的理解一下源和汇,其实源也就是一个图的起点,而汇就是我们要找的终点。
这里简单理解,源是起点,汇是终点,就OK了。
单源最短路,也就是从一个点出发,到其他各各点的最短路径,主要分为两种情况,无负权值边的情况和有负权值边的情况,每一种情况也有不同的算法。
先说无权值边的情况,主要是一种算法的两种版本,用于应对不同的情况。
这种算法的复杂度为O(n^2),(我们先规定,n为点的个数,m为边的个数),适用于点少边多(稠密图)的情况。
void dijkstra()
{
memset(dist, INF, sizeof(dist)); //dist代表 1 到第 n 个点的最短路
memset(vis, 0, sizeof(vis)); //vis标记已经确定的点
for(int i = 1; i < n; i ++)
{
int t = -1; // 用于寻找当前dist最短的点
for(int j = 1; j <= n; j ++)
{
if(!vis[j] && (t == -1 || dist[j] < dist[t]))
t = j;
}
vis[t] = 1;
for(int j = 1; j <= n; j ++)
dist[j] = min(dist[j], dist[t] + g[t][j]) //g是邻接矩阵,
//代表点 t 到点 j 的距离
}
}
这里还要提一下下,对于所有的稠密图,都可用邻接矩阵进行储存。
这个算法的时间复杂度是O(m * logn),很显然,在m < n^2 的情况下,堆优化的Dijstra算法可以更快的完成计算,所以对于边较少(稀疏图)的情况,我们应该用堆优化的算法来完成最短路。
const int N = 1e5 + 8;
const int INF = 0x3f;
int h[N], e[N], ne[N]; //稀疏图,用邻接表的方式存储
int dist[N], w[N]; //w用于记录权值
bool vis[N]; //用于标记该点距离是否已经确定
typedef pair<int, int> PLL; //因为最短路有两个数据,优先队列中用pair类型
void dijkstra()
{
priority_queue<PLL, vector<PLL>, greater<PLL>> head; //定义一个小根堆
memset(dist, INF, sizeof(dist));
dsit[1] = 0;
head.push({0, 1});
while(!head.empyt())
{
auto t = head.top();
head.pop();
int num = t.second, distance = t.first;
if(vis[num]) continue; //如果该点最短距离已经确定,直接搜下一个点
vis[num] = true;
for(int i = h[num]; i != -1; i = ne[i]) //查找最短距离
{
int j = e[i];
if(dist[j] > w[j] + distance)
{
dist[j] = w[j] + distance;
head.push({dist[j], j});
}
}
}
}
同样的,所有的稀疏图,都可以用邻接表的形式储存。
有负权值边的情况,有两种算法,在下面也会给出基本思路和模板QwQ
Bellman-Ford算法的时间复杂度为O(nm),通常用于有限制经过不超过多少条边的题目(因为另一种SPFA算法是这个算法的优化,没有这种情况了话用SPFA就好了QwQ)
const int N = 505, M = 100010;
const int INF = 0x3f;
int dist[N], backup[N] //backup用于拷贝数据
int n, m, k; //n为边的数量,m为边的数量,k为限制边数
struct Edge
{
int a, b, w;
}edges[M];
int bellman_ford()
{
memset(dist, INF, sizeof dist);
dist[1] = 0;
for(int i = 0; i < k; i ++)
{
memcpy(backup, dist, sizeof dist); //拷贝数据
for(int j = 0; j < m; j ++)
{
int a, b, w;
a = edges[j].a, b = edges[j].b, w = edges[j].w;
dist[b] = min(dist[b], backup[a] + w);
}
}
if(dist[n] > 0x3f3f3f3f / 2) return -1; //判断是否可以到达n
else return dist[n];
}
SPFA算法是Bellman_Ford算法的优化,时间复杂度一般为O(m),即便最坏的情况,复杂度也只为O(nm),与Bellman_Ford算法一样,所以在不限制边数的情况下,SPFA算法基本可以解决所有的最短路问题。
const int N = 100010;
const int INF = 0x3f;
int h[N], ne[N], e[N], w[N]; //也是用邻接表储存数据
int dist[N], vis[N];
int spfa()
{
queue<int> q;
memset(dist, INF, sizeof dist);
dist[1] = 0, vis[1] = 1;
q.push(1);
while(!q.empty())
{
auto t = q.front();
q.pop();
vis[t] = 0; //点被拿出,标记消除
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(!vis[j])
{
q.push(j);
vis[j] = 1;
}
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
这个算法和堆优化的Dijkstra算法长的蛮像的,两个可以一起记呀QwQ
SPFA算法还可以用来判断是否存在负环。。。这里也写一下吧QwQ
const int N = 100010;
int h[N], e[N], ne[N], w[N];
int dist[N], cnt[N]; //cnt用来存边的数量
bool vis[N];
bool spfa()
{
queue<int> q;
for(int i = 1; i <= n; i ++) //因为不知道负环位置,所以需要存入所有点
{
q.push(i);
vis[i] = true;
}
while(!q.empty())
{
auto t = q.front();
q.pop();
vis[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];
cnt[j] = cnt[j] + 1; //如果存在,边数加一
if(cnt[j] >= n) return true; //边数位n时,存在n + 1个点,则一定存在负环
if(!vis[j])
{
q.push(j);
vis[j] = true;
}
}
}
}
return false;
}
多源汇最短路只有一个算法,还蛮短的,嘿嘿,直接记吧QwQ
Floyd算法可以说是最短最好记的最短路算法了QwQ
唯一的缺点就是时间复杂度是O(n^3),可以说是非常的高了。。。
所以,有事没事别乱用QwQ
const int N = 205, INF = 0x3f3f3f;
int d[N][N];
int n, m;
void flody()
{
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]);
//用到了dp的思想
}
然后就是介个算法的初始化又一点点不同,我在下面写一写QwQ
for(int i = 1; i <+ n; i ++)
for(int j = 1; j <= n; j ++)
{
if(i == j) d[i][j] = 0;
else d[i][j] = INF;
}
好了,这些就是最短路了,背完模板就会了,真的一点都不难(才怪!!!QwQ)
终于写完了,嗷呜呜呜呜~~(菜鸡哀嚎)