From b91c2d09a43ef9fb108b6983e8171cfae70a2f29 Mon Sep 17 00:00:00 2001 From: luotaiqian <1147642922@qq.com> Date: Thu, 16 Jul 2026 15:35:14 +0800 Subject: [PATCH] init --- .../utils/ComplianceCheckUtil.java | 122 ++++++++++++------ .../infrastructure/utils/Dinic.java | 82 ++++++++++++ 2 files changed, 162 insertions(+), 42 deletions(-) create mode 100644 safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/utils/Dinic.java diff --git a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/utils/ComplianceCheckUtil.java b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/utils/ComplianceCheckUtil.java index f09760d..42515cc 100644 --- a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/utils/ComplianceCheckUtil.java +++ b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/utils/ComplianceCheckUtil.java @@ -21,7 +21,6 @@ import org.springframework.util.CollectionUtils; import java.math.BigDecimal; import java.util.*; -import java.util.function.Consumer; import java.util.stream.Collectors; import static org.qinan.safetyeval.domain.exception.ErrorCode.PERSON_NOT_ENOUGH; @@ -385,13 +384,11 @@ public class ComplianceCheckUtil { } - // 数据来源:industryProfessionalPersonCos(备案人员自带专业能力)+ certCapabilityList(证书专业能力) // 4. 贪心分配(一人一岗):按可选人数从少到多处理,优先满足最紧缺的专业能力 - Map assignedMap = assignPersonnelGreedy(personCapabilityMap, requiredMap); + boolean allMet = canFullyAssign(personCapabilityMap, requiredMap); + - // 5. 判断是否所有专业能力都达标,并生成失败消息 - boolean allMet = true; IndustryEnum industryEnum = IndustryEnum.ofCode(standardsDOList.get(0).getIndustryCode()); String industryName = industryEnum != null ? industryEnum.getValue() : standardsDOList.get(0).getIndustryCode(); @@ -417,53 +414,94 @@ public class ComplianceCheckUtil { return allMet ? Pair.of(true, null) : Pair.of(false, failureMsg.toString()); } + /** - * 一人一岗贪心分配 - *

- * 每个人员最多分配到一个专业能力岗位,人员不能重复使用。 - * 按可选人员池从小到大排序优先处理最紧缺的专业能力,尽量满足所有要求。 - *

