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

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 ( .Color == VertexColor.WHITE) 
     
      

                           { 
     
      

                               .Color = VertexColor.GRAY; 
     
      

                               .Distance = v.Distance + 1; 
     
      

                               .Parent = v; 
     
      

         
     
      

                               context.Enqueue(); 
     
      

                           } 
     
      

                       } 
     
      

         
     
      

                       v.Color = VertexColor.BLACK; 
     
      

                   } 
     
     
        }

4. 代码反思 

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

5. 代码下载 

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

Java版代码实现


    1. import
    2.    
    3. import
    4.    
    5.     
    6.    
    7. // 模块E  
    8.    
    9. public class
    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
    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
    40.    
    41. // 对角线上为0,其他的都为无穷大。  
    42.    
    43. }   
    44.    
    45.     
    46.    
    47. // 构造函数内一个是字符串数组,一个是edge的set集合  
    48.    
    49. public
    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
    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
    84.    
    85. return this.vertexlist.length();   
    86.    
    87. }   
    88.    
    89.     
    90.    
    91. // 根据编号得到该顶点  
    92.    
    93. public E get(int
    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
    112.    
    113. // 插入一条权值为weight的边<vi,vj>,若该边已有,则不插入  
    114.    
    115. {   
    116.    
    117. if (i >= 0 && i < vertexCount() && j >= 0
    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
    136.    
    137. if (edge != null)   
    138.    
    139. ;   
    140.    
    141. return
    142.    
    143. }   
    144.    
    145.     
    146.    
    147. public
    148.    
    149. String str = "顶点集合: " + vertexlist.toString() + "\n";   
    150.    
    151. str += "邻近矩阵:    \n";   
    152.    
    153. int
    154.    
    155. for (int i = 0; i < n; i++) {   
    156.    
    157. for (int j = 0; j < n; j++) {   
    158.    
    159. if
    160.    
    161. str += " ∞";// 最大值(不存在)的时候的显示方式;  
    162.    
    163. else
    164.    
    165. str += " " + adjmatrix[i][j];// 每一个顶点到其他顶点的权值  
    166.    
    167. }   
    168.    
    169. str += "\n";   
    170.    
    171. }   
    172.    
    173. return
    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
    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
    256.    
    257. // 遍历和v相关的点,得到下一个点  
    258.    
    259. return
    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
    284.    
    285. Edge[] mst = new Edge[this.vertexCount() - 1]; // n个顶点最小生成树有n-1条边  
    286.    
    287. int
    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
    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:"
    324.    
    325. + "start:" + minstart + "dest:"
    326.    
    327. u.add(mindest);   
    328.    
    329. Edge e = new
    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
    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
    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("遍历顶点"
    390.    
    391. for (int w = this.getFirstNeighbor(v); w >= 0; w = this
    392.    
    393. .getNextNeighbor(v, w)) {   
    394.    
    395. if
    396.    
    397. visited[w] = true;   
    398.    
    399. DFS(w, visited);   
    400.    
    401. }   
    402.    
    403. }   
    404.    
    405. }   
    406.    
    407.     
    408.    
    409. public void
    410.    
    411. int n = this.vertexCount();   
    412.    
    413. boolean[] visited = new boolean[n];   
    414.    
    415. MyQueue myqueue = new
    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
    428.    
    429. visited[j] = true;   
    430.    
    431. System.out.println("遍历起点:"
    432.    
    433. myqueue.EnQueue(j);   
    434.    
    435. while
    436.    
    437. int
    438.    
    439. System.out.println("遍历点:"
    440.    
    441. for (int w = this.getFirstNeighbor(v); w >= 0; w = this
    442.    
    443. .getNextNeighbor(v, w)) {   
    444.    
    445. if
    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
    470.    
    471. int n = this.vertexCount();   
    472.    
    473. int
    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] = "起点->"
    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
    500.    
    501. if
    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
    520.    
    521. // 通过当下最小值 访问到得其他顶点的距离小于原先的最小值 则进行交换值  
    522.    
    523. minmatrix[j] = minweight + adjmatrix[minUn][j];   
    524.    
    525. route[j] = route[minUn] + "->"
    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
    542.    
    543. System.out.println("没有到达该点的路径");   
    544.    
    545. } else
    546.    
    547. System.out.println("当前从V0出发到达该点的最短距离:"
    548.    
    549. System.out.println("当前从V0出发到达该点的最短距离:"
    550.    
    551.     
    552.    
    553. }   
    554.    
    555. }   
    556.    
    557. }   
    558.    
    559.     
    560.    
    561. // -------五,图的连通性-------------------------//  
    562.    
    563. public boolean
    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
    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
    622.    
    623. int n = this.vertexCount();   
    624.    
    625. int[] indegree = new int[n];   
    626.    
    627. MyStack mystack = new
    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
    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
    656.    
    657. int v = (Integer) mystack.pop();//从栈中删除该顶点  
    658.    
    659. route += "->"
    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("实现拓扑排序"
    686.    
    687.     
    688.    
    689. }   
    690.    
    691. }   
    692.    
    693.    
    694. }

     


    转载于:https://blog.51cto.com/tianya23/771711