虽然暂时用不到,还是花时间学习了一下,看网上玩ACM的大牛们都在做图论的题目,我也眼红了。。。

因为需要用到求强连通分量来判断AOE/PERT中的环路,先研究研究无向图的双连通分量。

对今天的学习做个总结:

无向图的连通分支(连通子图): 判断一个无向图是否连通,如果进行dfs或者bfs之后,还有未访问到的顶点,说明不是连通图,否则连通。

求解无向图的所有连通分支: 只需要重复调用dfs或者bfs 就可以解决:遍历顶点,如果v 未访问,则对其进行dfs, 然后标记访问。过程如下:

1 void dfs(int v){

2      node_pointer w;

3      visited[v] = TRUE;

4     for(w = graph[v]; w; w = w->link) {

5         if(!visited[w->vertex])

6              dfs(w->vertex);

7      }

8 }

9 void connect(){

10     int i;

11     for(i = 0; i < n; i++)

12         if(!visited[i]) {

13              dfs(i);

14          }

15 }

关节点(割点): 是图中一个顶点v, 如果删除它以及它关联的边后,得到的新图至少包含两个连通分支。

双连通图: 没有关节点的连通图。

连通无向图的双连通分支(双连通子图,块) : 是图G中一个最大双连通子图。

利用深度优先搜索dfs 可以求解双连通分支,因为dfs过程中,必定要经过关节点,并生成一棵深度优先搜索树。 而图G的连通子图必然是深搜树的一部分。

这张图很难看。 它有4个关节点:1,3,4,7, 将 图分为6个双连通分支。

如果以顶点3开始深搜,得到如下一棵树:

3是树根, 红色标号是 深度搜索访问节点的顺序, 红色边是图中深度搜索没有访问到的边(因为有的顶点可以多个边到达,深搜只要通过一个边到达顶点,就不再访问该顶点了),称作非树边,也就是树中没有的。 黑色的边是树边。

如果两个顶点u,v ,其中u是v的祖先或者v是u的祖先,那么非树边(u,v)叫做回退边。在深搜树中,所有的非树边都是回退边。 无向图的深搜树是一棵开放树,如果在其中添加一条回退边,就会形成环,该环路或扩大连通分量的范围,或者导致新的连通分量产生。

通过这个过程,可以发现一条规律:当v是树根,如果它有2个或者更多儿子,那么它是一个关节点。

当v不是树根,当且仅当它有至少一个儿子w, 且从w出发,不能通过w的后代顶点组成的路径和一条回退边到底u 的任意一个祖先顶点,此时v 是一个关节点。 其道理很明显,如果树根包含多个儿子,那么把根节点去掉,整棵树自然被分成多个不相干的部分,图也就断开了。如果v是非根顶点,如果其子树中的节点均没有指向v祖先的回边,那么去掉v以后,将会把v及其子树与图的其他部分分割开来,v自然是关节点。

例如顶点5,它的儿子只有6,而6 能到达的最低层顶点是5(通过 6->7->5), 无法访问到5的祖先顶点,因此5是一个关节点。

基于这样的规律,我们给每个顶点定义一个low值,low(u) 表示从u出发,经过一条其后代组成的路径和回退边,所能到达的最小深度的顶点的编号。( 如果这个编号大于等于u的编号,就说明它的后代无法到达比u深度更浅的顶点,即无法到达u的祖先,那么u就是个关节点)

low(u) = min{ dfn(u), min{ low(w) | w是u的儿子}, min{dfn(w), | (u,w) 是一条回退边} }

dfn(u) 是深搜过程中对顶点的编号值。

计算过程如下:

1 void dfnlow(int u, int v) {

2      node_pointer ptr;

3      int w;

4      dfn[u] = low[u] = num++;

5      for(ptr = graph[u]; ptr; ptr = ptr->link) {

6          w = ptr->vertex;

7          if(dfn[w] < 0) {

8              dfnlow(w, u);

9              low[u] = MIN(low[u], low[w]);

10          } else if( w != v)

11              low[u] = MIN(low[u], dfn[w]);

12      }

13 }

