实用算法实现-第 17 篇 强连通分支

算法导论 第22章 图的基本算法(四) 强连通分支 强连通分支    计算有向图的强连通分支是深度优先搜索经典应用,很多图相关的算法都是将图分解为各个强连通分支之后,然后在各个强连通分支上运行,最后根据各个强连通分支之间的关系将所有的解组合起来。    对于有向图G=(V,E)任意两个顶点u,v,如果u能够经过一条路径到达v,v也能通过一条路径到达u,即两者互相可达,它们将属于一个强连通分支,强连通用分支就是这样一个最大的互相可达的顶点集合。 阅读详情

求强连通分量的著名算法:Kosaraju算法,Gabow算法和Tarjan算法。其中Kosaraju算法要对原图和逆图都进行一次DFS,而另外两种算法只要进行一次DFS即可。[i]文是介绍Gabow算法的论文。

17.1    Kosaraju算法

Kosaraju算法虽然要进行两次DFS,但是复杂度仍然是O(V+E),而且比较容易理解。

17.1.1   实例

PKU JudgeOnline, 2186, Popular Cows.

17.1.2   问题描述

有一群牛,总数为N。给出牛之间的M个仰慕关系,该关系可以传递,比如:1仰慕2,2仰慕3,那么1也仰慕3,如果一头牛被所有的牛都仰慕,那么它将是最受欢迎的牛,求出有多少牛是最受欢迎的。

先输入N、M,再输入M个仰慕关系。

17.1.3   输入

33

12

21

2 3

17.1.4   输出

1

17.1.5   分析

先对图求强连通分支,将所有强连通子图合并为一个结点,形成一个新图。

不难证明:在图中,如果将强连通分支看做一个结点,那么如果该结点出度不为0,则该SCC中的牛不是被所有牛仰慕的牛。反证法即可证明。

更进一步,不难证明:如果图中有牛被所有牛仰慕,那么有且只有一个出度为0的,且从图中的任何一个结点都能到达(连通性)的SCC,该SCC包含被所有牛仰慕的牛。反证法可以证明。

也可以知道:如果超过一个SCC的出度为0,那么连通性得不到保障。

可以证明:存在所有牛仰慕的牛,当且仅当出度为0的SCC有且只有一个。“仅当”很容易证明,下面证明“当”。假设只有一个SCC出度不为0时,但是却不存在所有牛仰慕的牛。那么必然意味着,有结点不能到达该SCC。因为如果所有结点都能到达该SCC,那么很自然的该SCC就是包含所有被其它所有牛仰慕的牛。在不能到达该SCC的结点中至少存在一个结点,其出度为0。因为如果所有结点的出度都不为0,那么所有结点必然形成一个环。形成环的结点在构造强连通分支的时候是要合并在一起的,矛盾。所以必然至少存在一个结点,其出度为0。假设和结果矛盾,故此,命题得证。

所以要求被所有牛仰慕的牛的个数,只需要求强连通分支,然后统计出度为0的个数。如果个数不为1则输出0。然后找到出度为0的那个SCC,。如果能就输出该SCC的牛的个数。

1.1.6   程序

#include <stdio.h>
#include <string.h>
#define G_size 100000 //边的最大个数
#define V_size 11000 //点的最大个数
 
typedef struct Graph
{
     int id;//记录了结点的序号
     int next;
}Graph;
 
typedef struct Edge
{
     int s, e;
}Edge;
 
EdgeE[G_size];
GraphGA[G_size];
GraphGT[G_size];
int N, M;
int G_end;
int order[V_size];
int id[V_size];//在逆序的时候记录了SCC的序号
int vis[V_size];//遍历图的时候使用
int in[V_size];//计算反向SCC图的入度,也就是逆向之前的SCC图出度
int cnt, scnt, pos;
 
void Insert(int s, int e) //建立原图和逆图
{
     int p;
     p = s;
     while(GA[p].next){
         p = GA[p].next;
     }
     GA[G_end].id = e;
     GA[p].next = G_end;
 
     p = e;
     while(GT[p].next){
          p= GT[p].next;
     }
     GT[G_end].id = s;
     GT[p].next = G_end;
 
     G_end++;
}
 
