算法:图(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 ]
这个就不多说了,多数书籍使用都是邻接矩阵。
代码
class Vertex<T>
{
public T Value { get; set; } public bool WasVisited { get; set; }
} class Graph<T>
{
#region 私有字段 private int _maxSize;
private Vertex<T>[] _vertexs;
private bool[][] _edges;
private int _vertexCount = ; #endregion #region 构造方法 public Graph(int maxSize)
{
_maxSize = maxSize;
_vertexs = new Vertex<T>[_maxSize];
_edges = new bool[_maxSize][];
for (var i = ; i < _maxSize; i++)
{
_edges[i] = new bool[_maxSize];
}
} #endregion #region 添加顶点 public Graph<T> AddVertex(T value)
{
_vertexs[_vertexCount++] = new Vertex<T> { Value = value }; return this;
} #endregion #region 添加边 public Graph<T> AddUnDirectedEdge(T startItem, T endItem)
{
var startIndex = this.GetVertexIndex(startItem);
var endIndex = this.GetVertexIndex(endItem); _edges[startIndex][endIndex] = true;
_edges[endIndex][startIndex] = true; return this;
} public Graph<T> AddDirectedEdge(T startItem, T endItem)
{
var startIndex = this.GetVertexIndex(startItem);
var endIndex = this.GetVertexIndex(endItem); _edges[startIndex][endIndex] = true; return this;
} #endregion
}
图的常见算法
遍历
本文的遍历算法只适合一般的无向图。
深度优先遍历
深度优先遍历的原则是:尽可能的离开始节点远,二叉树的三种遍历算法都属于这种算法。
示例

从 A 开始的遍历顺序为(邻接的遍历顺序为字母升序):ABCEDBF。
栈版本
#region 深度优先遍历:栈版本
public void DepthFirstSearch(T startItem, Action<T> action)
{
var startIndex = this.GetVertexIndex(startItem);
var stack = new Stack<int>();
this.DepthFirstSearchVisit(stack, startIndex, action);
while (stack.Count > )
{
var adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(stack.Peek());
if (adjoinVertexIndex >= )
{
this.DepthFirstSearchVisit(stack, adjoinVertexIndex, action);
}
else
{
stack.Pop();
}
}
this.ResetVisited();
}
private void DepthFirstSearchVisit(Stack<int> stack, int index, Action<T> action)
{
_vertexs[index].WasVisited = true;
action(_vertexs[index].Value);
stack.Push(index);
}
#endregion
递归版本
#region 深度优先遍历:递归版本
public void DepthFirstSearchRecursion(T startItem, Action<T> action)
{
var startIndex = this.GetVertexIndex(startItem);
this.DepthFirstSearchRecursionVisit(startIndex, action);
this.ResetVisited();
}
private void DepthFirstSearchRecursionVisit(int index, Action<T> action)
{
_vertexs[index].WasVisited = true;
action(_vertexs[index].Value);
var adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(index);
while (adjoinVertexIndex >= )
{
this.DepthFirstSearchRecursionVisit(adjoinVertexIndex, action);
adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(index);
}
}
#endregion
广度优先遍历
广度优先遍历的原则是:尽可能的离开始节点近。这个算法没有办法通过递归实现,多数都是使用的队列(终于发现了队列的又一个用处)。
示例

从 A 开始的遍历顺序为(邻接的遍历顺序为字母升序):ABCEDFB。
代码
#region 广度优先遍历
public void BreadthFirstSearch(T startItem, Action<T> action)
{
var startIndex = this.GetVertexIndex(startItem);
var queue = new Queue<int>();
this.BreadthFirstSearchVisit(queue, startIndex, action);
while (queue.Count > )
{
var adjoinVertexIndexs = this.GetNextUnVisitedAdjoinVertexIndexs(queue.Dequeue());
foreach (var adjoinVertexIndex in adjoinVertexIndexs)
{
this.BreadthFirstSearchVisit(queue, adjoinVertexIndex, action);
}
}
this.ResetVisited();
}
private void BreadthFirstSearchVisit(Queue<int> queue, int index, Action<T> action)
{
_vertexs[index].WasVisited = true;
action(_vertexs[index].Value);
queue.Enqueue(index);
}
#endregion
最小生成树
本文的最小生成树算法只适合一般的无向图。
将 N 个顶点连接起来的最小树叫:最小生成树。
给定一个颗有 N 个顶点的连通图,则最小生成树的边为:N - 1。
不同的遍历算法生成的最小生成树是不同的,下面使用广度优先遍历来生成最小树。
示例