14

因此,我们在深搜过程中计算出 dfn 值和 low 值,如果发现 u有一个儿子w ,使得 low(w) >= dfn(u), 那么u就是关节点。

求解双连通分量的过程,可以通过深搜完成。 在搜索过程中,如果遇到一个新的边,则压栈,直到找到一个关节点,由于深搜是递归的,在找到一个关节点的同时,必定已经访问完了其子孙节点和其子树的边(包括回退边),而且这些边都在栈中,此时弹出栈中的边直到遇到关节点所在的边即是双连通分支包括的边。

完整代码:

1 #include

2 #define MAX_VERTICES 50

3 #define true 1

4 #define false 0

5 #define MIN(x,y) ((x) < (y) ? (x) : (y))

6 typedef struct node *node_pointer;

7 struct node {

8          int vertex;

9          struct node *link;

10 };

11

12 node_pointer graph[MAX_VERTICES];

13

14 int n = 0;

15 int dfn[MAX_VERTICES];

16 int low[MAX_VERTICES];

17

18 typedef struct {

19     int v;

20     int w;

21 }edge;

22 edge edges[100];

23 int top = 0;

24

25

26 int num = 0;

27

28 void printG() {

29      int i;

30      node_pointer e;

31      for(i=0;i<=n;i++) {

32          printf("[%d]",i);

33         for(e=graph[i];e;e=e->link)

34            printf(" (%d)->",e->vertex);

35          printf("\n");

36      }

37 }

38

39 void printDfnLow() {

40      int i = 0;

41      while(i<=n) {

42          printf("[%d]: dfn:%d   low:%d\n", i, dfn[i], low[i]);

43          ++i;

44      }

45 }

46

47

48 void addEdge(int v, int w) {

49      node_pointer e = (node_pointer)malloc(sizeof(struct node));

50      e->vertex = w;

51      e->link = graph[v];

52      graph[v] = e;

53 }

54 //无向图中一条边在邻接表中对应两个节点,1->2,2->1

55 void addREdge(int v,int w){

56      addEdge(v,w);

57      addEdge(w,v);

58 }

59

60

61 void init() {

62    int i = 0;

63    n = 9; //0 to n

64    while(i<=n) {

65        graph[i] = 0;

66

67        dfn[i] = low[i] = -1;

68        i++;

69    }

70

71    num = 0;

72

73

74    addREdge(3,5);

75    addREdge(5,7);

76    addREdge(5,6);

77

78    addREdge(6,7);

79    addREdge(7,9);

80    addREdge(7,8);

81    addREdge(0,1);

82    addREdge(1,2);

83    addREdge(1,3);

84    addREdge(2,4);

85    addREdge(4,3);

86

87 }

88

89 void dfnlow(int u, int v) {

90      node_pointer ptr;

91      int w;

92      dfn[u] = low[u] = num++;

93      for(ptr = graph[u]; ptr; ptr = ptr->link) {

94          w = ptr->vertex;

95          if(dfn[w] < 0) {

96              dfnlow(w, u);

97              low[u] = MIN(low[u], low[w]);

98          } else if( w != v)

99              low[u] = MIN(low[u], dfn[w]);

100      }

101 }

102

