有向图基本算法 -- 遍历算法

1. 图的表示

2. 有向图的遍历算法:深度优先

3. 有向图的遍历算法:广度优先

4 代码反思

5. 下载 

1. 图的表示  

1.1 图的定义

图G定义为V和E的集合G={V, E},其中V表示图中的所有的顶点集合,E表示的是G中的所有的边的集合。图按照E中的元素是否有方向,分为有向图和无向图。 

1.2 图的表示方法

上面给出的数学上图的定义,那么在计算机中如何表示图?通常意义上,有下面的两种方法:邻接表和邻接矩阵表示法。

无向图的邻接表和邻接矩阵表示如下所示:

 

有向图的邻接表和邻接矩阵表示如下所示:

 

根据上面的表示方法,下面定义图G的这种数据结构(邻接表),首先定义图的顶点GraphVertex:

// 顶点显示的符号
        public char Symbol { get; set; }
        
        // 顶点当前颜色
        public VertexColor Color { get; set; }
        
        // 顶点和开始节点之间的距离
        public int Distance { get; set; }
        
        // 广度遍历父节点
        public GraphVertex Parent { get; set; }
        
        // 深度优先搜索中的开始时间
        public int StartTime { get; set; }
        
        // 深度优先搜索中的结束时间
        public int FinishTime { get; set; }
 
        // 顶点对应的边

        public List<GraphEdge> FollowEdges { get; set; } 

定义图G的边的数据结构:

  // 边开始顶点,在邻接表的存储中其实没有必要存储
        public GraphVertex From { get; set; }
        // 结束顶点
        public GraphVertex To { get; set; } 
        // 边权重

        public int Weight { get; set; } 

定义图:

// 数据成员,这里假设的是顶点的symbol是各个不相同的
        private Hashtable graph = 
            new Hashtable();

        private int time = 0; 

整体上的结构如下:

 

2. 有向图的深度优先算法 

2.1 基本算法

其中d表明的是某个节点第一次被发现的时间点,f表明从节点出发的全部节点已经被发现的时间。  

 

2.2 设计实现 


  // 深度优先遍历算法
        public void DepthFirstVisit(GraphVertex v)
        {
            // 刚刚被发现,颜色为gray
            Console.WriteLine(v.Symbol);
            v.Color = VertexColor.GRAY;
            this.time++;
            // 开始时间
            v.StartTime = this.time;
 
            foreach (GraphEdge edge in v.FollowEdges)
            {
                // 还未被发现
                if (edge.To.Color == VertexColor.WHITE)
                {
                    edge.To.Parent = v;
                    DepthFirstVisit(edge.To);
                }
            }
            
            // 如果边都已经发现完成
            v.Color = VertexColor.BLACK;
            this.time++;
            v.FinishTime = this.time;
            
        }
 
        public void DepthFirstTravel()
        {
            // 全局时间变量
            this.time = 0;
 
            // 初始化
            GraphVertex v;
            foreach (DictionaryEntry e in this.graph)
            {
                v = (GraphVertex)e.Value;
                v.Color = VertexColor.WHITE;
                v.Parent = null;
            }
 
            // 递归调用
 
            // 队所有的顶点
            foreach (DictionaryEntry e in this.graph)
            {
                v = (GraphVertex)e.Value;
 
                // 顶点为白色
                if (v.Color == VertexColor.WHITE)
                {
                    DepthFirstVisit(v);
                }
            }

        } 


3. 有向图的遍历算法:广度优先 

3.1 基本算法

其中color域表示的是当前某个节点被发现的状态。如果是white表明没有被发现,gray表示当前顶点已经被发现,但是从该节点出发的节点还没有被全部发现。parent域定义的是在搜索算法时父节点。distance域表明的是从节点s到某个发现的节点v的路径距离。

3.2 设计实现 


