init
parent
c187397c3e
commit
b91c2d09a4
|
|
@ -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<String, Integer> 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());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 一人一岗贪心分配
|
||||
* <p>
|
||||
* 每个人员最多分配到一个专业能力岗位,人员不能重复使用。
|
||||
* 按可选人员池从小到大排序优先处理最紧缺的专业能力,尽量满足所有要求。
|
||||
* <p>
|
||||
* 注意:贪心为启发式,不保证全局最优,但满足"只需判断能否达标"的场景。
|
||||
* 判断是否存在一种分配方案,使所有能力都达到要求人数
|
||||
*
|
||||
* @param personCapabilityMap 人员→可担任专业能力code集合(同一人可能具备多个专业能力)
|
||||
* @param requiredMap 专业能力code→要求人数
|
||||
* @return 专业能力code→实际分配人数
|
||||
* @param personCapabilityMap 人员ID → 具备的能力code集合
|
||||
* @param requiredMap 能力code → 需求人数
|
||||
* @return true 当且仅当存在满足所有需求的分配
|
||||
*/
|
||||
private static Map<String, Integer> assignPersonnelGreedy(Map<Long, Set<String>> personCapabilityMap
|
||||
, Map<String, Long> requiredMap) {
|
||||
// 构建 专业能力code → 可用人员集合
|
||||
Map<String, Set<Long>> capabilityPersonMap = new HashMap<>();
|
||||
public static boolean canFullyAssign(
|
||||
Map<Long, Set<String>> personCapabilityMap, Map<String, Long> requiredMap) {
|
||||
// ---- 1. 构图 ----
|
||||
// 节点编号分配
|
||||
// 源点: 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()) {
|
||||
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<String> capOrder = new ArrayList<>(requiredMap.keySet());
|
||||
capOrder.sort(Comparator.comparingInt(c -> capabilityPersonMap.getOrDefault(c, Collections.emptySet()).size()));
|
||||
// 能力 → 汇点 (容量 = 需求人数)
|
||||
for (Map.Entry<String, Long> entry : requiredMap.entrySet()) {
|
||||
String cap = entry.getKey();
|
||||
long req = entry.getValue();
|
||||
totalRequired += req;
|
||||
int v = capNode.get(cap);
|
||||
dinic.addEdge(v, T, req);
|
||||
}
|
||||
|
||||
Set<Long> usedPersonIds = new HashSet<>();
|
||||
Map<String, Integer> assignedMap = new LinkedHashMap<>();
|
||||
for (String capCode : capOrder) {
|
||||
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;
|
||||
// ---- 2. 计算最大流 ----
|
||||
long maxFlow = dinic.maxFlow(S, T);
|
||||
return maxFlow == totalRequired;
|
||||
}
|
||||
if (usedPersonIds.add(pid)) {
|
||||
assigned++;
|
||||
}
|
||||
}
|
||||
}
|
||||
assignedMap.put(capCode, assigned);
|
||||
}
|
||||
return assignedMap;
|
||||
|
||||
|
||||
// 简单测试
|
||||
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