Mind and Hand Help

Cycle Analyzer

环分析,核心思路是递归回溯

具体实现

class CycleAnalyzer(graph: Graph) : GraphCompute(graph) { companion object { val log = getLogger(CycleAnalyzer::class.java) } init { checkEmpty() } /** * 查找图中所有的环 */ fun findCycles(quickReturn: Boolean = false): Result { val record = Result(graph.isDirected()) val vertices = graph.getVertexes() val visited = mutableSetOf<String>() // 全遍历 for (vertex in vertices) { if (vertex.name !in visited) { dfs(vertex, visited, mutableListOf(), mutableSetOf(), record, 1, quickReturn) } } return record } private fun dfs( from: Vertex, visited: MutableSet<String>, path: MutableList<Vertex>, marked: MutableSet<String>, record: Result, depth: Int, quickReturn: Boolean ): Boolean { visited.add(from.name) marked.add(from.name) path.add(from) val indent = "$depth ${"- ".repeat(depth)}" log.debug( "{}开始遍历 {} 节点|visited={}|marked={}", indent, from.name, visited.joinToString(" "), marked.joinToString(" ") ) val edges = graph.adjacentEdges(from.id) // 1. 如果邻居未被访问,则递归调用 DFS 继续搜索(注意传递了路径的副本) // 2. 如果邻居已被访问且在当前递归栈中,说明找到了一个环。记录从邻居到当前顶点的路径作为环 edges.forEach { edge -> val to = edge.to log.debug("{}开始处理边 {} --> {}", indent, edge.from.name, to.name) if (to.name !in visited) { log.debug("{}没有访问过 {} 节点,深度遍历", indent, to.name) val hasCycle = dfs(to, visited, path.toMutableList(), marked, record, depth + 1, quickReturn) if (hasCycle && quickReturn) { return true } } else { if (to.name in marked) { log.debug("{}在节点 {} 发现环|处理的边为 {}->{}", indent, from.name, edge.from.name, to.name) if (quickReturn) { return true } // 说明有环 val cycle = mutableListOf<Vertex>() val start = path.indexOf(to) for (i in start until path.size) { cycle.add(path[i]) } record.addCycle(cycle) } else { // important log.debug("{}重新处理 {} 节点; 边为 {}->{}", indent, to.name, edge.from.name, to.name) dfs(to, visited, path.toMutableList(), marked, record, depth + 1, quickReturn) } } } // 出栈 log.debug( "{}出栈 {} 节点,因为不确定是否还有其他点 ??? --> {} |visited={}|marked={}", indent, from.name, from.name, visited.joinToString(" "), marked.joinToString(" ") ) marked.remove(from.name) log.debug( "{}完成处理 {} 节点|visited={}|marked={}", indent, from.name, visited.joinToString(" "), marked.joinToString(" ") ) return record.cycles.isNotEmpty() } /** * 环检测结果 */ class Result internal constructor(private val directed: Boolean) { private val _cycles = mutableListOf<List<Vertex>>() val cycles: List<List<Vertex>> get() = _cycles.toList() private val cycleZip = mutableSetOf<String>() internal fun addCycle(cycle: List<Vertex>) { if (!directed) { val zip = cycle.map { it.name } .sorted() .joinToString(" ") if (zip in cycleZip) { return } cycleZip.add(zip) _cycles.add(cycle.toList()) } else { _cycles.add(cycle.toList()) } } /** * 打印所有找到的环 */ fun printCycles() { println("Cycles Found|Cycle's Number = ${cycles.size}") cycles.forEach(::printCycle) } /** * 打印单个环 */ private fun printCycle(cycle: List<Vertex>) { println("Printing Cycle|Vertex's Number = ${cycle.size}|Vertexes = ${cycle.joinToString(" ") { it.name }}") val lineStart = " " // 打印环,如果只有两个节点,则打印成 A - B // 如果超过两个节点,打印成三行,打印出一个可以理解的环 if (cycle.size == 2) { println("$lineStart${cycle[0].name} <=> ${cycle[1].name}") println() return } /* A -> B -> C | | D - E - F */ // 是否是偶数 val isEven = (cycle.size % 2 == 0) val mid = if (isEven) cycle.size / 2 else cycle.size / 2 + 1 // 打印上半部分 val upper = cycle.subList(0, mid) // A -> B -> C val upBuilder = StringBuilder(lineStart) for (i in upper.indices) { if (i == upper.size - 1) { upBuilder.append(upper[i].name) } else { upBuilder.append(upper[i].name).append( if (directed) " -> " else " - " ) } } println(upBuilder) // 打印中间部分 val midStart = lineStart + if (directed) "↑" else "|" val midEnd = when { isEven -> if (directed) "↓" else "|" else -> if (directed) "↙" else "/" } val sub = when { isEven -> 2 directed -> 4 else -> 3 } println(midStart + " ".repeat(upBuilder.length - lineStart.length - sub) + midEnd) // 打印下半部分 // D - E - F val lower = cycle.subList(mid, cycle.size) val downBuilder = StringBuilder(lineStart) for (i in lower.indices.reversed()) { if (i == 0) { downBuilder.append(lower[i].name) } else { downBuilder.append(lower[i].name).append( if (directed) " <- " else " - " ) } } println(downBuilder) println() } } }
27 January 2026