【基础知识】| 作者 / Edison Zhou
这是
恰童鞋骚年
的第
217
篇原创文章
上一篇介绍了图的基本知识及存储结构,由于邻接矩阵容易造成空间资源的浪费,这一篇我们主要来动手实现一个基于邻接表结构的图。 1总体结构实现
上一篇介绍了图的基本知识及存储结构,由于邻接矩阵容易造成空间资源的浪费,这一篇我们主要来动手实现一个基于邻接表结构的图。 1总体结构实现
(1)链表节点定义
① 表头节点Vertex
/// /// 嵌套类:存放于数组中的表头节点/// /// protected class Vertex<TValue>{ public TValue data; // 数据 public Node firstEdge; // 邻接点链表头指针 public bool isVisited; // 访问标志:遍历时使用 public Vertex() { this.data = default(TValue); } public Vertex(TValue value) { this.data = value; }}
② 表节点Node
/// /// 嵌套类:链表中的表节点/// protected class Node{ public Vertex adjvex; // 邻接点域 public Node next; // 下一个邻接点指针域 public Node() { this.adjvex = null; } public Node(Vertexvalue) { this.adjvex = value; }}
(2)邻接表总体定义
public class MyAdjacencyListwhere T : class{ private List>> items; public MyAdjacencyList() : this(10) { } public MyAdjacencyList(int capacity) { this.items = new List>>(capacity); } #region 基本方法:为图中添加顶点、添加有向与无向边 #endregion #region 辅助方法:图中是否包含某个元素、查找指定顶点、初始化visited标志 #endregion #region 嵌套类:表头节点与表节点定义 #endregion}
首先,我们使用了一个动态集合List来代替数组存储Vertex的集合,默认容量为10,且不需要数组存储空间不够的情况,简化了操作。其次,我们要定义一些基本方法,如添加顶点、添加边。还要定义一些辅助方法,如判断是否包含某个元素等(详见完整代码文件)。最后,我们再实现图的一些遍历算法,如深度优先遍历与广度优先遍历(本篇不作介绍,下一篇再介绍)。
2基本方法代码实现(1)添加一个顶点
这里其实就是往集合里边加入新元素:
/// /// 添加一个顶点/// /// 顶点元素datapublic void AddVertex(T item){ if (Contains(item)) { throw new ArgumentException("添加了重复的顶点!"); } Vertex newVertex = new Vertex(item); items.Add(newVertex);}
(2)添加一条边
这里需要分为两种情况,一种是添加无向图的边,这时无向图的两个顶点都需要记录边的信息。另一种则是添加有向图的边,这时只需要一条记录。
① 无向图
/// /// 添加一条无向边/// /// 头顶点data/// 尾顶点data/// 权值public void AddEdge(T from, T to){ Vertex fromVertex = Find(from); if (fromVertex == null) { throw new ArgumentException("头顶点不存在!"); } Vertex toVertex = Find(to); if (toVertex == null) { throw new ArgumentException("尾顶点不存在!"); } // 无向图的两个顶点都需要记录边的信息 AddDirectedEdge(fromVertex, toVertex); AddDirectedEdge(toVertex, fromVertex);}
这里可以看到这两句代码,对应的两个顶点都记录了边的信息。
// 无向图的两个顶点都需要记录边的信息AddDirectedEdge(fromVertex, toVertex);AddDirectedEdge(toVertex, fromVertex);
② 有向图
/// /// 添加一条有向边/// /// 头结点data/// 尾节点datapublic void AddDirectedEdge(T from, T to){ Vertex fromVertex = Find(from); if (fromVertex == null) { throw new ArgumentException("头顶点不存在!"); } Vertex toVertex = Find(to); if (toVertex == null) { throw new ArgumentException("尾顶点不存在!"); } AddDirectedEdge(fromVertex, toVertex);}
③ 如何添加边
在实现中,无论是无线图还是有向图都是添加的有向边,只不过无向图是添加了两条有向边:
/// /// 添加一条有向边/// /// 头顶点/// 尾顶点private void AddDirectedEdge(Vertex fromVertex, Vertex toVertex){ if (fromVertex.firstEdge == null) { fromVertex.firstEdge = new Node(toVertex); } else { Node temp = null; Node node = fromVertex.firstEdge; do { // 检查是否添加了重复边 if (node.adjvex.data.Equals(toVertex.data)) { throw new ArgumentException("添加了重复的边!"); } temp = node; node = node.next; } while (node != null); Node newNode = new Node(toVertex); temp.next = newNode; }}
(3)打印每个顶点及其邻接点的信息
/// /// 打印打印每个顶点和它的邻接点/// /// 是否是有向图public string GetGraphInfo(bool isDirectedGraph = false){ StringBuilder sb = new StringBuilder(); foreach (Vertex v in items) { sb.Append(v.data.ToString() + ":"); if (v.firstEdge != null) { Node temp = v.firstEdge; while (temp != null) { if (isDirectedGraph) { sb.Append(v.data.ToString() + "→" + temp.adjvex.data.ToString() + " "); } else { sb.Append(temp.adjvex.data.ToString()); } temp = temp.next; } } sb.Append("\r\n"); } return sb.ToString();}
这里判断了是否是有向图,如果是有向图则显示A→B的形式,如果是无向图则显示A:B的形式。
3基本功能测试这里我们对基本功能做一下测试,分为无向图和有向图,首先插入顶点及对应边,然后打印顶点及其邻接表的信息,要构造的无向图与有向图如上面两张图所示,测试代码如下所示:
static void MyAdjacencyListTest(){ Console.WriteLine("------------无向图------------"); MyAdjacencyList<string> adjList = new MyAdjacencyList<string>(); // 添加顶点 adjList.AddVertex("A"); adjList.AddVertex("B"); adjList.AddVertex("C"); adjList.AddVertex("D"); //adjList.AddVertex("D"); // 会报异常:添加了重复的节点 // 添加无向边 adjList.AddEdge("A", "B"); adjList.AddEdge("A", "C"); adjList.AddEdge("A", "D"); adjList.AddEdge("B", "D"); //adjList.AddEdge("B", "D"); // 会报异常:添加了重复的边 Console.Write(adjList.GetGraphInfo()); Console.WriteLine("------------有向图------------"); MyAdjacencyList<string> dirAdjList = new MyAdjacencyList<string>(); // 添加顶点 dirAdjList.AddVertex("A"); dirAdjList.AddVertex("B"); dirAdjList.AddVertex("C"); dirAdjList.AddVertex("D"); // 添加有向边 dirAdjList.AddDirectedEdge("A", "B"); dirAdjList.AddDirectedEdge("A", "C"); dirAdjList.AddDirectedEdge("A", "D"); dirAdjList.AddDirectedEdge("B", "D"); Console.Write(dirAdjList.GetGraphInfo(true));}
运行结果如下图所示:
4小结由于邻接矩阵容易造成空间资源的浪费,本篇欧中介绍了用C#代码来实现图的邻接矩阵结构实现。下一篇,我们会用C#代码来实现图的遍历算法。
5参考资料 程杰,《大话数据结构》 陈广,《数据结构(C#语言描述)》 段恩泽,《数据结构(C#语言版)》 往期 精彩 回顾每天5分钟用C#学习数据结构(20)
如果本文对你有用, 不妨点个“在看”或者转发朋友圈👇点击