103 void bicon(int u, int v) {

104      node_pointer ptr;

105      int w;

106      edge e;

107      dfn[u] = low[u] = num++;

108

109      for(ptr = graph[u]; ptr; ptr = ptr->link) {

110          w = ptr->vertex;

111

112          if(v!=w && dfn[w] < dfn[u]) { //v!=w   to avoid 1->2 2->1 in undirected graph

113                                        // dfn[w] < dfn[u] to avoid visited vertex who is decendant of u

114                  edges[top].v = u; // 新边压栈,v!=w是防止重复计算无向图中同一条边

//dfn[w]

//遇到的顶点只有两种情况,dfn[w]=-1新点, dfn[w]

//u,w 是回退边。二者的共同点是 dfn[w] < dfn[u],这两种

//边包括了G的所有边,因此对其他边的访问是重复的。

115                  edges[top].w = w;

116                  ++top;

117                  if(dfn[w]< 0) { //如果是新顶点(未访问过)

118                      bicon(w,u);     //递归计算

119                      low[u] = MIN(low[u], low[w]);// 更新当前u的low

120

121                      if(low[w] >= dfn[u]) { //如果发现u的孩子w 满足条件,说明u是关节点

122                          printf("New biconnected component:\n");

123                          do{

124                              e = edges[--top]; //此时栈中是上面的bicon压入的访问过的边,

//即该关节点下的子树边和回退边

125                              printf("", e.v, e.w);

126                          }while( !(e.v == u && e.w == w));

127                          printf("\n");

128                        }

129                  } else if (w!=v){

130                        low[u] = MIN(low[u], dfn[w]);

131                  }

132          }

133      }

134 }

135 int main(){

136      init();

137      printG();

138     //dfnlow(3,-1);

139      bicon(3,-1);

140      printDfnLow();

141      getchar();

142 }

143

注意在无向图深搜树中,只有两种边:树边(u->v u是v的父亲,v未访问)和回退边(u->v, v是u的祖先,v访问过)。 且无向图的边在邻接表中其实是"双向"的。因此我们要通过一些条件来只使用树边和回退边。

因此对于边u,v dfn[u] < dfn[v] && v 访问过 (即回退边的反向)   或者 dfn[u] > dfn[v],   v 是u的父亲(树边的反向),这两种都是已经访问过的边,不需要重复访问。

我对回退边的理解是,它圈定了一个由关节点分割的连通子图的范围。 因为当遇到一个u的子孙 w, 使 low[w] >= dfn[u], 就说明 w 以及w 的子孙都无法访问到 u的祖先,那么去掉u 后,w 以及其子树就被和图的其他部分分割开来,形成了连通子图。这里的割点就是low[w] 能到达的最底层节点,也就是深搜过程中最靠近这个连通子图的关节点。 那么下界其实就是w. 因为设 v是w的孩子, low[v] < dfn[w] , 那么low[w] 肯定 等于 low[v], 从而 low[w] 小于本来的 low[w], 和前提矛盾。如果 low[v] >= dfn[w] , 则 w是一个关节点,那么w自然是一个界限,将刚才的连通子图和新生成的连通子图分开来,也就是前一个连通图的下界。

