DFS应用(拓扑排序和强连通分支)

来源:互联网 发布:select linux 编辑:程序博客网 时间:2024/05/16 10:49

本文将用实例分析DFS搜索算法的两大应用:

1、运用深度优先搜索,对一个有向无回路图DAG进行拓扑排序;
2、运用深度优先搜索,将一个有向图分解为各强连通分支。


一、拓扑排序

首先拓扑排序是针对有向无回路图来说的,反之,如果图中有回路,就不可能存在这样的线性序列。 Topological-Sort算法可以产生一个有向无回路图G的拓扑序列。

1、Topological-Sort算法实现思路:

<1>调用DFS去搜索有向无回路图并记录每一个顶点的finishTime,也就是记录在DFS遍历过程中每个顶点的完成时间;

<2>当每个顶点完成时,将其从一个链表的头部插入(开始时简历一个空链表,每完成一个顶点便从链表头部插入);

<3>当DFS遍历结束后,返回这个链表便是有向无回路图的拓扑排序。

Topological-Sort算法的时间复杂度是O(V+E),也就是深度优先搜索的时间复杂度,因为算法中将|V|个顶点插入到链表中所用的时间是O(1)。

后文将给出Topological-Sort算法的Java实例分析!!!


二、强连通分支

有向图G的一个强连通分支就是一个最大的顶点集C ⊆ V,对于C中的每一个顶点 u 和 v,他们之间都是相互可达到的。在得到图的强连通分支时,需要使用到图G的转置,也就是原图G将所有边都反向,便可得到图的转置。使用Strongly-Connected-Comopnents算法,可以正确计算有向图G的强连通分支。

1、Strongly-Connected-Comopnents算法实现思路:

<1>对原图执行DFS算法,并得到每个顶点的finishTime;

<2>计算得到原图的转置;

<3>对原图的转置图在执行DFS算法,但是注意此时在DFS的主循环中按照上面得到的顶点的finishTime的降序依次考虑每个顶点;

<4>输出第<3>步DFS过程中得到的每一棵树便是原图的每一个强连通分支。最后形成的森林便是原图强连通分支的集合。

Strongly-Connected-Comopnents算法的时间复杂度也是O(E+V),便是两次深度优先搜索的时间复杂度。

下面将给出Strongly-Connected-Comopnents算法的Java实例分析!!!


三、拓扑排序和强连通分支的Java实现

我们将对下面的图进行拓扑排序并且生成其强连通分支

这里写图片描述

1、Graph类:描述图含有的属性

public class Graph {    Vertex[] vertexArray=new Vertex[100];    int verNum=0;    int edgeNum=0;}

2、Vertex类:描述图顶点包含的属性

public class Vertex {    String verName;    String color;    int discoverTime;    int finishTime;    Vertex parent;    Vertex nextNode;}

3、VertexNode类:实现链表的节点类

/** * 用于存储顶点对象的链表 * @author King */public class VertexNode {    Vertex vertexData;    VertexNode next;    public VertexNode(Vertex ver){        vertexData=ver;    }}

4、LinkList类:该类实现链表的部分操作

public class LinkList {    VertexNode first;    /**     * 构造函数,用于初始化头节点     */    public LinkList(){        this.first=null;    }    /**     * 向空链表中插入头结点     * @param data 头链表数据     */    public void addFirstNode(Vertex vertex){        VertexNode vertexNode=new VertexNode(vertex);        vertexNode.next=first;        first=vertexNode;    }    /**     * 从链表尾部插入节点     * @param vertex 顶点对象     */    public void addTailNode(Vertex vertex){        VertexNode vertexNode=new VertexNode(vertex);        VertexNode current=first;        if(current==null){            vertexNode.next=current;            first=vertexNode;        }else{            VertexNode present=current;            while(current!=null){                present=current;                current=current.next;            }            present.next=vertexNode;        }    }}

5、拓扑排序和强连通分支实现主类

