数据结构篇(9) 男默女泪 终于写完图了 图的实现和一些常用操作

//顶点表节点
class Vertex {
    firstEdge: string = ''//指向第一个邻接边的指针
    data: any//顶点域
    outNum: number = 0//在无向图中表示与顶点邻接的边的数量,在有向图中为初度
    inNum: number = 0//在有向图中为顶点的入度
    constructor(data: any) {
        this.data = data;
    }
}

//边表节点 
class Edge {
    data: any
    nextEdge: any
    weight: number
    constructor(data: any, weight: number = 0) {
        this.data = data; // 邻接点域
        this.weight = weight;  // 权重
    }
}

//图结构
class Graph {
    eNum: number = 0 //边的数目
    adj: Array<any> = [] //顶点表
    isDirect: boolean
    constructor(isDirect: boolean) {
        this.isDirect = isDirect;//是否是有向图
    }

    // 初始化顶点表
    initVertex(verArr: Array<string>) {
        for (let i = 0; i < verArr.length; i++) {
            let newVer = new Vertex(verArr[i]);
            this.adj[i] = newVer;
        }
    }

    // 找到节点x在adj中所在的位置
    // 前面加上下划线表示不应该在具体实例中调用该方法
    _find(x: string) {
        let pos = -1;
        for (let i = 0; i < this.adj.length; i++) {
            if (x === this.adj[i].data) {
                pos = i;
            }
        }
        return pos;
    }
    // 向图中插入新的顶点 
    insertVertex(x: string) {
        let newVer = new Vertex(x);
        this.adj.push(newVer);
    }
    // 与顶点x邻接的所有节点
    allNeightbors(x: string) {
        let pos = this._find(x);
        let curEdge = this.adj[pos].firstEdge;
        let arr = [];
        while (curEdge) {
            arr.push(curEdge.data);
            curEdge = curEdge.nextEdge;
        }
        return arr;
    }
    // 向图中插入边(x, y)
    addEdge(x: string, y: string, w: number = 0) {
        let posX = this._find(x);
        let posY = this._find(y);
        let newEdgeX = new Edge(x, w);
        let newEdgeY = new Edge(y, w);
        if (!this.isDirect) {//如果是无向图
            if (!this.hasEdge(x, y) && !this.hasEdge(y, x)) {//如果边不存在就继续执行]
                if (posX > -1) {//当前点存在顶点表中
                    let curEdge = this.adj[posX].firstEdge;
                    if (!curEdge) {//如果当前没有顶节点
                        this.adj[posX].firstEdge = newEdgeY;
                    } else {
                        let len = this.adj[posX].outNum - 1;//获取当前顶点的边的数量
                        while (len--) {
                            curEdge = curEdge.nextEdge;
                        }
                        curEdge.nextEdge = newEdgeY;
                    }
                    this.adj[posX].outNum++;
                }
                if (posY > -1) {  // 如果顶点y在顶点表中
                    let curEdge = this.adj[posY].firstEdge;

                    if (!curEdge) { // 如果当前顶点没有第一个边节点
                        this.adj[posY].firstEdge = newEdgeX;
                        this.adj[posY].outNum++;
                    } else {
                        let len = this.adj[posY].outNum - 1;

                        while (len--) {
                            curEdge = curEdge.nextEdge;
                        }

                        curEdge.nextEdge = newEdgeX;
                        this.adj[posY].outNum++;
                    }
                }
                this.eNum++;
            }
        } else {
            // 如果是有向图则只需要插入边<x, y>即可
            if (!this.hasEdge(x, y)) {
                if (posX > -1) {
                    let curEdge = this.adj[posX].firstEdge;
                    if (!curEdge) {
                        this.adj[posX].firstEdge = newEdgeY;
                    } else {
                        let len = this.adj[posX].outNum - 1;
                        while (len--) {
                            curEdge = curEdge.nextEdge;
                        }
                        curEdge.nextEdge = newEdgeY;
                    }
                    this.adj[posX].outNum++;
                    this.eNum++;
                }
                if (posY > -1) {
                    let curVer = this.adj[posY];
                    curVer.inNum++;//顶点y的入度增加 
                }
            }
        }
    }
    // 在图中删除边(x, y)
    removeEdge(x: string, y: string) {
        let posX = this._find(x);
        let posY = this._find(y);
        if (!this.isDirect) {//如果是无向图
            if (this.hasEdge(x, y) && this.hasEdge(y, x)) {
                if (posX > -1) {
                    let curEdge = this.adj[posX].firstEdge;
                    let preEdge = null;
                    if (curEdge.data === y) {
                        this.adj[posX].firstEdge = curEdge.nextEdge;
                        this.adj[posX].outNum--;
                        curEdge.data = 0;
                        curEdge = null;
                    }
                    while (curEdge) {
                        preEdge = curEdge;
                        curEdge = curEdge.nextEdge;
                        if (curEdge && curEdge.data === y) {
                            preEdge.nextEdge = curEdge.nextEdge;
                            this.adj[posX].outNum--;
                            curEdge.data = 0;
                            curEdge = null;
                        }
                    }
                }
                if (posY > -1) {
                    let curEdge = this.adj[posY].firstEdge;
                    let preEdge = null;
                    if (curEdge.data === x) {
                        this.adj[posY].firstEdge = curEdge.nextEdge;
                        this.adj[posY].outNum--;
                        curEdge.data = 0;
                        curEdge = null;
                    }
                    while (curEdge) {
                        preEdge = curEdge;
                        curEdge = curEdge.nextEdge;
                        if (curEdge && curEdge.data === x) {
                            preEdge.nextEdge = curEdge.nextEdge;
                            this.adj[posY].outNum--;
                            curEdge.data = 0;
                            curEdge = null;
                        }
                    }
                }
                this.eNum--;
            }
        } else {
            //有向图
            if (this.hasEdge(x, y)) {
                if (posX > -1) {
                    let curEdge = this.adj[posX].firstEdge;
                    let preEdge = null;
                    if (curEdge.data === y) {
                        this.adj[posX].firstEdge = curEdge.nextEdge;
                        curEdge.data = 0;
                        this.adj[posX].outNum--;
                        curEdge = null;
                    }
                    while (curEdge) {
                        preEdge = curEdge;
                        curEdge = curEdge.nextEdge;
                        if (curEdge.data == y) {
                            preEdge.nextEdge = curEdge.nextEdge;
                            curEdge.data = 0;
                            this.adj[posX].outNum--;
                            curEdge = null;
                        }
                    }
                    if (this.adj[posY]) {
                        this.adj[posY].inNum--;
                    }
                }
                this.eNum--;
            }
        }
    }

