参考:
https://www.cnblogs.com/lishanlei/p/10707808.html
https://blog.csdn.net/wang379275614/article/details/13990163
关键路径问题来源于实际的生产活动,是项目管理的经典问题。
在一个复杂的项目中,整体项目的完成依赖与各个子项目的完成,而子项目之间又互相依赖,如何找到影响项目交付的关键活动就是关键路径问题。
与最大流问题的关注点不同,关键路径涉及到事件(Node)的最早最晚开始时间和活动(Edge)持续时间的问题,所以需要重新构建图的数据结构
这种用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间的图,我们称之为AOE
一、关键路径的图数据结构
@Data @NoArgsConstructor @AllArgsConstructor public class AOEGraph implements Cloneable { private Set<Node> nodeSet; private Table<Node, Node, Edge> edgeTable; //图的深度克隆 @Override protected AOEGraph clone() throws CloneNotSupportedException { AOEGraph clone = null; try { clone = (AOEGraph) super.clone(); Set<Node> oldNodeSet = clone.getNodeSet(); Set<Node> newNodeSet = new HashSet<>(); for (Node node : oldNodeSet) { newNodeSet.add(node.clone()); } Table<Node, Node, Edge> oldEdgeTable = clone.getEdgeTable(); Table<Node, Node, Edge> newEdgeTable = HashBasedTable.create(); for (Table.Cell<Node, Node, Edge> oldCell : oldEdgeTable.cellSet()) { Node newRowKey = oldCell.getRowKey().clone(); Node newColumnKey = oldCell.getColumnKey().clone(); Edge newEdge = oldCell.getValue().clone(); newEdgeTable.put(newRowKey,newColumnKey,newEdge); } clone.setNodeSet(newNodeSet); clone.setEdgeTable(newEdgeTable); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } @Data @AllArgsConstructor @NoArgsConstructor static class Node implements Cloneable { private String name; //最早开始时间 private Integer early; //最晚开始时间 private Integer latest; public Node(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Node node = (Node) o; return name.equals(node.name); } @Override protected Node clone() throws CloneNotSupportedException { Node clone = null; try { clone = (Node) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } @Override public int hashCode() { return Objects.hash(name); } @Override public String toString() { return "Node{" + "name='" + name + '\'' + ", early=" + early + ", latest=" + latest + '}'; } } @Data @AllArgsConstructor @NoArgsConstructor static class Edge implements Cloneable { //活动名称 private String name; //持续时间 private Integer duration; //最早最晚时间 private Integer early; private Integer latest; public Edge(String name, Integer duration) { this.name = name; this.duration = duration; } @Override protected Edge clone() throws CloneNotSupportedException { Edge clone = null; try { clone = (Edge) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } } /** * 根据顶点名称获取顶点 * * @param nodeList * @param name * @return */ public static Node getNode(Set<Node> nodeList, String name) { for (Node node : nodeList) { if (node.getName().equals(name)) { return node; } } return null; } public void remove(Node node) { ArrayList<Pair<Node, Node>> pairs = Lists.newArrayList(); for (Table.Cell<Node, Node, Edge> cell : edgeTable.cellSet()) { if (cell.getRowKey().equals(node) || cell.getColumnKey().equals(node)) { pairs.add(new Pair<>(cell.getRowKey(), cell.getColumnKey())); } } for (Pair<Node, Node> pair : pairs) { edgeTable.remove(pair.getFrom(), pair.getTo()); } nodeSet.remove(node); } /** * 获取一个图的入度 * * @return */ public Map<Node, Integer> getInDegree() { HashMap<Node, Integer> map = new HashMap<>(); for (Node node : nodeSet) { map.put(node, 0); } for (Table.Cell<Node, Node, Edge> cell : edgeTable.cellSet()) { map.computeIfPresent(cell.getColumnKey(), (k, v) -> v + 1); } return map; } /** * 图的拓扑排序 * 因为要删减图的顶点和边,为了不改变原图,所以需要在克隆的副本上进行 * 但返回的nodelist需要是原图的node */ public List<Node> findPriority() throws CloneNotSupportedException { AOEGraph cloneGraph = this.clone(); Set<Node> nodeSet = cloneGraph.getNodeSet(); List<Node> nodes = Lists.newArrayList(); Map<Node, Integer> inDegree = cloneGraph.getInDegree(); while (nodeSet.size() != 0) { for (Map.Entry<Node, Integer> entry : inDegree.entrySet()) { if (entry.getValue().equals(0)) { Node key = entry.getKey(); //返回的拓扑序列需要是原图的node Node node = getNode(this.nodeSet, key.getName()); nodes.add(node); cloneGraph.remove(entry.getKey()); } } inDegree = cloneGraph.getInDegree(); } return nodes; } public static AOEGraph buildGraph() { List<String> nodes = Lists.newArrayList("C", "A", "B", "D"); List<String> hop = Lists.newArrayList("C->B", "A->D", "D->C", "A->C"); Set<Node> nodeList = nodes.stream().map(Node::new).collect(Collectors.toSet()); HashBasedTable<Node, Node, Edge> table = HashBasedTable.create(); for (String s : hop) { String[] split = s.split("->"); String from = split[0]; String to = split[1]; Node fromNode = getNode(nodeList, from); Node toNode = getNode(nodeList, to); table.put(fromNode, toNode, new Edge()); } return new AOEGraph(nodeList, table); } public static void main(String[] args) throws CloneNotSupportedException { AOEGraph aoeGraph = buildGraph(); List<Node> priority = aoeGraph.findPriority(); System.out.println(aoeGraph); } }
二、计算事件的最早最晚开始时间和活动的最早最晚开始时间求关键路径
需理解顶点(事件)和边(活动)各自的两个特征属性以及求法即可:
1.先根据首结点的Ve(j)=0由前向后计算各顶点的最早发生时间,多个取较大
2.再根据终结点的Vl(j)等于它的Ve(j)由后向前依次求解各顶点的最晚发生时间,多个取较小
3.根据边的e(i)等于它的发出顶点的Ve(j)计算各边的最早开始时间(最早开始,对应最早发生)
4.根据边的l(i)等于它的到达顶点的Vl(j)减去边的权值计算各边的最晚开始时间(最晚开始,对应最晚发生)
计算关键路径,只需求出上面的四个特征属性,然后取e(i)=l(i)的边即为关键路径上的边(关键路径可能不止一条)
public class AOEKeyPath { public static AOEGraph buildGraph() { List<String> nodes = Lists.newArrayList("v1", "v2", "v3", "v4", "v5", "v6", "v7"); Set<AOEGraph.Node> nodeList = nodes.stream().map(AOEGraph.Node::new).collect(Collectors.toSet()); HashBasedTable<AOEGraph.Node, AOEGraph.Node, AOEGraph.Edge> table = HashBasedTable.create(); AOEGraph.Node v1 = AOEGraph.getNode(nodeList, "v1"); assert v1 != null; v1.setEarly(0); AOEGraph.Node v2 = AOEGraph.getNode(nodeList, "v2"); AOEGraph.Node v3 = AOEGraph.getNode(nodeList, "v3"); AOEGraph.Node v4 = AOEGraph.getNode(nodeList, "v4"); AOEGraph.Node v5 = AOEGraph.getNode(nodeList, "v5"); AOEGraph.Node v6 = AOEGraph.getNode(nodeList, "v6"); AOEGraph.Node v7 = AOEGraph.getNode(nodeList, "v7"); table.put(v1, v2, new AOEGraph.Edge("a1",3)); table.put(v1, v4, new AOEGraph.Edge("a2",6)); table.put(v1, v3, new AOEGraph.Edge("a3",2)); table.put(v2, v5, new AOEGraph.Edge("a4",4)); table.put(v2, v4, new AOEGraph.Edge("a5",2)); table.put(v3, v4, new AOEGraph.Edge("a6",1)); table.put(v3, v6, new AOEGraph.Edge("a7",3)); table.put(v4, v5, new AOEGraph.Edge("a8",1)); table.put(v5, v7, new AOEGraph.Edge("a9",3)); table.put(v6, v7, new AOEGraph.Edge("a10",4)); return new AOEGraph(nodeList, table); } /** * 一、求事件的最早开始时间 * 1.从前向后,取大值:直接前驱结点的Ve(j)+到达边(指向顶点的边)的权值,有多个值的取较大者 * 2.首结点Ve(j)已知,为0 * @param aoeGraph * @throws CloneNotSupportedException */ public static void findNodeEarly(AOEGraph aoeGraph) throws CloneNotSupportedException { List<AOEGraph.Node> priority = aoeGraph.findPriority(); Table<AOEGraph.Node, AOEGraph.Node, AOEGraph.Edge> edgeTable = aoeGraph.getEdgeTable(); for (AOEGraph.Node node : priority) { Map<AOEGraph.Node, AOEGraph.Edge> column = edgeTable.column(node); if (column.size()==0) { node.setEarly(0); } else { int early=0; for (Map.Entry<AOEGraph.Node, AOEGraph.Edge> entry : column.entrySet()) { AOEGraph.Node preNode = entry.getKey(); Integer duration = entry.getValue().getDuration(); if (preNode.getEarly() + duration > early) { early=preNode.getEarly()+duration; } } node.setEarly(early); } } } /** * 二、求事件的最晚开始时间 * 1.从后向前,取小值:直接后继结点的Vl(j) –发出边(从顶点发出的边)的权值,有多个值的取较小者; * 2.终结点Vl(j)已知,等于它的Ve(j)) * @param aoeGraph * @throws CloneNotSupportedException */ public static void findNodeLatest(AOEGraph aoeGraph) throws CloneNotSupportedException { List<AOEGraph.Node> priority = aoeGraph.findPriority(); Table<AOEGraph.Node, AOEGraph.Node, AOEGraph.Edge> edgeTable = aoeGraph.getEdgeTable(); int lastIndex = priority.size() - 1; for (int i = lastIndex; i >=0; i--) { AOEGraph.Node node = priority.get(i); Map<AOEGraph.Node, AOEGraph.Edge> row = edgeTable.row(node); if (row.size() == 0) { node.setLatest(node.getEarly()); } else { int latest=Integer.MAX_VALUE; for (Map.Entry<AOEGraph.Node, AOEGraph.Edge> entry : row.entrySet()) { AOEGraph.Node nextNode = entry.getKey(); Integer duration = entry.getValue().getDuration(); if (nextNode.getLatest() - duration < latest) { latest=nextNode.getLatest() - duration; } } node.setLatest(latest); } } } /** * 三、求活动的最早开始时间 * 若活动ai由弧<vk,vj>表示,则活动ai的最早开始时间应该等于事件vk的最早发生时间。 * 因而,有:e[i]=ve[k];(即:边(活动)的最早开始时间等于,它的发出顶点的最早发生时间 * @param aoeGraph */ public static void findEdgeEarly(AOEGraph aoeGraph) { Table<AOEGraph.Node, AOEGraph.Node, AOEGraph.Edge> edgeTable = aoeGraph.getEdgeTable(); Set<Table.Cell<AOEGraph.Node, AOEGraph.Node, AOEGraph.Edge>> cells = edgeTable.cellSet(); for (Table.Cell<AOEGraph.Node, AOEGraph.Node, AOEGraph.Edge> cell : cells) { AOEGraph.Node rowKey = cell.getRowKey(); cell.getValue().setEarly(rowKey.getEarly()); } } /** * 四、求活动的最晚开始时间 * 若活动ai由弧<vk,vj>表示,则ai的最晚开始时间要保证事件vj的最迟发生时间不拖后。 * 因而有:l[i]=vl[j]-len<vk,vj>(为边(活动)的到达顶点的最晚发生时间减去边的权值) * @param aoeGraph */ public static void findEdgeLatest(AOEGraph aoeGraph) { Table<AOEGraph.Node, AOEGraph.Node, AOEGraph.Edge> edgeTable = aoeGraph.getEdgeTable(); for (Table.Cell<AOEGraph.Node, AOEGraph.Node, AOEGraph.Edge> cell : edgeTable.cellSet()) { AOEGraph.Node columnKey = cell.getColumnKey(); Integer nodeLatest = columnKey.getLatest(); Integer duration = cell.getValue().getDuration(); cell.getValue().setLatest(nodeLatest -duration); } } public static void main(String[] args) throws CloneNotSupportedException { AOEGraph aoeGraph = buildGraph(); findNodeEarly(aoeGraph); findNodeLatest(aoeGraph); findEdgeEarly(aoeGraph); findEdgeLatest(aoeGraph); System.out.println(aoeGraph.getNodeSet()); Table<AOEGraph.Node, AOEGraph.Node, AOEGraph.Edge> edgeTable = aoeGraph.getEdgeTable(); for (Table.Cell<AOEGraph.Node, AOEGraph.Node, AOEGraph.Edge> cell : edgeTable.cellSet()) { Integer early = cell.getValue().getEarly(); Integer latest = cell.getValue().getLatest(); if (early.equals(latest)) { System.out.println(cell.getValue().getName()); } } //关键路径有两条:a1 a4 a9和 a2 a8 a9 } }