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

本文将用实例分析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):
v1
v2
v3
v4
v5
v6
v7
v8
边的集合(13):
v1 v2
v1 v4
v2 v3
v2 v6
v4 v1
v4 v3
v5 v1
v5 v6
v5 v7
v6 v2
v7 v6
v7 v8
v8 v5
*/

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

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

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

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

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

猜你喜欢

转载自blog.csdn.net/feilong_csdn/article/details/70147928
今日推荐