图的算法模板(一)

在复习图部分,发现很多之前难以理解的问题现在可以理解了。
部分代码模板如下。

拓扑排序 topsort【一】

针对有向无环图(DAG),只有有向无环图才能有拓扑排序。

一个有向无环图一定至少存在一个入度为零的点。

  1. 将所有入度为零的点入队
  2. 每次取出队列中的点
  3. 枚举所有出边并删掉这条边,如果d[j]为零表示前面的所有点都已经排好序了
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 100010;
int e[N], ne[N], h[N], idx;
int n, m;
int q[N], d[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

bool topsort()
{
    int tt = 0, hh = 0;
    for (int i = 1; i <= n; i ++ )
    {
        if(!d[i]) q[tt ++] = i;
    }
    
    while (hh <= tt)
    {
        int t = q[hh ++];
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            d[j] --;
            if(!d[j]) q[tt ++] = j;
        }
    }
    return tt == n;
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
        d[b] ++;
    }
    
    if(topsort())
    {
        for (int i = 0; i < n; i ++ )
        {
            cout << q[i] << ' ';
        }
        puts("");
    }else
    {
        puts("-1");
    }
    return 0;
}

最短路问题【2】

最短路问题

稀疏图类问题大多用邻接表来做,稠密图大多用邻接矩阵

  1. 单源最短路
    1. 所有边权值都为正数
      1. 朴素Dijkstra算法 O(n^2) → 适合稠密图
      2. 堆优化版的Dijkstra算法 O(mlogn)
    2. 存在负权边
      1. Bellman-Ford O(mn)
      2. SPFA O(m),最坏 O(mn)【容易被卡】
  2. 多元汇最短路 Floyd算法 O(n^3)

朴素Dijkstra算法

思路

  1. d[1] = 0, d[i] = INF, S为当前已经确定最短距离的点
  2. 找到不在S中的距离最近的点,将其加到S中去,用t来更新其他点的距离,从t到其他点的距离是否大于原来的值 d[x] > d[t] + w
  3. 每次循环都可以确定一个点的最短距离,循环n次后就可以确定从1号点到其他n-1个点的最短距离

dijstra算法不能解决负权边的原因是,dijkstra要求每个点被确定后st[j] = true,此时d[j]已经是最短距离,无法被更新了,而如果存在负权边的话,已经确定的d[j]不一定是最短的。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 510;
int d[N], g[N][N];
bool st[N];
int n, m;
const int INF = 0x3f3f3f3f;

int dijkstra()
{
    memset(d, 0x3f, sizeof d);
    d[1] = 0;
    
    for (int i = 0; i < n; i ++ )
    {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
        {
            if(!st[j] && (t == -1 || d[t] > d[j]))
                t = j;
        }
        
        for (int j = 1; j <= n; j ++ )
        {
            d[j] = min(d[j], d[t] + g[t][j]);
        }
        st[t] = true;
    }
    
    if(d[n] == INF) return -1;
    return d[n];
}

int main()
{
    scanf("%d%d", &n, &m);
    memset(g, 0x3f, sizeof g);
    
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        g[a][b] = min(g[a][b], c);
    }
    
    printf("%d\n", dijkstra());
    return 0;
}

堆优化版的Dijkstra算法

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#define x first
#define y second

using namespace std;
typedef pair<int, int> PII;
const int N = 1000010, INF = 0x3f3f3f3f;

int d[N];
int e[N], h[N], ne[N], w[N], idx;
int n, m;
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

int dijkstra()
{
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});
    d[1] = 0;
    
    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();
        
        int x = t.x, y = t.y;
        if(st[y]) continue;
        st[y] = true;
        
        for (int i = h[y]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(d[j] > x + w[i])
            {
                d[j] = x + w[i];
                heap.push({d[j], j});
            }
        }
    }
    
    if(d[n] == INF) return -1;
    return d[n];
}

int main()
{
    memset(d, 0x3f, sizeof d);
    memset(h, -1, sizeof h);
    
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }
    
    printf("%d\n", dijkstra());
    
    return 0;
}

Bellman-Ford

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 510, M = 100010, INF = 0x3f3f3f3f;
int d[N], backup[N];
struct Node{
    int a, b, w;
}edges[M];
int n, m, k;

int bellman_ford()
{
    memset(d, 0x3f, sizeof d);
    d[1] = 0;
    
    for (int i = 0; i < k; i ++ )
    {
        memcpy(backup, d, sizeof d);
        for (int j = 0; j < m; j ++ )
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            d[b] = min(d[b], backup[a] + w);
        }
    }
    
    if(d[n] > INF / 2) return -1;
    return d[n];
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 0; i < m; i ++ )
    {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        edges[i] = {a, b, w};
    }
    
    int t = bellman_ford();
    if(t == -1)
    {
        puts("impossible");
    }else
    {
        cout << t << endl;
    }
    return 0;
}

SPFA

对bellman-ford算法的一个优化

对更新过的点,更新其到其他点的最短距离(只有前面的点变小了,后面的点才会更新

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 100010;
int e[N], h[N], ne[N], w[N], idx;
int d[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()
{
    memset(d, 0x3f, sizeof d);
    int hh = 0, tt = 0;
    int q[N];
    q[0] = 1;
    d[1] = 0;
    st[1] = true;
    
    while (hh <= tt)
    {
        int t = q[hh ++];
        
        st[t] = false;
        for (int i = h[t]; i != -1; i = ne[i] )
        {
            int j = e[i];
            if(d[j] > d[t] + w[i])
            {
                d[j] = d[t] + w[i];
                if(!st[j])
                {
                    q[++ tt] = j;
                    st[j] = true;
                }
            }
        }
    }
    
    if(d[n] == 0x3f3f3f3f) return -1;
    return d[n];
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }
    
    int t = spfa();
    if(t == -1) puts("impossible");
    else cout << t << endl;
    return 0;
}

Floyd算法

Floyd算法基于动态规划

是从i->j的最短路的长度

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 210, M = 200010, INF = 0x3f3f3f3f;
int d[N][N];
int n, m, Q;

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]);
}

int main()
{
    scanf("%d%d%d", &n, &m, &Q);
    
    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;
    
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        d[a][b] = min(d[a][b], c);
    }
    
    floyd();
    
    while (Q -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        if(d[a][b] > INF / 2) puts("impossible");
        else cout << d[a][b] << endl;
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值