// 广度优先遍历算法,同时生成广度优先树
        public void BreadthFirstTravel(GraphVertex s)
        {
            // 初始化所有节点
            GraphVertex v;
            foreach (DictionaryEntry e in this.graph)
            {
                v = (GraphVertex)e.Value;
                v.Color = VertexColor.WHITE;
                v.Distance = int.MaxValue;
                v.Parent = null;
            }
 
            // 发现第一个节点
            s.Color = VertexColor.GRAY;
            s.Distance = 0;
            s.Parent = null;
 
            // 初始化队列
            Queue context = 
                new Queue();
            context.Enqueue(s);
 
            // 如果队列不空的话
            while (context.Count != 0)
            {
                // 队首元素出队
                v = context.Dequeue() as GraphVertex;
                Console.WriteLine(v.Symbol);
 
                // 遍历v的节点
                foreach (GraphEdge item in v.FollowEdges)
                {
                    if ( item.To.Color == VertexColor.WHITE)
                    {
                        item.To.Color = VertexColor.GRAY;
                        item.To.Distance = v.Distance + 1;
                        item.To.Parent = v;
 
                        context.Enqueue(item.To);
                    }
                }
 
                v.Color = VertexColor.BLACK;
            }

        } 


4. 代码反思 

上面的搜索代码结构是比较典型的搜索结构:首先定义队列或者是栈来保存程序运行状态,如果容器不空,取出元素,然后对取出的元素做一些处理。 

5. 代码下载 

/Files/xuqiang/DirectedGraph1.rar 

=================================================================

Java版代码实现

 
    
  1. import java.util.ArrayList;   
  2.    
  3. import java.util.List;   
  4.    
  5.     
  6.    
  7. // 模块E   
  8.    
  9. public class AdjMatrixGraph<E> {   
  10.    
  11. protected SeqList<E> vertexlist; // 顺序表存储图的顶点集合   
  12.    
  13.     
  14.    
  15. protected int[][] adjmatrix; // 图的邻接矩阵 二维图 存储的是每个顶点的名称(A,B,C,D....)   
  16.    
  17.     
  18.    
  19. private final int MAX_WEIGHT = Integer.MAX_VALUE / 2;   
  20.    
  21.     
  22.    
  23. // private final int MAX_WEIGHT = 10000;   
  24.    
  25.     
  26.    
  27. // -------一,构造图:增删改查-------------------------//   
  28.    
  29. public AdjMatrixGraph(int n) {// n为顶点的数目   
  30.    
  31. this.vertexlist = new SeqList<E>(n);   
  32.    
  33. this.adjmatrix = new int[n][n];   
  34.    
  35. for (int i = 0; i < n; i++)   
  36.    
  37. for (int j = 0; j < n; j++)   
  38.    
  39. this.adjmatrix[i][j] = (i == j) ? 0 : MAX_WEIGHT;   
  40.    
  41. // 对角线上为0,其他的都为无穷大。   
  42.    
  43. }   
  44.    
  45.     
  46.    
  47. // 构造函数内一个是字符串数组,一个是edge的set集合   
  48.    
  49. public AdjMatrixGraph(E[] vertices, Edge[] edges) {   
  50.    
  51. this(vertices.length);   
  52.    
  53. for (int i = 0; i < vertices.length; i++)   
  54.    
  55. insertVertex(vertices[i]);// 添加顶点   
  56.    
  57. for (int j = 0; j < edges.length; j++)   
  58.    
  59. insertEdge(edges[j]);// 添加边   
  60.    
  61. }   
  62.    
  63.     
  64.    
  65. // 构造函数内一个是数组集合,一个是edge的set集合   
  66.    
  67. public AdjMatrixGraph(SeqList<E> list, Edge[] edges) {   
  68.    
  69. this(list.length());   
  70.    
  71. this.vertexlist = list;   
  72.    
  73. for (int j = 0; j < edges.length; j++)   
  74.    
  75. insertEdge(edges[j]);   
  76.    
  77. }   
  78.    
  79.     
  80.    
  81. // 显示出一共顶点的数目   
  82.    
  83. public int vertexCount() {   
  84.    
  85. return this.vertexlist.length();   
  86.    
  87. }   
  88.    
  89.     
  90.    
  91. // 根据编号得到该顶点   
  92.    
  93. public E get(int i) {   
  94.    
  95. return this.vertexlist.get(i);   
  96.    
  97. }   
  98.    
  99.     
  100.    
  101. public boolean insertVertex(E vertex) { // 插入一个顶点,若插入成功,返回true   
  102.    
  103.     
  104.    
  105. return this.vertexlist.add(vertex);   
  106.    
  107. }   
  108.    
  109.     
  110.    
  111. public boolean insertEdge(int i, int j, int weight)   
  112.    
  113. // 插入一条权值为weight的边<vi,vj>,若该边已有,则不插入   
  114.    
  115. {   
  116.    
  117. if (i >= 0 && i < vertexCount() && j >= 0 && j < vertexCount()   
  118.    
  119. && i != j && adjmatrix[i][j] == MAX_WEIGHT) {   
  120.    
  121. // 先判断该边两个顶点的编号是否在范围,该边的值是否为最大值,来确定所添加边的值是否存在;   
  122.    
  123. this.adjmatrix[i][j] = weight;// 添加权值   
  124.    
  125. return true;   
  126.    
  127. }   
  128.    
  129. return false;   
  130.    
  131. }   
  132.    
  133.     
  134.    
  135. public boolean insertEdge(Edge edge) {   
  136.    
  137. if (edge != null)   
  138.    
  139. ;   
  140.    
  141. return insertEdge(edge.start, edge.dest, edge.weight);   
  142.    
  143. }   
  144.    
  145.     
  146.    
  147. public String toString() {   
  148.    
  149. String str = "顶点集合: " + vertexlist.toString() + "\n";   
  150.    
  151. str += "邻近矩阵:    \n";   
  152.    
  153. int n = vertexCount();   
  154.    
  155. for (int i = 0; i < n; i++) {   
  156.    
  157. for (int j = 0; j < n; j++) {   
  158.    
  159. if (adjmatrix[i][j] == MAX_WEIGHT)   
  160.    
  161. str += " ∞";// 最大值(不存在)的时候的显示方式;   
  162.    
  163. else   
  164.    
  165. str += " " + adjmatrix[i][j];// 每一个顶点到其他顶点的权值   
  166.    
  167. }   
  168.    
  169. str += "\n";   
  170.    
  171. }   
  172.    
  173. return str;   
  174.    
  175. }   
  176.    
  177.     
  178.    
  179. public boolean removeEdge(int i, int j) // 删除边〈vi,vj〉,若成功,返回T   
  180.    
  181. {   
  182.    
  183. if (i >= 0 && i < vertexCount() && j >= 0 && j < vertexCount()   
  184.    
  185. && i != j && this.adjmatrix[i][j] != MAX_WEIGHT) {   
  186.    
  187. // 判断该边的两个顶点是否存在,以及改边的值是否为最大值来判断改边是否存在;   
  188.    
  189. this.adjmatrix[i][j] = MAX_WEIGHT; // 设置该边的权值为无穷大,说明已不存在;   
  190.    
  191. return true;   
  192.    
  193. }   
  194.    
  195. return false;   
  196.    
  197. }   
  198.    
  199.     
  200.    
  201. public boolean removeVertex(int v) // 删除序号为v的顶点及其关联的边   
  202.    
  203. {   
  204.    
  205. int n = vertexCount(); // 删除之前的顶点数   
  206.    
  207. if (v >= 0 && v < n) {// V的要求范围   
  208.    
  209. this.vertexlist.remove(v); // 删除顺序表的第i个元素,顶点数已减一   
  210.    
  211. for (int i = v; i < n - 1; i++)   
  212.    
  213. for (int j = 0; j < n; j++)   
  214.    
  215. this.adjmatrix[i][j] = this.adjmatrix[i + 1][j]; // 邻接矩阵:删除点以下往上移动一位   
  216.    
  217. for (int j = v; j < n - 1; j++)   
  218.    
  219. for (int i = 0; i < n - 1; i++)   
  220.    
  221. this.adjmatrix[i][j] = this.adjmatrix[i][j + 1]; // 邻接矩阵:删除点以右往左移动一位   
  222.    
  223. return true;   
  224.    
  225. }   
  226.    
  227. return false;   
  228.    
  229. }   
  230.    
  231.     
  232.    
  233. public int getFirstNeighbor(int v) // 返回顶点v的第一个邻接顶点的序号   
  234.    
  235. {   
  236.    
  237. return getNextNeighbor(v, -1);   
  238.    
  239. // 若不存在第一个邻接顶点,则返回-1   
  240.    
  241.     
  242.    
  243. public int getNextNeighbor(int v, int w) { // 返回v在w后的下一个邻接顶点   
  244.    
  245. if (v >= 0 && v < vertexCount() && w >= -1 && w < vertexCount()// 对v   
  246.    
  247. // w的范围限定   
  248.    
  249. && v != w)   
  250.    
  251. for (int j = w + 1; j < vertexCount(); j++)   
  252.    
  253. // w=-1时,j从0开始寻找下一个邻接顶点   
  254.    
  255. if (adjmatrix[v][j] > 0 && adjmatrix[v][j] < MAX_WEIGHT)   
  256.    
  257. // 遍历和v相关的点,得到下一个点   
  258.    
  259. return j;   
  260.    
  261. return -1;   
  262.    
  263. }   
  264.    
  265.     
  266.    
  267. // -------二,最小生成树-------------------------//   
  268.    
  269.     
  270.    
  271. /*  
  272.   
  273. * 普里姆算法的基本思想: 取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w。 在添加的顶点 w  
  274.   
  275. * 和已经在生成树上的顶点v之间必定存在一条边, 并且该边的权值在所有连通顶点 v 和 w 之间的边中取值最小。  
  276.   
  277. * 之后继续往生成树上添加顶点,直至生成树上含有 n-1 个顶点为止。  
  278.   
  279. */   
  280.    
  281.     
  282.    
  283. public AdjMatrixGraph minSpanTree_prim() {   
  284.    
  285. Edge[] mst = new Edge[this.vertexCount() - 1]; // n个顶点最小生成树有n-1条边   
  286.    
  287. int un;   
  288.    
  289. List<Integer> u = new ArrayList<Integer>();// 存放所有已访问过的顶点集合   
  290.    
  291. u.add(0);// 起始点默认为标识为0的顶点   
  292.    
  293. for (int i = 0; i < this.vertexCount() - 1; i++) {   
  294.    
  295. int minweight = MAX_WEIGHT;// 最小边的时候,权值   
  296.    
  297. int minstart = MAX_WEIGHT;// 最小边的时候,起点   
  298.    
  299. int mindest = MAX_WEIGHT;// 最小边的时候,终点   
  300.    
  301. for (int j = 0; j < u.size(); j++) {   
  302.    
  303. un = u.get(j);   
  304.    
  305. for (int k = 0; k < this.vertexCount(); k++) {   
  306.    
  307. // 获取最小值的条件:1.该边比当前情况下的最小值小;2.该边还未访问过;   
  308.    
  309. if ((minweight > adjmatrix[un][k]) && (!u.contains(k))) {   
  310.    
  311. minweight = adjmatrix[un][k];   
  312.    
  313. minstart = un;   
  314.    
  315. mindest = k;   
  316.    
  317. }   
  318.    
  319. }   
  320.    
  321. }   
  322.    
  323. System.out.println("一次遍历所添加的最小边:他的权值,起点,终点分别为:weight:" + minweight   
  324.    
  325. "start:" + minstart + "dest:" + mindest);   
  326.    
  327. u.add(mindest);   
  328.    
  329. Edge e = new Edge(minstart, mindest, adjmatrix[minstart][mindest]);   
  330.    
  331. mst[i] = e;   
  332.    
  333. }   
  334.    
  335. return new AdjMatrixGraph(this.vertexlist, mst); // 构造最小生成树相应的图对象   
  336.    
  337. }   
  338.    
  339.     
  340.    
  341. /*  
  342.   
  343. * public AdjMatrixGraph minSpanTree_kruskal() { }  
  344.   
  345. */   
  346.    
  347.     
  348.    
  349. // -------三,图的遍历(广度遍历,深度遍历)-------------------------//   
  350.    
  351. public void DFStraverse() {   
  352.    
  353. int n = this.vertexCount();   
  354.    
  355. boolean[] visited = new boolean[n];   
  356.    
  357. for (int i = 1; i < n; i++) {   
  358.    
  359. visited[i] = false;   
  360.    
  361. }   
  362.    
  363. // 编号0为起始点,进行一次深度优先遍历(一次得到一个连通分量)   
  364.    
  365. for (int j = 0; j < n; j++) {   
  366.    
  367. if (!visited[j]) {   
  368.    
  369. System.out.println("以该顶点为" + j + "起始点的遍历:");   
  370.    
  371. this.DFS(j, visited);   
  372.    
  373. }   
  374.    
  375. }   
  376.    
  377. }   
  378.    
  379.     
  380.    
  381. // 参数1:遍历起始点的编号,参数2:记录各个顶点是否被访问过   
  382.    
  383. public void DFS(int v, boolean[] visited2) {   
  384.    
  385. boolean[] visited = visited2;   
  386.    
  387. visited[v] = true;   
  388.    
  389. System.out.println("遍历顶点" + v);   
  390.    
  391. for (int w = this.getFirstNeighbor(v); w >= 0; w = this   
  392.    
  393. .getNextNeighbor(v, w)) {   
  394.    
  395. if (!visited[w]) {   
  396.    
  397. visited[w] = true;   
  398.    
  399. DFS(w, visited);   
  400.    
  401. }   
  402.    
  403. }   
  404.    
  405. }   
  406.    
  407.     
  408.    
  409. public void BFStraverse() {   
  410.    
  411. int n = this.vertexCount();   
  412.    
  413. boolean[] visited = new boolean[n];   
  414.    
  415. MyQueue myqueue = new MyQueue();   
  416.    
  417. for (int i = 1; i < n; i++) {   
  418.    
  419. visited[i] = false;   
  420.    
  421. }   
  422.    
  423.     
  424.    
  425. for (int j = 0; j < n; j++) {   
  426.    
  427. if (!visited[j]) {   
  428.    
  429. visited[j] = true;   
  430.    
  431. System.out.println("遍历起点:" + j);   
  432.    
  433. myqueue.EnQueue(j);   
  434.    
  435. while (!myqueue.empty()) {   
  436.    
  437. int v = (Integer) myqueue.DeQueue();   
  438.    
  439. System.out.println("遍历点:" + v);   
  440.    
  441. for (int w = this.getFirstNeighbor(v); w >= 0; w = this   
  442.    
  443. .getNextNeighbor(v, w)) {   
  444.    
  445. if (!visited[w]) {   
  446.    
  447. visited[w] = true;   
  448.    
  449. myqueue.EnQueue(w);   
  450.    
  451. }   
  452.    
  453. }   
  454.    
  455. }   
  456.    
  457. }   
  458.    
  459. }   
  460.    
  461.     
  462.    
  463. }   
  464.    
  465.     
  466.    
  467. // -------四,图的最短路径Dijkstra算法-------------------------//   
  468.    
  469. public void Dijkstra() {   
  470.    
  471. int n = this.vertexCount();   
  472.    
  473. int minweight = MAX_WEIGHT;   
  474.    
  475. int minUn = 0;   
  476.    
  477. int[] minmatrix = new int[n];// 存放当前起始点到其余各个顶点的距离;   
  478.    
  479. boolean[] isS = new boolean[n];// 判断各个是否被访问过   
  480.    
  481. String[] route = new String[n];// 每个字符串是显示对应顶点最短距离的路径;   
  482.    
  483. for (int i = 1; i < n; i++) {// 初始化   
  484.    
  485. minmatrix[i] = adjmatrix[0][i];   
  486.    
  487. isS[i] = false;   
  488.    
  489. route[i] = "起点->" + i;   
  490.    
  491. }   
  492.    
  493. for (int i = 1; i < n; i++) {   
  494.    
  495. // 选择 当前 和起点 连通的,且值最小的顶点;   
  496.    
  497. for (int k = 1; k < n; k++) {   
  498.    
  499. if (!isS[k]) {   
  500.    
  501. if (minmatrix[k] < minweight) {   
  502.    
  503. minweight = minmatrix[k];   
  504.    
  505. minUn = k;   
  506.    
  507. }   
  508.    
  509. }   
  510.    
  511. }   
  512.    
  513. isS[minUn] = true;// 将该点设置为已访问;   
  514.    
  515. for (int j = 1; j < n; j++) {   
  516.    
  517. if (!isS[j]) {// 判断:该顶点还没加入到S中/属于U-S;   
  518.    
  519. if (minweight + adjmatrix[minUn][j] < minmatrix[j]) {   
  520.    
  521. // 通过当下最小值 访问到得其他顶点的距离小于原先的最小值 则进行交换值   
  522.    
  523. minmatrix[j] = minweight + adjmatrix[minUn][j];   
  524.    
  525. route[j] = route[minUn] + "->" + j;   
  526.    
  527. }   
  528.    
  529. }   
  530.    
  531. }   
  532.    
  533. minweight = MAX_WEIGHT;// 因为要放到下一个循环中,所以一定要重设置一下,回到最大值   
  534.    
  535. }   
  536.    
  537. for (int m = 1; m < n; m++) {   
  538.    
  539. System.out.println("从V0出发到达" + m + "点");   
  540.    
  541. if (minmatrix[m] == MAX_WEIGHT) {   
  542.    
  543. System.out.println("没有到达该点的路径");   
  544.    
  545. else {   
  546.    
  547. System.out.println("当前从V0出发到达该点的最短距离:" + minmatrix[m]);   
  548.    
  549. System.out.println("当前从V0出发到达该点的最短距离:" + route[m]);   
  550.    
  551.     
  552.    
  553. }   
  554.    
  555. }   
  556.    
  557. }   
  558.    
  559.     
  560.    
  561. // -------五,图的连通性-------------------------//   
  562.    
  563. public boolean isConnect() {   
  564.    
  565. int n = this.vertexCount();   
  566.    
  567. boolean[] visited = new boolean[n];   
  568.    
  569. // 记录不能一次深度优先遍历通过的数目   
  570.    
  571. // 全部顶点作为出发点开始遍历,如果全部都不能一次遍历通过(notConnectNum == n),说明该图不连通。   
  572.    
  573. int notConnectNum = 0;   
  574.    
  575. for (int j = 0; j < n; j++) {   
  576.    
  577. for (int i = 0; i < n; i++) {   
  578.    
  579. visited[i] = false;   
  580.    
  581. }   
  582.    
  583. this.DFS(j, visited);   
  584.    
  585. for (int k = 0; k < n; k++) {   
  586.    
  587. System.out.println(visited[k]);   
  588.    
  589. if (visited[k] == false) {   
  590.    
  591. notConnectNum++;   
  592.    
  593. break;// 一旦有没有被遍历到的顶点(说明该顶点不属于该连通分量),跳出循环   
  594.    
  595. }   
  596.    
  597. }   
  598.    
  599. }   
  600.    
  601. if (notConnectNum == n) {   
  602.    
  603. System.out.println("此图是不连通的");   
  604.    
  605. return false;   
  606.    
  607. else {   
  608.    
  609. System.out.println("此图是连通的");   
  610.    
  611. return true;   
  612.    
  613. }   
  614.    
  615. }   
  616.    
  617.     
  618.    
  619. // -------六,图的拓扑排序-------------------------//   
  620.    
  621. public void topologicalSort() {   
  622.    
  623. int n = this.vertexCount();   
  624.    
  625. int[] indegree = new int[n];   
  626.    
  627. MyStack mystack = new MyStack();   
  628.    
  629. String route = "拓扑排序出发:";   
  630.    
  631. int count = 0;   
  632.    
  633. for (int i = 0; i < n; i++) {   
  634.    
  635. indegree[i] = 0;   
  636.    
  637. for (int j = 0; j < n; j++) {//获取每一个顶点的入度   
  638.    
  639. if (adjmatrix[j][i] != 0 && adjmatrix[j][i] != MAX_WEIGHT) {   
  640.    
  641. indegree[i] += 1;   
  642.    
  643. }   
  644.    
  645. }//先将入度为0的顶点加入到栈中   
  646.    
  647. if (indegree[i] == 0) {   
  648.    
  649. mystack.push(i);   
  650.    
  651. }   
  652.    
  653. }   
  654.    
  655. while (!mystack.empty()) {   
  656.    
  657. int v = (Integer) mystack.pop();//从栈中删除该顶点   
  658.    
  659. route += "->" + v;   
  660.    
  661. ++count;   
  662.    
  663. for (int w = this.getFirstNeighbor(v); w >= 0; w = this   
  664.    
  665. .getNextNeighbor(v, w)) {   
  666.    
  667. indegree[w] -= 1;//因为该顶点被“删除”,所有以该顶点为弧尾的边的弧头的入度减一   
  668.    
  669. if (indegree[w] == 0) {   
  670.    
  671. mystack.push(w);//先将入度为0的顶点加入到栈中   
  672.    
  673. }   
  674.    
  675. }   
  676.    
  677. }   
  678.    
  679. if (count < n) {//当经历拓扑排序遍历后,所有顶点都被“删除”时(count=n),此时实现拓扑排序   
  680.    
  681. System.out.println("存在回路,不满足拓扑排序的条件");   
  682.    
  683. else {   
  684.    
  685. System.out.println("实现拓扑排序" + route);   
  686.    
  687.     
  688.    
  689. }   
  690.    
  691. }   
  692.    
  693.    
  694. }