背景

不同的数据结构有不同的用途,像:数组、链表、队列、栈多数是用来做为基本的工具使用,二叉树多用来作为已排序元素列表的存储,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)的遍历、最小生成树和拓扑排序的更多相关文章

  1. 算法与数据结构(七) AOV网的拓扑排序

    今天博客的内容依然与图有关,今天博客的主题是关于拓扑排序的.拓扑排序是基于AOV网的,关于AOV网的概念,我想引用下方这句话来介绍: AOV网:在现代化管理中,人们常用有向图来描述和分析一项工程的计划 ...

  2. 算法与数据结构(七) AOV网的拓扑排序(Swift版)

    今天博客的内容依然与图有关,今天博客的主题是关于拓扑排序的.拓扑排序是基于AOV网的,关于AOV网的概念,我想引用下方这句话来介绍: AOV网:在现代化管理中,人们常用有向图来描述和分析一项工程的计划 ...

  3. 关于最小生成树,拓扑排序、强连通分量、割点、2-SAT的一点笔记

    关于最小生成树,拓扑排序.强连通分量.割点.2-SAT的一点笔记 前言:近期在复习这些东西,就xjb写一点吧.当然以前也写过,但这次偏重不太一样 MST 最小瓶颈路:u到v最大权值最小的路径.在最小生 ...

  4. 有向无环图的应用—AOV网 和 拓扑排序

    有向无环图:无环的有向图,简称 DAG (Directed Acycline Graph) 图. 一个有向图的生成树是一个有向树,一个非连通有向图的若干强连通分量生成若干有向树,这些有向数形成生成森林 ...

  5. Almost Acyclic Graph CodeForces - 915D (思维+拓扑排序判环)

    Almost Acyclic Graph CodeForces - 915D time limit per test 1 second memory limit per test 256 megaby ...

  6. NX二次开发-算法篇-冒泡排序(例子:遍历所有点并排序)

    NX9+VS2012 #include <uf.h> #include <uf_ui.h> #include <uf_curve.h> #include <u ...

  7. 拓扑排序-有向无环图(DAG, Directed Acyclic Graph)

    条件: 1.每个顶点出现且只出现一次. 2.若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面. 有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说. 一 ...

  8. 拓扑排序---AOV图

    对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中全部顶点排成一个线性序列, 使得图中随意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出如 ...

  9. BFS (1)算法模板 看是否需要分层 (2)拓扑排序——检测编译时的循环依赖 制定有依赖关系的任务的执行顺序 djkstra无非是将bfs模板中的deque修改为heapq

    BFS模板,记住这5个: (1)针对树的BFS 1.1 无需分层遍历 from collections import deque def levelOrderTree(root): if not ro ...

随机推荐

  1. Centos7配置vsftpd3.0.2

    1.安装vsftpd vsftp使用本地用户登陆方式 yum -y install vsftpd yum安装的版本3.0.2 2.配置vsftpd vim /etc/vsftpd/vsftpd.con ...

  2. SQL Server操作实例

    创建数据库 create database accountInfo/*创建账户信息数据库*/ 创建数据表 /*定义主码.外码.和人数.余额的取值范围.*/ /*创建储蓄所表*/ create tabl ...

  3. Hive(六)内置函数与高级操作

    一内置函数 1 数学函数 Return Type Name (Signature) Description DOUBLE round(DOUBLE a) Returns the rounded BIG ...

  4. shell在linux里摇摇晃晃

    1.shell不只是一种解释器,还是一种编程工具 查看系统中可用的shell,linux默认使用 Bash Shell [root@localhost ~]# cat /etc/shells /bin ...

  5. jdk与eclipse不匹配的各种问题。。。

  6. thinkphp签到的实现代码

    thinkphp签到的实现代码 数据表 1 2 3 4 5 6 7 8 9 10 11 CREATE TABLE `members_sign` (   `id` int(11) unsigned NO ...

  7. numpy.base_repr 方法解释

    首先看官方文档: numpy.base_repr(number, base=2, padding=0) 将给定的 number 值,换算成给定的 base 进制(默认 2 进制)的值,以字符串的形式返 ...

  8. java 代理设计模式

    首先代理(deleration)是什么,在日常生活中我们有很多这种的例子,比如你上个QQ,各种空间被什么代理刷屏,对的,代理不是生产产品的商家,也不是进购产品的卖家,他们只是帮别人卖东西,这就相当于一 ...

  9. CODEVS 4655 序列终结者-splay(区间更新、区间翻转、区间最值)

    4655 序列终结者  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 大师 Master 题解       题目描述 Description 网上有许多题,就是给定一个序列,要 ...

  10. 链路跟踪技术traceId的总结和实践

    目录 写作背景 什么是链路跟踪 目前常见的链路跟踪技术及其优缺点 链路跟踪技术的实现原理 代码示例 背景 由于最近系统上线后,访问量达,出现线上问题后往往无从下手排查,即使打印了很多日志,依然无法快速 ...