问题

Java 实现图的基本数据结构,并判断图中是否有环。

实现

GraphRelationPair 图中两个点的关系对实体类如下

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
/**
 * 图的关系对
 */
public class GraphRelationPair {
    // 当前节点
    private String point;
    
    // 下一个节点
    private String nextPoint;

    static GraphRelationPair of(String point, String nextPoint) {
        return new GraphRelationPair(point, nextPoint);
    }
}

如果需要添加附加信息,可以继承该类。

GraphUtil 图的工具类,其中实现了如下功能:
1、构建有向图的邻接表、判断有向图中是否有环;
2、构建无向图的邻接表、判断无向图中是否有环。

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;

import org.apache.commons.lang.StringUtils;

public class GraphUtil {
    public static void main(String[] args) {
        Set<GraphRelationPair> graph = buildData();
        // boolean haveLoop = GraphUtil.isHaveLoop(graph);
        // System.out.println(haveLoop);

        boolean dGHaveLoop = GraphUtil.dGHaveLoop(graph);
        System.out.println("【图】是否有环结果:" + dGHaveLoop);
    }

    private static Set<GraphRelationPair> buildData() {
        Set<GraphRelationPair> refs = new HashSet<>();
        refs.add(GraphRelationPair.of("0", "1"));
        refs.add(GraphRelationPair.of("0", "2"));
        refs.add(GraphRelationPair.of("0", "4"));
        refs.add(GraphRelationPair.of("1", "3"));
        refs.add(GraphRelationPair.of("2", "4"));
        refs.add(GraphRelationPair.of("4", "5"));
        refs.add(GraphRelationPair.of("5", "2"));
        refs.add(GraphRelationPair.of("5", "3"));
        return refs;
    }

    /**
     * 构建 有向图 邻接表
     * 
     * @return
     */
    public static Map<String, List<String>> builDGAdj(Set<GraphRelationPair> graph) {

        Map<String, List<String>> adj = new HashMap<String, List<String>>();
        if (Objects.isNull(graph) || graph.isEmpty()) {
            return adj;
        }
        for (GraphRelationPair edg : graph) {
            String node1 = edg.getPoint();
            String node2 = edg.getNextPoint();
            if (adj.get(node1) == null) {
                adj.put(node1, new ArrayList<>());
            }
            if (adj.get(node2) == null) {
                adj.put(node2, new ArrayList<>());
            }
            adj.get(node1).add(node2);
        }
        return adj;
    }

    /**
     * 有向图中判断是否有环
     * 
     * @param graph 图的连接边
     * @param n     图的节点个数
     * @return 是否存在环
     */
    public static boolean dGHaveLoop(Set<GraphRelationPair> graph) {
        // 习惯上转换成临接表的形式
        Map<String, List<String>> adj = builDGAdj(graph);
        // 定义一个节点状态数组 判断是否访问过
        Map<String, Boolean> visited = new HashMap<>();
        Stack<String> visitedStack = null;
        Set<String> keySet = adj.keySet();
        for (String key : keySet) {
            visited.put(key, false);
        }
        // 引用传递 函数内部修改值后退出函数可见
        for (String key : keySet) {
            visitedStack = new Stack<>();
            // 如果没有进行访问 则进行深度优先搜索回溯
            if (!visited.get(key)) {
                boolean dfs = dgDfsCycle(adj, key, "", visited, visitedStack);
                if (dfs) {
                    return true;
                } else {
                    visited.put(key, false);
                    visitedStack.pop();
                }
            }
        }
        return false;
    }

    /**
     * @param adj     图的临接表
     * @param current 当前节点
     * @param parent  父节点
     * @param visited 判断是否访问
     */
    private static boolean dgDfsCycle(Map<String, List<String>> adj, String current, String parent,
            Map<String, Boolean> visited, Stack<String> visitedStack) {
        // 首先 访问当前节点 并进行标记
        visited.put(current, true);
        visitedStack.push(current);

        // 获取到当前节点能够到达的所有节点
        List<String> list = adj.get(current);
        for (String can : list) {
            // 如果节点没有被访问过
            if (!visited.get(can)) {
                // 当前节点就是父节点,循环的节点就是子节点
                return dgDfsCycle(adj, can, current, visited, visitedStack);
            }
            // 在节点被访问过的情况下 说明有环
            else {
                return visitedStack.contains(can);
            }
        }
        return false;
    }

    /**
     * 构建无向图 邻接表
     * 
     * @return
     */
    public static Map<String, List<String>> buildUGAdj(Set<GraphRelationPair> graph) {

        Map<String, List<String>> adj = new HashMap<String, List<String>>();
        if (Objects.isNull(graph) || graph.isEmpty()) {
            return adj;
        }
        for (GraphRelationPair edg : graph) {
            String node1 = edg.getPoint();
            String node2 = edg.getNextPoint();
            if (adj.get(node1) == null) {
                adj.put(node1, new ArrayList<>());
            }
            if (adj.get(node2) == null) {
                adj.put(node2, new ArrayList<>());
            }
            adj.get(node1).add(node2);
            adj.get(node2).add(node1);
        }
        return adj;
    }

    /**
     * 无向图中判断是否有环
     * 
     * @param graph 图的连接边
     * @param n     图的节点个数
     * @return 是否存在环
     */
    public static boolean isHaveLoop(Set<GraphRelationPair> graph) {
        // 习惯上转换成临接表的形式
        Map<String, List<String>> adj = buildUGAdj(graph);
        // 定义一个节点状态数组 判断是否访问过
        Map<String, Boolean> visited = new HashMap<>();
        Set<String> keySet = adj.keySet();
        for (String key : keySet) {
            visited.put(key, false);
        }
        // 引用传递 函数内部修改值后退出函数可见
        int[] a = { 0 };
        for (String key : keySet) {
            // 如果没有进行访问 则进行深度优先搜索回溯
            if (visited.get(key) == false) {
                dfsCycle(adj, key, "", visited, a);
                // 只要有一次i循环时存在环路那就直接提前返回,说明存在环
                if (a[0] == 1) {
                    return true;
                }
            }
        }
        return a[0] == 1;
    }