c语言编程求无向图的连通分支,无向图的连通分支相关推荐

  1. c语言编程所得票数,C语言编程求1X2X3····Xn所得的数末尾有多少个零

    C语言编程求1X2X3····Xn所得的数末尾有多少个零 发布时间:2020-08-10 02:23:57 来源:51CTO 阅读:312 作者:sonissa 参见大数的阶乘 https://blo ...

  2. python输入一个英文句子、统计单词个数_C语言编程求一个英文句子中的单词数和最长单词的位置、长度及输出这个单词。c++编程 从键盘输入一个英文...

    C语言编程求一个英文句子中的单词数和最长单词的位置.长度及输出这个单词. c++编程 从键盘输入一个英文 www.zhiqu.org     时间: 2020-11-23 我刚做了一关于英文句子里面每 ...

  3. c语言编程求二元一次方程组方程,二元一次方程组练习题 已知二元一次方程的三个系数,用C语言编程求方程的......

    导航:网站首页 > 二元一次方程组练习题 已知二元一次方程的三个系数,用C语言编程求方程的... 二元一次方程组练习题 已知二元一次方程的三个系数,用C语言编程求方程的... 相关问题: 匿名网 ...

  4. 用c语言分别输出1 2 3,用C语言编程求出1!+2!+3!+……+20!的值

    用C语言编程求出1!+2!+3!+--+20!的值 关注:189  答案:5  手机版 解决时间 2021-02-23 18:44 提问者妳熄滅叻菸,説啓従偂 2021-02-23 12:26 求一到 ...

  5. c语言程序设计阶乘输出,C语言编写10的阶乘,用C语言编程求10的阶乘

    导航:网站首页 > C语言编写10的阶乘,用C语言编程求10的阶乘 C语言编写10的阶乘,用C语言编程求10的阶乘 匿名网友: 思路:先定义一个函数求一个数的阶乘,接着依次从1到10调用该函数就 ...

  6. 1000以内所有同构数java算法_C语言编程求出1~1000的同构数

    2015-10-06 C语言问题.要求编程求出总成绩并按总成绩排? #include int main() { int i,j,k; int tempX,tempY; int res[6][2]={0 ...

  7. c语言编程求导纳矩阵,电力系统短路故障的计算机算法程序设计

    电力系统短路故障的计算机算法程序设计 电力系统分析课程设计报告书 题目: 电力系统短路故障的计算机算法程序设计 专 业:电气工程及其自动化 班 级: 学 号: 学生姓名: 指导教师: 2012年 3 ...

  8. c语言编程求连续几日的温差最大 最小值,数控维修理论题库(含答案)X2份..doc

    数控维修理论题库(含答案)X2份. 数控装调维修工中级理论复习题库(含答案) (一)单项选择 (选择一个正确的答案,将相应的字母填入题内的括号中.) 1.伺服系统是数控系统的执行部分,它包括伺服驱动单 ...

  9. c语言编程求百位和个位的差,对任意一个键盘输入的3位整数,求出它的个位、十位和百位。 一道c语言题目?...

    #include int main(){ int n; int d=0,t=0,h=0,m; scanf("%d",&n); m=n; d=n%10; n=n/10; t= ...

最新文章

  1. js在PageOffice打开的Word文档光标处插入书签
  2. vs2008 c++ 调用java
  3. 【若依(ruoyi)】layui upload
  4. 三禧科技 工业机器人_redmi note 9 即将发布,三剑齐发! 三禧科技
  5. J storm战队成员_DOTA2J.Storm战队介绍-DOTA2ESL孟买站预选赛J.Storm战队介绍_牛游戏网攻略...
  6. 19年8月 字母哥 第二章 RESTFul接口实现与测试 看到这里了
  7. python岗位 上海_上海黑马Python24期,平均薪资10150元,16个工作日就业率70.73%
  8. 简单实用的PS亮度蒙版工具:Lumenzia Mac版
  9. linux基础-第十六单元 yum管理RPM包
  10. [转载] python标准库系列教程(三)——operator库详细教程
  11. 洛谷——P1144 最短路计数
  12. 《Java8实战》读书笔记
  13. java找不到符号或方法,java 找不到符号解决方法
  14. 使用定积分计算三角形面积
  15. Android开机速度优化 Android 开机时间优化
  16. 微信小助手插件WeChatTweak
  17. Kubuntu安装N卡驱动教程
  18. 国外大学诸多自学课程
  19. setcontext
  20. 2021年SpringBoot面试题30道

热门文章

  1. 最近最火的《大秦赋》,用Python抓取相关数据,发现了秘密
  2. 管道无损检测python_利用神经网络技术检测半透明材料缺陷的新方法
  3. 微信绑定了信用卡,为什么吃饭用微信支付只能用零钱而不能用信用卡里面的钱?
  4. 当今对商鞅的评价_了解以客户为中心对当今程序员意味着什么
  5. 实验一 离散时间信号分析
  6. dacom蓝牙耳机怎么重置_DACOM蓝牙耳机怎么用
  7. 【笔记】《软件工程导论(第6版)》-张海藩、牟永敏
  8. dokcer镜像理解
  9. python 实现指定时间段录制视频
  10. KeyBERT和labse提取字符串中的关键词