- * 注意:贪心为启发式,不保证全局最优,但满足"只需判断能否达标"的场景。 + * 判断是否存在一种分配方案,使所有能力都达到要求人数 * - * @param personCapabilityMap 人员→可担任专业能力code集合(同一人可能具备多个专业能力) - * @param requiredMap 专业能力code→要求人数 - * @return 专业能力code→实际分配人数 + * @param personCapabilityMap 人员ID → 具备的能力code集合 + * @param requiredMap 能力code → 需求人数 + * @return true 当且仅当存在满足所有需求的分配 */ - private static Map assignPersonnelGreedy(Map> personCapabilityMap - , Map requiredMap) { - // 构建 专业能力code → 可用人员集合 - Map> capabilityPersonMap = new HashMap<>(); + public static boolean canFullyAssign( + Map> personCapabilityMap, Map requiredMap) { + // ---- 1. 构图 ---- + // 节点编号分配 + // 源点: 0 + // 人员节点: 1 .. P + // 能力节点: P+1 .. P+C + // 汇点: P+C+1 + List personIds = new ArrayList<>(personCapabilityMap.keySet()); + List capCodes = new ArrayList<>(requiredMap.keySet()); + + int P = personIds.size(); + int C = capCodes.size(); + int S = 0; + int T = P + C + 1; + int totalNodes = T + 1; + + Dinic dinic = new Dinic(totalNodes); + + // 建立人员ID到节点编号的映射 + Map personNode = new HashMap<>(); + for (int i = 0; i < P; i++) { + personNode.put(personIds.get(i), i + 1); + } + + // 建立能力code到节点编号的映射 + Map capNode = new HashMap<>(); + for (int i = 0; i < C; i++) { + capNode.put(capCodes.get(i), P + 1 + i); + } + + long totalRequired = 0; + // 源点 → 人员 (容量1) + for (int i = 0; i < P; i++) { + dinic.addEdge(S, personNode.get(personIds.get(i)), 1); + } + + // 人员 → 能力 (容量1) for (Map.Entry> entry : personCapabilityMap.entrySet()) { Long pid = entry.getKey(); + int u = personNode.get(pid); for (String cap : entry.getValue()) { - if (requiredMap.containsKey(cap)) { - capabilityPersonMap.computeIfAbsent(cap, k -> new HashSet<>()).add(pid); + if (capNode.containsKey(cap)) { // 仅连有需求的能力 + dinic.addEdge(u, capNode.get(cap), 1); } } } - // 按可选人数从少到多排序,优先满足最紧缺的专业能力 - List capOrder = new ArrayList<>(requiredMap.keySet()); - capOrder.sort(Comparator.comparingInt(c -> capabilityPersonMap.getOrDefault(c, Collections.emptySet()).size())); - - Set usedPersonIds = new HashSet<>(); - Map assignedMap = new LinkedHashMap<>(); - for (String capCode : capOrder) { - long required = requiredMap.get(capCode); - Set candidates = capabilityPersonMap.get(capCode); - int assigned = 0; - if (candidates != null) { - for (Long pid : candidates) { - if (assigned >= required) { - break; - } - if (usedPersonIds.add(pid)) { - assigned++; - } - } - } - assignedMap.put(capCode, assigned); + // 能力 → 汇点 (容量 = 需求人数) + for (Map.Entry entry : requiredMap.entrySet()) { + String cap = entry.getKey(); + long req = entry.getValue(); + totalRequired += req; + int v = capNode.get(cap); + dinic.addEdge(v, T, req); } - return assignedMap; + + // ---- 2. 计算最大流 ---- + long maxFlow = dinic.maxFlow(S, T); + return maxFlow == totalRequired; + } + + + // 简单测试 + public static void main(String[] args) { + Map> personMap = new HashMap<>(); + personMap.put(1L, new HashSet<>(Arrays.asList("A", "B"))); + personMap.put(2L, new HashSet<>(Arrays.asList("A", "B"))); + personMap.put(3L, new HashSet<>(Arrays.asList("A"))); + personMap.put(4L, new HashSet<>(Arrays.asList("B"))); + personMap.put(5L, new HashSet<>(Arrays.asList("A"))); + + Map required = new HashMap<>(); + required.put("A", 2L); + required.put("B", 2L); + + System.out.println(canFullyAssign(personMap, required)); // true + + // 让需求不可满足:A需要3人,B需要2人 + required.put("A", 3L); + System.out.println(canFullyAssign(personMap, required)); // false } } diff --git a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/utils/Dinic.java b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/utils/Dinic.java new file mode 100644 index 0000000..4c09222 --- /dev/null +++ b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/utils/Dinic.java @@ -0,0 +1,82 @@ +package org.qinan.safetyeval.infrastructure.utils; + +import java.util.*; + +/** + * Dinic 算法实现 + */ +public class Dinic { + static class Edge { + int to, rev; + long cap; + + Edge(int to, int rev, long cap) { + this.to = to; + this.rev = rev; + this.cap = cap; + } + } + + List[] graph; + int[] level, iter; + + @SuppressWarnings("unchecked") + Dinic(int n) { + graph = new ArrayList[n]; + for (int i = 0; i < n; i++) graph[i] = new ArrayList<>(); + level = new int[n]; + iter = new int[n]; + } + + void addEdge(int from, int to, long cap) { + graph[from].add(new Edge(to, graph[to].size(), cap)); + graph[to].add(new Edge(from, graph[from].size() - 1, 0)); + } + + void bfs(int s) { + Arrays.fill(level, -1); + Queue q = new LinkedList<>(); + level[s] = 0; + q.offer(s); + while (!q.isEmpty()) { + int v = q.poll(); + for (Edge e : graph[v]) { + if (e.cap > 0 && level[e.to] < 0) { + level[e.to] = level[v] + 1; + q.offer(e.to); + } + } + } + } + + long dfs(int v, int t, long f) { + if (v == t) return f; + for (int i = iter[v]; i < graph[v].size(); i++) { + iter[v] = i; + Edge e = graph[v].get(i); + if (e.cap > 0 && level[v] < level[e.to]) { + long d = dfs(e.to, t, Math.min(f, e.cap)); + if (d > 0) { + e.cap -= d; + graph[e.to].get(e.rev).cap += d; + return d; + } + } + } + return 0; + } + + long maxFlow(int s, int t) { + long flow = 0; + while (true) { + bfs(s); + if (level[t] < 0) break; + Arrays.fill(iter, 0); + long f; + while ((f = dfs(s, t, Long.MAX_VALUE)) > 0) { + flow += f; + } + } + return flow; + } +}