图的关键路径

相关概念

AOE网:在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种有向图的边表示活动的网,我们称之为AOE网(Activity On Edge Network)。

关键路径与拓扑排序的区别:拓扑排序主要是解决一个工程能否顺利进行的问题,关键路径解决的是工程完成所需要的最短时间问题。换句话说,关键路径问题需要在拓扑排序的基础上得到完成工程的最短时间,因为工程首先必须是可以被完成的,也就是网中不存在环路时,才能进一步讨论工程完成所需的最短时间问题。

AOE网和AOV网:AOV网是顶点表示活动的网,它只描述活动之间的制约关系;AOE网是用边表示活动的网,边上的权值表示活动持续的时间。

关键路径:从源点到汇点具有最大长度的路径称为关键路径,在关键路径上的活动叫关键活动。只有缩短关键路径上的关键活动时间才可以减少整个工期长度。


关键路径算法参数介绍

事件的最早发生时间etv(earliest time of vertex):顶点Vn的最早发生时间;

事件的最晚发生时间ltv(latest tiem of vertex):顶点Vn的最晚发生时间;

活动的最早开工时间ete(earliest time of edge):弧En的最早发生时间;

活动的最晚开工时间lte(latest time of edge):弧En的最晚发生时间,也就是不推迟工期的最晚开工时间。

关键路径算法由etv和ltv求得ete和lte,当ete与lte相等时表示活动没有空闲时间,是关键活动。


etv和ltv数组的计算方法

etv数组计算公式.png

P[k]表示所有到达顶点Vk的弧的集合,即Vk是弧的终点。

ltv数组计算公式.png

S[k]表示所有从顶点Vk出发的弧的集合,即Vk是弧的起点。


源代码

带权有向图采用邻接表的数据结构表示

顶点表结点类

/**
 * 顶点表结点类
 * 
 * @author Shaw
 *
 */
class VertexNode {
    // 顶点入度
    private int in;
    // 顶点域
    private String data;
    // 指向边表
    private EdgeNode firstEdge;

    public VertexNode(int in, String data, EdgeNode firstEdge) {
        super();
        this.in = in;
        this.data = data;
        this.firstEdge = firstEdge;
    }

    public int getIn() {
        return in;
    }

    public void setIn(int in) {
        this.in = in;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public EdgeNode getFirstEdge() {
        return firstEdge;
    }

    public void setFirstEdge(EdgeNode firstEdge) {
        this.firstEdge = firstEdge;
    }
}

边表顶点类

/**
 * 边表顶点类
 * @author Shaw
 *
 */
class EdgeNode {
    //邻接点域,存储该顶点对应的下标
    private int adjvex;
    //权值域
    private int weight;
    //指向下一个邻接点
    private EdgeNode next;
    
    
    public EdgeNode(int adjvex, int weight, EdgeNode next) {
        super();
        this.adjvex = adjvex;
        this.weight = weight;
        this.next = next;
    }


    public int getAdjvex() {
        return adjvex;
    }


    public void setAdjvex(int adjvex) {
        this.adjvex = adjvex;
    }


    public int getWeight() {
        return weight;
    }


    public void setWeight(int weight) {
        this.weight = weight;
    }


    public EdgeNode getNext() {
        return next;
    }


    public void setNext(EdgeNode next) {
        this.next = next;
    }
}

关键路径算法核心类

/**
 * 关键路径算法
 * 
 * @author Shaw
 *
 */
class CriticalPath {
    private List<VertexNode> verList;
    private int[] etv; // (earliest time of edge)事件最早发生时间数组
    private int[] ltv; // (latest time of edge)事件最迟发生时间数组
    private Stack<Integer> stack2; // 用于存放拓扑排序结果的栈

    // 建图
    public void buildAoeGraph() {
        VertexNode v0 = new VertexNode(0, "v0", null);
        EdgeNode v0e1 = new EdgeNode(2, 4, null);
        EdgeNode v0e2 = new EdgeNode(1, 3, null);
        v0.setFirstEdge(v0e1);
        v0e1.setNext(v0e2);

        VertexNode v1 = new VertexNode(0, "v1", null);
        EdgeNode v1e1 = new EdgeNode(4, 6, null);
        EdgeNode v1e2 = new EdgeNode(3, 5, null);
        v1.setFirstEdge(v1e1);
        v1e1.setNext(v1e2);

        VertexNode v2 = new VertexNode(0, "v2", null);
        EdgeNode v2e1 = new EdgeNode(5, 7, null);
        EdgeNode v2e2 = new EdgeNode(3, 8, null);
        v2.setFirstEdge(v2e1);
        v2e1.setNext(v2e2);

        VertexNode v3 = new VertexNode(0, "v3", null);
        EdgeNode v3e1 = new EdgeNode(4, 3, null);
        v3.setFirstEdge(v3e1);

        VertexNode v4 = new VertexNode(0, "v4", null);
        EdgeNode v4e1 = new EdgeNode(7, 4, null);
        EdgeNode v4e2 = new EdgeNode(6, 9, null);
        v4.setFirstEdge(v4e1);
        v4e1.setNext(v4e2);

        VertexNode v5 = new VertexNode(0, "v5", null);
        EdgeNode v5e1 = new EdgeNode(7, 6, null);
        v5.setFirstEdge(v5e1);

        VertexNode v6 = new VertexNode(0, "v6", null);
        EdgeNode v6e1 = new EdgeNode(9, 2, null);
        v6.setFirstEdge(v6e1);

        VertexNode v7 = new VertexNode(0, "v7", null);
        EdgeNode v7e1 = new EdgeNode(8, 5, null);
        v7.setFirstEdge(v7e1);

        VertexNode v8 = new VertexNode(0, "v8", null);
        EdgeNode v8e1 = new EdgeNode(9, 3, null);
        v8.setFirstEdge(v8e1);

        VertexNode v9 = new VertexNode(0, "v9", null);

        verList = new ArrayList<>();
        verList.add(v0);
        verList.add(v1);
        verList.add(v2);
        verList.add(v3);
        verList.add(v4);
        verList.add(v5);
        verList.add(v6);
        verList.add(v7);
        verList.add(v8);
        verList.add(v9);
    }

    // 拓扑排序,用于关键路径的计算
    // 求出事件的最早发生时间数组etv[]
    public void topologicalSortForCriticalPath() {
        Stack<Integer> stack = new Stack<>();
        etv = new int[verList.size()];
        stack2 = new Stack<>();
        int count = 0;
        // 初始化所有顶点表结点的入度值
        for (int i = 0; i < verList.size(); i++) {
            // 初始化etv数组
            etv[i] = 0;
            EdgeNode edgeNode = verList.get(i).getFirstEdge();
            while (edgeNode != null) {
                // 边集表中出现的下标(adjvex)所对应的顶点入度加1
                VertexNode vertexNode = verList.get(edgeNode.getAdjvex());
                vertexNode.setIn(vertexNode.getIn() + 1);
                edgeNode = edgeNode.getNext();
            }
        }
        // 将所有入度为0的顶点入栈
        for (int i = 0; i < verList.size(); i++) {
            if (verList.get(i).getIn() == 0) {
                stack.push(i);
            }
        }

        while (!stack.isEmpty()) {
            // 从栈中弹出一个入度为0的顶点
            int vertex = stack.pop();
            // 将出栈的顶点压入stack2栈中用于关键路径算法
            stack2.push(vertex);
            count++;
            // 获取弹出的顶点指向的第一个边结点
            EdgeNode edgeNode = verList.get(vertex).getFirstEdge();
            while (edgeNode != null) {
                // 获取边表结点的Adjvex值,该值存储对应顶点表结点的下标值
                int adjvex = edgeNode.getAdjvex();
                // 获取边表结点指向的顶点表结点
                VertexNode vertexNode = verList.get(adjvex);
                // 删除边结点,也就是将对应的顶点表结点的入度减1
                vertexNode.setIn(vertexNode.getIn() - 1);
                // 如果该顶点表结点入度为0时压栈
                if (vertexNode.getIn() == 0) {
                    stack.push(adjvex);
                }
                // 求各顶点事件的最早发生时间值
                if ((etv[vertex] + edgeNode.getWeight()) > etv[adjvex]) {
                    etv[adjvex] = etv[vertex] + edgeNode.getWeight();
                }
                // 获取下一个边表结点的值
                edgeNode = edgeNode.getNext();
            }
        }
    }

    // 关键路径算法
    public void CriticalPath() {
        EdgeNode edgeNode;
        int i, stacktop, k, j;
        // 活动最早发生时间和最迟发生时间
        int ete, lte;
        topologicalSortForCriticalPath();
        System.out.println("etv数组:");
        for (i = 0; i < verList.size(); i++) {
            System.out.print(etv[i] + " ");
        }
        // 事件最晚发生时间
        ltv = new int[verList.size()];
        // 初始化ltv数组
        for (i = 0; i < verList.size(); i++) {
            ltv[i] = etv[verList.size() - 1];
        }
        // while循环计算具体的ltv数组的值
        while (!stack2.isEmpty()) {
            //从拓扑排序的结果从后往前遍历,因为先是从栈1中弹出后在压入栈2的,所以栈2的顶是拓扑排序的最后一个结果
            stacktop = stack2.pop();
            edgeNode = verList.get(stacktop).getFirstEdge();
            while (edgeNode != null) {
                k = edgeNode.getAdjvex();
                if (ltv[k] - edgeNode.getWeight() < ltv[stacktop]) {
                    ltv[stacktop] = ltv[k] - edgeNode.getWeight();
                }
                edgeNode = edgeNode.getNext();
            }
        }
        System.out.println("\nltv数组:");
        for (i = 0; i < verList.size(); i++) {
            System.out.print(ltv[i] + " ");
        }
        System.out.println("\n关键路径:");
            for (j = 0; j < verList.size(); j++) {
                edgeNode = verList.get(j).getFirstEdge();
                while (edgeNode != null) {
                    k = edgeNode.getAdjvex();
                    ete = etv[j];
                    lte = ltv[k] - edgeNode.getWeight();
                     if (ete == lte) {
                     System.out.println("<"+verList.get(j).getData()+","+verList.get(k).getData()+"> weight : "+edgeNode.getWeight());
                     }
                    edgeNode = edgeNode.getNext();
                }
            }
        }
    }

测试图以及邻接表

有向带权无换图.png

邻接表图.png

测试程序

public class CriticalPathMain {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        CriticalPath criticalPath = new CriticalPath();
        criticalPath.buildAoeGraph();
        criticalPath.CriticalPath();
    }
}

测试结果

关键路径算法结果图.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 关键路径:在AOV网中,路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫做关键路径。 ...
    lkmc2阅读 1,459评论 0 0
  • 第一章 绪论 什么是数据结构? 数据结构的定义:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 第二章...
    SeanCheney阅读 5,760评论 0 19
  • 大部分内容来自于《大话数据结构》,代码全部使用Swift实现。至于为什么抽风写这个?😊你懂的。 1.线性表 线性表...
    一剑孤城阅读 81,802评论 12 111
  • 从小到大,我走路都很慢,慢着慢着也都习惯落在同行人的后边,看到的更多是朋友的背影,就像被落下的影子,永远只能藏在光...
    大黄sunny阅读 113评论 0 0
  • 周末逛超市,你会发现,某些商品的促销已是常态化,甚至让一部分顾客已经养成了,某些商品,不搞活动我不买,有了...
    山东老卢阅读 393评论 0 1