注意时间复杂度
目录
dijkstra算法(单源最短路,无负权边,有两种版本)
Bellman_Ford算法(单源最短路,有负权边,可以处理不超过k条边问题)
spfa算法(单源最短路,有负权变,比Bellman好)
Floyd算法(多源最短路问题)
dijkstra算法
dijkstra算法分两种,一种是朴素版,另一种是堆优化版。朴素版适用于稠密图,时间复杂度与边数无关,堆优化版适用于稀疏图。
朴素版dijkstra算法思路:
朴素版的图用领接矩阵储存
- 初始化:将dist数组所有元素置为+无穷,起点置成0.
- 进行n-1次循环(起点已经确定最短路了,所以是n-1次),每次循环找到dist最小的且未标记的点,然后标记这个点
- 更新所有点的dist 方法:dist[i]=min(dist[i],dist[tar]+w[tar][i]).
int dijkstra(int start,int end){ memset(dist,inf,sizeof dist); dist[start]=0; for(int i=0;i<n-1;i++){ int t=-1; for(int j=1;j<=n;j++) if(!vis[j]&&(t==-1||dist[t]>dist[j])t=j; for(int j=1;j<=n;j++) dist[j]=min(dist[j],dist[t]+w[t][j])); vis[t]=1; } if(dist[end]==inf)return -1; return dist[end]; }
堆优化版dikstar算法
利用小根堆来寻找dist最小的点,能在o(1)的时间复杂度得到dist最小的点
堆优化版的图用邻接表储存
思路:
- 初始化:所有dist置成inf,起始点dist为0.
- 将起始点入堆
- 做循环,直到堆为空,每次循环取出最小dist点,并标记这个点(如果取出的点被标记,则直接continue),将与这个点联通的点的dist更新方法:dist[i]=min(dist[i],dist[tat]+w[tar][i]),将这个点放入堆中。
typedef pair<int,int> PII;
int dijkstra(int start ,int end)
{
memset(dist, 0x3f, sizeof dist);
dist[start] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, start}); // first存储距离,second存储节点编号
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}
if (dist[end] == 0x3f3f3f3f) return -1;
return dist[end];
}
Bellman_Ford算法
用于处理含负权边的单源最短路问题,但不如spfa算法,可以处理有边数限制的最短路问题
可以以任何方式储存图,但要能做到可以遍历到所有的点和边,建议用结构
注意:因为数值更新时可能发生串联现象,所以更新时要用未更新前的数据进行更新,要用另一个数组储存未更新前的值
思路:
- 初始化:所有点的dist置未inf,起点dist为0
- 进行二重循环,外层n(点的数目)次,内层m(边的数目)次,每次更新dist值。方法:dist[b]=min(dist[b],dist[a]+w)
struct Edge //边的结构
{
int a, b, w;
}
int bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n; i ++ )//如果不超过k条边,这里设置成k
{
memcpy(backup,dist,sizeof dist);
for (int j = 0; j < m; j ++ )
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
if (dist[b] > bakcup[a] + w)//bakcup是未更新前的数据
dist[b] = backup[a] + w;
}
}
if (dist[n] > 0x3f3f3f3f / 2) return -1;
return dist[n];
}
特别注意:如果要求不超过k条边,那么外层循环置成k次。
spfa算法
spfa实质是用优先队列优化的Bellman_Ford算法
用邻接表储存图,优化思路就是只有发生变化的值才会影响它后面的值,所有只需要对发生变化的值进行更新操作就可以优化算法
思路:
- 初始化:将所有点的dist置成inf,出发点为0
- 将初始点加入优先队列,并标记这个点
- 当优先队列不空时,取出队头元素,取消这个点的标记,然后将与这个点相通的点的dist更新。方法:dist[i]=min(dist[i],dist[t]+w[t][i]).
- 被更新的点未被标记时,将这个点加入优先队列中,并标记这个点
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = true;
while (q.size())
{
auto 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];
if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入
{
q.push(j);
st[j] = true;
}
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
spfa算法还可以判断负环,只要取消初始化,加上一个cnt数组,当某一个点的路径上通过的边数大于等于n时,就说明有负环
bool spfa()
{
// 不需要初始化dist数组
// 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。
queue<int> q;
for (int i = 1; i <= n; i ++ )
{
q.push(i);
st[i] = true;
}
while (q.size())
{
auto 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];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true; // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
Floyd算法
Floyd算法用于多源最短路问题,三重循环就可以了,基于动态规划
用领接矩阵储存图
思路:
- 将领接矩阵初始化(对于自环设置成0,其他的为正无穷)
- 读入边的情况
- 对领接矩阵进行操作。
int d[N][N];
void Floyd(){
for(int k=1;i<=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]);
}
}