此文讨论一个无向图中存在环的问题,在不管多复杂的连通图中寻找出所有的环,使用删除点的方法。
此外,这个版本的查找方法可以用于其他场景:找出无向图中所有的环的算法
结果能找到最小的环,或许要靠运气,输出该输出的环...............,这是原始算法。
改进:
可以输出最大环(通过跳过多度点),可以输出最小子环(通过使用最短路径)......................
使用一个图:
比如在上图中,存在多个环(1,2,3,4,5,6)(6,7,8,9)(1,2,5,6)(1,2,3,5,6).........
怎么寻找呢?
一、使用删除边法
本例中,从顶点9开始,构建DFS
1.首先删除所有度数为1的点,这样点14就被删除。这样只剩下多度顶点的环
2. 若从顶点9开始,找到了环(9,8,6,7),对于顶点8和顶点7,其所有链接的边都在当前环上,则可以删除掉两个顶点;
而9除了处于环中的边,还有其他边,则不能删除。此外,顶点6也不能删除。
判断条件: 若顶点X链接的边在这一个环上,则可以删除此顶点。
例如:在图中,删除的点为7和8,同时所有的链接边也删除。输出环(9.8.7.6)。
3. 使用堆栈,再次查找10,可以输出环(10,11,12,13),同时可以删除顶点(11,12,13)。此时顶点10 的度置位1,标识为已遍历状态。
4.每次查找环,都删除度为1的节点和连接边。此时可以删除10,9,树退化为一颗子树。
5.到了一个查找重复环的时候
5.1. 此时没有单度节点,
5.2. 出现问题,得重新考虑了!!!
从顶点6开始找到所有的环:注意此过程依然为生成树的过程,每一个边都被设定为有向边。
Code :
// 寻找联通集合的闭包,判断是否连通,返回闭包 public static void findSub(Boolean adjM[][], Set<Integer> votex, ArrayList<Set<Integer>> loopSets) { if (votex.size() < 2) {// 治标不治本 return; } for (int m = 0; m < adjM.length;++m){ adjM[m][m] =false; } // 计算顶点的度 Integer[] degrees = new Integer[adjM.length]; degrees = calVotexDegree(adjM); //循环查找,找到所有的度不为1的闭包 boolean isdeleted = true; while( isdeleted ){ isdeleted = false; degrees = calVotexDegree(adjM); delete1Degree(adjM, degrees);// 删除度数为1 的边 int l1 = votex.size(); // 更新联通集合 for (int i = 0; i < degrees.length; ++i) { if (degrees[i] == 0) { votex.remove(i); for (int m = 0; m < adjM.length;++m){ adjM[i][m] = false;//更新度,若被移除则相关的邻接都置为false adjM[m][i] = false; } } } int l2 = votex.size(); if(l1!=l2) isdeleted = true; } // 遍历所有节点,逐个取出,去除非环节点 Set<Integer> loop = new HashSet<Integer>(); Set<Integer> sub = new HashSet<Integer>(); boolean isBlankX = false; boolean isFind = false; for (int ob : votex) { int obj = ob; sub.add(obj); int idx = obj; for (int j = idx; j < adjM[idx].length;) { if (adjM[idx][j]) { if (sub.contains(j) && idx != j) { transSet(sub, loop);// 若已存在元素,则存在环,复制出环 loopSets.add(loop); votex.removeAll(loop); sub.clear(); findSub(adjM, votex, loopSets); isFind = true; if (votex.size() < 3) {// 剩余两个就不再寻找 isBlankX = true; } ++j; } else { sub.add(j); ++j; } } else ++j; } if (isFind) break; } return; }
// 输出顶点的度 public static Integer[] calVotexDegree(Boolean adjM[][]) { Integer[] degrees = new Integer[adjM.length]; for (int i = 0; i < adjM.length; ++i) { degrees[i] = 0; for (int j = 0; j < adjM[i].length; ++j) { if (adjM[i][j] == true) { degrees[i] = degrees[i] + 1; } } } return degrees; }
// 更新矩阵:删除权值为1和0的边 public static void delete1Degree(Boolean[][] adjM, Integer[] degrees) { for (int j = 0; j < adjM.length; ++j) { adjM[j][j] = false;// 自身边消除掉 if (degrees[j] < 2) { for (int k = 0; k < adjM[j].length; ++k) { adjM[j][k] = false; adjM[k][j] = false; } degrees[j] = 0; } } }
输出结果:输出结果具有一定的概率性,和顶点的排序有关。