本文将用实例分析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算法的实现思路。代码中写有注释,可以帮助大家理解。
测试输入截图(部分):
拓扑排序结果如下图:
得到的强连通分支如下图所示:
如有问题,欢迎大家指针,谢谢!