首页 > 编程语言 >有向图求强连通分量的几种算法

有向图求强连通分量的几种算法

时间:2023-11-28 19:57:38浏览次数:41  
标签:连通 有向图 pa dfs dfn low 节点 求强

概要

本文介绍了kosaraju, tarjan算法求强连通分量

概念

有一个有向图G, 有几个概念

  1. 强连通
    若图中有两个点u和v, 他们能互相到达, 则称他们强连通
  2. 强连通图
    若是G中任意2个点都可以互相到达, 则称G是一个强连通图
  3. 强连通分量
    有向非强连通图的极大强连通子图(可以有很多个)
  4. 完全图 fully connected graph
    全部节点互相之间都有边直接连接的图

我偷懒不画图了, 理解不了的看别的博客吧
还是画个图吧, 灵魂画手来了
image
我们可以看出1,2,3,4互相可达, 他们是一个强连通分量
而5, 6分别也是一个强连通分量

Kosaraju算法

求这个图的强连通分量.
怎么做呢

  1. DFS一遍, 在一个顶点搜索完成时把这个顶点加入到一个栈中(其实就是逆后序"Reverse Postorder")
  2. 图每条边反转, 得到一个反图G'
  3. 从栈顶取出一个节点, 若在G'上未访问过, 则在G'上进行dfs搜索, 搜索到的节点都放进一个component中
  4. 得到的component就是原图G的一个强连通分量
  5. 重复取出节点, 直至栈空

关于逆后序, 可以参考我的另一篇讲序的博客

一点粗略的小证明:
dfs(u)的过程中访问到了v节点, 提供了u->v的可达性
而反图G'中逆后序的dfs提供v->u的可达性

还有一点小证明, 逆后序是什么, 其实就是拓扑排序
所有环缩成点, 那么G会成为什么? 有向无环图
拓扑排序的功能是什么?

代码如下:

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

const int SIZE = 6;
vector<std::pair<int, int>> vec = {{1, 2}, {2, 3}, {3, 4}, {4, 1}, {3, 5}, {3, 6}, {6, 5}};
stack<int> s;
bool isvisit[SIZE];
vector<vector<int>> adj(SIZE);
vector<vector<int>> adj_rev(SIZE);

void dfs(int u)
{
    isvisit[u] = true;
    for (auto pa : adj[u])
    {
        if (!isvisit[pa])
        {
            dfs(pa);
        }
    }
    s.push(u);
}

void dfs2(int u, vector<int> &component)
{
    isvisit[u] = true;
    component.push_back(u);
    for (auto pa : adj_rev[u])
    {
        if (!isvisit[pa])
        {
            dfs2(pa, component);
        }
    }
}

int main()
{
    memset(isvisit, 0, sizeof(isvisit));
    for (auto &pa : vec)
    {
        pa.first--;
        pa.second--;
        adj[pa.first].push_back(pa.second);
        adj_rev[pa.second].push_back(pa.first);
    }
    for (int i = 0; i < SIZE; i++)
    {
        if (!isvisit[i])
            dfs(i);
    }
    memset(isvisit, 0, sizeof(isvisit));
    while (!s.empty())
    {
        if (!isvisit[s.top()])
        {
            vector<int> component{};
            dfs2(s.top(), component);
            for (int i = 0; i < component.size(); i++)
            {
                cout << component[i] + 1 << " \n"[i == component.size() - 1];
            }
        }
        s.pop();
    }
    return 0;
}

输出:

1 4 3 2
6
5

tarjan

tarjan在求最大连通分量的时候, 每个点只访问一次, 每条边也只访问一次
tarjan算法维护两个数据结构
dfn[n] 时间戳
low[n] 是最低可达索引(low-link value), 表示节点可以回溯到的最早访问节点
low说人话就是, 这个节点的low值为dfs过程中, 能访问到的节点中最低的low值.
我不太会讲, 丢个讲的好的博客在这里.
博客

  1. dfs过程中只干两个事, 一个是看邻接节点有没有遍历过(看dfn就行), 第二件事是看是否在栈中, 这两种情况都要更新low
  2. dfs到结束检测 dfn[u] = low[u], 满足的话就是一个连通分量, 一直pop到栈顶元素是u为止(u当然也是连通分量里的)

具体过程是这样的,
还是刚才那个图
image
从1开始dfs
节点1的dfn为1, low为1
然后到节点2, dfn为2, low为2
继续到节点3, dfn为3, low为3
继续遍历3的邻接(4,5,6), 先到了4, 然后dfn标为4, low标为4
然后发现4相连的节点1是遍历过的, 把4的low改成为std::min(4, 1) = 1
然后继续回溯到3
然后到5, dfn标为5, low标为5
发现没有邻接, 然后进入判断, 发现节点5的dfn = low, 那么这就是一个连通分量
同理得到6是一个连通分量
最后回溯到1, 发现1的dfn = low, 那么这就是第三个连通分量

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

const int SIZE = 6;
vector<std::pair<int, int>> vec = {{1, 2}, {2, 3}, {3, 4}, {4, 1}, {3, 5}, {3, 6}, {6, 5}};
stack<int> s;
vector<vector<int>> adj(SIZE);
vector<int> low(SIZE, 0);
vector<int> dfn(SIZE, 0);
vector<bool> isInStack(SIZE, false);
int dfs_num = 0;