void DFST(int x) //对原图进行搜索
{
     int p, q;
     vis[x] = 1;
     p = GT[x].next;
     while(p){
         q = GT[p].id;
         if(!vis[q])
         {
              DFST(q);
         }
         p = GT[p].next;
     }
     order[cnt++] = x;
}
 
void DFSA(int x) //对逆图进行搜索
{
     int p, q;
     vis[x] = 1;
     id[x] = cnt;
     p = GA[x].next;
     while (p){
         q = GA[p].id;
         if(!vis[q])
         {
              DFSA(q);
         }
         p = GA[p].next;
     }
}
 
void Solve() //主要过程
{
     int s, e;
     int i;
 
     memset(GA, 0, sizeof(GA));
     memset(GT, 0, sizeof(GT));
     memset(E, 0, sizeof(E));
     G_end = N + 1;
 
     for (i = 0;i < M; i++)
     {
         scanf("%d%d", &s, &e);
         E[i].s = s - 1;
         E[i].e = e - 1;
         Insert(s - 1, e - 1);
     }
 
     memset(vis, 0, sizeof(vis));
     cnt = 0;
     for (i = 0;i < N; i++)
     {
         if(!vis[i])
         {
              DFST(i);
         }
     }
 
     memset(vis, 0, sizeof(vis));
     cnt = 0;
     for (i = N- 1; i >= 0; i--)
     {
         if(!vis[order[i]])
         {
              DFSA(order[i]);
              cnt++;
         }
     }
 
     for (i = 0;i < M; i++)
     {
         s = id[E[i].s];
         e = id[E[i].e];
         if (s!= e)
         {
              in[s]++;
         }
     }
     scnt = cnt;
     cnt = 0;
     for (i = 0;i < scnt; i++){
         if(in[i] == 0)
         {
              pos = i;
              cnt++;
         }
     }
     if (cnt !=1){
         printf("0\n");
     }
     else{
         cnt = 0;
         for (i= 0; i < N; i++){
              if(in[id[i]] == pos)
              {
                   cnt++;
              }
         }
         printf("%d\n",cnt);
     }
}
 
int main()
{
     while (EOF!= scanf("%d %d", &N, &M))
         Solve();
     return 0;
}


17.2    Kosaraju算法判断单向连通性

17.2.1   实例

PKU JudgeOnline, 2762, Going from u to v or from v to u?.

17.2.2   问题描述

给定N个点和这N个点之间的M个有向连接。如果两个点之间能从其中一个点到另一个点,那么这两个点就是单向连通的。问这N个点是不是都是单向连通的。

先输入测试个数。每个测试,先输入N、M,然后是M个连接。

17.2.3   输入

1

33

12

23

31

17.2.4   输出

Yes

17.2.5   分析

可以通过求强连通分支,简化图形,使得每两个结点只有单向连接。

然后使用DFS方法,对新图进行拓扑排序。

不难证明:如果原图是单向连通的,那么拓扑排序之后的结点必有指向下一个结点的连接。这是因为:假设没有这个连接,原图又是连通的,会拓扑排序的定义相违背。拓扑排序:对于有向无回路图,进行排序之和,如果包含边(u, v)那么u就出现在v之前。

同时不难证明:如果原图是单向连通的,那么新图有且只有一个结点的入度为0。首先,强连通分支将所有的回路聚合了,所以新图不存在回路,故此至少有一个结点的入度为0。其次,如果超过一个结点的入度为0,那么这两个结点肯定不能到达彼此。

故此,只需要先求强连通分支,建立新图,然后新图判断入度为0的结点个数是不是只有一个。然后以这个结点为根,DFS遍历新图,进行拓扑排序。最后,判断排序好的结点到下一个结点是不是有连接。

这里用到的强连通算法仍然是Kosaraju算法,但是由于超时,就对原来的程序进行了优化。主要的优化在于加入数组,记录每个结点的最后一个子结点的保存位置。典型的空间换时间。

1.2.6   程序

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;
#define G_size 10100 //边的最大个数
#define V_size 1010 //点的最大个数
 
