当前位置: 首页 > news >正文

【算法】图相关算法和递归

【算法】图相关算法

  • 【一】问题案例
    • 【1】问题描述
    • 【2】思路解析
    • 【3】实现代码
  • 【二】图的相关算法
    • 【1】封装方法
    • 【2】测试案例
  • 【三】递归算法
    • 【1】实现递归算法的思路
    • 【2】递归的理解方法
    • 【3】递归案例
      • (1)阶乘计算
      • (2)斐波那契数列
      • (3)二叉树遍历
      • (4)链表递归操作
      • (5)归并排序
      • (6)快速排序
      • (7)N皇后问题

【一】问题案例

【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】实现代码

    /*** 按依赖关系对部门进行分层* 确保每一层的部门不依赖于同一层或更高层的部门* * @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@AllArgsConstructorclass Department {String id;String parentId;Integer depth;List<String> relyDepIdList;}
}

实现日志

开始处理节点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)实用工具:
依赖关系查询
路径存在性检查
图统计信息

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);}// 使用转置图进行BFSMap<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); }@Overridepublic 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;}// Getterspublic int getVertexCount() { return vertexCount; }public int getEdgeCount() { return edgeCount; }public boolean isHasCycle() { return hasCycle; }public int getStronglyConnectedComponents() { return stronglyConnectedComponents; }@Overridepublic String toString() {return String.format("图统计: 顶点数=%d, 边数=%d, 是否有环=%s, 强连通分量数=%d",vertexCount, edgeCount, hasCycle, stronglyConnectedComponents);}}
}

【2】测试案例

/*** 部门图工具类使用示例*/
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】递归的理解方法

// 递归模板
返回值类型 递归方法(参数) {// 1. 基准情况处理if (满足终止条件) {return 基准结果;}// 2. 问题分解将大问题分解为小问题;// 3. 递归调用返回值 = 递归方法(缩小的问题规模);// 4. 结果组合return 组合结果;
}

【3】递归案例

(1)阶乘计算

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));  // 120System.out.println("0! = " + factorial(0));  // 1}
}

(2)斐波那契数列

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));  // 102334155long end = System.currentTimeMillis();System.out.println("记忆化递归耗时: " + (end - start) + "ms");}
}

(3)二叉树遍历

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)链表递归操作

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)归并排序

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)快速排序

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皇后问题

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;}
}
http://www.dtcms.com/a/573143.html

相关文章:

  • Vue开发系列——读取本地资源报错‘Not allowed to load local resource:
  • 【Java基础14】函数式接口、lamba表达式、方法引用一网打尽(下)
  • 金仓KES vs. 达梦DM:全面对比解析迁移、运维与授权成本
  • 国际网站如何推做推广个人做百度云下载网站吗
  • 【Python爬虫基础-1】爬虫开发基础
  • 外贸设计网站邯郸微信托管
  • 深度学习_原理和进阶_PyTorch入门(1)
  • C# 实现在 Excel 中高效生成和操作表格
  • OpenTeleDB xstore vs GaussDB ustore表膨胀测试
  • 使用 OpenAI Responses API 构建生产级应用的终极指南—— 状态、流式、异步与文件处理
  • 2025/11/5 IO流(字节流、字符流、字节缓冲流、字符缓冲流) 计算机存储规则(ASCII、GBK、Unicode)
  • 解决excel复制页面行高无法复制的问题
  • SSO登录验证设计要点细节(以微软 Microsoft SSO为例) 基于react python
  • 郑州网站备案地址移动互联网开发工程师证书
  • 网站建设的难处wordpress 臃肿
  • 芯谷科技--D29152高性能低降压可调稳压器,驱动高效电源管理新体验
  • 代码随想录第59天 | 最短路算法:dijkstra和Bellman_ford
  • web自动化测试详解
  • 网站建设文章官网小程序定制开发中心
  • PortSwigger靶场之利用开放重定向漏洞绕过过滤器的 SSRF 攻击通关秘籍
  • 深入理解 Spring 原理:IOC、AOP 与事务管理
  • 做网站公司赚钱吗怎么怎么做网站
  • 使用ESP8266+SG90舵机实现物理远程开机
  • 第四阶段C#通讯开发-5:TCP
  • WABT 项目全解析:WebAssembly 二进制工具套件
  • 第四阶段C#通讯开发-5:Socket与RS485 / Modbus联调
  • 辽宁建设资质申报网站国外直播sdk
  • 适配的 GPU 服务器能让 AI 模型充分发挥算力优势
  • 【高并发服务器:HTTP应用】十五、HttpRequest请求模块 HttpResponse响应模块设计
  • 两台服务器 NFS 共享目录实战