Guava Graph遍历详解:拓扑排序、深度优先与广度优先遍历

1,233 阅读2分钟

介绍如何使用Guava的Graph库对图进行遍历。我们将详细讲解拓扑排序、深度优先搜索和广度优先搜索的实现方法,并在遍历过程中支持使用函数回调。以下是这三种遍历方式的Guava Graph实现示例:

1. 拓扑排序遍历

拓扑排序遍历是一种特殊的图遍历方法,用于对有向无环图(DAG)进行排序。以下是拓扑排序遍历的示例代码:

import com.google.common.graph.Graph;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class TopologyTraversal {
    public static <T> void topologyTraverse(Graph<T> graph, Function<T, Boolean> function) {
        // 1. 拓扑排序需要的辅助队列。
        Queue<T> queue = new LinkedList<>();

        // 2. 将root节点(入度为0的节点)入队列。
        graph.nodes().stream()
            .filter(task -> graph.inDegree(task) == 0)
            .forEach(node -> queue.add(node));

        // 3. 非root节点对应的入度记录表。
        Map<T, Integer> taskIntegerMap = graph.nodes().stream()
            .filter(task -> graph.inDegree(task) != 0)
            .collect(Collectors.toMap(node -> node, node -> graph.inDegree(node)));

        // 4. 拓扑遍历
        while (!queue.isEmpty()) {
            // 5. 从队列取节点。
            T current = queue.poll();

            // 6. 执行function逻辑。
            Boolean result = function.apply(current);

            // 7. 拓扑排序, 将当前节点的后继节点入度减一, 如果入度为0, 则加入队列, 等待调度。
            if (result) {
                graph.successors(current).forEach(node -> {
                    Integer i = taskIntegerMap.get(node);
                    if (--i == 0) {
                        taskIntegerMap.remove(node);
                        queue.offer(node);
                    } else {
                        taskIntegerMap.put(node, i);
                    }
                });
            }
        }
    }
}

2. 深度优先遍历

深度优先搜索是一种常见的图遍历算法,以下是Guava Graph深度优先遍历的示例代码:

import com.google.common.graph.Graph;
import java.util.*;
import java.util.function.Function;

public class DepthFirstTraversal {
    public static <T> void depthFirstTraverse(Graph<T> graph, T startNode, Function<T, Boolean> function) {
        dfs(graph, startNode, new HashSet<>(), function);
    }

    private static <T> void dfs(Graph<T> graph, T currentNode, Set<T> visited, Function<T, Boolean> function) {
        visited.add(currentNode);
        function.apply(currentNode);

        for (T neighbor : graph.adjacentNodes(currentNode)) {
            if (!visited.contains(neighbor)) {
                dfs(graph, neighbor, visited, function);
            }
        }
    }
}

3. 广度优先遍历

广度优先搜索是另一种常见的图遍历方法,以下是Guava Graph广度优先遍历的示例代码:

import com.google.common.graph.Graph;
import java.util.*;
import java.util.function.Function;

public class BreadthFirstTraversal {
    public static <T> void breadthFirstTraverse(Graph<T> graph, T startNode, Function<T, Boolean> function) {
        Set<T> visited = new HashSet<>();
        Queue<T> queue = new LinkedList<>();
        queue.add(startNode);
        visited.add(startNode);

        while (!queue.isEmpty()) {
            T current = queue.poll();
            function.apply(current);

            for (T neighbor : graph.adjacentNodes(current)) {
                if (!visited.contains(neighbor)) {
                    queue.add(neighbor);
                    visited.add(neighbor);
                }
            }
        }
    }
}

以上是拓扑排序、深度优先搜索和广度优先搜索在Guava Graph中的实现。这些实现都支持在遍历过程中使用函数回调,从而可以灵活地自定义节点处理逻辑。以下是一个使用这些遍历方法的例子:

import com.google.common.graph.Graph;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph;

public class GraphTraversalExample {
    public static void main(String[] args) {
        MutableGraph<String> graph = GraphBuilder.directed().build();
        graph.putEdge("A", "B");
        graph.putEdge("A", "C");
        graph.putEdge("B", "D");
        graph.putEdge("C", "D");
        graph.putEdge("D", "E");

        System.out.println("拓扑排序遍历:");
        TopologyTraversal.topologyTraverse(graph, node -> {
            System.out.println("Visited node: " + node);
            return true;
        });

        System.out.println("深度优先遍历:");
        DepthFirstTraversal.depthFirstTraverse(graph, "A", node -> {
            System.out.println("Visited node: " + node);
            return true;
        });

        System.out.println("广度优先遍历:");
        BreadthFirstTraversal.breadthFirstTraverse(graph, "A", node -> {
            System.out.println("Visited node: " + node);
            return true;
        });
    }
}

这个示例将创建一个简单的有向图,并分别使用拓扑排序、深度优先搜索和广度优先搜索进行遍历。在这个过程中,我们可以在回调函数中自定义节点处理逻辑,例如输出访问过的节点名称。