zoukankan      html  css  js  c++  java
  • 算法:图(Graph)的遍历、最小生成树和拓扑排序

    背景

    不同的数据结构有不同的用途,像:数组、链表、队列、栈多数是用来做为基本的工具使用,二叉树多用来作为已排序元素列表的存储,B 树用在存储中,本文介绍的 Graph 多数是为了解决现实问题(说到底,所有的数据结构都是这个目的),如:网络布局、任务安排等。

    图的基本概念

    示例

    顶点(Vertex)

    上图的 1、2、3、4、5、6 就是顶点。

    邻接(Adjoin)

    如果 A 和 B 通过定向边相连,且方向为 A -> B,则 B 为 A 的邻接,如果相连的边是没有方向的,则 A 和 B 互为邻接。

    边(Edge)

    顶点之间的连线就是边。

    连通图(Connected Graph)

    不考虑边的方向性,从任何一个节点都可以遍历到其它节点。本文的所有算法(除了拓扑排序)只适合连通图。

    定向图(Directed Graph)

    图中的所有边都带方向。

    权重图(Weighted Graph)

    图中的所有边都有权重。

    图的程序表示

    如果图 T 有 A、B、C 三个节点,有 A -> B,B -> C,C -> A 三个边(没有方向),如何在程序中表示 T 呢?

    邻接矩阵

        A   B   C
    A  0   1   0
    B  0   0   1
    C  1   0   0

    矩阵的行代表了定向边的起始顶点,矩阵的列代表了定向边的介绍顶点,矩阵中的值:1 代表了列是行的邻接,0 则反之。如果边没有方向,则矩阵是相对于正对角线是对称的。

    邻接列表

    A:[ B ]
    B:[ C ]
    C:[ A ]

    这个就不多说了,多数书籍使用都是邻接矩阵。

    代码

     1         class Vertex<T>
     2         {
     3             public T Value { get; set; }
     4 
     5             public bool WasVisited { get; set; }
     6         }
     7 
     8         class Graph<T>
     9         {
    10             #region 私有字段
    11 
    12             private int _maxSize;
    13             private Vertex<T>[] _vertexs;
    14             private bool[][] _edges;
    15             private int _vertexCount = 0;
    16 
    17             #endregion
    18 
    19             #region 构造方法
    20 
    21             public Graph(int maxSize)
    22             {
    23                 _maxSize = maxSize;
    24                 _vertexs = new Vertex<T>[_maxSize];
    25                 _edges = new bool[_maxSize][];
    26                 for (var i = 0; i < _maxSize; i++)
    27                 {
    28                     _edges[i] = new bool[_maxSize];
    29                 }
    30             }
    31 
    32             #endregion
    33 
    34             #region 添加顶点
    35 
    36             public Graph<T> AddVertex(T value)
    37             {
    38                 _vertexs[_vertexCount++] = new Vertex<T> { Value = value };
    39 
    40                 return this;
    41             }
    42 
    43             #endregion
    44 
    45             #region 添加边
    46 
    47             public Graph<T> AddUnDirectedEdge(T startItem, T endItem)
    48             {
    49                 var startIndex = this.GetVertexIndex(startItem);
    50                 var endIndex = this.GetVertexIndex(endItem);
    51 
    52                 _edges[startIndex][endIndex] = true;
    53                 _edges[endIndex][startIndex] = true;
    54 
    55                 return this;
    56             }
    57 
    58             public Graph<T> AddDirectedEdge(T startItem, T endItem)
    59             {
    60                 var startIndex = this.GetVertexIndex(startItem);
    61                 var endIndex = this.GetVertexIndex(endItem);
    62 
    63                 _edges[startIndex][endIndex] = true;
    64 
    65                 return this;
    66             }
    67 
    68             #endregion
    69         }

    图的常见算法

    遍历

    本文的遍历算法只适合一般的无向图。

    深度优先遍历

    深度优先遍历的原则是:尽可能的离开始节点远,二叉树的三种遍历算法都属于这种算法。

    示例

    从 A 开始的遍历顺序为(邻接的遍历顺序为字母升序):ABCEDBF。

    栈版本

     1             #region 深度优先遍历:栈版本
     2 
     3             public void DepthFirstSearch(T startItem, Action<T> action)
     4             {
     5                 var startIndex = this.GetVertexIndex(startItem);
     6                 var stack = new Stack<int>();
     7 
     8                 this.DepthFirstSearchVisit(stack, startIndex, action);
     9                 while (stack.Count > 0)
    10                 {
    11                     var adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(stack.Peek());
    12                     if (adjoinVertexIndex >= 0)
    13                     {
    14                         this.DepthFirstSearchVisit(stack, adjoinVertexIndex, action);
    15                     }
    16                     else
    17                     {
    18                         stack.Pop();
    19                     }
    20                 }
    21 
    22                 this.ResetVisited();
    23             }
    24 
    25             private void DepthFirstSearchVisit(Stack<int> stack, int index, Action<T> action)
    26             {
    27                 _vertexs[index].WasVisited = true;
    28                 action(_vertexs[index].Value);
    29                 stack.Push(index);
    30             }
    31 
    32             #endregion

    递归版本

     1             #region 深度优先遍历:递归版本
     2 
     3             public void DepthFirstSearchRecursion(T startItem, Action<T> action)
     4             {
     5                 var startIndex = this.GetVertexIndex(startItem);
     6 
     7                 this.DepthFirstSearchRecursionVisit(startIndex, action);
     8 
     9                 this.ResetVisited();
    10             }
    11 
    12             private void DepthFirstSearchRecursionVisit(int index, Action<T> action)
    13             {
    14                 _vertexs[index].WasVisited = true;
    15                 action(_vertexs[index].Value);
    16 
    17                 var adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(index);
    18                 while (adjoinVertexIndex >= 0)
    19                 {
    20                     this.DepthFirstSearchRecursionVisit(adjoinVertexIndex, action);
    21                     adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(index);
    22                 }
    23             }
    24 
    25             #endregion

    广度优先遍历

    广度优先遍历的原则是:尽可能的离开始节点近。这个算法没有办法通过递归实现,多数都是使用的队列(终于发现了队列的又一个用处)。

    示例

    从 A 开始的遍历顺序为(邻接的遍历顺序为字母升序):ABCEDFB。

    代码

     1             #region 广度优先遍历
     2 
     3             public void BreadthFirstSearch(T startItem, Action<T> action)
     4             {
     5                 var startIndex = this.GetVertexIndex(startItem);
     6                 var queue = new Queue<int>();
     7 
     8                 this.BreadthFirstSearchVisit(queue, startIndex, action);
     9                 while (queue.Count > 0)
    10                 {
    11                     var adjoinVertexIndexs = this.GetNextUnVisitedAdjoinVertexIndexs(queue.Dequeue());
    12                     foreach (var adjoinVertexIndex in adjoinVertexIndexs)
    13                     {
    14                         this.BreadthFirstSearchVisit(queue, adjoinVertexIndex, action);
    15                     }
    16                 }
    17 
    18                 this.ResetVisited();
    19             }
    20 
    21             private void BreadthFirstSearchVisit(Queue<int> queue, int index, Action<T> action)
    22             {
    23                 _vertexs[index].WasVisited = true;
    24                 action(_vertexs[index].Value);
    25                 queue.Enqueue(index);
    26             }
    27 
    28             #endregion

    最小生成树

    本文的最小生成树算法只适合一般的无向图。

    将 N 个顶点连接起来的最小树叫:最小生成树。

    给定一个颗有 N 个顶点的连通图,则最小生成树的边为:N - 1。

    不同的遍历算法生成的最小生成树是不同的,下面使用广度优先遍历来生成最小树。

    示例

    从 A 开始的遍历顺序为(邻接的遍历顺序为字母升序):A->B、B->C、C->E、E->D、E->F、D->B。

    代码

     1             #region 最小生成树
     2 
     3             public void DisplayMinimumSpanningTree(T startItem)
     4             {
     5                 var startIndex = this.GetVertexIndex(startItem);
     6                 var queue = new Queue<int>();
     7 
     8                 _vertexs[startIndex].WasVisited = true;
     9                 queue.Enqueue(startIndex);
    10                 while (queue.Count > 0)
    11                 {
    12                     var currentIndex = queue.Dequeue();
    13                     var adjoinVertexIndexs = this.GetNextUnVisitedAdjoinVertexIndexs(currentIndex);
    14                     foreach (var adjoinVertexIndex in adjoinVertexIndexs)
    15                     {
    16                         Console.WriteLine(_vertexs[currentIndex].Value + "->" + _vertexs[adjoinVertexIndex].Value);
    17 
    18                         _vertexs[adjoinVertexIndex].WasVisited = true;
    19                         queue.Enqueue(adjoinVertexIndex);
    20                     }
    21                 }
    22 
    23                 this.ResetVisited();
    24             }
    25 
    26             #endregion

    拓扑排序

    拓扑排序只适合定向图,且图中不存在循环结构。其算法非常简单,依次获取图中的没有邻接顶点的顶点,然后删除该顶点。

    示例

    上图出现了循环结构,假如没有顶点 C,拓扑排序的结果为(顶点的遍历顺序为字母升序):EFDAB。

    算法

     1             #region 拓扑排序
     2 
     3             public List<T> TopologySort()
     4             {
     5                 var cloneVertexs = (Vertex<T>[])_vertexs.Clone();
     6                 var cloneEdges = (bool[][])_edges.Clone();
     7                 var cloneVertexCount = _vertexCount;
     8 
     9                 var results = new List<T>();
    10                 while (_vertexCount > 0)
    11                 {
    12                     var successor = this.NextSuccessor();
    13                     if (successor == -1)
    14                     {
    15                         throw new InvalidOperationException("出现循环了!");
    16                     }
    17 
    18                     results.Insert(0, _vertexs[successor].Value);
    19 
    20                     this.RemoveVertex(successor);
    21                     _vertexCount--;
    22                 }
    23 
    24                 _vertexs = cloneVertexs;
    25                 _edges = cloneEdges;
    26                 _vertexCount = cloneVertexCount;
    27 
    28                 return results;
    29             }
    30 
    31             private int NextSuccessor()
    32             {
    33                 for (var row = 0; row < _vertexCount; row++)
    34                 {
    35                     if (_edges[row].Take(_vertexCount).All(x => !x))
    36                     {
    37                         return row;
    38                     }
    39                 }
    40 
    41                 return -1;
    42             }
    43 
    44             private void RemoveVertex(int successor)
    45             {
    46                 for (int i = successor; i < _vertexCount - 1; i++)
    47                 {
    48                     _vertexs[i] = _vertexs[i + 1];
    49                 }
    50 
    51                 for (int row = successor; row < _vertexCount - 1; row++)
    52                 {
    53                     for (int column = 0; column < _vertexCount; column++)
    54                     {
    55                         _edges[row][column] = _edges[row + 1][column];
    56                     }
    57                 }
    58 
    59                 for (int column = successor; column < _vertexCount - 1; column++)
    60                 {
    61                     for (int row = 0; row < _vertexCount; row++)
    62                     {
    63                         _edges[row][column] = _edges[row][column + 1];
    64                     }
    65                 }
    66             }
    67 
    68             #endregion

    完整代码

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Collections.ObjectModel;
      4 using System.Linq;
      5 using System.Text;
      6 using System.Threading.Tasks;
      7 
      8 namespace DataStuctureStudy.Graphs
      9 {
     10     class GraphTest
     11     {
     12         public static void Test()
     13         {
     14             var graph = new Graph<String>(50);
     15             graph
     16                 .AddVertex("A")
     17                 .AddVertex("B")
     18                 .AddVertex("C")
     19                 .AddVertex("D")
     20                 .AddVertex("E")
     21                 .AddVertex("F")
     22                 .AddVertex("G")
     23                 .AddVertex("H")
     24                 .AddVertex("I");
     25             graph
     26                 .AddDirectedEdge("A", "B").AddDirectedEdge("A", "C").AddDirectedEdge("A", "D").AddDirectedEdge("A", "E")
     27                 .AddDirectedEdge("B", "F")
     28                 .AddDirectedEdge("D", "G")
     29                 .AddDirectedEdge("F", "H")
     30                 .AddDirectedEdge("G", "I");
     31 
     32             Console.WriteLine("深度遍历,栈版本:");
     33             graph.DepthFirstSearch("A", Console.Write);
     34             Console.WriteLine();
     35             Console.WriteLine();
     36 
     37             Console.WriteLine("深度遍历,递归版本:");
     38             graph.DepthFirstSearchRecursion("A", Console.Write);
     39             Console.WriteLine();
     40             Console.WriteLine();
     41 
     42             Console.WriteLine("广度遍历:");
     43             graph.BreadthFirstSearch("A", Console.Write);
     44             Console.WriteLine();
     45             Console.WriteLine();
     46 
     47             Console.WriteLine("最小生成树:");
     48             graph.DisplayMinimumSpanningTree("A");
     49             Console.WriteLine();
     50             Console.WriteLine();
     51 
     52             Console.WriteLine("拓扑排序:");
     53             var results = graph.TopologySort();
     54             Console.WriteLine(String.Join("->", results.ToArray()));
     55             Console.WriteLine();
     56         }
     57 
     58         class Vertex<T>
     59         {
     60             public T Value { get; set; }
     61 
     62             public bool WasVisited { get; set; }
     63         }
     64 
     65         class Graph<T>
     66         {
     67             #region 私有字段
     68 
     69             private int _maxSize;
     70             private Vertex<T>[] _vertexs;
     71             private bool[][] _edges;
     72             private int _vertexCount = 0;
     73 
     74             #endregion
     75 
     76             #region 构造方法
     77 
     78             public Graph(int maxSize)
     79             {
     80                 _maxSize = maxSize;
     81                 _vertexs = new Vertex<T>[_maxSize];
     82                 _edges = new bool[_maxSize][];
     83                 for (var i = 0; i < _maxSize; i++)
     84                 {
     85                     _edges[i] = new bool[_maxSize];
     86                 }
     87             }
     88 
     89             #endregion
     90 
     91             #region 添加顶点
     92 
     93             public Graph<T> AddVertex(T value)
     94             {
     95                 _vertexs[_vertexCount++] = new Vertex<T> { Value = value };
     96 
     97                 return this;
     98             }
     99 
    100             #endregion
    101 
    102             #region 添加边
    103 
    104             public Graph<T> AddUnDirectedEdge(T startItem, T endItem)
    105             {
    106                 var startIndex = this.GetVertexIndex(startItem);
    107                 var endIndex = this.GetVertexIndex(endItem);
    108 
    109                 _edges[startIndex][endIndex] = true;
    110                 _edges[endIndex][startIndex] = true;
    111 
    112                 return this;
    113             }
    114 
    115             public Graph<T> AddDirectedEdge(T startItem, T endItem)
    116             {
    117                 var startIndex = this.GetVertexIndex(startItem);
    118                 var endIndex = this.GetVertexIndex(endItem);
    119 
    120                 _edges[startIndex][endIndex] = true;
    121 
    122                 return this;
    123             }
    124 
    125             #endregion
    126 
    127             #region 深度优先遍历:栈版本
    128 
    129             public void DepthFirstSearch(T startItem, Action<T> action)
    130             {
    131                 var startIndex = this.GetVertexIndex(startItem);
    132                 var stack = new Stack<int>();
    133 
    134                 this.DepthFirstSearchVisit(stack, startIndex, action);
    135                 while (stack.Count > 0)
    136                 {
    137                     var adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(stack.Peek());
    138                     if (adjoinVertexIndex >= 0)
    139                     {
    140                         this.DepthFirstSearchVisit(stack, adjoinVertexIndex, action);
    141                     }
    142                     else
    143                     {
    144                         stack.Pop();
    145                     }
    146                 }
    147 
    148                 this.ResetVisited();
    149             }
    150 
    151             private void DepthFirstSearchVisit(Stack<int> stack, int index, Action<T> action)
    152             {
    153                 _vertexs[index].WasVisited = true;
    154                 action(_vertexs[index].Value);
    155                 stack.Push(index);
    156             }
    157 
    158             #endregion
    159 
    160             #region 深度优先遍历:递归版本
    161 
    162             public void DepthFirstSearchRecursion(T startItem, Action<T> action)
    163             {
    164                 var startIndex = this.GetVertexIndex(startItem);
    165 
    166                 this.DepthFirstSearchRecursionVisit(startIndex, action);
    167 
    168                 this.ResetVisited();
    169             }
    170 
    171             private void DepthFirstSearchRecursionVisit(int index, Action<T> action)
    172             {
    173                 _vertexs[index].WasVisited = true;
    174                 action(_vertexs[index].Value);
    175 
    176                 var adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(index);
    177                 while (adjoinVertexIndex >= 0)
    178                 {
    179                     this.DepthFirstSearchRecursionVisit(adjoinVertexIndex, action);
    180                     adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(index);
    181                 }
    182             }
    183 
    184             #endregion
    185 
    186             #region 广度优先遍历
    187 
    188             public void BreadthFirstSearch(T startItem, Action<T> action)
    189             {
    190                 var startIndex = this.GetVertexIndex(startItem);
    191                 var queue = new Queue<int>();
    192 
    193                 this.BreadthFirstSearchVisit(queue, startIndex, action);
    194                 while (queue.Count > 0)
    195                 {
    196                     var adjoinVertexIndexs = this.GetNextUnVisitedAdjoinVertexIndexs(queue.Dequeue());
    197                     foreach (var adjoinVertexIndex in adjoinVertexIndexs)
    198                     {
    199                         this.BreadthFirstSearchVisit(queue, adjoinVertexIndex, action);
    200                     }
    201                 }
    202 
    203                 this.ResetVisited();
    204             }
    205 
    206             private void BreadthFirstSearchVisit(Queue<int> queue, int index, Action<T> action)
    207             {
    208                 _vertexs[index].WasVisited = true;
    209                 action(_vertexs[index].Value);
    210                 queue.Enqueue(index);
    211             }
    212 
    213             #endregion
    214 
    215             #region 最小生成树
    216 
    217             public void DisplayMinimumSpanningTree(T startItem)
    218             {
    219                 var startIndex = this.GetVertexIndex(startItem);
    220                 var queue = new Queue<int>();
    221 
    222                 _vertexs[startIndex].WasVisited = true;
    223                 queue.Enqueue(startIndex);
    224                 while (queue.Count > 0)
    225                 {
    226                     var currentIndex = queue.Dequeue();
    227                     var adjoinVertexIndexs = this.GetNextUnVisitedAdjoinVertexIndexs(currentIndex);
    228                     foreach (var adjoinVertexIndex in adjoinVertexIndexs)
    229                     {
    230                         Console.WriteLine(_vertexs[currentIndex].Value + "->" + _vertexs[adjoinVertexIndex].Value);
    231 
    232                         _vertexs[adjoinVertexIndex].WasVisited = true;
    233                         queue.Enqueue(adjoinVertexIndex);
    234                     }
    235                 }
    236 
    237                 this.ResetVisited();
    238             }
    239 
    240             #endregion
    241 
    242             #region 拓扑排序
    243 
    244             public List<T> TopologySort()
    245             {
    246                 var cloneVertexs = (Vertex<T>[])_vertexs.Clone();
    247                 var cloneEdges = (bool[][])_edges.Clone();
    248                 var cloneVertexCount = _vertexCount;
    249 
    250                 var results = new List<T>();
    251                 while (_vertexCount > 0)
    252                 {
    253                     var successor = this.NextSuccessor();
    254                     if (successor == -1)
    255                     {
    256                         throw new InvalidOperationException("出现循环了!");
    257                     }
    258 
    259                     results.Insert(0, _vertexs[successor].Value);
    260 
    261                     this.RemoveVertex(successor);
    262                     _vertexCount--;
    263                 }
    264 
    265                 _vertexs = cloneVertexs;
    266                 _edges = cloneEdges;
    267                 _vertexCount = cloneVertexCount;
    268 
    269                 return results;
    270             }
    271 
    272             private int NextSuccessor()
    273             {
    274                 for (var row = 0; row < _vertexCount; row++)
    275                 {
    276                     if (_edges[row].Take(_vertexCount).All(x => !x))
    277                     {
    278                         return row;
    279                     }
    280                 }
    281 
    282                 return -1;
    283             }
    284 
    285             private void RemoveVertex(int successor)
    286             {
    287                 for (int i = successor; i < _vertexCount - 1; i++)
    288                 {
    289                     _vertexs[i] = _vertexs[i + 1];
    290                 }
    291 
    292                 for (int row = successor; row < _vertexCount - 1; row++)
    293                 {
    294                     for (int column = 0; column < _vertexCount; column++)
    295                     {
    296                         _edges[row][column] = _edges[row + 1][column];
    297                     }
    298                 }
    299 
    300                 for (int column = successor; column < _vertexCount - 1; column++)
    301                 {
    302                     for (int row = 0; row < _vertexCount; row++)
    303                     {
    304                         _edges[row][column] = _edges[row][column + 1];
    305                     }
    306                 }
    307             }
    308 
    309             #endregion
    310 
    311             #region 帮助方法
    312 
    313             private int GetVertexIndex(T item)
    314             {
    315                 for (var i = 0; i < _vertexCount; i++)
    316                 {
    317                     if (_vertexs[i].Value.Equals(item))
    318                     {
    319                         return i;
    320                     }
    321                 }
    322                 return -1;
    323             }
    324 
    325             private int GetNextUnVisitedAdjoinVertexIndex(int index)
    326             {
    327                 for (var i = 0; i < _vertexCount; i++)
    328                 {
    329                     if (_edges[index][i] && _vertexs[i].WasVisited == false)
    330                     {
    331                         return i;
    332                     }
    333                 }
    334 
    335                 return -1;
    336             }
    337 
    338             private List<int> GetNextUnVisitedAdjoinVertexIndexs(int index)
    339             {
    340                 var results = new List<int>();
    341 
    342                 for (var i = 0; i < _vertexCount; i++)
    343                 {
    344                     if (_edges[index][i] && _vertexs[i].WasVisited == false)
    345                     {
    346                         results.Add(i);
    347                     }
    348                 }
    349 
    350                 return results;
    351             }
    352 
    353             private void ResetVisited()
    354             {
    355                 for (var i = 0; i < _vertexCount; i++)
    356                 {
    357                     _vertexs[i].WasVisited = false;
    358                 }
    359             }
    360 
    361             #endregion
    362         }
    363     }
    364 }
    View Code

    备注

    本文没有解释权重图,下篇再介绍。

  • 相关阅读:
    算法学习笔记: 珂朵莉树
    算法学习笔记:2SAT
    0x62 图论最小生成树
    Codeforces Round #632 (Div. 2) C. Eugene and an array(尺取法/前缀和)
    BZOJ1912 异象石(LCA/DFS序/set)
    (六1)Firefox插件安装
    (六2)八种定位方式为了查找元素
    (六3)从查找元素到操作元素
    【OSI】网络协议模型
    读《Wireshark网络分析就这么简单》读书笔记
  • 原文地址:https://www.cnblogs.com/happyframework/p/3493606.html
Copyright © 2011-2022 走看看