图论中的模板

有向图的拓扑排序

cpp 复制代码
//有向图无环图中才有拓扑排序,且都是前面的编号的点指向后面编号的点
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5 + 9;
int e[N], ne[N], h[N], idx, n, m, d[N], q[N];

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

bool topsort()
{
    int hh = 0, tt = -1;
    //将所有入度为0的点入队,编号从1~n
    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]--;//因为t指向j,又因为t删除了,所以j入度减一
            if (!d[j]) q[++tt] = j;
        }
    }
    return tt == (n - 1);//如果每个点都入队了表明为拓扑排序
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    memset(h, -1, sizeof h);
    cin >> n >> m;
    for (int i = 0; i < m; ++i)
    {
        int a, b; cin >> a >> b;
        add(a, b);
        d[b]++;//入度加一
    }
    //队列中的顺序刚好就是拓扑排序,排序不唯一
    if (topsort()) for (int i = 0; i < n; ++i) cout << q[i] << " ";
    else cout << -1;
    return 0;
}

dijkstra求最短路

cpp 复制代码
//dijkstra堆优化
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 1e5 + 9;
typedef pair<int, int> PII;
int h[N], e[N], ne[N], idx, n, m, dist[N], w[N];
bool st[N];

void add(int x, int y, int z)
{
    e[idx] = y, w[idx] = z, ne[idx] = h[x], h[x] = idx++;
}

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    //first是距离。second是编号
    //小根堆
    //有更改就加入
    //可能加入的点会有冗余,比如1号点一个是10,一个是15,所以用一个st[]数组来标记
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({ 0, 1 });
    while (heap.size())
    {
        //每次取出距离最小的点
        auto t = heap.top();
        heap.pop();
        int ver = t.second, distance = t.first;
        //不更新也对,不过会有很多冗余情况考虑
        //同一个顶点x,可能在队列中有(dis1, x)、(dis2, x)存在
        //而dis1 < dis2,所以(dis1, x)先出队并更新x的邻接顶点
        //当(dis2, x)出队的时候,由于dis2 > dis1
        //所以x的邻接顶点此时都不会更新,那么(dis2, x)这一步也就冗余了
        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[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    memset(h, -1, sizeof h);
    cin >> n >> m;

    while (m--)
    {
        int x, y, z; cin >> x >> y >> z;
        add(x, y, z);
    }
    cout << dijkstra();
    return 0;
}

有边数限制的最短路

cpp 复制代码
//bellman_ford()算法
//dijkstra()不能解决负权边最短路问题
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510, M = 1e4 + 9;
//back[]存储上一次的dist[]
int dist[N], back[N], n, m, k;

struct Edge
{
    int a, b, w;
} edges[M];

int  bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    //存在边数限制,只能用bellman_ford做
    //关于为什么循环k次可以保证得到的dist就是相应k条件下的值?
    //当明白了备份的作用,就能理解了
    //备份的意义就是保证了每条边a->b在每次刷新时a都是独立的
    //在遍历过程中dist不会受其他点的影响,只和a上一次的值有关
    //而这个备份的操作就是放在k循环下的,所以得到的dist就是相应k条件下的值
    for (int i = 0; i < k; ++i)
    {
        //做备份
        //防止发生串联
        //串联:由于这个算法的特性决定,
        //每次更新得到的必然是在多考虑 1 条边之后能得到的全局的最短路
        //而串联指的是一次更新之后考虑了不止一条边:由于使用了松弛
        //某节点的当前最短路依赖于其所有入度的节点的最短路
        //假如在代码中使用dist[e.b]=min(dist[e.b],dist[e.a] + e.c)
        //我们无法保证dist[e.a]是否也在本次循环中被更新,如果被更新了
        //并且dist[e.b] > dist[e.a] + e.c
        //那么会造成当前节点在事实上"即考虑了一条从某个节点指向a的边
        //也考虑了a->b",共两条边
        //而使用dist[e.b]=min(dist[e.b],last[e.a] + e.c)
        //可以保证a在dist更新后不影响对b的判定,因为后者使用last数组
        //保存着上一次循环中的dist的值
        memcpy(back, dist, sizeof dist);
        for (int j = 0; j < m; ++j)
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            dist[b] = min(dist[b], back[a] + w);
        }
    }
    //因为可能存在到n的路径中有负权边存在, 所以导致dist[n]的值小于0x3f3f3f3f
    //根据题目给的数据范围最多减去500*10000,也就是500w的值,所以大于一半就好
    //注意不要返回-1,因为-1可能就是dist[n]的距离
    if (dist[n] > 0x3f3f3f3f >> 1) return -0x3f3f3f3f;
    return dist[n];
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m >> k;

    for (int i = 0; i < m; ++i)
    {
        int x, y, z; cin >> x >> y >> z;
        edges[i].a = x, edges[i].b = y, edges[i].w = z;
    }
    int t = bellman_ford();
    if (t == -0x3f3f3f3f) cout << "impossible";
    else cout << dist[n];
    return 0;
}

spfa求最短路

cpp 复制代码
//spfa
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 1e5 + 9;
int h[N], e[N], ne[N], st[N], n, m, idx, w[N], dist[N];

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

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])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    if (dist[n] == 0x3f3f3f3f) return -0x3f3f3f3f;
    return dist[n];
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    memset(h, -1, sizeof h);
    cin >> n >> m;
    while (m--)
    {
        int x, y, z; cin >> x >> y >> z;
        add(x, y, z);
    }

    int t = spfa();
    if (t == -0x3f3f3f3f) cout << "impossible";
    else cout << t;
    return 0;
}

spfa判负环