    // // 从图中删除顶点x
    deleteVertex(x: string) {
        let pos = this._find(x);
        if (pos > -1) {
            let curEdge = this.adj[pos].firstEdge;
            //删除从x出发的边
            while (curEdge) {
                this.removeEdge(x, curEdge.data);
                curEdge = curEdge.nextEdge;
            }
            //删除所有终点是x的边
            for (let i = 0; i < this.adj.length; i++) {
                let temVer = this.adj[i].firstEdge;
                while (temVer) {
                    if (temVer.data === x) {
                        this.removeEdge(this.adj[i].data, temVer.data);
                    }
                    temVer = temVer.nextEdge;
                }
            }
            this.adj.splice(pos, 1);
        }
    }
    // 判断是否存在边(x,y)或者<x, y>
    hasEdge(x: string, y: string) {
        let pos = this._find(x);
        if (pos > -1) {
            let curEdge = this.adj[pos].firstEdge;
            while (curEdge) {
                if (curEdge.data == y) return true;
                curEdge = curEdge.nextEdge;
            }
            return false;
        }
    }
    // 获取图中所有的边 
    getAllEdge(): Array<any> {
        let arr = new Array(this.adj.length).fill(0).map(item => new Array(this.adj.length).fill(0));
        let j = 0;
        for (let i = 0; i < this.adj.length; i++) {
            let curEdge = this.adj[i].firstEdge;
            while (curEdge) {
                arr[j++] = curEdge;
                curEdge = curEdge.nextEdge;
            }
        }
        return arr;
    }
    // 获取边(x, y)或<x, y>对应的权值
    getEdgeWeight(x: string, y: string) {
        let pos = this._find(x);
        if (pos > -1) {
            let curEdge = this.adj[pos].firstEdge;
            while (curEdge) {
                if (curEdge.data === y) {
                    return curEdge.weight;
                }
                curEdge = curEdge.nextEdge;
            }
            return 0;
        }
    }
    // 获得图中最大的权值
    getMaxEdgeWeight() {
        let i = 0;
        let curVer = this.adj[i];
        let max = 0;
        while (curVer) {
            let curEdge = curVer.firstEdge;
            while (curEdge) {
                if (curEdge.weight > max) {
                    max = curEdge.weight;
                }
                curEdge = curEdge.next;
            }
            curVer = this.adj[++i];
        }
        return max;
    }
    // 获得图中最小的权值
    getMinEdgeWeight() {
        let i = 0;
        let curVer = this.adj[i];
        let min = Number.MAX_SAFE_INTEGER;
        while (curVer) {
            let curEdge = curVer.firstEdge;
            while (curEdge) {
                if (curEdge.weight < min) {
                    min = curEdge.weight;
                }
                curEdge = curEdge.next;
            }
            curVer = this.adj[++i];
        }
        return min;
    }
    // 设置边(x, y)或<x, y>的权值
    setEdgeWeight(x: string, y: string, w: number) {

    }
    // 广度优先遍历
    /**
     * 初始时,BFSTraverse()函数设置一个visited数组,将其全部赋值为false表示所有的节点都没有被访问过。然后使用_BFS()函数来求以顶点x为起点的连通分量,并将visited数组作为参数传入。
     * 当_BFS()函数求出以顶点x为起点的连通分量后,BFSTraverse()函数则再次遍历visited数组,查看是否还有未被访问过的节点。如果还有,则再次调用_BFS()函数,并传入未被访问过的顶点和visited数组。等到所有的节点都被访问过后,返回结果。
     */
    BFSTraverse(x: string = this.adj[0].data) {//x为广度优先遍历的起始顶点
        let len = this.adj.length;
        let visited = new Array(len).fill(false);  // 访问标记数组,标记数组和顶点表唯一的联系就是下标
        let result = '';
        result = this._BFS(x, visited);
        for (let i = 0; i < len; i++) {
            if (!visited[i]) {//如果还存在未访问的元素则重新调用
                result += `&${this._BFS(this.adj[i].data, visited)}`;
            }
        }
        return result;
    }

