今天准备好好复习下最短路和最小生成树
最短路下面都以这道题为例吧:
先是Dijkstra算法
这个算法要求图中不存在负权边,但适用于无向图,是经典的单源最短路径算法,用于计算一个节点到其它节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
放一段两个月前背的代码:
其实这个算法说白了就是用一个优先队列去维护最当前最短的距离,然后更新一次就把它入队。
#include<bits/stdc++.h>
using namespace std;
int n, m, s, t, tot = 0 ;
int head[1020];
struct ty
{
int t, l, next;
}edge[20010];
void addedge(int x,int y, int z)
{
edge[++tot].l = z;
edge[tot].t = y;
edge[tot].next = head[x];
head[x] = tot;
}
struct ty2
{
int x, dis;
bool operator < (const ty2 &a) const
{
return dis > a.dis;
}
};
priority_queue<ty2> q;
int dis[1020];
bool vis[1020];
int dij(int s, int t)
{
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[s] = 0;
ty2 tmp;
tmp.x = s, tmp.dis = 0;
q.push(tmp);
while(!q.empty())
{
ty2 tmp = q.top();
q.pop();
if (vis[tmp.x]) continue;
vis[tmp.x] = 1;
for (int i = head[tmp.x]; i != -1; i = edge[i].next)
{
int y = edge[i].t;
if (vis[y]) continue;
if (dis[y] > dis[tmp.x] + edge[i].l)
{
dis[y] = dis[tmp.x] + edge[i].l;
ty2 tmp2;
tmp2.x = y, tmp2.dis =dis[y];
q.push(tmp2);
}
}
}
if (dis[t] >= 0x3f3f3f3f) return -1;
return dis[t];
}
int main()
{
scanf("%d%d%d%d", &n, &m, &s, &t);
memset(head, -1, sizeof(head));
for (int i = 1; i <= m; i++)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
addedge(x, y, z);
addedge(y, x, z);
}
printf("%d\n", dij(s, t));
return 0;
}
为了能找到能够求解含负权边的带权有向图的单源最短路问题,Bellman和Ford提出了从源点逐次绕过其他顶点,以缩短到达终点的最短路径长度的方法。
枚举所有的点,能松弛就进行松弛操作,直到所有点都不能松弛了。
Bellman-Ford算法的限制条件:要求图中不能包含权值总和为负值回路(负权值回路)
然后就是Bellman - Ford的队列优化 —— SPFA
每一次松弛的时候bellman-ford都要枚举所有的点,而其实有很多点都是不需要枚举的,所以又很多无效枚举,于是效率显得略低。
改进:每次松弛的时候只需要枚举与上次被松弛的点相连的点就可以了。
SPFA算法的期望时间复杂度为O(km)。m为边数,k是每个点的平均入队次数(k一般为10)
SPFA的代码实现:
#include<bits/stdc++.h>
using namespace std;
int n, m, s, t, tot = 0 ;
int head[1020];
struct ty
{
int t, l, next;
}edge[20010];
void addedge(int x,int y, int z)
{
edge[++tot].l = z;
edge[tot].t = y;
edge[tot].next = head[x];
head[x] = tot;
}
struct ty2
{
int x, dis;
bool operator < (const ty2 &a) const
{
return dis > a.dis;
}
};
priority_queue<ty2> q;
int dis[1020];
bool vis[1020];
queue<int> q1;
int spfa(int s, int t)
{
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[s] = 0;
vis[s] = 1;
q1.push(s);
while(!q1.empty())
{
int x = q1.front();
q1.pop();
vis[x] = 0;
for (int i = head[x]; i != -1; i= edge[i].next)
{
int y = edge[i].t;
if (dis[y] > dis[x] + edge[i].l)
{
dis[y] = dis[x] + edge[i].l;
if (!vis[y])
{
q1.push(y);
vis[y] = 1;
}
}
}
}
if (dis[t] >= 0x3f3f3f3f) return -1;
return dis[t];
}
int main()
{
scanf("%d%d%d%d", &n, &m, &s, &t);
memset(head, -1, sizeof(head));
for (int i = 1; i <= m; i++)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
addedge(x, y, z);
addedge(y, x, z);
}
printf("%d\n", spfa(s, t));
return 0;
}
再是Floyd算法
for(int k = 1;k <= n;k ++ )
for(int i = 1;i <= n;i ++ )
for(int j = 1;j <= n;j ++ )
if( (i != j) && (j != k) && (k != i) )
{
if(f[i][k] + f[k][j] <= f[i][j])
f[i][j] = f[i][j] + f[k][j];
}
这个算法一般是对时间要求很低的情况下用的。
———————————————————————————————————————————
最小生成树
先是Prim算法
题目就以这道题目为例了
Prim代码:
#include<bits/stdc++.h>
using namespace std;
int n, m;
struct ty
{
int t, l,next;
}edge[2*500000+100];
int head[100010];
int cnt = 0;
void addedge(int x,int y,int z)
{
edge[++cnt].t = y;
edge[cnt].l = z;
edge[cnt].next = head[x];
head[x] = cnt;
}
bool vis[100010];
struct ty2
{
int x, len;
bool operator < (const ty2 &a) const
{
return len > a.len;
}
};
priority_queue<ty2> q;
void prim()
{
vis[1] = 1;
ty2 tmp;
for (int i = head[1]; i != -1; i = edge[i].next)
{
tmp.x = edge[i].t;
tmp.len = edge[i].l;
q.push(tmp);
}
int ans = 0;
while(!q.empty())
{
ty2 tmp = q.top();
q.pop();
int x = tmp.x;
if (vis[x]) continue;
vis[x] = 1;
ans += tmp.len;
for (int i = head[x]; i != -1; i = edge[i].next)
{
if (vis[edge[i].t]) continue;
tmp.x = edge[i].t;
tmp.len = edge[i].l;
q.push(tmp);
}
}
cout << ans;
}
int main(){
scanf("%d%d", &n, &m);
memset(head, -1, sizeof(head));
for (int i = 1; i <= m; i++)
{
int a, b,v;
scanf("%d%d%d", &a, &b, &v);
addedge(a, b, v);
addedge(b, a, v);
}
prim();
return 0;
}
这个代码和求最短路的Dijstra算法非常类似。
然后再是Kruskal算法
这个算法的思想就是贪心的选取最短的边来组成一棵最小的生成树。
具体做法:先将所有的边排序,然后利用并查集做判断来优先选择较小的边,直到建成一棵生成树。
还是以上面那道题目为例
代码:
#include<bits/stdc++.h>
using namespace std;
int n, m;
struct ty
{
int x, y, z;
}edge[500010];
bool comp(ty a, ty b)
{
return a.z < b.z;
}
int fa[100010];
int find(int x)
{
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
int main(){
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &edge[i].x, &edge[i].y, &edge[i].z);
}
sort(edge+1, edge+1+m, comp);
int ans = 0;
for (int i = 1; i <= m; i++)
{
int fx = find(edge[i].x);
int fy = find(edge[i].y);
if (fx == fy) continue;
ans += edge[i].z;
fa[fx] = fy;
}
cout << ans;
return 0;
}
一般大家都乐于用这种代码,因为代码比较简短。
下面是两种代码的比较: