Java解决关键路径问题

摘要:
在一个复杂的项目中,整个项目的完成取决于每个子项目的完成,并且子项目之间相互依赖。如何找到影响项目交付的关键活动是关键路径问题。

参考:

https://www.cnblogs.com/lishanlei/p/10707808.html

https://blog.csdn.net/wang379275614/article/details/13990163

Java解决关键路径问题第1张

 关键路径问题来源于实际的生产活动,是项目管理的经典问题。

在一个复杂的项目中,整体项目的完成依赖与各个子项目的完成,而子项目之间又互相依赖,如何找到影响项目交付的关键活动就是关键路径问题。

与最大流问题的关注点不同,关键路径涉及到事件(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
    }
}

免责声明:文章转载自《Java解决关键路径问题》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇MySQL 中&amp;lt;=&amp;gt;用法(长知识)(六)学习CSS之color属性下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

基于Nodejs的BigPipe实现

简介 BigPipe是facebook推出的用于优化网页加载速度的技术,它突破了传统网页的加载方式,通过把网页内容进行分块,然后对这些块进行并行传输从,使得浏览器的渲染无需等到整个页面加载完毕,大大提升网页呈现速度。天猫上首页就有这种实现。 Bigpie适用于网页分块清晰,且规模达到一定程度。使用bigpipe要达到优化的效果才有意义。 实现原理 利用ht...

深入浅出Node.js(上)

(一):什么是Node.js Node.js从2009年诞生至今,已经发展了两年有余,其成长的速度有目共睹。从在github的访问量超过Rails,到去年底Node.jsS创始人Ryan Dalh加盟Joyent获得企业资助,再到今年发布Windows移植版本,Node.js的前景获得了技术社区的肯定。InfoQ一直在关注Node.js的发展,在今年的两次...

C#获取当前路径

//获取包含清单的已加载文件的路径或 UNC 位置。         public static string sApplicationPath = Assembly.GetExecutingAssembly ( ).Location;         //result: X:\xxx\xxx\xxx.dll (.dll文件所在的目录+.dll文件名)...

c#实现http请求并解析返回之json

   C#是通过HttpWebRequest类和HttpWebResponseL类来实现http请求的发出和http响应的接收的,由于本人刚用这两个类,不是太熟悉,所以属性和方法就不在这里给大家讲解了。        代码如下: using System; using System.Collections.Generic; using System.Lin...

Java如何连接Access数据库(两种方式实例代码)

import java.sql.*; public class ConnectAccess { /** * 1:先建立一个access文件a1.mdb,并放在D:/下; * 2:在数据库文件a1.mdb中建立一个表Table1; * 3:为Table1添加一列,并插入至少一条记录; * 4:本文是一个完整的类...

Golang中使用lua进行扩展

前言 最近在项目中需要使用lua进行扩展,发现github上有一个用golang编写的lua虚拟机,名字叫做gopher-lua.使用后发现还不错,借此分享给大家. 数据类型 lua中的数据类型与golang中的数据类型对应关系作者已经在文档中说明,值得注意的是类型是以L开头的,类型的名称是以LT开头的.golang中的数据转换为lua中的数据就必须转...