typedef struct Graph
{
     int id;//记录了结点的序号
     int next;
}Graph;
 
typedef struct Edge
{
     int s, e;
}Edge;
 
EdgeE[G_size];
GraphGA[G_size];
GraphGT[G_size];
int N, M;
int G_end;
int order[V_size];
int id[V_size];//在逆序的时候记录了边所属的SCC的序号
int vis[V_size];//遍历图的时候使用
int in[V_size];//计算SCC图的入度
int cnt, scnt, pos;
int lastSonGA[V_size];
int lastSonGT[V_size];
void Insert(int s, int e) //建立原图和逆图
{
     int p;
     p = lastSonGA[s];
     GA[G_end].id = e;
     GA[p].next = G_end;
     lastSonGA[s] = G_end;
 
     p = lastSonGT[e];
     GT[G_end].id = s;
     GT[p].next = G_end;
     lastSonGT[e] = G_end;
 
     G_end++;
}
void DFST(int x) //对逆图进行搜索
{
     int p, q;
     vis[x] = 1;
     p = GT[x].next;
     while(p){
         q = GT[p].id;
         if(!vis[q])
         {
              DFST(q);
         }
         p = GT[p].next;
     }
     order[cnt++] = x;
}
 
void DFSA(int x) //对原图进行搜索
{
     int p, q;
     vis[x] = 1;
     id[x] = cnt;
     p = GA[x].next;
     while (p){
         q = GA[p].id;
         if(!vis[q])
         {
              DFSA(q);
         }
         p = GA[p].next;
     }
}
int GSCC_end;
GraphGSCC[G_size];
int lastSonGSCC[V_size];
void InsertGSS(int s, int e) //建立原图和逆图
{
     int p;
     p = lastSonGSCC[s];
     GSCC[GSCC_end].id = e;
     GSCC[p].next = GSCC_end;
     lastSonGSCC[s] = GSCC_end;
     GSCC_end++;
}
int TopologcalOrder[V_size];
void DFS_GSS(int x)
{
     int p, q;
     vis[x] = 1;
     p = GSCC[x].next;
     while (p){
         q = GSCC[p].id;
         if(!vis[q])
         {
              DFS_GSS(q);
         }
         p = GSCC[p].next;
     }
     TopologcalOrder[cnt++] = x;
}
int linked[V_size][V_size];
void Solve() //主要过程
{
     int s, e;
     int p;
     int i;
     int fail;
     memset(GA, 0, sizeof(GA));
     memset(GT, 0, sizeof(GT));
     memset(E, 0, sizeof(E));
     for(i = 1;i <= N; i++){
         lastSonGT[i] = i;
         lastSonGA[i] = i;
     }
     G_end = N + 1;
     for (i = 1;i <= M; i++)
     {
         scanf("%d%d", &s, &e);
         E[i].s = s;
         E[i].e = e;
         Insert(s, e);
     }
 
     memset(vis, 0, sizeof(vis));
     cnt = 0;
     for (i = 1;i <= N; i++)
     {
         if(!vis[i])
         {
              DFST(i);
         }
     }
 
     memset(vis, 0, sizeof(vis));
     cnt = 0;
     for (i = N- 1; i >= 0; i--)
     {
         if(!vis[order[i]])
         {
              cnt++;
              DFSA(order[i]);
         }
     }
 
     scnt = cnt;
     for(i = 1;i <= cnt; i++){
         lastSonGSCC[i] = i;
     }
     GSCC_end = cnt + 1;
     memset(GSCC, 0, sizeof(GSCC));
     memset(in, 0, sizeof(in));
     for (i = 1;i <= M; i++)
     {
         s = id[E[i].s];
         e = id[E[i].e];
         if (s!= e)
         {
              InsertGSS(s, e);
              linked[s][e] = 1;
              in[e]++;
         }
     }
     cnt = 0;
     for (i = 1;i <= scnt; i++){
         if(in[i] == 0)
         {
              pos = i;
              cnt++;
         }
     }
     fail = 0;
     if (cnt !=1){
         fail = 1;
     }else{
         memset(vis, 0, sizeof(vis));
         cnt = 0;
         DFS_GSS(pos);
         for(i =scnt - 1; i > 0; i--)
         {
              s = TopologcalOrder[i];
              e = TopologcalOrder[i - 1];
              if(linked[s][e]== 0)// && linked[e][s] == 0)
              {
                   fail = 1;
                   break;
              }
         }
     }
     if(fail ==1)
     {
         printf("No\n");
     }else{
         printf("Yes\n");
     }
}
 
