图论常用算法记录

本文章为参照这位博主图论总结进行学习的笔记 引用了部分该博主的内容

欧拉通路与欧拉回路问题

基本概念

欧拉通路:通过图中所有边一次且仅一次行遍所有顶点的通路
欧拉回路:通过图中所有边一次且仅一次行遍所有顶点的回路
欧拉图:具有欧拉回路的图
半欧拉图:具有欧拉通路而无欧拉回路的图

欧拉通路/回路的判定

无向图通路

除了起点和终点外都是偶度点

无向图回路

全都是偶度点

有向图通路

除了两个顶点(一个入度=出度+1,一个出度=入度+1)外其他点的入度等于出度

有向图回路

所有点的入度等于出度

算法实现

并查集判断是否有回路

首先记录连通分量,若连通分量大于1,则不存在回路。然后再统计是否存在奇度点,如果存在就不存在回路。

#include <iostream>
#include <cstring>
#define maxn 100
using namespace std;

int f[maxn], degree[maxn];
int Find(int x)
{
   
    if (f[x] == x) return x;
    return f[x] = Find(f[x]);
}

void merge(int x, int y)
{
   
    int f1 = Find(x), f2 = Find(y);
    if (f1 != f2) f[f1] = f2;
}

int main()
{
   
    for (int i = 0; i < maxn; i++)
        f[i] = i;
    memset(degree, 0, sizeof(degree));
    int n, x, y, group = 0, cnt = 0;
    cin >> n; //输入点的个数
    for (int i = 0; i < n; i++)
    {
   
        scanf("%d%d", &x, &y);
        degree[x]++;
        degree[y]++;
        merge(x, y);
    }
    for (int i = 0; i < n; i++)
    {
   
        if (f[i] == i) group++;   //集合数统计
        if (degree[i] & 1) cnt++; //奇度点统计
    }
    //如果集合数大于1,或者存在奇度点,则不存在欧拉回路
    if (group > 1 || cnt) cout << "no Eurler way" << endl;
    return 0;
}

Fleury 算法求无向图回路

在已经确定存在欧拉回路的情况下任选一个点开始遍历,经过一个点就将其入栈,并删去这条边,当某个点没有边的时候出栈,记录路径,然后从栈的下一个点开始遍历,不断重复以上过程直至所有的边都被删去。

//假设输入的数据保证是有回路的
#include <cstring>
#include <iostream>
#include <stack>
#define maxn 1000
using namespace std;

int g[maxn][maxn], path[maxn], n, m, cnt = 0;
stack<int> S;

void dfs(int x)
{
   
    S.push(x);
    for (int i = 1; i <= n; i++)
    {
   
        if (g[x][i])
        {
   
            g[x][i] = 0;
            g[i][x] = 0;
            dfs(i);
            break;
        }
    }
}
void fleury(int x)
{
   
    S.push(x);
    while (S.size())
    {
   
        int flag = 0, x = S.top();
        S.pop();
        for (int i = 1; i <= n; i++) //寻找与该点连接的边
        {
   
            if (g[x][i])
            {
   
                flag = 1;
                break;
            }
        }
        if (flag)
            dfs(x);
        else
            path[cnt++] = x;
    }
}
int main()
{
   
    freopen("in.txt", "r", stdin);
    memset(g, 0, sizeof(g));
    int x, y;
    cin >> n >> m;//输入点数和边数
    for (int i = 1; i <= m; i++)
    {
   
        scanf("%d%d", &x, &y);
        g[x][y] = g[y][x] = 1;
    }
    fleury(2);
    cout << "path: ";
    for (int i = 0; i < cnt; i++)
    {
   
        cout << path[i] << " ";
    }
    return 0;
}

通路的路径求法

从奇度点开始用Fleury算法遍历即可。

例题

一笔画问题
一笔画问题加强版(一条边可以通过多次)
例题

拓扑排序

概念和实现算法

将入度为0的点输出,每输出一个入度为0的点后就将该点所连接的所有点的入度减一,然后再输出剩下点中入度为0的点,不断重复。

AOV网

AOV网是一个有向无环图。点与点之间存在先后关系,必须完成所有前驱的点后才能进入下一个点,比如排课(修完了基础课程后才能学习这门课)。
例题

AOE网

在AOV网的基础上增加了边的权值,比如工程问题(权值代表工期)。
例题

图的连通性

概念

连通图:在无向图中从任意结点出发都能达到其他结点
连通分量:具有连通性的无向子图
强连通图:在有向图中从任意结点出发都能达到其他结点
连通分量:具有强连通性的有向子图

连通性判断

一次遍历能经过所有的点即可,dfs+栈

强连通性判断*

kosaraju算法