import java.util.ArrayList;import java.util.Scanner;public class TopologicalSort {    int time;//  下面链表用于记录拓扑序列    LinkList linkList=new LinkList();//  下面是为了实现反向图而采用的数据结果    ArrayList<String> arrayEdge1=new ArrayList<String>();    ArrayList<String> arrayEdge2=new ArrayList<String>();    /**     * 根据用户输入的string类型的顶点返回该顶点     * @param graph 图     * @param str 输入数据     * @return返回一个顶点     */    public Vertex getVertex(Graph graph,String str){        for(int i=0;i<graph.verNum;i++){            if(graph.vertexArray[i].verName.equals(str)){                return graph.vertexArray[i];            }        }        return null;    }    /**     * 根据用户输入的数据初始化一个图,以邻接表的形式构建!     * @param graph 生成的图     */    public void initialGraph(Graph graph){        @SuppressWarnings("resource")        Scanner scan=new Scanner(System.in);        System.out.println("请输入顶点数和边数:");        graph.verNum=scan.nextInt();        graph.edgeNum=scan.nextInt();        System.out.println("请依次输入定点名称:");        for(int i=0;i<graph.verNum;i++){            Vertex vertex=new Vertex();            String name=scan.next();            vertex.verName=name;            vertex.color="white";            vertex.discoverTime=0;            vertex.finishTime=0;            vertex.parent=null;            vertex.nextNode=null;            graph.vertexArray[i]=vertex;        }        System.out.println("请依次输入图的便边:");        for(int i=0;i<graph.edgeNum;i++){            String preV=scan.next();            String folV=scan.next();            arrayEdge1.add(preV);            arrayEdge2.add(folV);            Vertex v1=getVertex(graph,preV);            if(v1==null)                System.out.println("输入边存在图中没有的顶点!");            Vertex v2=new Vertex();            v2.verName=folV;            v2.nextNode=v1.nextNode;            v1.nextNode=v2;        }    }    /**     * 输入图的邻接表     * @param graph 待输出的图     */    public void outputGraph(Graph graph){        for(int i=0;i<graph.verNum;i++){            Vertex vertex=graph.vertexArray[i];            System.out.print(vertex.verName);            Vertex current=vertex.nextNode;            while(current!=null){                System.out.print("-->"+current.verName);                current=current.nextNode;            }            System.out.println();        }    }    /**     *利用图的DFS遍历完成拓扑排序主函数     * @param graph 图     */    public void dfsTopoligical(Graph graph){        for(int i=0;i<graph.verNum;i++){            if(graph.vertexArray[i].color.equals("white")){                topological(graph.vertexArray[i],graph);                System.out.println();            }        }    }    /**     * 利用DFS完成图的拓扑排序辅助函数     * @param vertex 顶点     * @param graph 图     */    public void topological(Vertex vertex,Graph graph){        vertex.color="gray";        time=time+1;        vertex.discoverTime=time;        System.out.print(vertex.verName+"-->");        Vertex current=vertex.nextNode;        while(current!=null){            Vertex currentNow=getVertex(graph, current.verName);            if(currentNow.color.equals("white"))                topological(currentNow,graph);            current=current.nextNode;        }        vertex.color="black";        time=time+1;        vertex.finishTime=time;        linkList.addFirstNode(vertex);    }    /**     * 构建最初输入图的转置,即各边全部变成原来的反向边     * @param graph     */    public void reverseGraph(Graph reverseGraph,Graph graph){        reverseGraph.edgeNum=graph.edgeNum;        reverseGraph.verNum=graph.verNum;        for(int i=0;i<graph.verNum;i++){            graph.vertexArray[i].nextNode=null;            graph.vertexArray[i].color="white";            graph.vertexArray[i].discoverTime=0;            graph.vertexArray[i].finishTime=0;            reverseGraph.vertexArray[i]=graph.vertexArray[i];        }        for(int i=0;i<arrayEdge1.size();i++){            String preV=arrayEdge2.get(i);            String folV=arrayEdge1.get(i);            Vertex v1=getVertex(graph,preV);            if(v1==null)                System.out.println("输入边存在图中没有的顶点!");            Vertex v2=new Vertex();            v2.verName=folV;            v2.nextNode=v1.nextNode;            v1.nextNode=v2;        }    }    /**     * 此函数用于寻找出图的强连通分支     * @param reverseGraph 原图的转置图     */    public void strongConnectedComponent(Graph reverseGraph){//      和DFS遍历的区别边在于是在DFS的主循环中按照finishTime的降序顺序遍历每个顶点        VertexNode current=linkList.first;        while(current!=null){            Vertex ver=getVertex(reverseGraph, current.vertexData.verName);            if(ver.color.equals("white")){                topological(ver, reverseGraph);                System.out.println();            }            current=current.next;        }    }    public static void main(String[] args) {        TopologicalSort topologicalSort=new TopologicalSort();        Graph graph=new Graph();        topologicalSort.initialGraph(graph);        System.out.println("输出图的邻接链表表示为:");        topologicalSort.outputGraph(graph);        System.out.println("\n输出图的DFS遍历结果为:");        topologicalSort.dfsTopoligical(graph);        System.out.println("\n图的拓扑排序结果为:");        VertexNode current=topologicalSort.linkList.first;        while(current!=null){            System.out.print(current.vertexData.verName+"-->");            current=current.next;        }        System.out.println("null");        Graph reverseGraph=new Graph();        topologicalSort.reverseGraph(reverseGraph,graph);        System.out.println("\n输出原图的转置图的邻接链表表示为:");        topologicalSort.outputGraph(reverseGraph);        System.out.println("\n输出该图的强连通分支为:");        topologicalSort.strongConnectedComponent(reverseGraph);    }}/*测试数据:顶点(8):v1v2v3v4v5v6v7v8边的集合(13):v1 v2v1 v4v2 v3v2 v6v4 v1v4 v3v5 v1v5 v6v5 v7v6 v2v7 v6v7 v8v8 v5*/

大家可以仔细观察操作的图,和下面的结果进行比对,理解Topological-Sort算法和Strongly-Connected-Comopnents算法的实现思路。代码中写有注释,可以帮助大家理解。
这里写图片描述

测试输入截图(部分):
这里写图片描述

拓扑排序结果如下图:
这里写图片描述

得到的强连通分支如下图所示:
这里写图片描述

如有问题,欢迎大家指针,谢谢!

3 0