Java KSP 算法实现
思路
KSP算法
先用BFS/Dijkstra算出第一条路径P;
把P上除了终点以外的其他点,作为偏移点,并将偏移点在P上的出路作为必排,偏移点作为起点,重新算出偏移点到终点的新路,补上起点到偏移点的路径,取所有新路里权重最小的路作为P2;
以P2作为原路径,重复上一步,得到P3;
依次循环k次(k人为决定),得到Pk。
代码
涉及的数据结构复用Java 迪杰斯特拉 算法实现 里的结构
public class KSP {
private final List<GraphNode> graph;
private final String startNodeId;
private final String endNodeId;
private final Constraint constraint;
private final Integer cycleNum;
public KSP(List<GraphNode> graphNodes, String startNodeId, String endNodeId, Constraint constraint, int k) {
this.graph = new ArrayList<>(graphNodes);
this.startNodeId = startNodeId;
this.endNodeId = endNodeId;
this.constraint = constraint;
this.cycleNum = k;
}
public CalcResult calcShortPath() {
Dijkstra dijkstra = new Dijkstra(graph, startNodeId, endNodeId, constraint);
CalcResult calcResult = dijkstra.calcShortPath(false);
List<String> lastTimePath = new ArrayList<>(calcResult.getResultPath());
List<CalcResult> curCalcResults = new ArrayList<>();
for (int i = 1; i < cycleNum; i++) {
for (int m = 0; m < lastTimePath.size() - 1; m++) {
String offsetNode = lastTimePath.get(m);
Pair<String, String> excludeLink = new Pair<>(offsetNode, lastTimePath.get(m + 1));
// 偏移点之前的路径和权重
Pair<List<String>, Integer> subResult = getSubOffsetPath(lastTimePath, m);
// 重新组织约束
Constraint cycleConstraint = createCycleConstraint(excludeLink, subResult);
// 偏移点为起点,重新算路
dijkstra = new Dijkstra(graph, offsetNode, endNodeId, cycleConstraint);
CalcResult tempResult = dijkstra.calcShortPath(false);
// 和偏移点前的路径组合,拼成完整路径
stitchFullPath(calcResult, curCalcResults, subResult, tempResult);
}
if (curCalcResults.isEmpty()) {
printResult(calcResult);
return calcResult;
}
// 过滤出当前轮次权重最小的路径,放入最终结果中,并作为下一轮算路的P路径
lastTimePath = filterMinWeightPath(calcResult, curCalcResults);
}
printResult(calcResult);
return calcResult;
}
private List<String> filterMinWeightPath(CalcResult calcResult, List<CalcResult> curCalcResults) {
List<String> lastTimePath = new ArrayList<>();
Optional<CalcResult> curMinWeightResult = curCalcResults.stream().min(Comparator.comparing(CalcResult::getWeight));
if(curMinWeightResult.isPresent()){
lastTimePath = curMinWeightResult.get().getResultPath();
calcResult.getOtherPaths().add(new Pair<>(curMinWeightResult.get().getResultPath(), curMinWeightResult.get().getWeight()));
curCalcResults.clear();
}
return lastTimePath;
}
private void printResult(CalcResult calcResult) {
System.out.println("first path:" + calcResult.getResultPath());
System.out.println("first weight:" + calcResult.getWeight());
calcResult.getOtherPaths().forEach(pair -> {
System.out.println("second path:" + pair.getKey());
System.out.println("second weight:" + pair.getValue());
});
}
private void stitchFullPath(CalcResult calcResult, List<CalcResult> curCalcResults, Pair<List<String>, Integer> subResult, CalcResult tempResult) {
subResult.getKey().addAll(tempResult.getResultPath());
tempResult.setResultPath(subResult.getKey().stream().distinct().collect(Collectors.toList()));
tempResult.setWeight(subResult.getValue() + tempResult.getWeight());
if (!hasSameRoute(calcResult, tempResult)) {
curCalcResults.add(tempResult);
}
}
private Pair<List<String>, Integer> getSubOffsetPath(List<String> lastTimePath, int m) {
List<String> subOffsetPath = new ArrayList<>();
Integer subOffsetWeight = 0;
if (m != 0) {
List<String> temp = new ArrayList<>(lastTimePath);
subOffsetPath = temp.subList(0, m + 1);
subOffsetWeight = getWeight(subOffsetPath);
}
return new Pair<>(subOffsetPath,subOffsetWeight);
}
private Constraint createCycleConstraint(Pair<String, String> excludeLink, Pair<List<String>,Integer> subOffsetResult) {
Constraint cycleConstraint = new Constraint();
cycleConstraint.setExcludeLink(Lists.newArrayList(excludeLink));
if (constraint != null) {
cycleConstraint.getExcludeLink().addAll(constraint.getExcludeLink());
cycleConstraint.setExcludeNode(constraint.getExcludeNode());
for (String node : constraint.getIncludeNode()) {
if (subOffsetResult.getKey().contains(node)) {
continue;
}
cycleConstraint.getIncludeNode().add(node);
}
}
if(!subOffsetResult.getKey().isEmpty()){
cycleConstraint.getExcludeNode().addAll(subOffsetResult.getKey().subList(0, subOffsetResult.getKey().size() - 1));
}
return cycleConstraint;
}
private boolean hasSameRoute(CalcResult calcResult, CalcResult tempResult) {
List<List<String>> allRoute = new ArrayList<>();
allRoute.add(calcResult.getResultPath());
calcResult.getOtherPaths().forEach(pair -> allRoute.add(pair.getKey()));
List<String> tempRoute = tempResult.getResultPath();
return allRoute.stream().anyMatch(route -> {
if (route.size() != tempRoute.size()) {
return false;
}
for (int i = 0; i < route.size(); i++) {
if (!Objects.equals(route.get(i), tempRoute.get(i))) {
return false;
}
}
return true;
});
}
private Integer getWeight(List<String> subOffsetPath) {
if (subOffsetPath == null || subOffsetPath.isEmpty()) {
return 0;
}
Integer weight = 0;
for (int i = 0; i < subOffsetPath.size() - 1; i++) {
String firstNodeId = subOffsetPath.get(i);
String secondNodeId = subOffsetPath.get(i + 1);
GraphNode firstNode = this.graph.stream().filter(node ->
Objects.equals(node.getNodeId(), firstNodeId)).findFirst().orElse(null);
if (firstNode != null) {
Map<String, Integer> nearNode = firstNode.getNearNodeValueTable();
weight += nearNode.get(secondNodeId);
}
}
return weight;
}
}