void tarjan(int u)
{
    dfn[u] = low[u] = ++dfs_num;
    isInStack[u] = true;
    s.push(u);
    for (auto v : adj[u])
    {
        if (!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if (isInStack[v])
        {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (dfn[u] == low[u])
    {
        vector<int> component = {u};
        while (s.top() != u)
        {
            isInStack[s.top()] = false;
            component.push_back(s.top());
            s.pop();
        }
        isInStack[u] = false;
        s.pop();
        for (int i = 0; i < component.size(); i++)
        {
            cout << component[i] + 1 << " \n"[i == component.size() - 1];
        }
    }
}

int main()
{
    for (auto &pa : vec)
    {
        pa.first--;
        pa.second--;
        adj[pa.first].push_back(pa.second);
    }
    tarjan(0);
    return 0;
}

输出:

5
6
1 4 3 2

标签:连通,有向图,pa,dfs,dfn,low,节点,求强
From: https://www.cnblogs.com/ryougi/p/17862840.html

相关文章

  • 自动化ping测网络连通性监测与Excel自动记录
    根据现有提供海量ip进行检测网络质量,如果手动操作那将成为一项很难完成的操作。为了简化这一任务,可以使用Python自动化脚本,利用openpyxl和pythonping库,自动执行ping测试并记录结果到Excel文件。openpyxl:openpyxl是一个用于操作Excel文件的库。它允许你读取、写入和......
  • matlab函数_连通区域
    ​1、matlab函数bwareaopen──删除小面积对象格式:BW2=bwareaopen(BW,P,conn)作用:删除二值图像BW中面积小于P的对象,默认情况下使用8邻域。算法:(1)Determinetheconnectedcomponents. L=bwlabeln(BW,conn);(2)Computetheareaofeachcomponent. S=regionp......
  • To_Heart—总结——连通块 dp(抑或 连续块 dp)
    简介有一类问题,他们需要计算满足在序列上插入给定顺序的元素,其贡献/限制只与两旁的元素有关,但元素插入的位置是不一定的,所以会有代价的最值和方案数的统计。而对于这类问题,我们其实可以不关心每一次插入的具体位置在哪里,而只关注他的相对位置(比如在某个数左边、在某个数左边且与......
  • P5227 [AHOI2013] 连通图
    P5227[AHOI2013]连通图(膜拜并感谢@Genius_Z给予本题解思路)因为这一题是线段树合并板题,所以我们使用LCT。考虑最暴力的想法,维护一棵树和很多不在树上的边,每一次询问就暴力拆边,从那些没有被禁的边里面补到树上。这个时候我们就会发现,每次“补边”的操作非常的消耗时间。......
  • 2023-11-01:用go语言,沿街有一排连续的房屋。每间房屋内都藏有一定的现金, 现在有一位小
    2023-11-01:用go语言,沿街有一排连续的房屋。每间房屋内都藏有一定的现金,现在有一位小偷计划从这些房屋中窃取现金,由于相邻的房屋装有相互连通的防盗系统,所以小偷不会窃取相邻的房屋,小偷的窃取能力定义为他在窃取过程中能从单间房屋中窃取的最大金额,给你一个整数数组nums......
  • 2023-11-01:用go语言,沿街有一排连续的房屋。每间房屋内都藏有一定的现金, 现在有一位小
    2023-11-01:用go语言,沿街有一排连续的房屋。每间房屋内都藏有一定的现金,现在有一位小偷计划从这些房屋中窃取现金,由于相邻的房屋装有相互连通的防盗系统,所以小偷不会窃取相邻的房屋,小偷的窃取能力定义为他在窃取过程中能从单间房屋中窃取的最大金额,给你一个整数数组nums表示每......
  • WGCLOUD体验 - 监测数据库的连通性
    WGCLOUD是一款运维监测平台,可以监测服务器、主机、数据库、服务接口、网络设备等资源我是一名DBA,日常工作中,主要关注数据库方面的监测情况,正好WGCLOUD有一个模块可以检测数据库是否能连通,如果发现不能连通,会立刻发送告警通知(可以用邮件、钉钉、wx等方式)如下WGCLOUD不但可以检测数据......
  • 博弈论(Nim游戏 , 有向图游戏)
    博弈论专题Nim游戏内容: 有n堆石子,每堆石子的石子数给出,甲乙两人回合制取石子,每次可以取任意一堆石子的任意多个(可以直接取完,但不能不取),每个人都按照最优策略来取(抽象),问先手必胜或先手必败? 结论: 设有n堆石子,每堆的个数分别为a1,a2,a3,……,an-1,an。则......
  • 有向图的拓扑排序
    给定一个n个点m条边的有向图,点的编号是1到n,图中可能存在重边和自环。请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出−1。若一个由图中所有点构成的序列A满足:对于图中的每条边(x,y),x在A中都出现在y之前,则称A是该图的一个拓扑序列。输入格式第一行包含两个整数......
  • 有向图计数优化版原理及C++实现
    题目见前面章节。有向图访问计数的原理及C++实现第一版不需要拓扑排序,也不需要并集查找,直接dfs了。完成以下三个职责:一,DFS那些端点在环上。二,DFS环上各点此环的长度。三,DFS非环上各点。分析cur是当前dfs的节点,next为edges[cur]。从后向前分析:判定处理ret的值返回值找到环尾ret......