    /**
     * @param adj     图的临接表
     * @param current 当前节点
     * @param parent  父节点
     * @param visited 判断是否访问
     * @param flag    是否存在环
     */
    private static void dfsCycle(Map<String, List<String>> adj, String current, String parent,
            Map<String, Boolean> visited, int[] flag) {
        // 首先 访问当前节点 并进行标记
        visited.put(current, true);
        // 获取到当前节点能够到达的所有节点
        List<String> list = adj.get(current);
        for (String can : list) {
            // 如果节点没有被访问过
            if (visited.get(can) == false) {
                // 当前节点就是父节点,循环的节点就是子节点
                dfsCycle(adj, can, current, visited, flag);
            }
            // 在节点被访问过的情况下 如果该节点不等于父节点 ,说明有环
            else if (!StringUtils.equals(can, parent)) {
                flag[0] = 1;
            }
        }
    }
}

邻接表

邻接表是一种表示图的数据结构,其中每个顶点都与其相邻顶点列表相关联。以下是一个示表表示:

顶点1: 2, 3, 4
顶点2: 1, 3
顶点3: 1, 2, 4
顶点4: 1, 3

在这个示例中,图由四个顶点组成。顶点1与顶点2、3、4相邻,顶点2与顶点1、3相邻,以此类推。邻接表,我们可以轻松地查找每个顶点的相邻顶点列表。

DFS 算法

DFS(Depth-First Search)即深度优先搜索,是一种用于图和树的遍历算法。它通过尽可能深地探索图的分支直到无法继续为止,然后回溯到上一个未完全探索的节点,继续探索其他分支。

以下是使用DFS算法遍历图的示例代码:

import java.util.*;

// 图的顶点类
class GraphNode {
    int val;
    List<GraphNode> neighbors;

    public GraphNode(int val) {
        this.val = val;
        this.neighbors = new ArrayList<>();
    }
}

public class DFS {
    private Set<GraphNode> visited // 用于记录访问过的顶点

    public DFS() {
        this.visited = new HashSet<>();
    }

    // 图的深度优先搜索
    public void depthFirstSearch(GraphNode node) {
        if (visited.contains(node)) {
            return;
        }

        visited.add(node);
        System.out.print(node.val + " ");

        for (GraphNode neighbor : node.neighbors) {
            depthFirstSearch(neighbor);
        }
    }

    public static void main(String[] args) {
        // 创建图的顶点
        GraphNode node1 = new GraphNode(1);
        GraphNode node2 = new GraphNode(2);
        GraphNode node3 = new GraphNode(3);
        GraphNode node4 = new GraphNode(4);

        // 设置顶点之间的关系
        node1.neighbors.add(node2);
        node1.neighbors.add(node3);
        node2.neighbors.add(node3);
        node3.neighbors.add(node4);

        DFS dfs = new DFS();
        dfs.depthFirstSearch(node1);
    }
}

在这个示例中,我们创建了一个有向图,并使用GraphNode类表示图的顶点。DFS类实现了深度优先搜索算法,其中depthFirstSearch方法接收一个图的顶点作为输入,并通过递归方式深度遍历与该顶点直接相连的顶点,并打印它们的值。

main方法中,我们创建了一个有向图,并设置了顶点之间的关系。然后创建DFS对象并调用depthFirstSearch方法,从图的始顶点开始进行深度优先搜索。

有向图

有向图(Directed Graph),也称为有向图形或有向网络,是一种图的类型,其中图中的边有方向。在有向图中,边从一个顶点指向另一个顶点,并且具有确定的方向,顶点表示图中的实体或节点,而有向边表示节点之间的有向关系或连接。每条边从一个顶点(称为起始顶点或源顶点)指向另一个顶点(称为终止顶点或目标顶点),因此存在一个从起始顶点到终止顶点的指向性。

与有向图不同,无向图中的边没有方向,可以双向通行。有向图可以用来表示许多现实世界中的关系,如网页之间的链接、交通路线、依赖关系等。

有向图可以用多种方式表示,包括邻接矩阵和邻接表。对于有向图的算法和遍历,需要考虑边的方向性。例如,深度优先搜索(DFS)和广度优先搜索(BFS)算法可以应用于有向图,但在遍历时需注意边的方向以防止形成死循环。

无向图

无向图(Undirected Graph),也称为无向图形,是一种图的类型,其中图中的边没有方向。在无向图中,边连接两个顶点,表示两个顶点之间的关系或连接,但没有指定从一个顶点到另一个顶点的单向性。

无向图可以看作是由无序的边集和顶点集组成的集合。每条边连接两个顶点,而两个顶点之间存在双向通行的能力。例如,如果存在一条边连接顶点A和顶点B,则可以从A到B,同时也可以从B到A。

无向图在许多情况下都有实际应用,如社交网络中的人际关系、计算机网络中的通信连接以及地图中的道路网络等。

无向图可以用多种方式表示,包括邻接矩阵和邻接表。对于无向图的算法和遍历,可以直接应用深度优先搜索(DFS)和广度优先搜索(BFS)等常用的图遍历算法。在遍历无向图时,不需要考虑边的方向性,因为边没有指定的方向。