int main()
{
     int cases;
     scanf("%d",&cases);
     for(; cases> 0; cases--){
         scanf("%d%d", &N, &M);
         Solve();
     }
     return 0;
}

17.3    实例

PKU JudgeOnline, 2186, Popular Cows.

PKU JudgeOnline, 2762, Going from u to v or from v to u?.

本文章欢迎转载,请保留原始博客链接http://blog.youkuaiyun.com/fsdev/article

[i] Path-Based Depth-first Search for Strong and Biconnected Components.Harold N. Gabow. Information Processing Letters 74 (2000) 107-114.

图论--强连通分支 阅读详情

相关推荐

Kosaraju 算法求解一个有向图的强连通分支个数

基本介绍网上看了很多关于求解一个有向图的强连通分支个数的算法,其中最著名的莫过于: Kosaraju 算法看的比较晕!过程如下: 1。 创建一个空的栈 S,并做一次 DFS 遍历。在 DFS 遍历中,当在递归调用 DSF 访问邻接顶点时,将当前顶点压入栈中; 2。 置换图(Transpose Graph); 3。 从栈 S 中逐个弹出顶点 v,以 v 为源点进行 DFS 遍历。从 v 开始

好记性不如烂笔头的专栏 4350

ACM图论__有向图的强连通分支算法

求有向图的强连通分支,主要有两种算法tarjan算法和kosaraju算法,这里介绍tarjan算法先来看几个定义:(1)连通:两个点可以相互到达(2)强连通(strongly connected): 在一个有向图G里,设两个点 a b 发现,由a有一条路可以走到b,由b又有一条路可以走到a,我们就叫这两个顶点(a,b)强连通。(3)强连通分量strongly connected componen...

untilyouydc 3594

算法之强连通分支

深度优先搜索有一种经典的应用:把一个有向图分解为各强连通分支。很多有关有向图的算法都是从这种步骤开始的。(算法导论P338,觉得简洁而精妙,分享下) STRONGLY-CONNECTED-COMPONENTS(G) 1 call DFS(G) to compute finishing times f[u] for each vertex u 2 compute GT       3 cal

smartxxyx的专栏 3040

图的应用:强连通分支

强连通分支 关注一下互联网相关的非常巨大图: 由主机通过网线(或无线)连接而成的图 以及由网页通过超链接连接而形成的图 网页所形成的图 以网页(URL作为id)为顶点,网页内包含的超链接作为边,可以转换为一个有向图 路德学院计算机系网站链接情况,有三个有趣的现象 其中包含了许多路德学院其他系的网站 还包括一些爱荷华其他大学学院的网站 还有一些人文学院的网站 我们可以猜想web的底层结构可能存在某些同类网站的聚集 在图中发现高度聚集节点群的算法,即寻找"强连通分支Strongly Conne

Leopold的博客 780

图论 极大强连通分支

发现在有向图中极大强连通分支的另一种求法: 算法思想:从图中任意取一个点,将所有与该点可以互通的点形成一个点集,加上该点,以及这些点之间的边就是图的一个极大强连通分支;接下来,从图中继续取点,重复同样的操作,直到点都被取完为止。 如果a与点b互通,那么以a为边的尾,可以通到b,而且以b为头;并且以a为头,并且存在通路,以b为尾; 算法步骤: 1.从图中任取一点没有被标记成集

yumao19921006的专栏 2081

有向图的强连通分支及DAG

有向图的强连通分支:在有向图G中

ZT在努力 2058

连通图的强连通分支

