暑期留校——7月2日算法学习(最短路,最小生成树复习)

今天准备好好复习下最短路和最小生成树

最短路下面都以这道题为例吧:

 

先是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;
}

 

一般大家都乐于用这种代码,因为代码比较简短。

下面是两种代码的比较:

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值