【算法】图相关算法
【一】问题案例
【1】问题描述
一个类Department有以下字段:String id,List relyDepIdList。每个类对象的id是唯一的,对应的relyDepIdList有多个,表示一个部门可以依赖于多个部门。如果依赖部门B不在给定的部门列表中,那么当前的部门就放在第一层。现在要根据部门的依赖关系分层,按层级批量存数据,保证被依赖的部门先存入数据库
【2】思路解析
(1)Map<String, Integer> depthMap存放最终的节点深度
(2)Map<String, Department> idToDepartmentMap存放的是全部节点的id映射
(3)当节点开始处理深度计算的时候,把节点加入visited,用来判断是否出现循环依赖
因为按照一个链路往下递归的时候,重复出现一个节点就表示出现了循环依赖
(4)计算最大深度的原理就是递归计算相关依赖子级的全部深度
【3】实现代码
java
/**
* 按依赖关系对部门进行分层
* 确保每一层的部门不依赖于同一层或更高层的部门
*
* @param departments 部门列表
* @return 分层后的部门列表,外层List表示层级,内层List表示该层的部门
*/
public List<List<Department>> layerDepartmentsByDependency(List<Department> departments) {
List<Department> departments = Arrays.asList(
new Department("yz001", Arrays.asList()),
new Department("yz002", Arrays.asList()),
new Department("yz003", Arrays.asList()),
new Department("ps001", Arrays.asList("yz001")),
new Department("ps002", Arrays.asList("yz002")),
new Department("ps003", Arrays.asList("yz003")),
new Department("ps004", Arrays.asList("yz004")),
new Department("fh001", Arrays.asList("ps001", "ps002", "yz003")),
new Department("fh002", Arrays.asList("ps002", "ps003", "yz004")),
new Department("fh003", Arrays.asList("fh001", "fh002", "yz003")),
new Department("fh004", Arrays.asList("fh003"))
);
// 创建ID到部门的映射
Map<String, Department> idToDepartmentMap = new HashMap<>();
for (Department dept : departments) {
idToDepartmentMap.put(dept.getId(), dept);
}
// 计算每个部门的依赖深度
Map<String, Integer> depthMap = new HashMap<>();
for (Department dept : departments) {
System.out.println(StrUtil.format("开始处理节点{}=============================================",dept.getId()));
calculateDepth(dept, idToDepartmentMap, depthMap, new HashSet<>());
System.out.println(StrUtil.format("结束处理节点{}=============================================",dept.getId()));
}
// 按深度分组
return groupByDepth(depthMap, idToDepartmentMap);
}
/**
* 计算部门的依赖深度(递归方法)
*/
private int calculateDepth(Department dept,
Map<String, Department> idToDepartmentMap,
Map<String, Integer> depthMap,
Set<String> visited) {
// 如果已经计算过深度,直接返回
if (depthMap.containsKey(dept.getId())) {
System.out.println(StrUtil.format("已经计算过深度:{},{}",dept.getId(),depthMap.get(dept.getId())));
return depthMap.get(dept.getId());
}
// 检测循环依赖
if (visited.contains(dept.getId())) {
throw new RuntimeException("检测到循环依赖,部门ID: " + dept.getId());
}
// 开始计算深度,当前节点加入,用来判断是否循环依赖
visited.add(dept.getId());
System.out.println(StrUtil.format("当前节点加入visited:{},当前visited:{}",dept.getId(),visited.stream().collect(Collectors.joining(","))));
int maxDepth = 0;
// 遍历所有依赖的部门,计算最大深度
for (String relyDepId : dept.getRelyDepIdList()) {
Department relyDept = idToDepartmentMap.get(relyDepId);
if (relyDept != null) {
int relyDepth = calculateDepth(relyDept, idToDepartmentMap, depthMap, visited);
maxDepth = Math.max(maxDepth, relyDepth + 1);
System.out.println(StrUtil.format("当前节点:{},依赖节点{},当前深度{}",dept.getId(),relyDepId,maxDepth));
}
}
System.out.println(StrUtil.format("当前节点移除visited:{}",dept.getId()));
visited.remove(dept.getId());
System.out.println(StrUtil.format("当前节点:{},最终深度{}",dept.getId(),maxDepth));
depthMap.put(dept.getId(), maxDepth);
return maxDepth;
}
/**
* 按深度对部门进行分组
*/
private List<List<Department>> groupByDepth(Map<String, Integer> depthMap,
Map<String, Department> idToDepartmentMap) {
// 找出最大深度
int maxDepth = depthMap.values().stream().max(Integer::compare).orElse(0);
// 初始化分层结果
List<List<Department>> layeredDepartments = new ArrayList<>();
for (int i = 0; i <= maxDepth; i++) {
layeredDepartments.add(new ArrayList<>());
}
// 按深度分组
for (Map.Entry<String, Integer> entry : depthMap.entrySet()) {
String deptId = entry.getKey();
int depth = entry.getValue();
Department dept = idToDepartmentMap.get(deptId);
if (dept != null) {
layeredDepartments.get(depth).add(dept);
}
}
return layeredDepartments;
}
@Getter
@Setter
@AllArgsConstructor
class Department {
String id;
String parentId;
Integer depth;
List<String> relyDepIdList;
}
}
实现日志
java
开始处理节点yz001=============================================
当前节点加入visited:yz001,当前visited:yz001
当前节点移除visited:yz001
当前节点:yz001,最终深度0
结束处理节点yz001=============================================
开始处理节点yz002=============================================
当前节点加入visited:yz002,当前visited:yz002
当前节点移除visited:yz002
当前节点:yz002,最终深度0
结束处理节点yz002=============================================
开始处理节点yz003=============================================
当前节点加入visited:yz003,当前visited:yz003
当前节点移除visited:yz003
当前节点:yz003,最终深度0
结束处理节点yz003=============================================
开始处理节点ps001=============================================
当前节点加入visited:ps001,当前visited:ps001
已经计算过深度:yz001,0
当前节点:ps001,依赖节点yz001,当前深度1
当前节点移除visited:ps001
当前节点:ps001,最终深度1
结束处理节点ps001=============================================
开始处理节点ps002=============================================
当前节点加入visited:ps002,当前visited:ps002
已经计算过深度:yz002,0
当前节点:ps002,依赖节点yz002,当前深度1
当前节点移除visited:ps002
当前节点:ps002,最终深度1
结束处理节点ps002=============================================
开始处理节点ps003=============================================
当前节点加入visited:ps003,当前visited:ps003
已经计算过深度:yz003,0
当前节点:ps003,依赖节点yz003,当前深度1
当前节点移除visited:ps003
当前节点:ps003,最终深度1
结束处理节点ps003=============================================
开始处理节点ps004=============================================
当前节点加入visited:ps004,当前visited:ps004
当前节点移除visited:ps004
当前节点:ps004,最终深度0
结束处理节点ps004=============================================
开始处理节点fh001=============================================
当前节点加入visited:fh001,当前visited:fh001
已经计算过深度:ps001,1
当前节点:fh001,依赖节点ps001,当前深度2
已经计算过深度:ps002,1
当前节点:fh001,依赖节点ps002,当前深度2
已经计算过深度:yz003,0
当前节点:fh001,依赖节点yz003,当前深度2
当前节点移除visited:fh001
当前节点:fh001,最终深度2
结束处理节点fh001=============================================
开始处理节点fh002=============================================
当前节点加入visited:fh002,当前visited:fh002
已经计算过深度:ps002,1
当前节点:fh002,依赖节点ps002,当前深度2
已经计算过深度:ps003,1
当前节点:fh002,依赖节点ps003,当前深度2
当前节点移除visited:fh002
当前节点:fh002,最终深度2
结束处理节点fh002=============================================
开始处理节点fh003=============================================
当前节点加入visited:fh003,当前visited:fh003
已经计算过深度:fh001,2
当前节点:fh003,依赖节点fh001,当前深度3
已经计算过深度:fh002,2
当前节点:fh003,依赖节点fh002,当前深度3
已经计算过深度:yz003,0
当前节点:fh003,依赖节点yz003,当前深度3
当前节点移除visited:fh003
当前节点:fh003,最终深度3
结束处理节点fh003=============================================
开始处理节点fh004=============================================
当前节点加入visited:fh004,当前visited:fh004
已经计算过深度:fh003,3
当前节点:fh004,依赖节点fh003,当前深度4
当前节点移除visited:fh004
当前节点:fh004,最终深度4
结束处理节点fh004=============================================
【二】图的相关算法
【1】封装方法
(1)遍历算法:
深度优先遍历(递归/迭代)
广度优先遍历(带层级)
(2)路径计算:
最短路径(BFS)
最长路径(拓扑排序+DAG)
所有最短路径
(3)循环依赖检测:
检测是否存在循环依赖
获取所有循环路径
(4)拓扑排序:
DFS实现
Kahn算法实现
(5)连通性分析:
强连通分量(Kosaraju算法)
可达性分析
影响性分析
(6)实用工具:
依赖关系查询
路径存在性检查
图统计信息
java
import java.util.*;
import java.util.stream.Collectors;
/**
* 部门依赖关系图工具类
* 封装了图的遍历、路径计算、循环依赖检测等实用功能
*/
public class DepartmentGraph {
private final Map<String, Department> idToDepartmentMap;
private final Map<String, List<String>> adjacencyList;
public DepartmentGraph(List<Department> departments) {
this.idToDepartmentMap = new HashMap<>();
this.adjacencyList = new HashMap<>();
buildGraph(departments);
}
/**
* 构建图结构
*/
private void buildGraph(List<Department> departments) {
// 创建部门ID到部门的映射
for (Department dept : departments) {
idToDepartmentMap.put(dept.getId(), dept);
adjacencyList.put(dept.getId(), new ArrayList<>());
}
// 构建邻接表(依赖关系)
for (Department dept : departments) {
for (String relyId : dept.getRelyDepIdList()) {
adjacencyList.get(dept.getId()).add(relyId);
}
}
}
// ==================== 基础遍历算法 ====================
/**
* 深度优先遍历(递归实现)
*/
public List<String> dfsTraversal(String startDeptId) {
if (!idToDepartmentMap.containsKey(startDeptId)) {
throw new IllegalArgumentException("部门ID不存在: " + startDeptId);
}
List<String> result = new ArrayList<>();
Set<String> visited = new HashSet<>();
dfsRecursive(startDeptId, visited, result);
return result;
}
private void dfsRecursive(String currentId, Set<String> visited, List<String> result) {
if (visited.contains(currentId)) return;
visited.add(currentId);
result.add(currentId);
for (String neighbor : adjacencyList.get(currentId)) {
if (idToDepartmentMap.containsKey(neighbor)) {
dfsRecursive(neighbor, visited, result);
}
}
}
/**
* 深度优先遍历(迭代实现)
*/
public List<String> dfsIterative(String startDeptId) {
if (!idToDepartmentMap.containsKey(startDeptId)) {
throw new IllegalArgumentException("部门ID不存在: " + startDeptId);
}
List<String> result = new ArrayList<>();
Set<String> visited = new HashSet<>();
Stack<String> stack = new Stack<>();
stack.push(startDeptId);
while (!stack.isEmpty()) {
String current = stack.pop();
if (!visited.contains(current)) {
visited.add(current);
result.add(current);
// 将邻居逆序入栈以保证顺序一致性
List<String> neighbors = adjacencyList.get(current);
for (int i = neighbors.size() - 1; i >= 0; i--) {
String neighbor = neighbors.get(i);
if (idToDepartmentMap.containsKey(neighbor) && !visited.contains(neighbor)) {
stack.push(neighbor);
}
}
}
}
return result;
}
/**
* 广度优先遍历
*/
public List<String> bfsTraversal(String startDeptId) {
if (!idToDepartmentMap.containsKey(startDeptId)) {
throw new IllegalArgumentException("部门ID不存在: " + startDeptId);
}
List<String> result = new ArrayList<>();
Set<String> visited = new HashSet<>();
Queue<String> queue = new LinkedList<>();
visited.add(startDeptId);
queue.offer(startDeptId);
while (!queue.isEmpty()) {
String current = queue.poll();
result.add(current);
for (String neighbor : adjacencyList.get(current)) {
if (idToDepartmentMap.containsKey(neighbor) && !visited.contains(neighbor)) {
visited.add(neighbor);
queue.offer(neighbor);
}
}
}
return result;
}
/**
* 带层级的广度优先遍历
*/
public Map<Integer, List<String>> bfsWithLevels(String startDeptId) {
if (!idToDepartmentMap.containsKey(startDeptId)) {
throw new IllegalArgumentException("部门ID不存在: " + startDeptId);
}
Map<Integer, List<String>> levels = new HashMap<>();
Set<String> visited = new HashSet<>();
Queue<String> queue = new LinkedList<>();
Map<String, Integer> distance = new HashMap<>();
visited.add(startDeptId);
distance.put(startDeptId, 0);
queue.offer(startDeptId);
// 初始化第0层
levels.put(0, new ArrayList<>(Collections.singletonList(startDeptId)));
while (!queue.isEmpty()) {
String current = queue.poll();
int currentLevel = distance.get(current);
for (String neighbor : adjacencyList.get(current)) {
if (idToDepartmentMap.containsKey(neighbor) && !visited.contains(neighbor)) {
visited.add(neighbor);
distance.put(neighbor, currentLevel + 1);
queue.offer(neighbor);
// 添加到对应层级
levels.computeIfAbsent(currentLevel + 1, k -> new ArrayList<>()).add(neighbor);
}
}
}
return levels;
}
// ==================== 路径计算 ====================
/**
* 计算两个部门之间的最短路径(依赖路径)
*/
public List<String> shortestPath(String fromDeptId, String toDeptId) {
if (!idToDepartmentMap.containsKey(fromDeptId) || !idToDepartmentMap.containsKey(toDeptId)) {
throw new IllegalArgumentException("部门ID不存在");
}
if (fromDeptId.equals(toDeptId)) {
return Collections.singletonList(fromDeptId);
}
// BFS寻找最短路径
Map<String, String> predecessor = new HashMap<>();
Set<String> visited = new HashSet<>();
Queue<String> queue = new LinkedList<>();
visited.add(fromDeptId);
queue.offer(fromDeptId);
predecessor.put(fromDeptId, null);
boolean found = false;
while (!queue.isEmpty() && !found) {
String current = queue.poll();
for (String neighbor : adjacencyList.get(current)) {
if (idToDepartmentMap.containsKey(neighbor) && !visited.contains(neighbor)) {
visited.add(neighbor);
predecessor.put(neighbor, current);
queue.offer(neighbor);
if (neighbor.equals(toDeptId)) {
found = true;
break;
}
}
}
}
if (!found) {
return Collections.emptyList(); // 不可达
}
// 重构路径
return reconstructPath(predecessor, toDeptId);
}
/**
* 计算两个部门之间的最长路径(关键路径)
* 注意:仅适用于有向无环图(DAG)
*/
public List<String> longestPath(String fromDeptId, String toDeptId) {
if (!idToDepartmentMap.containsKey(fromDeptId) || !idToDepartmentMap.containsKey(toDeptId)) {
throw new IllegalArgumentException("部门ID不存在");
}
// 先检测循环依赖
if (hasCycle()) {
throw new IllegalStateException("图中存在循环依赖,无法计算最长路径");
}
// 拓扑排序
List<String> topologicalOrder = topologicalSort();
// 初始化距离
Map<String, Integer> dist = new HashMap<>();
Map<String, String> predecessor = new HashMap<>();
for (String deptId : idToDepartmentMap.keySet()) {
dist.put(deptId, Integer.MIN_VALUE);
}
dist.put(fromDeptId, 0);
// 按照拓扑顺序处理每个顶点
for (String deptId : topologicalOrder) {
if (dist.get(deptId) != Integer.MIN_VALUE) {
for (String neighbor : adjacencyList.get(deptId)) {
if (dist.get(neighbor) < dist.get(deptId) + 1) {
dist.put(neighbor, dist.get(deptId) + 1);
predecessor.put(neighbor, deptId);
}
}
}
}
if (dist.get(toDeptId) == Integer.MIN_VALUE) {
return Collections.emptyList(); // 不可达
}
return reconstructPath(predecessor, toDeptId);
}
/**
* 计算所有部门到指定部门的最短路径
*/
public Map<String, List<String>> allShortestPathsTo(String targetDeptId) {
if (!idToDepartmentMap.containsKey(targetDeptId)) {
throw new IllegalArgumentException("部门ID不存在: " + targetDeptId);
}
Map<String, List<String>> allPaths = new HashMap<>();
for (String deptId : idToDepartmentMap.keySet()) {
if (!deptId.equals(targetDeptId)) {
List<String> path = shortestPath(deptId, targetDeptId);
if (!path.isEmpty()) {
allPaths.put(deptId, path);
}
}
}
return allPaths;
}
// ==================== 循环依赖检测 ====================
/**
* 检测图中是否存在循环依赖
*/
public boolean hasCycle() {
Set<String> visited = new HashSet<>();
Set<String> recursionStack = new HashSet<>();
for (String deptId : idToDepartmentMap.keySet()) {
if (!visited.contains(deptId)) {
if (hasCycleDFS(deptId, visited, recursionStack)) {
return true;
}
}
}
return false;
}
/**
* 获取所有循环依赖
*/
public List<List<String>> getAllCycles() {
List<List<String>> cycles = new ArrayList<>();
Set<String> visited = new HashSet<>();
Set<String> recursionStack = new HashSet<>();
Map<String, String> parent = new HashMap<>();
for (String deptId : idToDepartmentMap.keySet()) {
if (!visited.contains(deptId)) {
findCyclesDFS(deptId, visited, recursionStack, parent, cycles);
}
}
return cycles;
}
private boolean hasCycleDFS(String current, Set<String> visited, Set<String> recursionStack) {
if (recursionStack.contains(current)) {
return true;
}
if (visited.contains(current)) {
return false;
}
visited.add(current);
recursionStack.add(current);
for (String neighbor : adjacencyList.get(current)) {
if (idToDepartmentMap.containsKey(neighbor)) {
if (hasCycleDFS(neighbor, visited, recursionStack)) {
return true;
}
}
}
recursionStack.remove(current);
return false;
}
private void findCyclesDFS(String current, Set<String> visited, Set<String> recursionStack,
Map<String, String> parent, List<List<String>> cycles) {
visited.add(current);
recursionStack.add(current);
for (String neighbor : adjacencyList.get(current)) {
if (!idToDepartmentMap.containsKey(neighbor)) continue;
if (!visited.contains(neighbor)) {
parent.put(neighbor, current);
findCyclesDFS(neighbor, visited, recursionStack, parent, cycles);
} else if (recursionStack.contains(neighbor)) {
// 发现环
List<String> cycle = new ArrayList<>();
String node = current;
while (!node.equals(neighbor)) {
cycle.add(node);
node = parent.get(node);
}
cycle.add(neighbor);
cycle.add(current); // 闭合环
Collections.reverse(cycle);
cycles.add(cycle);
}
}
recursionStack.remove(current);
}
// ==================== 拓扑排序 ====================
/**
* 拓扑排序(仅适用于无环图)
*/
public List<String> topologicalSort() {
if (hasCycle()) {
throw new IllegalStateException("图中存在循环依赖,无法进行拓扑排序");
}
List<String> result = new ArrayList<>();
Set<String> visited = new HashSet<>();
for (String deptId : idToDepartmentMap.keySet()) {
if (!visited.contains(deptId)) {
topologicalSortDFS(deptId, visited, result);
}
}
Collections.reverse(result);
return result;
}
private void topologicalSortDFS(String current, Set<String> visited, List<String> result) {
visited.add(current);
for (String neighbor : adjacencyList.get(current)) {
if (idToDepartmentMap.containsKey(neighbor) && !visited.contains(neighbor)) {
topologicalSortDFS(neighbor, visited, result);
}
}
result.add(current);
}
/**
* Kahn算法拓扑排序
*/
public List<String> topologicalSortKahn() {
// 计算入度
Map<String, Integer> inDegree = new HashMap<>();
for (String deptId : idToDepartmentMap.keySet()) {
inDegree.put(deptId, 0);
}
for (String deptId : idToDepartmentMap.keySet()) {
for (String neighbor : adjacencyList.get(deptId)) {
if (idToDepartmentMap.containsKey(neighbor)) {
inDegree.put(neighbor, inDegree.get(neighbor) + 1);
}
}
}
// 初始化队列(入度为0的顶点)
Queue<String> queue = new LinkedList<>();
for (Map.Entry<String, Integer> entry : inDegree.entrySet()) {
if (entry.getValue() == 0) {
queue.offer(entry.getKey());
}
}
List<String> result = new ArrayList<>();
int count = 0;
while (!queue.isEmpty()) {
String current = queue.poll();
result.add(current);
count++;
for (String neighbor : adjacencyList.get(current)) {
if (idToDepartmentMap.containsKey(neighbor)) {
inDegree.put(neighbor, inDegree.get(neighbor) - 1);
if (inDegree.get(neighbor) == 0) {
queue.offer(neighbor);
}
}
}
}
if (count != idToDepartmentMap.size()) {
throw new IllegalStateException("图中存在循环依赖,无法进行拓扑排序");
}
return result;
}
// ==================== 连通性分析 ====================
/**
* 获取强连通分量(Kosaraju算法)
*/
public List<List<String>> getStronglyConnectedComponents() {
// 第一步:DFS遍历获取完成时间
Stack<String> stack = new Stack<>();
Set<String> visited = new HashSet<>();
for (String deptId : idToDepartmentMap.keySet()) {
if (!visited.contains(deptId)) {
fillOrderDFS(deptId, visited, stack);
}
}
// 第二步:转置图
Map<String, List<String>> transposedGraph = transposeGraph();
// 第三步:按照完成时间逆序DFS遍历转置图
visited.clear();
List<List<String>> sccList = new ArrayList<>();
while (!stack.isEmpty()) {
String deptId = stack.pop();
if (!visited.contains(deptId)) {
List<String> scc = new ArrayList<>();
dfsTransposed(deptId, visited, transposedGraph, scc);
sccList.add(scc);
}
}
return sccList;
}
private void fillOrderDFS(String current, Set<String> visited, Stack<String> stack) {
visited.add(current);
for (String neighbor : adjacencyList.get(current)) {
if (idToDepartmentMap.containsKey(neighbor) && !visited.contains(neighbor)) {
fillOrderDFS(neighbor, visited, stack);
}
}
stack.push(current);
}
private Map<String, List<String>> transposeGraph() {
Map<String, List<String>> transposed = new HashMap<>();
for (String deptId : idToDepartmentMap.keySet()) {
transposed.put(deptId, new ArrayList<>());
}
for (String deptId : idToDepartmentMap.keySet()) {
for (String neighbor : adjacencyList.get(deptId)) {
if (idToDepartmentMap.containsKey(neighbor)) {
transposed.get(neighbor).add(deptId);
}
}
}
return transposed;
}
private void dfsTransposed(String current, Set<String> visited,
Map<String, List<String>> graph, List<String> component) {
visited.add(current);
component.add(current);
for (String neighbor : graph.get(current)) {
if (!visited.contains(neighbor)) {
dfsTransposed(neighbor, visited, graph, component);
}
}
}
/**
* 获取从指定部门可达的所有部门
*/
public Set<String> getReachableDepartments(String startDeptId) {
if (!idToDepartmentMap.containsKey(startDeptId)) {
throw new IllegalArgumentException("部门ID不存在: " + startDeptId);
}
Set<String> reachable = new HashSet<>();
Queue<String> queue = new LinkedList<>();
reachable.add(startDeptId);
queue.offer(startDeptId);
while (!queue.isEmpty()) {
String current = queue.poll();
for (String neighbor : adjacencyList.get(current)) {
if (idToDepartmentMap.containsKey(neighbor) && !reachable.contains(neighbor)) {
reachable.add(neighbor);
queue.offer(neighbor);
}
}
}
return reachable;
}
/**
* 获取影响指定部门的所有部门(反向可达)
*/
public Set<String> getAffectingDepartments(String targetDeptId) {
if (!idToDepartmentMap.containsKey(targetDeptId)) {
throw new IllegalArgumentException("部门ID不存在: " + targetDeptId);
}
// 使用转置图进行BFS
Map<String, List<String>> transposedGraph = transposeGraph();
Set<String> affecting = new HashSet<>();
Queue<String> queue = new LinkedList<>();
affecting.add(targetDeptId);
queue.offer(targetDeptId);
while (!queue.isEmpty()) {
String current = queue.poll();
for (String neighbor : transposedGraph.get(current)) {
if (!affecting.contains(neighbor)) {
affecting.add(neighbor);
queue.offer(neighbor);
}
}
}
return affecting;
}
// ==================== 实用工具方法 ====================
/**
* 获取部门的直接依赖
*/
public List<Department> getDirectDependencies(String deptId) {
if (!idToDepartmentMap.containsKey(deptId)) {
throw new IllegalArgumentException("部门ID不存在: " + deptId);
}
return adjacencyList.get(deptId).stream()
.filter(idToDepartmentMap::containsKey)
.map(idToDepartmentMap::get)
.collect(Collectors.toList());
}
/**
* 获取部门的所有依赖(直接和间接)
*/
public Set<Department> getAllDependencies(String deptId) {
Set<String> reachableIds = getReachableDepartments(deptId);
reachableIds.remove(deptId); // 排除自身
return reachableIds.stream()
.map(idToDepartmentMap::get)
.collect(Collectors.toSet());
}
/**
* 获取依赖指定部门的所有部门
*/
public Set<Department> getAllDependents(String deptId) {
Set<String> affectingIds = getAffectingDepartments(deptId);
affectingIds.remove(deptId); // 排除自身
return affectingIds.stream()
.map(idToDepartmentMap::get)
.collect(Collectors.toSet());
}
/**
* 检查两个部门之间是否存在依赖路径
*/
public boolean hasDependencyPath(String fromDeptId, String toDeptId) {
if (!idToDepartmentMap.containsKey(fromDeptId) || !idToDepartmentMap.containsKey(toDeptId)) {
return false;
}
Set<String> reachable = getReachableDepartments(fromDeptId);
return reachable.contains(toDeptId);
}
/**
* 获取图的统计信息
*/
public GraphStatistics getStatistics() {
int vertexCount = idToDepartmentMap.size();
int edgeCount = adjacencyList.values().stream()
.mapToInt(List::size)
.sum();
boolean hasCycle = hasCycle();
List<List<String>> sccList = getStronglyConnectedComponents();
int sccCount = sccList.size();
return new GraphStatistics(vertexCount, edgeCount, hasCycle, sccCount);
}
// ==================== 辅助方法 ====================
private List<String> reconstructPath(Map<String, String> predecessor, String target) {
LinkedList<String> path = new LinkedList<>();
String current = target;
while (current != null) {
path.addFirst(current);
current = predecessor.get(current);
}
return new ArrayList<>(path);
}
// ==================== 内部类和工具类 ====================
/**
* 部门类
*/
public static class Department {
private final String id;
private final List<String> relyDepIdList;
public Department(String id, List<String> relyDepIdList) {
this.id = id;
this.relyDepIdList = new ArrayList<>(relyDepIdList);
}
public String getId() { return id; }
public List<String> getRelyDepIdList() { return new ArrayList<>(relyDepIdList); }
@Override
public String toString() {
return "Department{id='" + id + "', dependencies=" + relyDepIdList + "}";
}
}
/**
* 图统计信息
*/
public static class GraphStatistics {
private final int vertexCount;
private final int edgeCount;
private final boolean hasCycle;
private final int stronglyConnectedComponents;
public GraphStatistics(int vertexCount, int edgeCount, boolean hasCycle, int stronglyConnectedComponents) {
this.vertexCount = vertexCount;
this.edgeCount = edgeCount;
this.hasCycle = hasCycle;
this.stronglyConnectedComponents = stronglyConnectedComponents;
}
// Getters
public int getVertexCount() { return vertexCount; }
public int getEdgeCount() { return edgeCount; }
public boolean isHasCycle() { return hasCycle; }
public int getStronglyConnectedComponents() { return stronglyConnectedComponents; }
@Override
public String toString() {
return String.format(
"图统计: 顶点数=%d, 边数=%d, 是否有环=%s, 强连通分量数=%d",
vertexCount, edgeCount, hasCycle, stronglyConnectedComponents
);
}
}
}
【2】测试案例
java
/**
* 部门图工具类使用示例
*/
public class DepartmentGraphExample {
public static void main(String[] args) {
// 创建测试数据
List<DepartmentGraph.Department> departments = createTestDepartments();
// 创建图
DepartmentGraph graph = new DepartmentGraph(departments);
// 1. 基本遍历
System.out.println("=== 基本遍历 ===");
System.out.println("DFS遍历: " + graph.dfsTraversal("D1"));
System.out.println("BFS遍历: " + graph.bfsTraversal("D1"));
// 2. 路径计算
System.out.println("\n=== 路径计算 ===");
System.out.println("最短路径 D1->D4: " + graph.shortestPath("D1", "D4"));
System.out.println("是否存在路径 D1->D5: " + graph.hasDependencyPath("D1", "D5"));
// 3. 循环依赖检测
System.out.println("\n=== 循环依赖检测 ===");
System.out.println("是否存在循环依赖: " + graph.hasCycle());
System.out.println("所有循环: " + graph.getAllCycles());
// 4. 拓扑排序
System.out.println("\n=== 拓扑排序 ===");
try {
System.out.println("拓扑排序: " + graph.topologicalSort());
} catch (IllegalStateException e) {
System.out.println("无法进行拓扑排序: " + e.getMessage());
}
// 5. 连通性分析
System.out.println("\n=== 连通性分析 ===");
System.out.println("强连通分量: " + graph.getStronglyConnectedComponents());
System.out.println("D1可达的部门: " + graph.getReachableDepartments("D1"));
System.out.println("影响D4的部门: " + graph.getAffectingDepartments("D4"));
// 6. 统计信息
System.out.println("\n=== 统计信息 ===");
System.out.println(graph.getStatistics());
// 7. 实用功能
System.out.println("\n=== 实用功能 ===");
System.out.println("D1的所有依赖: " +
graph.getAllDependencies("D1").stream()
.map(DepartmentGraph.Department::getId)
.collect(Collectors.toList()));
}
private static List<DepartmentGraph.Department> createTestDepartments() {
List<DepartmentGraph.Department> departments = new ArrayList<>();
// 创建测试部门数据
departments.add(new DepartmentGraph.Department("D1", Arrays.asList("D2", "D3")));
departments.add(new DepartmentGraph.Department("D2", Arrays.asList("D4")));
departments.add(new DepartmentGraph.Department("D3", Arrays.asList("D4", "D5")));
departments.add(new DepartmentGraph.Department("D4", Arrays.asList("D6")));
departments.add(new DepartmentGraph.Department("D5", Arrays.asList()));
departments.add(new DepartmentGraph.Department("D6", Arrays.asList("D2"))); // 创建循环依赖
return departments;
}
}
【三】递归算法
【1】实现递归算法的思路
递归指的是函数直接或间接调用自身的方法。递归通常用于解决可以分解为相同问题的子问题的问题。理解递归的关键在于理解如何将问题分解为更小的相同问题,并设置递归终止条件,避免无限递归。
(1)定义递归函数:明确函数的功能、参数和返回值。
(2)找出递归终止条件:考虑最简单的情况,直接返回结果。
(3)缩小问题规模:将原问题分解为一个或多个子问题,这些子问题与原问题具有相同的结构,但规模更小。
(4)组合子问题的结果:将子问题的结果组合起来,形成原问题的解。
递归的三要素
(1)明确的终止条件
(2)问题规模不断缩小
(3)自身调用
【2】递归的理解方法
java
// 递归模板
返回值类型 递归方法(参数) {
// 1. 基准情况处理
if (满足终止条件) {
return 基准结果;
}
// 2. 问题分解
将大问题分解为小问题;
// 3. 递归调用
返回值 = 递归方法(缩小的问题规模);
// 4. 结果组合
return 组合结果;
}
【3】递归案例
(1)阶乘计算
java
public class FactorialExample {
/**
* 计算n的阶乘(递归实现)
* 时间复杂度:O(n)
* 空间复杂度:O(n) - 调用栈深度
*/
public static long factorial(int n) {
// 基准情况
if (n == 0 || n == 1) {
return 1;
}
// 递归情况:n! = n * (n-1)!
return n * factorial(n - 1);
}
/**
* 阶乘的迭代实现(对比)
*/
public static long factorialIterative(int n) {
long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
public static void main(String[] args) {
System.out.println("5! = " + factorial(5)); // 120
System.out.println("0! = " + factorial(0)); // 1
}
}
(2)斐波那契数列
java
public class FibonacciExample {
/**
* 斐波那契数列 - 朴素递归(性能差)
* F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)
* 时间复杂度:O(2^n) - 指数级
*/
public static long fibonacciNaive(int n) {
// 基准情况
if (n == 0) return 0;
if (n == 1) return 1;
// 递归情况
return fibonacciNaive(n - 1) + fibonacciNaive(n - 2);
}
/**
* 斐波那契数列 - 记忆化递归(性能优化)
* 时间复杂度:O(n)
*/
public static long fibonacciMemo(int n) {
long[] memo = new long[n + 1];
Arrays.fill(memo, -1);
return fibonacciHelper(n, memo);
}
private static long fibonacciHelper(int n, long[] memo) {
// 基准情况
if (n == 0) return 0;
if (n == 1) return 1;
// 如果已经计算过,直接返回结果
if (memo[n] != -1) {
return memo[n];
}
// 计算并存储结果
memo[n] = fibonacciHelper(n - 1, memo) + fibonacciHelper(n - 2, memo);
return memo[n];
}
public static void main(String[] args) {
System.out.println("F(10) = " + fibonacciMemo(10)); // 55
// 性能对比
long start = System.currentTimeMillis();
System.out.println("F(40) = " + fibonacciMemo(40)); // 102334155
long end = System.currentTimeMillis();
System.out.println("记忆化递归耗时: " + (end - start) + "ms");
}
}
(3)二叉树遍历
java
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) {
this.val = val;
}
}
public class TreeRecursion {
/**
* 递归前序遍历:根 -> 左 -> 右
*/
public static void preorderTraversal(TreeNode root, List<Integer> result) {
if (root == null) return; // 基准情况
result.add(root.val); // 访问根节点
preorderTraversal(root.left, result); // 递归左子树
preorderTraversal(root.right, result); // 递归右子树
}
/**
* 计算二叉树深度
*/
public static int treeDepth(TreeNode root) {
if (root == null) {
return 0; // 基准情况:空树深度为0
}
int leftDepth = treeDepth(root.left); // 左子树深度
int rightDepth = treeDepth(root.right); // 右子树深度
return Math.max(leftDepth, rightDepth) + 1; // 当前节点深度 = 最大子树深度 + 1
}
/**
* 判断二叉树是否对称
*/
public static boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return isMirror(root.left, root.right);
}
private static boolean isMirror(TreeNode t1, TreeNode t2) {
// 基准情况
if (t1 == null && t2 == null) return true;
if (t1 == null || t2 == null) return false;
if (t1.val != t2.val) return false;
// 递归情况:比较镜像位置
return isMirror(t1.left, t2.right) && isMirror(t1.right, t2.left);
}
}
(4)链表递归操作
java
class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
}
public class LinkedListRecursion {
/**
* 递归反转链表
*/
public static ListNode reverseList(ListNode head) {
// 基准情况:空链表或单个节点
if (head == null || head.next == null) {
return head;
}
// 递归反转剩余部分
ListNode newHead = reverseList(head.next);
// 将当前节点连接到反转后的链表末尾
head.next.next = head;
head.next = null;
return newHead;
}
/**
* 递归遍历链表
*/
public static void printList(ListNode head) {
if (head == null) {
System.out.println("null");
return;
}
System.out.print(head.val + " -> ");
printList(head.next);
}
}
(5)归并排序
java
public class MergeSortRecursion {
/**
* 归并排序 - 典型的分治递归算法
*/
public static void mergeSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return; // 基准情况
}
mergeSort(arr, 0, arr.length - 1);
}
private static void mergeSort(int[] arr, int left, int right) {
// 基准情况:单个元素或空数组
if (left >= right) return;
// 分:找到中间点
int mid = left + (right - left) / 2;
// 治:递归排序左右两半
mergeSort(arr, left, mid); // 递归排序左半部分
mergeSort(arr, mid + 1, right); // 递归排序右半部分
// 合:合并两个有序数组
merge(arr, left, mid, right);
}
private static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
// 合并两个有序数组
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
// 复制剩余元素
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
// 将临时数组复制回原数组
System.arraycopy(temp, 0, arr, left, temp.length);
}
}
(6)快速排序
java
public class QuickSortRecursion {
/**
* 快速排序 - 另一种分治递归算法
*/
public static void quickSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
quickSort(arr, 0, arr.length - 1);
}
private static void quickSort(int[] arr, int low, int high) {
// 基准情况
if (low >= high) return;
// 分区操作
int pivotIndex = partition(arr, low, high);
// 递归排序分区
quickSort(arr, low, pivotIndex - 1); // 递归排序左分区
quickSort(arr, pivotIndex + 1, high); // 递归排序右分区
}
private static int partition(int[] arr, int low, int high) {
int pivot = arr[high]; // 选择最后一个元素作为基准
int i = low - 1; // 较小元素的索引
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, high);
return i + 1;
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
(7)N皇后问题
java
public class NQueensRecursion {
/**
* 解决N皇后问题
*/
public static List<List<String>> solveNQueens(int n) {
List<List<String>> result = new ArrayList<>();
char[][] board = new char[n][n];
// 初始化棋盘
for (int i = 0; i < n; i++) {
Arrays.fill(board[i], '.');
}
backtrack(board, 0, result);
return result;
}
private static void backtrack(char[][] board, int row, List<List<String>> result) {
// 基准情况:所有行都放置了皇后
if (row == board.length) {
result.add(constructSolution(board));
return;
}
// 在当前行的每一列尝试放置皇后
for (int col = 0; col < board.length; col++) {
if (isValid(board, row, col)) {
// 放置皇后
board[row][col] = 'Q';
// 递归放置下一行的皇后
backtrack(board, row + 1, result);
// 回溯:移除皇后
board[row][col] = '.';
}
}
}
private static boolean isValid(char[][] board, int row, int col) {
// 检查列
for (int i = 0; i < row; i++) {
if (board[i][col] == 'Q') return false;
}
// 检查左上对角线
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q') return false;
}
// 检查右上对角线
for (int i = row - 1, j = col + 1; i >= 0 && j < board.length; i--, j++) {
if (board[i][j] == 'Q') return false;
}
return true;
}
private static List<String> constructSolution(char[][] board) {
List<String> solution = new ArrayList<>();
for (char[] row : board) {
solution.add(new String(row));
}
return solution;
}
}