//双连通分量方法一: //定义一:给定的有向图G=(V,E),图中任意两个顶点u,v都属于V;如果u,v可以 //互相到达,则称G是强连通图。 //定义二:有向图的极大强连通子图称为强连通分支。 //由定义可2以得知有向图的强连通分支是一个最大的顶点集合,在这个集合中

hechenghai 2407

图论,求有向图的强连通分支

<br />求有向图的强联通分支十一个典型的图论题目。<br />本来想把他写成一个通用的算法,但是发现有很多不太好处理的地方,比如选用邻接链表还是选用邻接矩阵来表示图,有的情况是不一样的,再者很难把强联通分支缩点,并且再构建一个图出来,主要是强联通分支之间的边表示的时候要考虑一些情况,比如边的费用什么的。<br />但是也可以尝试做一个稍微通用的一点的算法,就是把所有点属于那个强连通标识(用id[i]表示)出来。<br />如果对于原图中两点i,j。 如果id[i]==id[j],则他们在一个强联通分支中

zhupopo的专栏 1769

数据结构与算法Python版 拓扑排序与强连通分支

介绍拓扑排序Topological Sort。介绍强连通分支Strongly Connected Components,以及寻找强连通分支(在图中发现高度聚集节点群)的应用。

分享、交流、合作 1588

51单片机IAP在线升级

51单片机IAP在线升级 爱矽半导体E85F3325单片机IAP在线升级教程,此处可查看更新及demo下载 文章目录前言一、ROM资源二、KEIL有关知识1.BL51连接器:2.LX51连接器:三、Keil其他相关项说明:四、MCU启动文件说明:五、例程BOOTLOADER使用说明六、例程使用可更改说明:七、程序使用通信方式:XMODEM八、APP程序说明九、APP代码升级说明十、代码合并烧写 前言 扩展-IAP 主要用于产品出厂后应用程序的更新作用 ,类似于其他引导装载升级程序的方法,在系统中必须始终

qq_42415953的博客 6667

Win10和Ubantu16.04双系统下,卸载Ubantu

Win10和Ubantu16.04双系统下,卸载Ubantu 机器配置 华硕主板,英伟达显卡(这不是重点) Win10与Ubantu16.04均为UFFI分区(如果不知道这个也不需要在意,一般默认的都是这样,鸵鸟原则) 清除Ubantu系统 一般按照常规的方法来删除Ubantu,都是直接将磁盘管理器里面的关于Ubantu的磁盘清空,但是还会留下一个UFFI分区,如果不进行删除的话,开机时候还是会有...

qq_33289694的博客 391

6.3.1 强连通分支算法--Kosaraju算法、Tarjan算法和Gabow算法

强连通分支算法 本节内容将详细讨论有向图的强连通分支算法(strongly connected component),该算法是图深度优先搜索算法的另一重要应用。强分支算法可以将一个大图分解成多个连通分支,某些有向图算法可以分别在各个联通分支上独立运行,最后再根据分支之间的关系将所有的解组合起来。 在无向图中,如果顶点s到t有一条路径,则可以知道从t到s也有一条路径;在有向无环图中个,如果顶点s

ajian005的专栏 1473

DAC8568控制器实战:如何用STM32实现多通道同步输出(附完整代码)

本文详细介绍了如何使用STM32微控制器驱动TI的DAC8568多通道DAC控制器,实现高精度的多通道同步模拟信号输出。内容涵盖硬件设计要点、SPI时序精准配置、驱动层命令构建,并重点解析了通过LDAC引脚实现同步更新的核心策略,提供了完整的代码框架和实战案例,帮助工程师快速解决多通道同步输出的工程难题。

weixin_29179311的博客 170

强连通分支

强连通分支 有向图中,如果一个点集中所有点对之间都可以相互到达,那么这个点对组成的极大集合就叫做强联通分支。 求解强联通分支的方法这里介绍两种。 两边DFS法 主要的依据就在于,一个强联通分支中的点都是可以互相到达的,那么当我们翻转图中的边的方向后,我们就可以得到一个逆图,在这个新的图中,强联通分支不变,如果我们把强连通分支看做一个缩点,那么只要我们按照拓扑逆序遍历即可得到所有强连通分支,而如何求解拓扑排序,实际上有一种DFS遍历的方法,即DFS遍历完成节点的逆序就是拓扑序列。 这样我们只需要一遍DFS求出