cpp 复制代码
//spfa判断负环
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N = 1e4 + 9;
//cnt[]存储点最短路径的边数
int e[N], ne[N], h[N], w[N], dist[N], cnt[N], idx, n, m;

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

bool spfa()
{
    memset(dist, 0x3f, sizeof dist);
    queue<int> q;
    //注意这里是讲所有点都入队,因为负环可能不在1号点这个路径上
    //这里dist初不初始化都行,但要是求单点到单点路径上是否存在回路,必须初始化dist
    //且dist[s] = 0
    for (int i = 1; i <= n; ++i) q.push(i);

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        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;
                //边数 >= n,点数 >= n + 1,存在回路
                if (cnt[j] >= n) return true;
                q.push(j);
            }
        }
    }
    return false;
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    memset(h, -1, sizeof h);
    cin >> n >> m;
    while (m--)
    {
        int x, y, z; cin >> x >> y >> z;
        add(x, y, z);
    }
    if (spfa()) cout << "Yes";
    else cout << "No";
    return 0;
}

多源汇最短路问题-具有多个源点:Floyd求最短路

cpp 复制代码
//Floyd求多源汇最短路
#include<iostream>
#include<cstring>
using namespace std;
const int N = 210, INF = 1e9;
int n, m, Q, d[N][N];

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()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> 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, z; cin >> a >> b >> z;
        d[a][b] = min(d[a][b], z);//选一个最短的重边
    }

    floyd();

    while (Q--)
    {
        int a, b; cin >> a >> b;
        //因为存在负权边,所以ab之间的距离可能小于无穷
        if (d[a][b] > INF / 2) cout << "impossible" << '\n';
        else cout << d[a][b] << '\n';
    }

    return 0;
}

Kruskal算法求最小生成树

cpp 复制代码
//kruskal求最小生成树
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 2e5 + 9;

struct Edge
{
    int a, b, w;
    bool operator< (const Edge& W) const
    {
        return w < W.w;
    }
} edges[N];

int n, m, p[N], res, cnt;

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m;

    for (int i = 0; i < m; ++i)
    {
        int a, b, w; cin >> a >> b >> w;
        edges[i] = { a, b, w };
    }
    //从小到大排序
    sort(edges, edges + m);
    //并查集数组初始化
    for (int i = 1; i <= n; ++i) p[i] = i;
    //如果这个边与之前选择的所有边不会组成回路,就选择这条边分;反之,舍去。
    //判断是否会产生回路的方法为:使用并查集。
    //每次将未加入的边加入到集合中去
    for (int i = 0; i < m; ++i)
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
        //不在一个集合里面
        a = find(a), b = find(b);
        if (a != b)
        {
            res += w;
            cnt++;
            p[a] = b;//加入集合
        }
    }
    //如果集合中的边数小于n - 1,说明不存在最小生成树
    if (cnt < n - 1) cout << "impossible";
    else cout << res;
    return 0;
}

判定是否是二分图

cpp 复制代码
#include<iostream>
#include<cstring>

using namespace std;

const int N = 100010;

int f[N];
int e[N*2],h[N],ne[N*2],idx;
//数组模拟邻接表模板
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
//并查集find函数模板:找到祖宗节点
int find(int x)
{
    if(f[x]!=x) f[x]=find(f[x]);
    return f[x];
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    //并查集初始化
    for(int i=1;i<=n;i++) f[i]=i;
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
    //遍历每一个点
    for(int u=1;u<=n;u++)
    {
        //遍历当前点的邻接点
        for(int i=h[u];~i;i=ne[i])
        {
            int j=e[i];
            //如果邻接点和当前节点属于同一集合,不满足二分,直接NO
            if(find(j)==find(u))
            {
                puts("No");
                return 0;
            }
            //将所有邻接点合并到同一个集合
            f[find(e[h[u]])]=find(j);
        }
    }
    puts("Yes");
    return 0;
}

二分图的最大匹配

cpp 复制代码
//二分图的最大匹配
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510, M = 1e5 + 9;
int h[N], e[M], ne[M], n1, n2, match[N], m, idx, res;
bool st[N];

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

bool find(int x)
{
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i];
        //没被遍历过
        if (!st[j])
        {
            //标记为匹配过
            st[j] = true;
            //如果这个姑娘还没有被匹配过,或者找到这个男生喜欢别的姑娘
            if (match[j] == 0 || find(match[j]))
            {
                match[j] = x;
                return true;
            }
        }
    }
    return false;
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    memset(h, -1, sizeof h);

    cin >> n1 >> n2 >> m;

    while (m--)
    {
        int a, b; cin >> a >> b;
        //不需要存双向边
        add(a, b);
    }

    for (int i = 1; i <= n1; ++i)
    {
        //每次将姑娘全初始化为没有遍历,因为之前别人喜欢的姑娘我也可能喜欢
        memset(st, false, sizeof st);
        if (find(i)) res++;//如果找到数量就加一
    }
    cout << res;
    return 0;
}
相关推荐
ALISHENGYA2 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(实战训练三)
数据结构·c++·算法·图论
code04号18 小时前
C++练习:图论的两种遍历方式
开发语言·c++·图论
TENET信条1 天前
day53 第十一章:图论part04
开发语言·c#·图论
TENET信条3 天前
代码随想录 day50 第十一章 图论part01
图论
闻缺陷则喜何志丹3 天前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
TENET信条3 天前
代码随想录 day52 第十一章 图论part03
图论
南宫生3 天前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
single5943 天前
【c++笔试强训】(第四十一篇)
java·c++·算法·深度优先·图论·牛客
_nut_3 天前
图论基础算法/DFS+BFS+Trie树
算法·深度优先·图论
南宫生3 天前
力扣-图论-70【算法学习day.70】
java·学习·算法·leetcode·图论