    // 实际进行广度遍历的函数,每次遍历都是得到一个以顶点x为起点的连通分量
    /**
     * _BFS()函数中使用了两个循环,外层循环每次从队列中取出一个顶点,使用内层循环依次访问该顶点的边表中的所有节点。
     * 内层循环不断的将当前起始节点的所有边表节点加入队列(只有在这些节点未被访问过时),当遍历完当前顶点的所有的边表节点后,从队列中取出一个节点再次开始循环,直到队列为空结束。
     * 简而言之,_BFS()函数干的事情就是从顶点x出发,依次访问与x相邻的节点,然后再从这些相邻的节点挨个出发再访问各自的相邻节点。为了不重复访问同一个节点,使用visited数组做访问标识,如果发现要访问的节点已经被访问过了就跳过这个节点。
     */
    _BFS(x: string, visited: Array<any>) {
        let pos: any = this._find(x);
        let result = '';
        let queue = [];
        if (pos > -1) {
            result += `${x}`;
            queue.push(pos);
            visited[pos] = true;//设置标记数组对应的下标为true
            while (queue.length) {
                pos = queue.shift();//获得当前队列顶元素
                let curVer = this.adj[pos].firstEdge;//获得对应顶点的第一条边
                while (curVer) {
                    pos = this._find(curVer.data);
                    if (!visited[pos]) {
                        visited[pos] = true;
                        result += `->${this.adj[pos].data}`;
                        queue.push(pos);
                    }
                    curVer = curVer.nextEdge;
                }
            }
        }
        return result;
    }
    // 深度优先遍历
    /**
     * 
     * 初始时,DFSTraverse()函数设置一个visited数组,将其全部赋值为false表示所有的节点都没有被访问过。然后使用_DFS()函数来求以顶点x为起点的连通分量,并将visited数组作为参数传入。
     * 当_DFS()函数求出以顶点x为起点的连通分量后,DFSTraverse()函数再次遍历visited数组,查看是否还有未被访问过的节点。如果还有,则再次调用_DFS()函数,并传入未被访问过的顶点和visited数组。等到所有的节点都被访问过后,返回结果。
     */
    DFSTraverse(x: string) {
        let len = this.adj.length;
        let visited = new Array(len).fill(false);
        let result = '';
        result = this._DFS(x, visited);
        for (let i = 0; i < this.adj.length; i++) {
            if (!visited[i]) {//如果未访问过则继续遍历
                result += this._DFS(this.adj[i].data, visited)
            }
        }
        return result;
    }