qq_37957064的博客 3221

SMMU软件指南SMMU编程之流表(Stream Table)

SMMU使用一组内存中的数据结构来定位翻译数据,详见“转换过程概述”(Translation process overview)。SMMU_(*_)STRTAB_BASE 存储初始结构(流表,Stream Table)的基地址。流表项(Stream Table Entry, STE)包含二级(Stage 2)转换表的基址指针,同时也定位一级(Stage 1)配置结构,这些配置结构包含转换表的基址指针。 传入事务的 StreamID,由 SEC_SID 进行限定,决定了用于查找的流表,并定位相应的STE。

AI-安全-功耗 1189

【TCAD】AlGaN 器件的Sentaurus仿真

否则,必须在 LatticeParameters Sentaurus Device 的参数文件中的部分。Sentaurus Device 有两个工具实例:第一个工具实例, sd_fdiv ,模拟正向偏置特性,第二个工具实例, sd_rviv ,模拟反向偏置特性直至击穿。对于牛顿求解器,通常应该将非线性迭代次数限制为一个足够小的值,例如 Iterations=20 ,同时通过指定非阻尼迭代次数高于允许的牛顿迭代次数来避免解的阻尼( NotDamped=30 )。晶胞中的 Ga-N 键为橙色。

荷塘阅色 1215

networkx 有向图强连通_有向图的强连通分支

在有向图G中,如果任意两个不同的顶点相互可达,则称该有向图是强连通的。有向图G的极大强连通子图称为G的强连通分支。把有向图分解为强连通分支是的一个经典应用实例。下面介绍如何使用两个深度优先搜索过程来进行这种分解,很多有关有向图的算法都从分解步骤开始,这种分解可把原始的问题分成数个子问题,其中每个子子问题对应一个强连通分支。构造强连通分支之间的联系也就把子问题的解决方法联系在一起,我们可以用一种称之...

weixin_39586235的博客 1613

ChatGPT启蒙之旅:弟弟妹妹的关键概念入门

本文主要介绍了ChatGPT启蒙之旅:弟弟妹妹的关键概念探险,希望能对初学大模型的同学们有所帮助。 文章目录 1. 背景 2. GPT是个什么样的人? 3. GPT使用的核心思维 4. GPT使用的场景介绍

herosunly的博客 16万+

tarjan算法求强连通分支

Tarjan算法详解 近期做数据结构课程设计,遇到了有向图的强连通分支的问题,网上采用的是基于深度优先遍历的tarjan算法,在考了许多博客后,还是有些迷,在思考了一番后,终于明白了,所以写了这篇文章。 首先,明确以下几点。 tarjan算法基于深度优先遍历。使用时,会用到两个栈。一个栈用于DFS,(如果是递归的tarjan算法,是隐式地使用了系统堆栈);另一个栈就是要保存连通分支的信息了,以...

zzc_zhuyu的博客 553

Weka学习笔记01:初探Weka世界

下载、安装和启动Weka、Weka数据格式

howard2005的专栏 1471

PowerDesigner 数据库建模使用详解

PowerDesigner 数据库建模使用详解

congge 7082

有向图的强连通分支

在有向图G中,如果任意两个不同的顶点相互可达,则称该有向图是强连通的。有向图G的极大强连通子图称为G的强连通分支。 把有向图分解为强连通分支是深度优先搜索的一个经典应用实例。下面介绍如何使用两个深度优先搜索过程来进行这种分解,很多有关有向图的算法都从分解步骤开始,这种分解可把原始的问题分成数个子问题,其中每个子子问题对应一个强连通分支。构造强连通分支之间的联系也就把子问题的解决...

bandiaodai5734的博客 994

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

上一篇: 实用算法实现-第 16 篇 拓扑排序
下一篇: 实用算法实现-第 18 欧拉回路
存储李希
博客等级 码龄15年 418粉丝 69原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值