问题
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)等常用的图遍历算法。在遍历无向图时,不需要考虑边的方向性,因为边没有指定的方向。