一.简介
本文将实现不同的图的存储结构,代码为C#代码.
下面是通用的图类型枚举:
/************************************ * 创建人:movin * 创建时间:2021/7/3 15:50:19 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 图类型 /// </summary> public enum EGraphType { /// <summary> /// 无向图 /// </summary> UndirectedGraph, /// <summary> /// 有向图 /// </summary> DirectedGraph, } }
二.图的实现
图的实现我们最直观的方式就是使用链表去存储,但是这样存储太过于复杂,而且首先图并不是顺序结构,它是没有头尾的,其次图中任意两个顶点之间都可能存在边,所以使用链表的方式存储是不合适的,使用多重链表又会造成巨大的空间浪费,一般我们采用下面的方案实现图的存储:
1.邻接矩阵
图由顶点和边(弧)组成,因此我们可以考虑分别存储顶点和边(弧).顶点可以使用数组结构存储,边(弧)可以使用一个矩阵(二维数组)存储:
/************************************ * 创建人:movin * 创建时间:2021/7/3 15:57:42 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 邻接矩阵顶点 /// </summary> public struct AdjacencyMatrixVertex { /// <summary> /// 顶点中可以存储数据,目前暂定存储int类型,可以根据需要自行修改 /// </summary> public int Content { get; set; } public AdjacencyMatrixVertex(int content) { Content = content; } } }
/************************************ * 创建人:movin * 创建时间:2021/7/3 15:50:19 * 版权所有:个人 ***********************************/ using System; namespace GraphCore { /// <summary> /// 邻接矩阵结构的图 /// </summary> public class AdjacencyMatrixGraph { /// <summary> /// 图的类型,有向图或无向图 /// </summary> public EGraphType type; /// <summary> /// 所有的顶点存储为一个一维数组 /// </summary> public AdjacencyMatrixVertex[] vertices; /// <summary> /// 邻接矩阵,存储所有顶点的连接信息 /// </summary> public int[,] adjacencyMatrix; /// <summary> /// 顶点数量 /// </summary> public int Count { get; private set; } /// <summary> /// 构造方法 /// </summary> /// <param name="vertexCount">顶点数量</param> public AdjacencyMatrixGraph(int vertexCount,EGraphType type) { //参数校验不合格抛出参数异常 if(vertexCount <= 0) { throw new ArgumentException(); } vertices = new AdjacencyMatrixVertex[vertexCount]; adjacencyMatrix = new int[vertexCount, vertexCount]; for (int i = 0; i < adjacencyMatrix.GetLength(0); i++) { for (int j = 0; j < adjacencyMatrix.GetLength(1); j++) { if(i == j) { adjacencyMatrix[i, j] = 0; } else { adjacencyMatrix[i, j] = int.MaxValue; } } } Count = vertexCount; this.type = type; } /// <summary> /// 添加一条边/弧 /// </summary> /// <param name="startIndex">起始顶点下标</param> /// <param name="endIndex">终止顶点下标</param> /// <param name="power">权重</param> public void AddEdgeOrArc(int startIndex,int endIndex,int weight = 1) { if (startIndex < 0 || endIndex < 0 || startIndex >= Count || endIndex >= Count || weight == int.MaxValue) { return; } switch (type) { case EGraphType.DirectedGraph: adjacencyMatrix[startIndex, endIndex] = weight; break; case EGraphType.UndirectedGraph: adjacencyMatrix[startIndex, endIndex] = weight; adjacencyMatrix[endIndex, startIndex] = weight; break; } } } }
2.邻接表
对于稀疏图来说,使用矩阵存储边(弧)的信息会造成空间的浪费,因此我们可以使用链表存储边(弧)的信息,这就是邻接表:
/************************************ * 创建人:movin * 创建时间:2021/7/3 16:34:09 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 邻接表的边表节点 /// </summary> public class AdjacencyListEdgeNode { /// <summary> /// 指向的顶点下标 /// </summary> public int vertexIndex; /// <summary> /// 权重 /// </summary> public int weight; /// <summary> /// 下一个节点的指针 /// </summary> public AdjacencyListEdgeNode next; public AdjacencyListEdgeNode(int vertexIndex, int weight = 0, AdjacencyListEdgeNode next = null) { this.vertexIndex = vertexIndex; this.weight = weight; this.next = next; } } }
/************************************ * 创建人:movin * 创建时间:2021/7/3 16:29:30 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 邻接表顶点 /// </summary> public struct AdjacencyListVertex { /// <summary> /// 顶点中可以存储数据,目前暂定存储int类型,可以根据需要自行修改 /// </summary> public int Content { get; set; } /// <summary> /// 第一个边表节点的指针 /// </summary> public AdjacencyListEdgeNode firstEdge; public AdjacencyListVertex(int content,AdjacencyListEdgeNode firstEdge = null) { Content = content; this.firstEdge = firstEdge; } } }
/************************************ * 创建人:movin * 创建时间:2021/7/3 16:11:36 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 邻接表结构的图 /// </summary> public class AdjacencyListGraph { /// <summary> /// 图的类型,有向图或无向图 /// </summary> public EGraphType type; /// <summary> /// 所有的顶点存储为一个一维数组 /// </summary> public AdjacencyListVertex[] vertices; /// <summary> /// 顶点数量 /// </summary> public int Count { get; private set; } public AdjacencyListGraph(int vertexCount,EGraphType type) { //参数校验不合格抛出参数异常 if (vertexCount <= 0) { throw new ArgumentException(); } vertices = new AdjacencyListVertex[vertexCount]; Count = vertexCount; this.type = type; } /// <summary> /// 添加一条边/弧 /// </summary> /// <param name="startIndex">起始顶点下标</param> /// <param name="endIndex">终点下标</param> /// <param name="weight">权重</param> public void AddEdgeOrArc(int startIndex, int endIndex, int weight = 1) { if (startIndex < 0 || endIndex < 0 || startIndex >= Count || endIndex >= Count || weight == int.MaxValue) { return; } switch (type) { case EGraphType.DirectedGraph: AddNodeFirst(startIndex, endIndex, weight); break; case EGraphType.UndirectedGraph: AddNodeFirst(startIndex, endIndex, weight); AddNodeFirst(endIndex, startIndex, weight); break; } } /// <summary> /// 在指定顶点边表末尾添加一条弧 /// </summary> /// <param name="index">顶点下标</param> /// <param name="nodeContentIndex">边表结点中保存的下标</param> /// <param name="weight">权重</param> private void AddNodeLast(int index,int nodeContentIndex,int weight = 1) { AdjacencyListEdgeNode node = new AdjacencyListEdgeNode(nodeContentIndex, weight, null); AdjacencyListEdgeNode temp = vertices[index].firstEdge; while (temp.next != null) { temp = temp.next; } temp.next = node; } /// <summary> /// 在指定顶点边表头节点添加一条弧 /// </summary> /// <param name="index">顶点下标</param> /// <param name="nodeContentIndex">边表结点中保存的下标</param> /// <param name="weight">权重</param> private void AddNodeFirst(int index,int nodeContentIndex,int weight = 1) { AdjacencyListEdgeNode node = new AdjacencyListEdgeNode(nodeContentIndex, weight, null); node.next = vertices[index].firstEdge; vertices[index].firstEdge = node; } } }
3.十字链表:对于有向图来说,可以将邻接表和逆邻接表结合起来,就形成了十字链表:
/************************************ * 创建人:movin * 创建时间:2021/7/3 17:04:45 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 十字链表的边表节点 /// </summary> public class OrthogonalListEdgeNode { /// <summary> /// 指向的顶点下标 /// </summary> public int vertexIndex; /// <summary> /// 权重 /// </summary> public int weight; /// <summary> /// 下一个入节点的指针 /// </summary> public OrthogonalListEdgeNode headNext; /// <summary> /// 下一个出节点的指针 /// </summary> public OrthogonalListEdgeNode tailNext; public OrthogonalListEdgeNode(int vertexIndex, int weight = 0, OrthogonalListEdgeNode headNext = null, OrthogonalListEdgeNode tailNext = null) { this.vertexIndex = vertexIndex; this.weight = weight; this.headNext = headNext; this.tailNext = tailNext; } } }
/************************************ * 创建人:movin * 创建时间:2021/7/3 17:03:16 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 十字链表顶点 /// </summary> public class OrthogonalListVertex { /// <summary> /// 顶点中可以存储数据,目前暂定存储int类型,可以根据需要自行修改 /// </summary> public int Content { get; set; } /// <summary> /// 第一个边表节点的指针 /// </summary> public OrthogonalListEdgeNode firstIn; /// <summary> /// 第一个边表节点的指针 /// </summary> public OrthogonalListEdgeNode firstOut; public OrthogonalListVertex(int content, OrthogonalListEdgeNode firstIn = null, OrthogonalListEdgeNode firstOut = null) { Content = content; this.firstIn = firstIn; this.firstOut = firstOut; } } }
/************************************ * 创建人:movin * 创建时间:2021/7/3 17:14:46 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 十字链表结构的图 /// 有向图 /// </summary> public class OrthogonalListGraph { /// <summary> /// 所有的顶点存储为一个一维数组 /// </summary> public OrthogonalListVertex[] vertices; /// <summary> /// 顶点数量 /// </summary> public int Count { get; private set; } public OrthogonalListGraph(int vertexCount) { //参数校验不合格抛出参数异常 if (vertexCount <= 0) { throw new ArgumentException(); } vertices = new OrthogonalListVertex[vertexCount]; Count = vertexCount; } } }
4.邻接多重表:对于无向图的邻接表,也可以采用十字链表类似的方式优化存储结构,在边表节点中同时记录边的关联顶点下标及这条边从属于当前关联顶点时的下一条边节点的指针:
/************************************ * 创建人:movin * 创建时间:2021/7/3 19:36:26 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 邻接多重表边表节点 /// </summary> public class AdjacencyMultiListEdgeNode { /// <summary> /// 关联顶点i下标 /// </summary> public int iVertexIndex; /// <summary> /// 指向i的下一个节点的指针 /// </summary> public AdjacencyMultiListEdgeNode iLinkNext; /// <summary> /// 关联顶点j下标 /// </summary> public int jVertexIndex; /// <summary> /// 指向j的下一个节点的指针 /// </summary> public AdjacencyMultiListEdgeNode jLinkNext; /// <summary> /// 权重 /// </summary> public int weight; public AdjacencyMultiListEdgeNode(int iVertexIndex,int jVertexIndex,AdjacencyMultiListEdgeNode iLinkNext = null,AdjacencyMultiListEdgeNode jLinkNext = null,int weight = 0) { this.iVertexIndex = iVertexIndex; this.weight = weight; this.iLinkNext = iLinkNext; this.jVertexIndex = jVertexIndex; this.jLinkNext = jLinkNext; } /// <summary> /// 获取下一个边表节点 /// </summary> /// <param name="index">获取下一个边表节点的顶点下标</param> /// <returns></returns> public AdjacencyMultiListEdgeNode GetNextEdgeNode(int index) { if(index == iVertexIndex) { return iLinkNext; } if(index == jVertexIndex) { return jLinkNext; } return null; } } }
/************************************ * 创建人:movin * 创建时间:2021/7/3 19:33:52 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 邻接多重表顶点 /// </summary> public class AdjacencyMultiListVertex { /// <summary> /// 顶点中可以存储数据,目前暂定存储int类型,可以根据需要自行修改 /// </summary> public int Content { get; set; } /// <summary> /// 第一个边表节点的指针 /// </summary> public AdjacencyMultiListEdgeNode firstEdge; public AdjacencyMultiListVertex(int content, AdjacencyMultiListEdgeNode firstEdge = null) { Content = content; this.firstEdge = firstEdge; } } }
/************************************ * 创建人:movin * 创建时间:2021/7/3 19:31:48 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 邻接多重表结构的图 /// 无向图 /// </summary> public class AdjacencyMultiListGraph { /// <summary> /// 所有的顶点存储为一个一维数组 /// </summary> public AdjacencyMultiListVertex[] vertices; /// <summary> /// 顶点数量 /// </summary> public int Count { get; private set; } public AdjacencyMultiListGraph(int vertexCount) { //参数校验不合格抛出参数异常 if (vertexCount <= 0) { throw new ArgumentException(); } vertices = new AdjacencyMultiListVertex[vertexCount]; Count = vertexCount; } } }
5.边集数组:如果仅是存储图结构,而不考虑遍历或者计算顶点的度时,就可以考虑使用两个数组分别存储边和顶点的方式将图存储起来,这就是边集数组:
/************************************ * 创建人:movin * 创建时间:2021/7/3 19:52:23 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 边集数组的边表节点 /// </summary> public class EdgesetArrayEdgeNode { /// <summary> /// 尾节点下标 /// </summary> public int tailIndex; /// <summary> /// 头节点下标 /// </summary> public int headIndex; /// <summary> /// 权重 /// </summary> public int weight; public EdgesetArrayEdgeNode(int tailIndex, int headIndex, int weight = 0) { this.tailIndex = tailIndex; this.headIndex = headIndex; this.weight = weight; } } }
/************************************ * 创建人:movin * 创建时间:2021/7/3 19:52:55 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 边集数组顶点 /// </summary> public class EdgesetArrayVertex { /// <summary> /// 顶点中可以存储数据,目前暂定存储int类型,可以根据需要自行修改 /// </summary> public int Content { get; set; } public EdgesetArrayVertex(int content) { Content = content; } } }
/************************************ * 创建人:movin * 创建时间:2021/7/3 19:50:50 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 边集数组结构的图 /// 无向图 /// </summary> public class EdgesetArrayGraph { /// <summary> /// 所有的顶点存储为一个一维数组 /// </summary> public EdgesetArrayVertex[] vertices; /// <summary> /// 所有边集节点存储为一个数组 /// </summary> public EdgesetArrayEdgeNode[] edgeNodes; /// <summary> /// 顶点数量 /// </summary> public int Count { get; private set; } public EdgesetArrayGraph(int vertexCount) { //参数校验不合格抛出参数异常 if (vertexCount <= 0) { throw new ArgumentException(); } vertices = new EdgesetArrayVertex[vertexCount]; } } }
三.总结
对于图的存储(假设图的顶点数为n,边/弧数为e),一般都需要将图分为顶点和边(弧)两部分分别存储,顶点和边(弧)都需要首先抽象为类或结构体.不论采用哪种存储方式,顶点都是使用一个数组存储,区别在于顶点中是否需要存储边表的第一个节点的下标.边弧的存储可以采用一维数组的形式存储(边集数组),这样存储查找或者计算顶点的度的时候都需要对数组进行遍历,时间复杂度为O(e).可以采用二维数组的形式存储边/弧(邻接矩阵),这样在查找边/弧或者计算节点的度的时候时间复杂度为O(n),显然对于稀疏图邻接矩阵的方式浪费空间也浪费时间.可以使用链式结构存储(邻接表十字链表邻接多重表):无论哪种链式结构存储的边/弧,都是使用链表存储某个顶点的所有关联边/弧(这个顶点的边表),一个边表结点对应一条边/弧, 在顶点数据结构中存储这个链表的头结点指针,区别只是连接的形式不同.邻接表是最简单的链式结构存储边/弧的形式,但是对于有向图来说,一个邻接表只能存储入度的弧或者出度的弧,无法同时存储,所以将邻接表进行优化后就形成了十字链表,十字链表在边表结点中同时存储入度和出度的下一个结点的指针,十字链表是适用于有向图的存储的优化,但是同样的结构也可以用来存储无向图,只需要对顶点的数据结构稍加优化就形成了邻接多重表,十字链表的顶点中需要同时存储入度和出度的第一个弧结点的指针,而邻接多重表只需要在顶点数据结构中存储第一条边的结点指针即可.