    /**
     * 1、如果发现这个节点的相邻节点中有未被访问过的,则访问这个节点,并将其压入堆栈,做已访问标识,再访问这个节点的一个相邻节点...
     * 2、如果这个节点的所有相邻节点都被访问过了,而此时堆栈还不为空,就将其弹出去。再取堆栈的栈顶元素重复步骤1的操作,如果这个节点的所有相邻节点又全被访问过了,就再将其弹出去...,依此往复,直到堆栈为空结束。
     */
    _DFS(x: string, visited: Array<any>) {
        let result = '';
        let stack = [];//辅助栈
        let pos = this._find(x);
        let curVer = this.adj[pos];//获得当前节点
        if (pos > -1) {
            stack.push(curVer);
            result += `${x}`;
            visited[pos] = true;//设置标记数组对应的节点为true
            while (stack.length) {
                curVer = stack[stack.length - 1]//获取栈顶元素
                pos = this._find(curVer.data);
                curVer = this.adj[pos].firstEdge;
                while (curVer) {
                    pos = this._find(curVer.data);
                    if (visited[pos]) { //如果改节点已经访问过了,则访问该节点的下一个节点
                        curVer = curVer.nextEdge;
                    } else {
                        stack.push(curVer);
                        result += `->${curVer.data}`;
                        visited[pos] = true;
                        break;
                    }
                }
                if (!curVer) stack.pop();//如果所有的邻接节点都访问过
            }
        }
        return result;
    }
    // 判断当前的图是否是连通图
    // 任选一个顶点作为起点
    isConnected(x = this.adj[0].data) {
        let len = this.adj.length;
        let visited = new Array(len).fill(0);
        //广度遍历一遍 如果可以全部访问则是连通图
        this._BFS(x, visited);
        for (let i = 0; i < len; i++) {
            if (!visited[i]) {
                return false;
            }
        }
        return true;
    }
    //最小生成树(普利姆算法)
    getPrimMSTree() {
        if (!this.isConnected) {//如果不是连通图则直接退出
            return false;
        }
        let V = this.adj;//顶点集合
        let Vt = [V[0]];//添加任意一个顶点
        let VVt = V.filter(item => Vt.indexOf(item) === -1);//VVt = Vt - V;
        let MSTree = new Graph(this.isDirect);//初始化空树
        V.forEach(x => MSTree.insertVertex(x.data));//先将所有顶点放入树中
        while (Vt.length != V.length) {
            let mVt = null; //当找到权值最小的边时,mVTs是边的一个顶点
            let mVVt = null; //当找到权值最小的边时,mV_VT是边的另一个顶点
            let minW = Number.MAX_SAFE_INTEGER;
            let i = Vt.length - 1;
            for (let j = 0; j < VVt.length; j++) {
                let weight = this.getEdgeWeight(Vt[i].data, VVt[j].data);
                if (weight && minW > weight) {
                    minW = weight;
                    mVt = Vt[i];
                    mVVt = VVt[j];
                }
            }
            Vt.push(mVVt);
            MSTree.addEdge(mVt.data, mVVt.data, minW);
            VVt = V.filter(x => Vt.indexOf(x) === -1);
        }
        return MSTree;
    }

