美文网首页
图论:DFS、BFS和优先级搜索框架实现最小支撑树、最短路

图论:DFS、BFS和优先级搜索框架实现最小支撑树、最短路

作者: MachinePlay | 来源:发表于2019-08-19 21:00 被阅读0次

    1.BFS

    优先访问最先发现的节点,可以根据FIFO队列的特点,选用队列实现
    可以实现连通域分解和最短路径等问题。
    等同于树结构中的层次遍历

    //whole graph
    template <typename Tv, typename Te>
    void Graph<Tv,Te>::bfs(int s){
        reset();
        int clock=0;
        int v=s;
        do 
            if(status(v)==UNDISCOVERED){
                BFS(v,clock);
            }
        while(s!=(v=(++v%n)));
    }
    //single connection component
    template <typename Tv, typename Te>
    void Graph<Tv,Te>::BFS( int v, int& clock){
        std::queue<int > Q;
        status(v)=DISCOVERED;
        Q.push(v);
        while(!Q.empty()){
            int v=Q.front();
            Q.pop();
            dTime(v)=++clock;
    
            for(int u=firstNbr(v) ;-1<u ; u = nextNbr(v , u) ){
                if(status(u)==UNDISCOVERED){
                    status(u)=DISCOVERED;
                    Q.push(u);
                    type(v,u)=TREE;
                    parent(u)=v;
                }
                else{
                    type(v,u)=CROSS;
                }
    
            }
        status(v)=VISITED;
        }
    }
    
    
    
    

    2.DFS

    优先选取最后一个被访问到的顶点的邻居
    于是,以顶点s为基点的DFS搜索,将首先访问顶点s;再从s所有尚未访问到的邻居中任取 其一,并以之为基点,递归地执行DFS搜索。故各顶点被访问到的次序,类似于树的先序遍历 ;而各顶点被访问完毕的次序,则类似于树的后序遍历

    //DFS
    template <typename Tv, typename Te>
    void Graph<Tv,Te>::dfs(int s){
        reset();
        int clock=0;
        int v=s;
    do
    {
        if(UNDISCOVERED==status(v)){
            DFS(v,clock);
        }
    } while (s!=(v=++v%this->n));
    
    }
    
    template <typename Tv, typename Te>
    void Graph<Tv, Te>::DFS(int v, int&clock){
        dTime(v)=++clock;
        status(v)=DISCOVERED;
        for(int u=firstNbr(v);u>-1;u=nextNbr(v,u)){
            if( status(u)==UNDISCOVERED){
                type(v,u)=TREE;
                parent(u)=v;
                DFS(u,clock);
            }
            else if(u==DISCOVERED){
                type(v,u)=BACKWARD;
            }
            else {
                type(v,u)=(dTime(v)<dTime(u))?FORWARD:CROSS;
            }
        }
         status(v)=VISITED;
         fTime(v)=++clock;
    }
    

    算法的实质功能,由子算法DFS()递归地完成。每一递归实例中,都先将当前节点v标记为 DISCOVERED(已发现)状态,再逐一核对其各邻居u的状态并做相应处理。待其所有邻居均已处 理完毕之后,将顶点v置为VISITED(访问完毕)状态,便可回溯。

    若顶点u尚处于UNDISCOVERED(未发现)状态,则将边(v, u)归类为树边(tree edge),并将v记作u的父节点。此后,便可将u作为当前顶点,继续递归地遍历。 若顶点u处于DISCOVERED状态,则意味着在此处发现一个有向环路。此时,在DFS遍历树中u必为v的祖先,故应将边(v, u)归类为后向边(back edge)。 这里为每个顶点v都记录了被发现的和访问完成的时刻,对应的时间区间[dTime(v), fTime(v)]均称作v的活跃期(active duration)。实际上,任意顶点v和u之间是否存在祖先-后代的“血缘”关系,完全取决于二者的活跃期是否相互包含。

    对于有向图,顶点u还可能处于VISITED状态。此时,只要比对v与u的活跃期,即可判定在 DFS树中v是否为u的祖先。若是,则边(v, u)应归类为前向边(forward edge);否则,二者必然来自相互独立的两个分支,边(v, u)应归类为跨边(cross edge)。

    DFS(s)返回后,所有访问过的顶点通过parent[]指针依次联接,从整体上给出了顶点s所属连通或可达分量的一棵遍历树,称作深度优先搜索树或DFS树(DFS tree)。与BFS搜索一样, 此时若还有其它的连通或可达分量,则可以其中任何顶点为基点,再次启动DFS搜索。
    最终,经各次DFS搜索生成的一系列DFS树,构成了DFS森林(DFS forest)

    应用:拓扑排序

    有向无环图一定有拓扑排序
    1.入度为零法
    考察图G,寻找入度为0的顶点,去掉后删除相关边,再找下一个入度为零点。直到仅剩一个顶点。
    2.VISITED顺序的倒序即是一个拓扑排序
    同理,有限偏序集中也必然存在极小元素(同样,未必唯一)。该元素作为顶点,出度必然 为零。 而在对有向无环图的DFS搜索中,首先因访问完成而转 换至VISITED状态的顶点m,也必然具有这一性质;反之亦然。

    进一步地,根据DFS搜索的特性,顶点m(及其关联边)对此后的搜索过程将不起任何作用。 于是,下一转换至VISITED状态的顶点可等效地理解为是,从图中剔除顶点m(及其关联边)之 后的出度为零者在拓扑排序中,该顶点应为顶点m的前驱。由此可见,DFS搜索过程中各顶 点被标记为VISITED的次序,恰好(按逆序)给出了原图的一个拓扑排序。

    此外,DFS搜索善于检测环路的特性,恰好可以用来判别输入是否为有向无环图。具体地, 搜索过程中一旦发现后向边,即可终止算法并报告“因非DAG而无法拓扑排序”。

    template <typename Tv, typename Te>
    std::stack<Tv>* Graph<Tv,Te>::tSort(int s){
        reset();
        int clock=0;
        int v=s;
        std::stack<Tv> S=new std::stack<Tv>;
        do
        {
            if(status(v)==UNDISCOVERED){
                //topoogy sort and break&clear the stack
                if(!Tsort(v,clock,S)){
                    while(!S.empty()){
                        S.pop();
                    }
                    break;
                }
            }
        }while(s!=(v=++v%this->n));
    
        return S;
    }
    
    template <typename Tv ,typename Te>
    bool Graph<Tv,Te>::Tsort(int v,int &clock, std::stack<Tv> &S){
        status(v)=DISCOVERED;
        dTime(v)=++clock;
        for(int u=firstNbr(v);u>-1;u=nextNbr(v)){
            if(status(u)==UNDISCOVERED){
                type(v,u)=TREE;parent(u)=v;
                //continue search deeper
                if(!Tsort(u,clock,S)){
                    return false;
                }
            else if(status(u)==DISCOVERED){
                type(v,u)=BACKWARD;
                return false;
            }
            else {
                type(v,u)=(dTime(v)<dTime(u))?FORWARD:CROSS;
            }
            }
        }
        status(v)=VISITED;
        S.push(vertex(v));
        return true;
    }
    

    分解双连通域

    若节点C的移除导致其某一棵(比如以D为根的)真子树与其真祖先(比如A)之间无法连通,则C必为关节点。反之,若C的所有真子树都能(如以E为根的子 树那样)与C的某一真祖先连通,则C就不可能是关节点。


    image.png

    当然,在原无向图的DFS树中,C的真子树只可能通过后向边与C的真祖先连通。因此,只要 在DFS搜索过程记录并更新各顶点v所能(经由后向边)连通的最高祖先(highest connected ancestor, HCA)hca[v],即可及时认定关节点,并报告对应的双连通域。

    //bcc
    template <typename Tv,typename Te>
    void Graph<Tv, Te>::bcc(int s){
        reset();
        int v=s;
        int clock=0;
        std::stack<int> S;
        do{
            if(status(v)==UNDISCOVERED){
                BCC(v,clock,S);
                S.pop();
            }
        }while(s!=(v=++v%this->n));
    }
    #define hca(x) (fTime(x))
    template <typename Tv,typename Te>
    void Graph<Tv,Te>::BCC(int v, int &clock, std::stack<int> &S){
        hca(v)=dTime(v)=++clock; 
        status(v)=DISCOVERED;
        S.push(v);
        for(int u=firstNbr(v);u>-1;u=nextNbr(v)){
            if(status(u)==UNDISCOVERED){
                parent(u)=v;
                type(v,u)=TREE;
                BCC(u,clock,S);
                //ancester  u connected to ancester of v through backward
                if(hca(u)<dTime(v)){
                    hca(v)=(hca(v)<hca(u))?hca(v):hca(u);//the highest connected component
                }
                else{//v is the articulation point
                    while(v!=S.top()){
                        S.pop();
                    }//only keep v
                }
            }
            else if(status(u)==DISCOVERED){
                type(v,u)=BACKWARD;
                hca(v)=(dTime(u)<hca(v))?dTime(u):hca(v);
            }
        }
    }
    

    3.优先级搜索

    图搜索虽然各具特点,但其基本框架依然类似。总体而言,都是迭代逐一发现新顶点,纳入遍历树中处理。各算法再送能上的差异,主要在每一步迭代新顶点但选择上。BFS优先选取更早发现但顶点,DFS则优先选取最后发现但顶点。

    每种搜索策略都等效于,赋予顶点不同的优先级,算法调整中选取优先级最高的。

    按照这种理解,包括BFS和DFS在内的 几乎所有图搜索,都可纳入统一的框架。鉴于优先级在其中所扮演的关键角色,故亦称作优先级 搜索(priority-first search, PFS),或最佳优先搜索(best-first search, BFS)。

    落实以上理解,可为顶点提供了priority()接口,以支持对顶点优先级数(priority number)的读取和修改。在实际应用中,引导优化方向的指标往往对应于某种有限的资源或成本(如光纤长度、通讯带宽等),故不妨约定优先级数越大(小)顶点的优先级越低(高)。相应地,在算法的初始化阶段(reset()),通常都将顶点的优 先级数统一置为最大(比如INT_MAX)或等价地,优先级最低。
    按照上述思路,优先级搜索算法的框架可具体实现如代码

    //遍历所有顶点,如果没有访问过就依次进行PFS,得到多棵不相干的遍历树
    //主要是为了搜索所有顶点,防止遗漏连通域
    template <typename Tv,typename Te> template <typename PU>
    void Graph<Tv,Te>::pfs(int s, PU prioUpdater){
        reset(); int v=s;
        do{
            if(status(v)==UNDISCOVERED){
                PFS(v,prioUpdater);
            }
        }while(s!=(v=v++%n));
    }
    
    //对一个顶点进行优先级搜索
    template <typename Tv,typename Te> template <typename PU>
    void Graph<Tv,Te>::PFS(int s, PU prioUpdater){
        priority(s)=0; status(s)=VISITED;parent(s)=-1;//initialize
        while(true){
            for(int u=firstNbr(s);u>-1;u=nextNbr(s,u)){
                prioUpdater(this,s,u);//updata priorty and it's father
            }
            for(int shortest=INT_MAX, u=0;u>n;u++){
                if(status(u)==UNDISCOVERED){
                    if(shortest>priority(u)){
                        shortest=priority(u);
                        s=u;//max priority point
                    }
                }
            }
            if(VISITED==status(s)) break;
            status(s)=VISITED;
            type(parent(s),s)=TREE;
        }
    }
    
    

    如上所述,每次都是引入当前优先级最高(优 先级数最小)的顶点s,然后按照不同的策略更新其邻接顶点的优先级数。
    这里借助函数对象prioUpdater,使算法设计者得以根据不同的问题需求,简明地描述和实现对应的更新策略。具体地,只需重新定义prioUpdater对象即可,而不必重复实现公共部分。 比如,此前的BFS搜索和DFS搜索都可按照此模式统一实现

    下面,以最小支撑树和最短路径这两个经典的图算法为例,深入介绍这一框架的具体应用。

    应用:

    1.最小支撑树

    若图G为一带权网络,则每一棵支撑树的成本(cost)即为其所采用各边权重的总和。在G 的所有支撑树中,成本最低者称作最小支撑树(minimum spanning tree, MST)。
    聚类分析、网络架构设计、VLSI布线设计等诸多实际应用问题,都可转化并描述为最小支 撑树的构造问题。在这些应用中,边的权重大多对应于某种可量化的成本,因此作为对应优化问 题的基本模型,最小支撑树的价值不言而喻。

    消除歧义

    尽管同一带权网络通常都有多棵支撑树,但总数毕竟有限,故必有最低的总体成本。然而, 总体成本最低的支撑树却未必唯一。以包含三个顶点的完全图为例,若三条边的权重相等,则其 中任意两条边都构成总体成本最低的一棵支撑树。
    故严格说来,此类支撑树应称作极小支撑树(minimal spanning tree)。当然, 通过强制附加某种次序即可消除这种歧义性。

    暴力算法

    由最小支撑树的定义,可直接导出蛮力算法大致如下:逐一考查G的所有支撑树并统计其成 本,从而挑选出其中的最低者。然而根据Cayley公式,由n个互异顶点组成的完全图共有nn-2棵 支撑树,即便忽略掉构造所有支撑树所需的成本,仅为更新最低成本的记录就需要O(nn-2)时间。事实上基于PFS搜索框架,并采用适当的顶点优先级更新策略,即可得出如下高效的最小支 撑树构造算法。

    Prim算法

    图G = (V; E)中,顶点集V的任一非平凡子集U及其补集V\U都构成G的一个割(cut),记作(U : V\U)。若边uv满足u属于U且v不属于U,则称作该割的一条跨越边(crossing edge)。

    Prim算法的正确性基于以下事实:最小支撑树总是会采用联接每一割的最短跨越边。

    由以上性质,可基于贪心策略导出一个迭代式算法。每一步迭代之前,假设已经得到最小支 撑树T的一棵子树Tk = (Vk; Ek),其中Vk包含k个顶点,Ek包含k - 1条边。于是,若将Vk及其补 集视作原图的一个割,则在找到该割的最短跨越边ek之后,即可 将Tk扩展为一棵更大的子树Tk+1 = (Vk+1; Ek+1),其中Vk+1 = Vk 并 {uk},Ek+1 = Ek 并 {ek}。 最初的T1不含边而仅含单个顶点,故可从原图的顶点中任意选取。

    //prim MST
    template <class Tv ,class Te>
    struct PrimPu{
        virtual void operator()(Graph<Tv,Te> &g,int uk,int v){
                if(UNDISCOVERED==g.status(v)){
                    if(g.priority(v)>g.weight(uk,v)){
                        g.priority(v)=g.weight(uk,v);
                        g.parent(v)=uk;
                    }
                }
        }
    };
    

    替换掉PFS框架里的PrioUpdater即可实现最小支撑树。
    以上Prim算法的时间复杂度为O(n2),作为PFS搜索的特例,Prim算法的效率 也可借助优先级队列进一步提高

    2.最短路径

    给定带权网络G = (V, E),以及源点(source)s,对于所有的其它顶点v, s到v的最短通路有多长?该通路由哪些边构成?

    首先我们要分析最短路径树等特征

    • 单调性:最短路径的任意前缀路径必须是最短路径
    • 歧义:最短路径不唯一,等权边会导致结果不唯一,负权边导致定义失效
    • 无环路

    Dijkstra算法

    将顶点ui到起点s的距离记作:di = dist(s, ui),1 <i < n。不妨设di按非降序排列,
    即di <dj当且仅当i < j。于是与s自身相对应地必有:u1 = s。

    • 最短路径子树序列
      在从最短路径树T中删除顶点{ uk+1, uk+2, ..., un }及其关联各边之后,将残存的子图记 作Tk。于是Tn = T,T1仅含单个顶点s。实际上,Tk必为一棵树。为验证这一点,只需归纳证明 Tk是连通的。为从Tk+1转到Tk而删除的顶点uk+1,在Tk+1中必是叶节点。而根据最短路径的单调性, 作为Tk+1中距离最远的顶点,uk+1不可能拥有后代。
      于是,如上定义的子树{ T1, T2, ..., Tn },便构成一个最短路径子树序列。
    • 贪心迭代
      颠倒上述思路可知,只要能够确定uk+1,便可反过来将Tk扩展为Tk+1。如此,便可按照到s距离的非降次序,逐一确定各个顶点{ u1, u2, ..., un },同时得到各棵最短路径子树,并得到 最终的最短路径树T = Tn。现在,问题的关键就在于:

    如何才能高效地找到uk+1?

    实际上,由最短路径子树序列的上述性质,每一个顶点uk+1都是在Tk之外,距离s最近者。 若将此距离作为各顶点的优先级数,则与最小支撑树的Prim算法类似,每次将uk+1加入Tk并将其 拓展至Tk+1后,需要且只需要更新那些仍在Tk+1之外,且与Tk+1关联的顶点的优先级数。

    为此,每次由Tk扩展至Tk+1时,可将Vk之外各顶点u到s的距离看作u的优先级数,如此,每一最短跨越边ek所对应的顶点uk,都会因拥有 最小的优先级数(或等价地,最高的优先级)而被选中

    template <class Tv, class Te>
    struct DijkstraPU{
        virtual void operator()(Graph<Tv,Te> &g, int uk, int v){
            if(g.status(v)==UNDISCOVERED){
                if(g.priority(v)>g.priority(uk)+g.weight(uk,v)){
                    g.priority(v)=g.priority(uk)+g.weight(uk,v);
                    g.parent(v)=uk;
                }
            }
        }
    };
    

    每次更新新顶点v的权重priority(v)等于上一个顶点uk的权重priority(uk)加上边uk-v的权重。事实上,每个顶点的权重等于该顶点到初始节点的边权重和

    图模版

    #include <stack>
    #include "vector.h"
    #include <queue>
    typedef enum{UNDISCOVERED,DISCOVERED,VISITED} VStatus;
    typedef enum{UNDETERMINED,TREE,CROSS,FORWARD,BACKWARD} EType;
    
    template <class Tv,class Te>
    class Graph{
        private:
            /**
             * reset
             */
            void reset(){
                for(int i=0;i<n;i++){
                    status(i)=UNDISCOVERED;
                    dTime(i)=fTime(i)=-1;
                    parent(i)=-1;
                    priority(i)=__INT_MAX__;
                    for (int j=0;j<n;j++){
                        if(exists(i,j)) type(i,j)==UNDETERMINED;
                    }
    
                }
            }
            /**
             * BFS
             */
            void BFS(int ,int &);
    
            /**
             * DFS
             */
            void DFS(int ,int &);
    
            /**
             * BCC based on DFS
             */
            void BCC(int ,int &, std::stack<int>& );
    
            /**
             * Tsort based on DFS
             */
            bool Tsort(int ,int &, std::stack<Tv>&);
    
            /**
             * PSU
             */
            template <class PU>
            void PFS(int ,PU);
    
        public:
            int n;//vertex number
            /**
             * insert
             */
            virtual int insert(Tv const&)=0;
            /**
             * remove vertex n
             */
            virtual Tv remove(int )=0;
    
            /**
             * get vertex n data
             */
            virtual Tv& vertex(int)=0;
    
            /**
             * get inDegree of vertex n
             */
            virtual int inDegree(int )=0;
    
            /**
             * get outDegree of vertex n
             */
            virtual int outDegree(int)=0;
    
            /**
             * vertex v's first neibhor
             */
            virtual int firstNbr(int )=0;
    
            virtual int nextNbr(int i,int j)=0;
    
            /**
             * get vertex status 
             */
            virtual VStatus& status(int )=0;
    
            /**
             * dTime of vertex
             */
            virtual int& dTime(int)=0;
    
            /**
             * ftime
             */
            virtual int&fTime(int)=0;
    
            /**
             * parent vertex of traverse tree
             */
            virtual int& parent(int)=0;
    
            /**
             * priority of vertex
             */
            virtual int& priority(int)=0;
    
            //edge treat undigraph as reverse digraph
            int e;//number of edge;
            
            /**
             * e(i,j) exists
             */
            virtual bool exists(int ,int )=0; 
    
            /**
             * insert edge with weight w between u,and v
             */
            virtual void insert(Te const& e,int ,int ,int)=0;
    
            /**
             * remove edge e between u,v return info
             */
            virtual Te remove(int ,int)=0;
    
            /**
             * type of e(u,v)
             */
            virtual EType& type(int ,int )=0;
    
            /**
             * get number of e(u,v)
             */
            virtual Te& edge(int ,int )=0;
    
            /**
             * get weight of e(u,v)
             */
            virtual int &weight(int,int )=0;
    
            //algorithms
            /**
             * bradth first search
             */
            void bfs(int);
    
            /**
             * dfs
             */
            void dfs(int);
    
            /**
             * bcc double conncetion based on dfs
             */
            void bcc(int);
    
            /**
             * topologic sort based on dfs
             */
            std::stack<Tv>* tSort(int);
    
            /**
             * prim
             */
            void prim(int);
    
            /**
             * dijkstra
             */
            void dijkstra(int);
            
            /**
             * pfs
             */
            template <class PU>
            void pfs(int ,PU);
    
    };
    //GraphMatrix
    /**
     * @bref Vertex
     */
    template <typename Tv>
    struct Vertex{
        Tv data;
        int inDegree;
        int outDegree;
        VStatus status;
    
        int dTime;
        int fTime;
        
        int parent;
        int priority;
    
        /**
         * constructor
         */
        Vertex(Tv const& d=(Tv)0):data(d),inDegree(0),outDegree(0),status(UNDISCOVERED),dTime(-1),fTime(-1),parent(-1),priority(__INT_MAX__){};
    };
    
    /**
     * @bref Edge
     */
    template<typename Te>
    struct Edge{
        Te data;
        int weight;
        EType type;
        /**
         * constructor
         */
        Edge(Te const&d,int w):data(d),weight(w),type(UNDETERMINED){};
    };
    
    //GraphMatrix
    /**
     * GraphMatrix
     */
    template<typename Tv,typename Te>
    class GraphMatrix:public Graph<Tv,Te>{
        private:
            Vector< Vertex<Tv> > V;//store the vertex
            Vector< Vector< Edge<Te>* > >E;//store the edgematrix double vector
        
        public :
        GraphMatrix(){
            this->n=0;
            this->e=0;
            }
        ~GraphMatrix(){
            for (int j=0;j<this->n;j++){
                for(int k=0;k<this->n;k++){
                    delete E[j][k];
                }
            }
        }
    
        //vertex
        virtual Tv& vertex(int i){
            return V[i].data;
        }
    
        virtual int inDegree(int i){
            return V[i].inDegree;
        }
    
        virtual int outDegree(int i){
            return V[i].outDegree;
        }
    
        virtual int firstNbr(int i){
            return nextNbr(i,this->n);
        }
    
        virtual int nextNbr(int i,int j){
            while((-1<j)&&(!exists(i,--j)));
            return j;
        }
    
        virtual VStatus& status(int i){
            return V[i].status;
        }
    
        virtual int&dTime(int i){
            return V[i].dTime;
        }
    
        virtual int &fTime(int i){
            return V[i].fTime;
        }
    
        virtual int &parent(int i){
            return V[i].parent;
        }
    
        virtual int &priority(int i){
            return V[i].priority;
        }
    
        //dynamic operator of vertex
        virtual int insert(Tv const& vertex){
            for(int j=0;j<this->n;j++){
                E[j].insert(nullptr);//each edge set a default no connect edge to newVertex
            }
            this->n++;
            //new edge
            E.insert(Vector< Edge<Te>* > (this->n,(Edge<Te>) nullptr));
            return V.insert(Vertex<Tv> (vertex));
        }
    
        virtual Tv remove(int i){
            for(int j=0;j<this->n;j++){
                    if(exists(i,j)){
                        delete E[i][j];
                        V[j].inDegree--;
                    }
            }
            E.remove(i);
            this->n--;
            Tv vBackup=V[i].data;
            V.remove(i);
    
            for(int j=0;j<this->n;j++){
                if(Edge<Te> *e=E[j].remove(i)){delete e;V[j].outDegree--;}
            }
            return vBackup;
        }
    
    
        //confirm e[i][j] exist
        virtual bool exists(int i,int j){
            return (0<=i)&&(i<this->n)&&(j>=0)&&(j<this->n)&&(E[i][j]!=nullptr);
        }
    
        //edge operator
        virtual EType& type(int i,int j){
            return E[i][j].type;
        } 
    
        virtual Te& edge(int i,int j){
            return E[i][j]->data;
        }
    
        virtual int& weight(int i, int j){
            return E[i][j]->weight;
        }
    
        //dynamic operator of edge
        virtual void insert(Te const& edge, int w, int i, int j){
            if(exists(i,j)) return ;
            E[i][j]=new Edge<Te>(edge,w);
            this->e++;
            V[i].outDegree++;
            V[j].inDegree++;
        }
    
        virtual Te remove(int i, int j){
            if(!exists(i,j)) exit(-1);
            Te eBackup=edge(i,j);
            delete E[i][j];
            E[i][j]=nullptr;
            this->e--;
            V[i].outDegree--;
            V[j].inDegree--;
            return eBackup;
        }
    
    
    
    
    
    };
    

    相关文章

      网友评论

          本文标题:图论:DFS、BFS和优先级搜索框架实现最小支撑树、最短路

          本文链接:https://www.haomeiwen.com/subject/ajqjsctx.html