kosaraju算法是先正向dfs一次,该点的所有边都dfs完后入栈,然后根据栈的顺序反向dfs一遍(反向的意思是原来A→B的方向变为B→A)。

//kosaraju实现代码
#include <bits/stdc++.h>
#define maxn 105
using namespace std;

vector<int> g[maxn], rg[maxn]; //rg中保存的是反图
bool vis[maxn] = {
   0};
stack<int> S;
int f[maxn]; //所属的强连通分量

inline void addEdge(int u, int v)
{
   
    g[u].push_back(v);
    rg[v].push_back(u);
}
void dfs1(int x)//正向dfs
{
   
    vis[x] = 1;
    for (int i = 0; i < g[x].size(); i++)
        if (!vis[g[x][i]])
            dfs1(g[x][i]);
    S.push(x);
}
void dfs2(int x, int k)//反向dfs
{
   
    f[x] = k; //记录该顶点属于哪个强连通分量
    vis[x] = 0;
    for (int i = 0; i < rg[x].size(); i++)
        if (vis[rg[x][i]])
            dfs2(rg[x][i], k);
}

int main()
{
   
    //freopen("in.txt", "r", stdin);
    int n, m, u, v, cnt = 0;
    cin >> n >> m; //输入点数和边数
    while (m--)
    {
   
        cin >> u >> v;
        addEdge(u, v);
    }
    for (int i = 1; i <= n; i++)
        if (!vis[i])
            dfs1(i);
    while (!S.empty())
    {
   
        //正向dfs完后所有的vis都变为1,因此dfs反图时根据vis是否为1进行判断
        if (vis[S.top()])
            dfs2(S.top(), cnt++);
        S.pop();
    }
    cout << cnt << endl;
    return 0;
}

tarjan算法(通用模板见缩点中的tarjan函数)

tarjan算法也是用dfs,用dfn数组来记录一个点被访问到的顺序,用low数组来记录这个点属于哪个强连通分量里(也可以理解为这个强连通分量里最早被访问的点的dfn值)
在dfs的同时还要用一个栈来压入被访问的点,以及一个vis数组来记录某个点是否位于栈中(毕竟不可能将栈中的点一个个弹出来判断某个点是否在里面)一旦dfs中遇到了栈中的点,就更新当前点的low值。如果某个点的dfn和low相同,说明这个强连通分量中所有的点都被访问过了,那么就进行出栈。
B站视频讲解
例题:【NOIP2015】信息传递

//tarjun算法
//【NOIP2015】信息传递
#include <bits/stdc++.h>
#define maxn 200005
using namespace std;

int to[maxn]; //信息传递的下一个人
stack<int> stk;
int low[maxn]; //可以从哪个点到达(也可理解为属于哪个强连通分量里)
int dfn[maxn]; //被访问的顺序
int vis[maxn]; //记录是否在栈中
int tot = 0, ans = 0x3f3f3f3f;
void tarjan(int x)
{
   
    low[x] = dfn[x] = ++tot;
    vis[x] = 1;
    stk.push(x);
    int v = to[x];
    //更新low[x]有两种方式,根据v的情况进行选择(画一个图会很好理解)
    if (!dfn[v]) //v还没有入栈
    {
   
        tarjan(v);
        low[x] = min(low[x], low[v]); //如果v能够到达在栈中的点,那么low[v]会更新,low[x]也将随之更新
    }
    else if (vis[v]) //v已经入过栈
        low[x] = min(low[x], dfn[v]);
    //回溯到强连通分量的起点处,开始出栈
    if (low[x] == dfn[x])
    {
   
        int cnt = 1;
        while (stk.top() != x)
        {
   
            vis[stk.top()] = 0;
            stk.pop();
            cnt++;
        }
        stk.pop();
        //游戏进行几轮取决于最小的强连通分量中点的个数(前提是点数大于1)
        if (cnt > 1) ans = min(ans, cnt);
    }
}
int main()
{
   
    int n, v;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
   
        scanf("%d", &v);
        to[i] = v;
    }
    for (int i = 1; i <= n; i++)
        if (!dfn[i]) tarjan(i);
    cout << ans << endl;
    return 0;
}

缩点

对于一个有向图,求出最少加几条边可以将这个图变成一个强连通图。可以将每个强连通分量看作一个点,然后枚举每一个点,如果这个点能到达另一个连通集的点,那么该点的连通集出度为0,另一点的连通集入度为0,假设入度不为0的连通集个数为a,出度不为0 的连通集个数为b,最后的结果就是max(a,b)

#include <bits/stdc++.h>
#define maxn 100
using namespace std;

int dfn[maxn] = {
   0}, low[maxn] = {
   0}, group[maxn] = {
   0}, tot = 0, cnt = 0, m, n;
bool vis[maxn] 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值