从 A 开始的遍历顺序为(邻接的遍历顺序为字母升序):A->B、B->C、C->E、E->D、E->F、D->B。
代码
#region 最小生成树
public void DisplayMinimumSpanningTree(T startItem)
{
var startIndex = this.GetVertexIndex(startItem);
var queue = new Queue<int>();
_vertexs[startIndex].WasVisited = true;
queue.Enqueue(startIndex);
while (queue.Count > )
{
var currentIndex = queue.Dequeue();
var adjoinVertexIndexs = this.GetNextUnVisitedAdjoinVertexIndexs(currentIndex);
foreach (var adjoinVertexIndex in adjoinVertexIndexs)
{
Console.WriteLine(_vertexs[currentIndex].Value + "->" + _vertexs[adjoinVertexIndex].Value);
_vertexs[adjoinVertexIndex].WasVisited = true;
queue.Enqueue(adjoinVertexIndex);
}
}
this.ResetVisited();
}
#endregion
拓扑排序
拓扑排序只适合定向图,且图中不存在循环结构。其算法非常简单,依次获取图中的没有邻接顶点的顶点,然后删除该顶点。
示例

上图出现了循环结构,假如没有顶点 C,拓扑排序的结果为(顶点的遍历顺序为字母升序):EFDAB。
算法
#region 拓扑排序
public List<T> TopologySort()
{
var cloneVertexs = (Vertex<T>[])_vertexs.Clone();
var cloneEdges = (bool[][])_edges.Clone();
var cloneVertexCount = _vertexCount;
var results = new List<T>();
while (_vertexCount > )
{
var successor = this.NextSuccessor();
if (successor == -)
{
throw new InvalidOperationException("出现循环了!");
}
results.Insert(, _vertexs[successor].Value);
this.RemoveVertex(successor);
_vertexCount--;
}
_vertexs = cloneVertexs;
_edges = cloneEdges;
_vertexCount = cloneVertexCount;
return results;
}
private int NextSuccessor()
{
for (var row = ; row < _vertexCount; row++)
{
if (_edges[row].Take(_vertexCount).All(x => !x))
{
return row;
}
}
return -;
}
private void RemoveVertex(int successor)
{
for (int i = successor; i < _vertexCount - ; i++)
{
_vertexs[i] = _vertexs[i + ];
}
for (int row = successor; row < _vertexCount - ; row++)
{
for (int column = ; column < _vertexCount; column++)
{
_edges[row][column] = _edges[row + ][column];
}
}
for (int column = successor; column < _vertexCount - ; column++)
{
for (int row = ; row < _vertexCount; row++)
{
_edges[row][column] = _edges[row][column + ];
}
}
}
#endregion
完整代码
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace DataStuctureStudy.Graphs
{
class GraphTest
{
public static void Test()
{
var graph = new Graph<String>();
graph
.AddVertex("A")
.AddVertex("B")
.AddVertex("C")
.AddVertex("D")
.AddVertex("E")
.AddVertex("F")
.AddVertex("G")
.AddVertex("H")
.AddVertex("I");
graph
.AddDirectedEdge("A", "B").AddDirectedEdge("A", "C").AddDirectedEdge("A", "D").AddDirectedEdge("A", "E")
.AddDirectedEdge("B", "F")
.AddDirectedEdge("D", "G")
.AddDirectedEdge("F", "H")
.AddDirectedEdge("G", "I"); Console.WriteLine("深度遍历,栈版本:");
graph.DepthFirstSearch("A", Console.Write);
Console.WriteLine();
Console.WriteLine(); Console.WriteLine("深度遍历,递归版本:");
graph.DepthFirstSearchRecursion("A", Console.Write);
Console.WriteLine();
Console.WriteLine(); Console.WriteLine("广度遍历:");
graph.BreadthFirstSearch("A", Console.Write);
Console.WriteLine();
Console.WriteLine(); Console.WriteLine("最小生成树:");
graph.DisplayMinimumSpanningTree("A");
Console.WriteLine();
Console.WriteLine(); Console.WriteLine("拓扑排序:");
var results = graph.TopologySort();
Console.WriteLine(String.Join("->", results.ToArray()));
Console.WriteLine();
} class Vertex<T>
{
public T Value { get; set; } public bool WasVisited { get; set; }
} class Graph<T>
{
#region 私有字段 private int _maxSize;
private Vertex<T>[] _vertexs;
private bool[][] _edges;
private int _vertexCount = ; #endregion #region 构造方法 public Graph(int maxSize)
{
_maxSize = maxSize;
_vertexs = new Vertex<T>[_maxSize];
_edges = new bool[_maxSize][];
for (var i = ; i < _maxSize; i++)
{
_edges[i] = new bool[_maxSize];
}
} #endregion #region 添加顶点 public Graph<T> AddVertex(T value)
{
_vertexs[_vertexCount++] = new Vertex<T> { Value = value }; return this;
} #endregion #region 添加边 public Graph<T> AddUnDirectedEdge(T startItem, T endItem)
{
var startIndex = this.GetVertexIndex(startItem);
var endIndex = this.GetVertexIndex(endItem); _edges[startIndex][endIndex] = true;
_edges[endIndex][startIndex] = true; return this;
} public Graph<T> AddDirectedEdge(T startItem, T endItem)
{
var startIndex = this.GetVertexIndex(startItem);
var endIndex = this.GetVertexIndex(endItem); _edges[startIndex][endIndex] = true; return this;
} #endregion #region 深度优先遍历:栈版本 public void DepthFirstSearch(T startItem, Action<T> action)
{
var startIndex = this.GetVertexIndex(startItem);
var stack = new Stack<int>(); this.DepthFirstSearchVisit(stack, startIndex, action);
while (stack.Count > )
{
var adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(stack.Peek());
if (adjoinVertexIndex >= )
{
this.DepthFirstSearchVisit(stack, adjoinVertexIndex, action);
}
else
{
stack.Pop();
}
} this.ResetVisited();
} private void DepthFirstSearchVisit(Stack<int> stack, int index, Action<T> action)
{
_vertexs[index].WasVisited = true;
action(_vertexs[index].Value);
stack.Push(index);
} #endregion #region 深度优先遍历:递归版本 public void DepthFirstSearchRecursion(T startItem, Action<T> action)
{
var startIndex = this.GetVertexIndex(startItem); this.DepthFirstSearchRecursionVisit(startIndex, action); this.ResetVisited();
} private void DepthFirstSearchRecursionVisit(int index, Action<T> action)
{
_vertexs[index].WasVisited = true;
action(_vertexs[index].Value); var adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(index);
while (adjoinVertexIndex >= )
{
this.DepthFirstSearchRecursionVisit(adjoinVertexIndex, action);
adjoinVertexIndex = this.GetNextUnVisitedAdjoinVertexIndex(index);
}
} #endregion #region 广度优先遍历 public void BreadthFirstSearch(T startItem, Action<T> action)
{
var startIndex = this.GetVertexIndex(startItem);
var queue = new Queue<int>(); this.BreadthFirstSearchVisit(queue, startIndex, action);
while (queue.Count > )
{
var adjoinVertexIndexs = this.GetNextUnVisitedAdjoinVertexIndexs(queue.Dequeue());
foreach (var adjoinVertexIndex in adjoinVertexIndexs)
{
this.BreadthFirstSearchVisit(queue, adjoinVertexIndex, action);
}
} this.ResetVisited();
} private void BreadthFirstSearchVisit(Queue<int> queue, int index, Action<T> action)
{
_vertexs[index].WasVisited = true;
action(_vertexs[index].Value);
queue.Enqueue(index);
} #endregion #region 最小生成树 public void DisplayMinimumSpanningTree(T startItem)
{
var startIndex = this.GetVertexIndex(startItem);
var queue = new Queue<int>(); _vertexs[startIndex].WasVisited = true;
queue.Enqueue(startIndex);
while (queue.Count > )
{
var currentIndex = queue.Dequeue();
var adjoinVertexIndexs = this.GetNextUnVisitedAdjoinVertexIndexs(currentIndex);
foreach (var adjoinVertexIndex in adjoinVertexIndexs)
{
Console.WriteLine(_vertexs[currentIndex].Value + "->" + _vertexs[adjoinVertexIndex].Value); _vertexs[adjoinVertexIndex].WasVisited = true;
queue.Enqueue(adjoinVertexIndex);
}
} this.ResetVisited();
} #endregion #region 拓扑排序 public List<T> TopologySort()
{
var cloneVertexs = (Vertex<T>[])_vertexs.Clone();
var cloneEdges = (bool[][])_edges.Clone();
var cloneVertexCount = _vertexCount; var results = new List<T>();
while (_vertexCount > )
{
var successor = this.NextSuccessor();
if (successor == -)
{
throw new InvalidOperationException("出现循环了!");
} results.Insert(, _vertexs[successor].Value); this.RemoveVertex(successor);
_vertexCount--;
} _vertexs = cloneVertexs;
_edges = cloneEdges;
_vertexCount = cloneVertexCount; return results;
} private int NextSuccessor()
{
for (var row = ; row < _vertexCount; row++)
{
if (_edges[row].Take(_vertexCount).All(x => !x))
{
return row;
}
} return -;
} private void RemoveVertex(int successor)
{
for (int i = successor; i < _vertexCount - ; i++)
{
_vertexs[i] = _vertexs[i + ];
} for (int row = successor; row < _vertexCount - ; row++)
{
for (int column = ; column < _vertexCount; column++)
{
_edges[row][column] = _edges[row + ][column];
}
} for (int column = successor; column < _vertexCount - ; column++)
{
for (int row = ; row < _vertexCount; row++)
{
_edges[row][column] = _edges[row][column + ];
}
}
} #endregion #region 帮助方法 private int GetVertexIndex(T item)
{
for (var i = ; i < _vertexCount; i++)
{
if (_vertexs[i].Value.Equals(item))
{
return i;
}
}
return -;
} private int GetNextUnVisitedAdjoinVertexIndex(int index)
{
for (var i = ; i < _vertexCount; i++)
{
if (_edges[index][i] && _vertexs[i].WasVisited == false)
{
return i;
}
} return -;
} private List<int> GetNextUnVisitedAdjoinVertexIndexs(int index)
{
var results = new List<int>(); for (var i = ; i < _vertexCount; i++)
{
if (_edges[index][i] && _vertexs[i].WasVisited == false)
{
results.Add(i);
}
} return results;
} private void ResetVisited()
{
for (var i = ; i < _vertexCount; i++)
{
_vertexs[i].WasVisited = false;
}
} #endregion
}
}
}
备注
本文没有解释权重图,下篇再介绍。
算法:图(Graph)的遍历、最小生成树和拓扑排序的更多相关文章
- 算法与数据结构(七) AOV网的拓扑排序
今天博客的内容依然与图有关,今天博客的主题是关于拓扑排序的.拓扑排序是基于AOV网的,关于AOV网的概念,我想引用下方这句话来介绍: AOV网:在现代化管理中,人们常用有向图来描述和分析一项工程的计划 ...
- 算法与数据结构(七) AOV网的拓扑排序(Swift版)
今天博客的内容依然与图有关,今天博客的主题是关于拓扑排序的.拓扑排序是基于AOV网的,关于AOV网的概念,我想引用下方这句话来介绍: AOV网:在现代化管理中,人们常用有向图来描述和分析一项工程的计划 ...
- 关于最小生成树,拓扑排序、强连通分量、割点、2-SAT的一点笔记
关于最小生成树,拓扑排序.强连通分量.割点.2-SAT的一点笔记 前言:近期在复习这些东西,就xjb写一点吧.当然以前也写过,但这次偏重不太一样 MST 最小瓶颈路:u到v最大权值最小的路径.在最小生 ...
- 有向无环图的应用—AOV网 和 拓扑排序
有向无环图:无环的有向图,简称 DAG (Directed Acycline Graph) 图. 一个有向图的生成树是一个有向树,一个非连通有向图的若干强连通分量生成若干有向树,这些有向数形成生成森林 ...
- Almost Acyclic Graph CodeForces - 915D (思维+拓扑排序判环)
Almost Acyclic Graph CodeForces - 915D time limit per test 1 second memory limit per test 256 megaby ...
- NX二次开发-算法篇-冒泡排序(例子:遍历所有点并排序)
NX9+VS2012 #include <uf.h> #include <uf_ui.h> #include <uf_curve.h> #include <u ...
- 拓扑排序-有向无环图(DAG, Directed Acyclic Graph)
条件: 1.每个顶点出现且只出现一次. 2.若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面. 有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说. 一 ...
- 拓扑排序---AOV图
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中全部顶点排成一个线性序列, 使得图中随意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出如 ...
- BFS (1)算法模板 看是否需要分层 (2)拓扑排序——检测编译时的循环依赖 制定有依赖关系的任务的执行顺序 djkstra无非是将bfs模板中的deque修改为heapq
BFS模板,记住这5个: (1)针对树的BFS 1.1 无需分层遍历 from collections import deque def levelOrderTree(root): if not ro ...
随机推荐
- Java字符串常见实例与函数
字符串比较 字符串函数 compareTo (string) ,compareToIgnoreCase(String) 及 compareTo(object string) 来比较两个字符串,并返回字 ...
- 开发 Material Design+RxJava+Retrofit+MVP App 参考资料
前言 在开发一个基于 Material Design+RxJava+Retrofit+MVP 框架的 App 过程中学习的资料整理 —— 由G军仔分享 这里记录了我开发 大象 项目时,所学习的 ...
- CentOS 7.2 下 PXE+kickstart 自动安装系统
一.简单概述 1.1 Kickstart 概述 对于网络安装系统,在linux 下面最熟悉的应该就是 Kickstart 以及 cobbler.写这篇文章的目的在于我公司目前使用的就是 Kicksta ...
- python错误:UnicodeDecodeError: 'utf8' codec can't decode byte 0xe6 in position 0: unexpected end of data
一.错误原因 在学习selenium自动化测试框架的时候,进行模仿浏览器搜索功能,输入英文是没问题,但是输入中文就报错,报错代码 def test_baidu_search(self): " ...
- python中list和str互转
1.list转str 假设有一个名为test_list的list,转换后的str名为test_str 则转换方法: test_str = "".join(test_list) 例子 ...
- ubuntu12.04上的mongodb卸载
如果您需要卸载 mongodb,然后有几种方法来完成这取决于你想实现. 一.卸载只是 mongodb 这将删除只是 mongodb 包本身. 1 sudo apt-get remove mongodb ...
- ref:CodeIgniter框架内核设计缺陷可能导致任意代码执行
ref:https://www.seebug.org/vuldb/ssvid-96217 简要描述: 为准备乌云深圳沙龙,准备几个0day做案例. 官方承认这个问题,说明会发布补丁,但不愿承认这是个『 ...
- 《Android源码设计模式》----面向对象六大原则
1.单一职责原则 Single Respoonsibility Principle(SRP) --封装 2.开闭原则 Open Close Principle(OCP)--对扩展开放,对修改封闭 3. ...
- python3.6.5中pip3无法使用
1.在python命令行窗口中: python3 -m ensurepip 创建出pip3.exe.2.再在python3.6的安装目录下的Scripts路径下命令行 pip3 install XXX ...
- 哪来的gou zi 阿龙(最新更新于1.21日)
众所周知,信息竞赛教室有一个特gou zi的人,叫做阿龙. 这个人呢,特别好玩,特别gou zi 还有一个人,叫Sugar,这个人特别喜欢和阿龙闹,so,一系列爆笑无脑的事就发生了! 1.谁是鱼? 一 ...