init
parent
c187397c3e
commit
b91c2d09a4
|
|
@ -21,7 +21,6 @@ import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.qinan.safetyeval.domain.exception.ErrorCode.PERSON_NOT_ENOUGH;
|
import static org.qinan.safetyeval.domain.exception.ErrorCode.PERSON_NOT_ENOUGH;
|
||||||
|
|
@ -385,13 +384,11 @@ public class ComplianceCheckUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 数据来源:industryProfessionalPersonCos(备案人员自带专业能力)+ certCapabilityList(证书专业能力)
|
// 数据来源:industryProfessionalPersonCos(备案人员自带专业能力)+ certCapabilityList(证书专业能力)
|
||||||
// 4. 贪心分配(一人一岗):按可选人数从少到多处理,优先满足最紧缺的专业能力
|
// 4. 贪心分配(一人一岗):按可选人数从少到多处理,优先满足最紧缺的专业能力
|
||||||
Map<String, Integer> assignedMap = assignPersonnelGreedy(personCapabilityMap, requiredMap);
|
boolean allMet = canFullyAssign(personCapabilityMap, requiredMap);
|
||||||
|
|
||||||
|
|
||||||
// 5. 判断是否所有专业能力都达标,并生成失败消息
|
|
||||||
boolean allMet = true;
|
|
||||||
IndustryEnum industryEnum = IndustryEnum.ofCode(standardsDOList.get(0).getIndustryCode());
|
IndustryEnum industryEnum = IndustryEnum.ofCode(standardsDOList.get(0).getIndustryCode());
|
||||||
String industryName = industryEnum != null ? industryEnum.getValue() : 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());
|
return allMet ? Pair.of(true, null) : Pair.of(false, failureMsg.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一人一岗贪心分配
|
* 判断是否存在一种分配方案,使所有能力都达到要求人数
|
||||||
* <p>
|
|
||||||
* 每个人员最多分配到一个专业能力岗位,人员不能重复使用。
|
|
||||||
* 按可选人员池从小到大排序优先处理最紧缺的专业能力,尽量满足所有要求。
|
|
||||||
* <p>
|
|
||||||
* 注意:贪心为启发式,不保证全局最优,但满足"只需判断能否达标"的场景。
|
|
||||||
*
|
*
|
||||||
* @param personCapabilityMap 人员→可担任专业能力code集合(同一人可能具备多个专业能力)
|
* @param personCapabilityMap 人员ID → 具备的能力code集合
|
||||||
* @param requiredMap 专业能力code→要求人数
|
* @param requiredMap 能力code → 需求人数
|
||||||
* @return 专业能力code→实际分配人数
|
* @return true 当且仅当存在满足所有需求的分配
|
||||||
*/
|
*/
|
||||||
private static Map<String, Integer> assignPersonnelGreedy(Map<Long, Set<String>> personCapabilityMap
|
public static boolean canFullyAssign(
|
||||||
, Map<String, Long> requiredMap) {
|
Map<Long, Set<String>> personCapabilityMap, Map<String, Long> requiredMap) {
|
||||||
// 构建 专业能力code → 可用人员集合
|
// ---- 1. 构图 ----
|
||||||
Map<String, Set<Long>> capabilityPersonMap = new HashMap<>();
|
// 节点编号分配
|
||||||
|
// 源点: 0
|
||||||
|
// 人员节点: 1 .. P
|
||||||
|
// 能力节点: P+1 .. P+C
|
||||||
|
// 汇点: P+C+1
|
||||||
|
List<Long> personIds = new ArrayList<>(personCapabilityMap.keySet());
|
||||||
|
List<String> 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<Long, Integer> personNode = new HashMap<>();
|
||||||
|
for (int i = 0; i < P; i++) {
|
||||||
|
personNode.put(personIds.get(i), i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立能力code到节点编号的映射
|
||||||
|
Map<String, Integer> 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<Long, Set<String>> entry : personCapabilityMap.entrySet()) {
|
for (Map.Entry<Long, Set<String>> entry : personCapabilityMap.entrySet()) {
|
||||||
Long pid = entry.getKey();
|
Long pid = entry.getKey();
|
||||||
|
int u = personNode.get(pid);
|
||||||
for (String cap : entry.getValue()) {
|
for (String cap : entry.getValue()) {
|
||||||
if (requiredMap.containsKey(cap)) {
|
if (capNode.containsKey(cap)) { // 仅连有需求的能力
|
||||||
capabilityPersonMap.computeIfAbsent(cap, k -> new HashSet<>()).add(pid);
|
dinic.addEdge(u, capNode.get(cap), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按可选人数从少到多排序,优先满足最紧缺的专业能力
|
// 能力 → 汇点 (容量 = 需求人数)
|
||||||
List<String> capOrder = new ArrayList<>(requiredMap.keySet());
|
for (Map.Entry<String, Long> entry : requiredMap.entrySet()) {
|
||||||
capOrder.sort(Comparator.comparingInt(c -> capabilityPersonMap.getOrDefault(c, Collections.emptySet()).size()));
|
String cap = entry.getKey();
|
||||||
|
long req = entry.getValue();
|
||||||
Set<Long> usedPersonIds = new HashSet<>();
|
totalRequired += req;
|
||||||
Map<String, Integer> assignedMap = new LinkedHashMap<>();
|
int v = capNode.get(cap);
|
||||||
for (String capCode : capOrder) {
|
dinic.addEdge(v, T, req);
|
||||||
long required = requiredMap.get(capCode);
|
|
||||||
Set<Long> 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);
|
|
||||||
}
|
}
|
||||||
return assignedMap;
|
|
||||||
|
// ---- 2. 计算最大流 ----
|
||||||
|
long maxFlow = dinic.maxFlow(S, T);
|
||||||
|
return maxFlow == totalRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 简单测试
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Map<Long, Set<String>> 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<String, Long> 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<Edge>[] 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<Integer> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue