zoukankan      html  css  js  c++  java
  • 重拾算法(4)——图的广度优先和深度优先搜索算法的实现与33867个测试用例

    重拾算法(4)——图的广度优先和深度优先搜索算法的实现与33867个测试用例

    本篇继续上一篇的方式,给出图的深度优先和广度优先搜索算法,然后用33867个测试用例进行自动化测试,以证明算法的正确性。

    用邻接表(adjacency list)表示图(graph)

     1     public partial class AdjacencyListGraph<TVertex, TEdge> : ICloneable
     2     {        
     3         public AdjacencyListGraph()
     4         {
     5             this.Vertexes = new List<AdjacencyListVertex<TVertex, TEdge>>();
     6         }
     7         
     8         public IList<AdjacencyListVertex<TVertex, TEdge>> Vertexes { get; protected set; }
     9         
    10         /**/
    11     }
    12     
    13     public class AdjacencyListVertex<TVertex, TEdge>
    14     {
    15         public TVertex Value { get;set; }
    16         public IList<AdjacencyListEdge<TVertex, TEdge>> Edges { get;set; }
    17         
    18         public AdjacencyListVertex()
    19         {
    20             this.Edges = new List<AdjacencyListEdge<TVertex, TEdge>>();
    21         }
    22     }
    23     
    24     public class AdjacencyListEdge<TVertex, TEdge>
    25     {
    26         public TEdge Value { get;set; }
    27         public AdjacencyListVertex<TVertex, TEdge> Vertex1 { get;set; }
    28         public AdjacencyListVertex<TVertex, TEdge> Vertex2 { get;set; }
    29         
    30         public AdjacencyListEdge(AdjacencyListVertex<TVertex, TEdge> vertex1, AdjacencyListVertex<TVertex, TEdge> vertex2)
    31         {
    32             this.Vertex1 = vertex1; 
    33             this.Vertex2 = vertex2;
    34         }
    35     }

    图的广度优先算法

    图的广度优先算法和树的层次遍历是类似的。

     1         SearchReport<TVertex, TEdge> BreadthFirstTraverse(GraphNodeWorker<TVertex, TEdge> worker, bool reportNeeded)
     2         {
     3             SearchReport<TVertex, TEdge> result = null;
     4             if (reportNeeded) { result = new SearchReport<TVertex, TEdge>(); }
     5             var visited = new Dictionary<AdjacencyListVertex<TVertex, TEdge>, bool>();
     6             foreach (var vertex in this.Vertexes)
     7             {
     8                 if ((!visited.ContainsKey(vertex)) || (!visited[vertex]))
     9                 { 
    10                     BFS(vertex, visited, worker); 
    11                     if (reportNeeded) { result.ConnectedComponents.Add(vertex); }
    12                 }
    13             }
    14             return result;
    15         }
    16         
    17         void BFS(AdjacencyListVertex<TVertex, TEdge> headNode, Dictionary<AdjacencyListVertex<TVertex, TEdge>, bool> visited, GraphNodeWorker<TVertex, TEdge> worker)
    18         {
    19             var queue = new Queue<AdjacencyListVertex<TVertex, TEdge>>();
    20             queue.Enqueue(headNode);
    21             while (queue.Count > 0)
    22             {
    23                 var vertex = queue.Dequeue();
    24                 if ((!visited.ContainsKey(vertex)) || (!visited[vertex]))
    25                 {
    26                     if (vertex != null)
    27                     {
    28                         worker.DoActionOnNode(vertex);
    29                         if (!visited.ContainsKey(vertex))
    30                         { visited.Add(vertex, true); }
    31                         else
    32                         { visited[vertex] = true; }
    33                         var neighbourVertexes = from edge in vertex.Edges
    34                                                 select GetNeighbourVertex(vertex, edge);
    35                         foreach (var v in neighbourVertexes)
    36                         {
    37                             if ((!visited.ContainsKey(v)) || (!visited[v]))
    38                             { queue.Enqueue(v); }
    39                         }
    40                     }
    41                 }
    42             }
    43         }

    其中的SearchReport<TVertex, TEdge>是一个统计搜索结果的对象,定义如下

    1     public class SearchReport<TVertex, TEdge>
    2     {
    3         public List<AdjacencyListVertex<TVertex, TEdge>> ConnectedComponents { get;set; }
    4         public SearchReport()
    5         {
    6             ConnectedComponents = new List<AdjacencyListVertex<TVertex, TEdge>>();
    7         }
    8     }

    ConnectedComponents有多少个元素,就表示这个图有多少个连通分量

    图的深度优先搜索算法

    图的深度优先搜索可以用"递归"、"栈"和"优化的栈"三种形式实现。

     1         SearchReport<TVertex, TEdge> DepthFirstTraverse(GraphNodeWorker<TVertex, TEdge> worker, bool reportNeeded, DepthFirstTraverseOption option)
     2         {
     3             SearchReport<TVertex, TEdge> result = null;
     4             if (reportNeeded) { result = new SearchReport<TVertex, TEdge>(); }
     5             var visited = new Dictionary<AdjacencyListVertex<TVertex, TEdge>, bool>();
     6             foreach (var vertex in this.Vertexes)
     7             {
     8                 if ((!visited.ContainsKey(vertex)) || (!visited[vertex]))
     9                 {
    10                     switch (option)
    11                     {
    12                     case DepthFirstTraverseOption.DFSRecursively:
    13                         DFS(vertex, visited, worker);
    14                         break;
    15                     case DepthFirstTraverseOption.DFSByStack:
    16                         DFSByStack(vertex, visited, worker);
    17                         break;
    18                     case DepthFirstTraverseOption.DFSByStackOptimized:
    19                         DFSByStackOptimized(vertex, visited, worker);
    20                         break;
    21                     default:
    22                         throw new NotImplementedException();
    23                     }
    24                     if (reportNeeded) { result.ConnectedComponents.Add(vertex);}
    25                 }
    26             }
    27             return result;
    28         }

    用递归实现深度优先搜索

     1         void DFS(AdjacencyListVertex<TVertex, TEdge> vertex, Dictionary<AdjacencyListVertex<TVertex, TEdge>, bool> visited, GraphNodeWorker<TVertex, TEdge> worker)
     2         {
     3             //if ((!visited.ContainsKey(vertex)) || (!visited[vertex]))
     4             {
     5                 worker.DoActionOnNode(vertex);
     6                 if (!visited.ContainsKey(vertex))
     7                 { visited.Add(vertex, true); }
     8                 else
     9                 { visited[vertex] = true; }
    10                 var neighbourVertexes = from edge in vertex.Edges
    11                                         select GetNeighbourVertex(vertex, edge);
    12                 foreach (var v in neighbourVertexes)
    13                 {
    14                     if ((!visited.ContainsKey(v)) || (!visited[v]))
    15                     { DFS(v, visited, worker); }
    16                 }
    17             }
    18         }

    其中GetNeighbourVertex是个辅助函数,用于获取与指定结点相连的结点。

     1         AdjacencyListVertex<TVertex, TEdge> GetNeighbourVertex(AdjacencyListVertex<TVertex, TEdge> vertex, AdjacencyListEdge<TVertex, TEdge> edge)
     2         {
     3             if (vertex == null || edge == null) { return null; }
     4             Debug.Assert(!((vertex != edge.Vertex1) && (vertex != edge.Vertex2)));
     5             
     6             AdjacencyListVertex<TVertex, TEdge> result = null;
     7             if (vertex != edge.Vertex1) { result = edge.Vertex1; }
     8             else { result = edge.Vertex2; }
     9             
    10             return result;
    11         }

    用栈实现深度优先搜索

     1         void DFSByStack(AdjacencyListVertex<TVertex, TEdge> root, Dictionary<AdjacencyListVertex<TVertex, TEdge>, bool> visited, GraphNodeWorker<TVertex, TEdge> worker)
     2         {
     3             var stack = new Stack<AdjacencyListVertex<TVertex, TEdge>>();
     4             stack.Push(root);
     5 
     6             while (stack.Count > 0)
     7             {
     8                 var vertex = stack.Pop();
     9                 if (vertex != null)
    10                 {
    11                     if ((!visited.ContainsKey(vertex)) || (!visited[vertex]))
    12                     {
    13                         worker.DoActionOnNode(vertex);
    14                         if (!visited.ContainsKey(vertex))
    15                         { visited.Add(vertex, true); }
    16                         else
    17                         { visited[vertex] = true; }
    18                         
    19                         var neighbourVertexes = from edge in vertex.Edges
    20                                                 select GetNeighbourVertex(vertex, edge);
    21                         foreach (var v in neighbourVertexes.Reverse())
    22                         {
    23                             if ((!visited.ContainsKey(v)) || (!visited[v]))
    24                             {
    25                                 stack.Push(v);
    26                             }
    27                         }
    28                     }
    29                 }
    30             }
    31         }

    这个用栈实现的深度优先搜索算法,其特点是与上文用递归实现的算法相比,两者对图上结点的遍历顺序完全相同。因此我用这个两个算法对比以验证他们两个是否正确。

    优化过的用栈实现深度优先搜索

    这个用栈实现的深度优先搜索算法还有可优化的空间。优化后的算法如下。

     1         void DFSByStackOptimized(AdjacencyListVertex<TVertex, TEdge> root, Dictionary<AdjacencyListVertex<TVertex, TEdge>, bool> visited, GraphNodeWorker<TVertex, TEdge> worker)
     2         {
     3             var stack = new Stack<AdjacencyListVertex<TVertex, TEdge>>();
     4             stack.Push(root);
     5             if (!visited.ContainsKey(root)) { visited.Add(root, false); }
     6             else { visited[root] = false; }
     7 
     8             while (stack.Count > 0)
     9             {
    10                 var vertex = stack.Pop();
    11                 if (vertex != null)
    12                 {
    13                     worker.DoActionOnNode(vertex);
    14                     visited[vertex] = true;
    15                     var neighbourVertexes = from edge in vertex.Edges
    16                                             select GetNeighbourVertex(vertex, edge);
    17                     foreach (var v in neighbourVertexes)
    18                     {
    19                         if (!visited.ContainsKey(v))
    20                         {
    21                             stack.Push(v);
    22                             visited.Add(v, false);
    23                         }
    24                     }
    25                 }
    26             }
    27         }

    这一版的算法,避免了不必要的入栈出栈,减少了对visited的判定次数,去掉了不必要的Reverse()。

    要注意的是,优化后的算法,对图上结点的遍历顺序与优化前有所不同

    测试

    我的测试思路如下:

    • 编程自动生成具有1、2、3、4、5、6个结点的图的所有情形(一共有33867个。结点数目相同时,连线的不同意味着情形的不同)
    • 打印33867个图的情形。
    • 对33867个图,分别进行基于递归和栈的深度优先搜索,若搜索结果完全相同,就说明这两个算法是正确的。
    • 在上一步基础上,若基于优化的栈的深度优先搜索结果与上一步的搜索结果相比,只有访问顺序不同,就说明基于优化的栈的算法是正确的。
    • 在上一步基础上,若广度优先搜索结果与上一步的遍历结果相比,只有访问顺序不同,就说明广度优先搜索算法是正确的。

    自动生成33867个不同的图

    这个程序的实现思路与上一篇是一样的。在得到了所有具有N个结点的图后,给每个图增加一个结点,就成了N+1个结点的新图,一个这样的新图可以扩展出2^N个新的情形。而最初的具有1个结点的图就只有那么1个。利用数学归纳法,生成33867个不同的图的问题就解决了。

    在控制台显示图结构

    在控制台显示一个二叉树结构还算常见,但要显示图就复杂一点。我设计了按如下形式显示图结构。

     1 graph 9485:
     2 component 0:
     3 000
     4  ┕┑
     5 001 6  ┕┙
     7 
     8 component 1:
     9 002
    10  ┝┑
    11  ┕┿┑
    12 003││
    13  ┝┙│
    14  ┕━┿┑
    15 004  ││
    16  ┝━┙│
    17  ┕━━┙
    18 
    19 component 2:
    20 005

    受字体影响可能看不出效果,把上述内容复制到notepad里是这样的:

    可见这个图是生成的第9485个图。图中的"001""002""003""004"是结点,黑线代表边。它有3个连通分量(component)。其中component0包含2个结点和1条边,component1包含3个结点和3条边,component2包含1个结点,不含边。

    这样直观地看到图的结构,就容易进行排错调试了。

    至于遍历、比较、判定是否正确的程序,就没有什么新意可言了。

    总结

    没有这样的测试,我是不敢相信我的算法实际可用的。虽然为了测试花掉好几天时间,不过还是很值得的。现在我可以放心大胆地说,我给出的图的广度优先和深度优先搜索算法是真正正确的!

    需要工程源码的同学麻烦点个赞并留言你的Email~

  • 相关阅读:
    bzoj:2423: [HAOI2010]最长公共子序列
    bzoj:3994:vijos1949: [SDOI2015]约数个数和
    bzoj4332;vijos1955:JSOI2012 分零食
    bzoj:1726: [Usaco2006 Nov]Roadblocks第二短路
    bzoj:1724: [Usaco2006 Nov]Fence Repair 切割木板
    bzoj:1666: [Usaco2006 Oct]Another Cow Number Game 奶牛的数字游戏
    bzoj:1230: [Usaco2008 Nov]lites 开关灯
    bzoj:1941: [Sdoi2010]Hide and Seek
    bzoj:1699;poj 3264: [Usaco2007 Jan]Balanced Lineup排队
    bzoj 1705;poj 3612:[Usaco2007 Nov]Telephone Wire 架设电话线
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/algorithm-4-33867-test-cases-4-breadth-and-depth-first-search-of-graph.html
Copyright © 2011-2022 走看看