    // 克鲁斯卡尔算法
    // 算法的基本思想是先找权重最小的边,再找权重次小的边
    /**
     * 
     * 在算法中,新建一颗空树,并用给定的连通图中的顶点来初始化这颗树。一开始的时候,由于这个空树中只有顶点,所以在这棵空树中的连通分量就是顶点的数目,每次找到一条符合条件的权值最小的边加入这颗空树中后,该空树的连通分量就会减一,如果循环一直继续的话,随着边的不断加入,如果不加以控制的话,该最小生成树最后就会形成回路,需要在其形成回路前终止循环,也就是随着边的不断加入,该最小生成树中的连通分量在等于1之前结束循环。
     * 在之前的算法描述中曾提到:如果找到的边加入最小生成树中不构成回路就保留,否则舍弃。那么如何在算法中实现这个过程呢?
     * 在找到权值最小的边之后,假设这条边叫做(u, v),从顶点u开始对该尚未形成的最小生成树进行一次广度或者深度优先遍历,因为之前的遍历算法中,如果一张图中具有多个连通分量,那么对这张图进行遍历后的结果就会以 '&' 将每个连通分量隔开,因为是从顶点u开始遍历的,那么在遍历后的结果中,顶点u所在的连通分量一定在第一个 '&' 分隔符之前,所以算法中在遍历的结果中只取第一个 '&' 前面的字符串,如果发现顶点v不在该字符串当中,那就说明u和v属于树中不同的连通分量,可以放心大胆的将边(u,v)加入要生成的最小生成树当中而不必担心产生回路。而如果顶点u和顶点v属于同一个连通分量的话,就说明顶点u和顶点v之间已经具有路径了,此时如果再直接连接顶点u和顶点v则必然会产生回路,所以就应该舍弃这条边了。
     */
    getKruskalMST() {
        if (!this.isConnected()) {//如果不是连通图则无意义 直接退出
            return;
        }

        let V = this.adj;  // 顶点集V
        let numS = V.length;  // 树中的连通分量
        let E = this.getAllEdge();  // 在E中存放图中所有的边
        let mEdge: any = null;

        let MSTree = new Graph(this.isDirect);  // 初始化空树
        V.forEach(x => MSTree.insertVertex(x.data));  // 树中只有顶点
        while (numS > 1) {
            let mWeight = Number.MAX_SAFE_INTEGER;
            let j: any = 0;
            // 从图中取出权值最小的边(u, v);
            for (let i = 0; i < E.length; i++) {
                if (E[i].weight < mWeight) {
                    mEdge = E[i];
                    j = i + 1;
                    mWeight = mEdge.weight;
                }
            }
            j = j / this.adj.length;//获得当前的顶点下标
            j = parseInt(j);
            let result = MSTree.BFSTraverse(this.adj[j].data);  // 广度优先遍历
            result = result.split('&')[0];  // 只取&前面的字符串
            let pos = result.indexOf(mEdge.data);

            // 如果u和v属于树中不同的连通分量,就将此边加入生成树中
            // 从顶点j遍历一遍发现没有当前节点,说明两个顶点不在一个连通分量之中
            if (pos === -1) {
                MSTree.addEdge(this.adj[j].data, mEdge.data, mEdge.weight);
                numS--;
            }

            E = E.filter(x => x !== mEdge);  // 去掉E中权值最小的边
        }

        return MSTree;
    }

    //获得图中权重之和
    getSumOfWeight() {
        // 当图不是连通的时候,获取权重之和没有意义
        if (!this.isConnected()) return;

        let sum = 0;
        let vertex = this.adj;

        if (!this.isDirect) {  // 如果是无向图
            for (let i = 0; i < vertex.length - 1; i++) {
                for (let j = i; j < vertex.length; j++) {
                    let weight = this.getEdgeWeight(vertex[i].data, vertex[j].data);
                    if (weight) sum += weight;
                }
            }
        } else {
            for (let i = 0; i < vertex.length; i++) {
                for (let j = 0; j < vertex.length; j++) {
                    let weight = this.getEdgeWeight(vertex[i].data, vertex[j].data);
                    if (weight) sum += weight;
                }
            }
        }

        return sum;
    }
    /**
     *  集合S初始时为{V0},dist的初始值dist[i] = arcs[0][i],i 表示顶点Vi。0表示顶点V0。
     * 从顶点集合V-S中选出一个顶点,假设为Vj,其满足dist[j] = Min {dist[i] | Vi∈V-S},Vj就是当前求得的V0到Vj的最短路径的终点,并令S = S ∪ { Vj }。
     * 修改从V0出发到集合V-S上任意一个顶点Vk可到达的最短路径,V0初始时可能并不能直接到达顶点Vk,这时可以通过顶点 Vj 作为中转看看是否能够到达,或者通过Vj作为中转后到达的路径权值之后小于之前已有的数值,那么就可以做出一些修改了:如果dist[j] + arcs[j][k] < dist[k],则令dist[k] = dist[j] + arcs[j][k]。
     * 重复步骤2和3,直到所有的顶点都包含在集合S中。
     */
    // 求带权图顶点x到其他顶点的最短路径
    getShortestPath(x: string) {
        // 使用Dijkstra算法,
        // 如果是无向图或者边有负的权值时退出
        // 如果x不存在于图中时退出
        // 如果从顶点x到不了图中任意一个顶点则退出
        if (!this.isDirect
            || this.getMinEdgeWeight() < 0
            || this._find(x) === -1
            || !this.isConnected(x)) { return -1; }

        let MAX = Number.MAX_SAFE_INTEGER;

        // 初始化
        let len = this.adj.length;

        // 在dist数组中,dist[i]的初值为顶点x到顶点i之间的权值,
        // x到i没有路径时,dist[i]记为无穷大
        let dist = [];
        let path = [];  // path[i]表示顶点x到i的最短路径
        let vers = [];  // 顶点集
        let exts = [x];  // 已找到最短路径的点的集合

        // 初始化path和dist数组
        for (let i = 0; i < len; i++) {
            vers[i] = this.adj[i].data;
            dist[i] = this.getEdgeWeight(x, vers[i]) || MAX;
            if (dist[i] !== MAX) {
                path[i] = `${x}->${vers[i]}`;
            } else {
                path[i] = '';
            }
        }

        let rem = vers.filter(x => exts.indexOf(x) === -1);  // 剩余的顶点
        let n = 1;

        while (n < len) {
            // 在dist中寻找最小值
            let min = MAX;
            let idx = -1;

            for (let i = 0; i < len; i++) {
                if (min > dist[i]) {
                    min = dist[i];
                    idx = i;
                }
            }

            let Vj = vers[idx];  // 直接找到Vj
            dist[idx] = MAX;
            exts.push(Vj);
            rem = vers.filter(x => exts.indexOf(x) === -1);

            console.log(path[idx]);  // 输出最短路径

            // 松弛工作
            for (let i = 0; i < rem.length; i++) {
                // Vj到其他节点的距离
                let w = this.getEdgeWeight(Vj, rem[i]) || MAX;
                let k = vers.indexOf(rem[i]);

                if (w + min < dist[k]) {
                    dist[k] = w + min;
                    path[k] = `${path[idx]}->${rem[i]}`;
                }
            }

            n++;
        }
    }
    // 拓扑排序
    /**
     * 1,找出当前入度为0的顶点,将其入栈
     * 2,删除顶点与之相关的连线,并改变其他顶点与之相连的入度
     * 3.不断重复执行1,2操作,直到顶点数组为空,如果中途有一次1操作之后栈的长度为空则说明图有环路,返回false
     */
    getTopoSort() {
        if (this.isDirect) {
            let adj = JSON.parse(JSON.stringify(this.adj));//深拷贝,防止改变原图数组
            let stack: any = [];
            let result = '';
            let count = 0;
            for (let i = 0; i < adj.length; i++) {
                if (adj[i].inNum === 0) {
                    stack.push(i);
                }
            }
            while (stack.length) {
                let gettop = stack.pop();
                result+=gettop+'->';
                count++;
                for (let e = adj[gettop].firstEdge; e; e = e.nextEdge) {
                    let k = this._find(e.data);
                    if(!(adj[k].inNum -= 1)) {
                        stack.push(k);
                    }
                }
            }
            //图中有环
            if(count != adj.length) {
                return false;
            }
            return result;
        }
    }
    // 获得Etv数组
    topologicalSort() {
        if (!this.isDirect) {
            return;
        }
        let top = 0, count = 0;
        let adj = JSON.parse(JSON.stringify(this.adj));
        let gettop: any, k;
        let result = '';//结果
        let stack = [];
        let stack2 = [];
        let etv = new Array(adj.length).fill(0);
        for (let i = 0; i < adj.length; i++) {
            if (this.adj[i].inNum == 0) {
                stack.push(i);
            }
        }
        while (stack.length) {
            gettop = stack.pop();
            result += adj[gettop].data + '-> ';
            count++;
            stack2.push(gettop);
            for (let e = adj[gettop].firstEdge; e; e = e.nextEdge) {
                k = this._find(e.data);
                if (!(adj[k].inNum -= 1)) {
                    stack.push(k);
                }
                if (etv[gettop] + e.weight > etv[k]) {
                    etv[k] = etv[gettop] + e.weight;
                }
            }
        }
        if (count < adj.length) {
            console.info('发生错误,有环路存在');
            return false;
        }
        return {
            etv: etv,
            stack: stack2
        };
    }
    // 关键路径
    getCriticalPath() {
        let topological:any = this.topologicalSort();
        let etv = topological.etv;//最早发生时间
        let stack = topological.stack;
        let adj = JSON.parse(JSON.stringify(this.adj));

        console.info('可计算的最早发生时间数组etv:' + etv);
        console.info('拓扑序列:' + stack);
        let gettop, k;
        let ltv = new Array();//最迟发生时间
        for (let i = 0; i < this.adj.length; i++) {
            ltv[i] = etv[this.adj.length - 1];
        }
        while (stack.length) {
            gettop = stack.pop();
            for (let e = adj[gettop].firstEdge; e; e = e.nextEdge) {
                k = this._find(e.data);
                if (ltv[k] - e.weight < ltv[gettop]) {
                    ltv[gettop] = ltv[k] - e.weight;
                }
            }
        }
        for (let j = 0; j <adj.length; j++) {
            for (let e = adj[j].firstEdge; e; e = e.nextEdge) {
                k = this._find(e.data);
                if (etv[j] == ltv[k] - e.weight) {
                    console.info(adj[j].data + '到' + adj[k].data + '(' + e.weight + ')');
                }
            }
        }
    }
}

let arr2 = ['v0', 'v1', 'v2', 'v3', 'v4', 'v5', 'v6', 'v7', 'v8', 'v9'];
let myGraph = new Graph(true);  // 1表示有向图
myGraph.initVertex(arr2);

myGraph.addEdge('v0', 'v2', 4);
myGraph.addEdge('v0', 'v1', 3);
myGraph.addEdge('v1', 'v4', 6);
myGraph.addEdge('v1', 'v3', 5);
myGraph.addEdge('v2', 'v5', 7);
myGraph.addEdge('v2', 'v3', 8);
myGraph.addEdge('v3', 'v4', 3);
myGraph.addEdge('v4', 'v7', 4);
myGraph.addEdge('v4', 'v6', 9);
myGraph.addEdge('v5', 'v7', 6);
myGraph.addEdge('v6', 'v9', 2);
myGraph.addEdge('v7', 'v8', 5);
myGraph.addEdge('v8', 'v9', 3);

myGraph.getCriticalPath();
posted @ 2022-04-05 17:44  ajajaz  阅读(48)  评论(0编辑  收藏  举报