个人随笔
目录
图的存储结构之邻接矩阵和邻接表(Java实现)
2020-04-21 23:51:07

一、图的存储结构讨论

对于线性表来说,是一对一的关系,所以用数组或者链表均可以简单存放。
对于树结构是一对多的关系,所以我们要将数组和链表的特性结合在一起才能更好的存放。
对于图来说,是多对多的情况,另外图上的任意一个顶点都可以被看做是第一个顶点,任一顶点的邻接点之间也不存在次序关系

如下图:实际是一个图结构,只不过顶点位置不同。

由于图的结构复杂,任意两个顶点之间都可能存在联系,因此无法以数据元素在内存中的物理位置来表示元素之间的关系,也就是说,图不可能用简单的顺序存储结构来表示。内存物理位置是线性的,图的元素关系是平面的。

虽然我们可以向树结构中说到的那样使用多重链表,但是我们需要先确定最大的度,然后按照这个度最大的顶点设计结点结构,若是每个顶点的度数相差较大,就会造成大量的存储单元浪费。

二、图的存储结构(1)—-邻接矩阵

考虑到图是由顶点和边(弧)两部分组成的,合在一起是比较困难的,那就很自然的考虑到分为两个结构来分别存储

顶点因为不区分大小,主次,所以用一个一维数组来存储时不错的选择。

而边或弧由于是顶点与顶点之间的关系,所以我们最好使用二维数组来存储,图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。

1、无向图

  1. 其中1表示两个顶点之间存在关系,0表示无关,不存在顶点间的边。

对称矩阵:就是n阶矩阵满足a[i][j]=a[j][i](0<=i,j<=n)。即从矩阵的左上角到右下角的主对角线为轴,右上角的源与左下角相对应的元都是相等的。

根据这个矩阵,我们可以很容易的知道图中的信息。

  • 1.我们要判定容易两顶点是否有边无边就非常容易了。
  • 2.我们要知道某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行(或第i列)的元素之和。比如上图顶点v1的度就是1+0+1+0=2
  • 3.求顶点vi的所有邻接点就是将矩阵第i行元素扫描一遍,arc[i][j]为1就是邻接点

2、有向图

对于上面的无向图,二维对称矩阵似乎浪费了一半的空间。若是存放有向图,会更大程度利用起来空间

其中顶点数组是一样的和无向图,弧数组也是一个矩阵,但因为是有向图,所以这个矩阵并不对称:例如v1->v0有弧,arc[1][0]=1,而v0到v1没有弧,所以arc[0][1]=0。

另外有向图,要考虑入度和出度,顶点v1的入度为1,正好是第v1列的各数之和,顶点v1的出度为2,正好是第v2行的各数之和

3、网

每条边上带有权的有向图就叫做网

这里‘∞’表示一个计算机允许的,大于所有边上权值的值

4、Java实现无向图、有向图、网的邻接矩阵创建

  1. public class G2 {
  2. public static void main(String[] args) {
  3. //1、无向图
  4. //定点数组vertex
  5. String[] v1 = new String[]{"V0","V1","V2","V3"};
  6. //矩阵表示边的关系edge 其中1表示两个顶点之间存在关系,0表示无关,不存在顶点间的边。
  7. int[][] e1 = new int[][] {
  8. {0,1,1,1},
  9. {1,0,1,0},
  10. {1,1,0,1},
  11. {1,0,1,0}
  12. };
  13. System.out.println("输出无向图:");
  14. for (int i = 0; i < v1.length; i++) {
  15. System.out.print(v1[i]+"\t");
  16. }
  17. System.out.println();
  18. for (int i = 0; i < v1.length; i++) {
  19. for (int j = 0; j < v1.length; j++) {
  20. System.out.print(e1[i][j]+"\t");
  21. }
  22. System.out.println();
  23. }
  24. System.out.println("--------------------");
  25. //2、有向图
  26. String[] v2 =new String[]{"V0","V1","V2","V3"};
  27. int[][] e2 = new int[][] {
  28. {0,0,0,1},
  29. {1,0,1,0},//V1的出度为2
  30. {1,1,0,0},//V2的出度为2
  31. {0,0,0,0}
  32. //V1的入度为1
  33. };
  34. System.out.println("输出有向图:");
  35. for (int i = 0; i < v2.length; i++) {
  36. System.out.print(v2[i]+"\t");
  37. }
  38. System.out.println();
  39. for (int i = 0; i < v2.length; i++) {
  40. for (int j = 0; j < v2.length; j++) {
  41. System.out.print(e2[i][j]+"\t");
  42. }
  43. System.out.println();
  44. }
  45. System.out.println("--------------------");
  46. //3、网,每条边上带有权的图就叫做网
  47. String[] v3 =new String[]{"V0","V1","V2","V3","V4"};
  48. //m表示不可达
  49. int m=65535;
  50. int[][] e3 = new int[][] {
  51. {0,m,m,m,6},
  52. {9,0,3,m,m},
  53. {2,m,0,5,m},
  54. {m,m,m,0,1},
  55. {m,m,m,m,0}
  56. };
  57. System.out.println("输出网:");
  58. for (int i = 0; i < v3.length; i++) {
  59. System.out.print(v3[i]+"\t");
  60. }
  61. System.out.println();
  62. for (int i = 0; i < v3.length; i++) {
  63. for (int j = 0; j < v3.length; j++) {
  64. System.out.print(e3[i][j]+"\t");
  65. }
  66. System.out.println();
  67. }
  68. }
  69. }

代码中的图跟上面说的图一一对应,运行结果如下:

  1. 输出无向图:
  2. V0 V1 V2 V3
  3. 0 1 1 1
  4. 1 0 1 0
  5. 1 1 0 1
  6. 1 0 1 0
  7. --------------------
  8. 输出有向图:
  9. V0 V1 V2 V3
  10. 0 0 0 1
  11. 1 0 1 0
  12. 1 1 0 0
  13. 0 0 0 0
  14. --------------------
  15. 输出网:
  16. V0 V1 V2 V3 V4
  17. 0 65535 65535 65535 6
  18. 9 0 3 65535 65535
  19. 2 65535 0 5 65535
  20. 65535 65535 65535 0 1
  21. 65535 65535 65535 65535 0

三、图的存储结构(2)—-邻接表

上面的邻接矩阵是一种不错的图存储结构,便于理解,但是当我们的边数相对于顶点较少的图,这种结构是存在对存储空间的极大的浪费。

我们可以考虑使用链表来动态分配空间,避免使用数组一次分配而造成空间浪费问题。

同树中,孩子表示法,我们将结点存放入数组,对结点的孩子进行链式存储,不管有多少孩子,都不会存在空间浪费。这种思路也适用于图的存储。我们把这种数组与链表相结合的存储方法称为邻接表。

邻接表处理办法

  • 1.图中顶点用一个一维数组。当然,顶点也可以用单链表来存储,不过数组可以较容易的读取顶点信息,更加方便。另外,对于顶点数组中,每个数据元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息

  • 2.图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表

    1、无向图


    这样的结构,对于我们要获得图的相关信息也是很方便。比如:
    我们要获取某个顶点的度,就要去查找这个顶点的边表中结点的个数。
    我们要判断vi到vj是否存在边,只需要测试vi的边表链表中是否存在结点vj的下标j即可。
    我们若是要获取顶点的所有邻接点,就是对此顶点的边表进行遍历。

2、有向图

有向图由于有方向,我们是以顶点为弧尾来存储边表的,这样很容易就可以得到每个顶点的出度。但是由于有时也需要确定顶点的入度或以顶点作为弧头的弧,我们可以建立一个有向图的逆邻接表,即对每个顶点vi都建立一个链接为vi为弧头的表。

3、带权值的网

们可以在边表结点定义中再增加一个weight数据域,存储权值信息即可。

4、Java实现无向图、有向图、网的邻接链表创建

首先我们看一下《算法导论》中关于图的邻接表的定义:图G=(V,E)的邻接表表示有一个包含 |V| 个列表的数组Adj所组成,其中每个列表对应于V中的一个顶点,对于每一个u∈V,邻接表Adj[u]包含所有满足条件(u,v)∈E的顶点v,亦即,Adj[u]包含图G中所有和顶点u相邻的顶点。(或者他也可能指向这些顶点的指针),每个邻接表中的顶点一般以任意的顺序存储。(注意一下这句话!)

这里的实现方法有多重多样,下面是按我的思路实现的代码示例,一般都会有两个对象,一个是顶点,如下:

  1. //定义定点
  2. class Vertex{
  3. String value;//顶点的值
  4. Edge firstEdge;//第一条边
  5. public Vertex(String value, Edge firstEdge) {
  6. super();
  7. this.value = value;
  8. this.firstEdge = firstEdge;
  9. }
  10. }

这里我们可已将顶点放在一个数组中,然后每个顶点都从第一条边衍生出去。当然可以按具体需求多加属性,比如顶点的出度也可以当做一个属性什么的,我这里就不加了!

然后定义边的对象:

  1. class Edge{
  2. String value;//该边对应的顶点
  3. int weight;//权重,无向图,有向图权重为0
  4. Edge next;//下一个边
  5. /**
  6. * 构建一条边 weight未0表示无向图或者有向图 不为0表示网
  7. * @param value
  8. * @param weight
  9. * @param next
  10. */
  11. public Edge(String value, int weight, Edge next) {
  12. super();
  13. this.value = value;
  14. this.weight = weight;
  15. this.next = next;
  16. }
  17. }

我这里因为要举无向图,有向图,网的例子,所以这个边就直接有权重,但是无向图,有向图的权重为0。

然后下面是完整的代码,跟上面三个示例图对应:

  1. /**
  2. * 邻接链表
  3. * @author suibibk.com
  4. */
  5. public class Graph {
  6. public int num;//顶点个数
  7. //顶点
  8. List<Vertex> vertexs;
  9. public Graph(int num) {
  10. this.num = num;
  11. //初始化图的大小
  12. vertexs = new ArrayList<Vertex>(num);
  13. }
  14. //插入顶点
  15. public void addVertex(String value) {
  16. vertexs.add(new Vertex(value, null));
  17. }
  18. //获取顶点
  19. public Vertex getVertex(String value) {
  20. for (int i = 0; i < num; i++) {
  21. if(vertexs.get(i).value.equals(value)) {
  22. return vertexs.get(i);
  23. }
  24. }
  25. return null;
  26. }
  27. /**
  28. * 添加无向图的边
  29. * @param vertex1 第一个顶点
  30. * @param vertex2 第二个顶点
  31. */
  32. public void addUndigraphEdge(String vertex1,String vertex2) {
  33. //因为是无向图,所以就直接加入
  34. addEdgeByVertex(vertex1,vertex2,0);
  35. addEdgeByVertex(vertex2,vertex1,0);
  36. }
  37. /**
  38. * 添加有向图的边
  39. * @param start 开始节点
  40. * @param end 结束节点
  41. */
  42. public void addDigraphEdge(String start,String end) {
  43. //因为是有向图,所以只有一条边
  44. addEdgeByVertex(start,end,0);
  45. }
  46. /**
  47. * 添加网的边
  48. * @param start 开始节点
  49. * @param end 结束节点
  50. * @param weight 权重
  51. */
  52. public void addWebEdge(String start,String end,int weight) {
  53. //网就是有向图有权重
  54. addEdgeByVertex(start,end,weight);
  55. }
  56. /**
  57. * 添加一条由start-->end的边
  58. * @param start
  59. * @param end
  60. * @param weight 权重未0表示无向图或者有向图,部位0表示网
  61. */
  62. private void addEdgeByVertex(String start,String end,int weight) {
  63. //1、找到第一个顶点
  64. Vertex v1 = this.getVertex(start);
  65. //2、检查这条边是否已经存在,已经存在就直接报错
  66. Edge firstEdge = v1.firstEdge;
  67. while(firstEdge!=null) {
  68. //获取这个边
  69. String value = firstEdge.value;
  70. if(end.equals(value)) {
  71. System.out.println("边"+start+"-->"+end+"已经加入,不可以再加入");
  72. return;
  73. }else {
  74. Edge next = firstEdge.next;
  75. firstEdge=next;
  76. }
  77. }
  78. //到这里就可以加入这一条边了
  79. firstEdge = v1.firstEdge;
  80. //到这里就可以加入这条边
  81. if(firstEdge==null) {
  82. firstEdge = new Edge(end,weight, null);
  83. v1.firstEdge=firstEdge;
  84. }else {
  85. //当前节点变为第一个节点
  86. Edge nowEdge = new Edge(end,weight, null);
  87. v1.firstEdge=nowEdge;
  88. nowEdge.next=firstEdge;
  89. }
  90. }
  91. /**
  92. * 输出图
  93. */
  94. public void print() {
  95. for (int i = 0; i <num; i++) {
  96. Vertex vertex = vertexs.get(i);
  97. System.out.print(vertex.value+"-->");
  98. Edge edge = vertex.firstEdge;
  99. while(edge!=null) {
  100. System.out.print(edge.value+"["+edge.weight+"]");
  101. Edge next = edge.next;
  102. edge=next;
  103. if(next!=null) {
  104. System.out.print("-->");
  105. }else {
  106. System.out.print("-->∧");
  107. }
  108. }
  109. System.out.println();
  110. }
  111. }
  112. public static void main(String[] args) {
  113. //测试无向图
  114. Graph g = new Graph(4);
  115. g.addVertex("V0");
  116. g.addVertex("V1");
  117. g.addVertex("V2");
  118. g.addVertex("V3");
  119. //插入边:无向图
  120. g.addUndigraphEdge("V0", "V1");
  121. g.addUndigraphEdge("V1", "V2");
  122. g.addUndigraphEdge("V2", "V3");
  123. g.addUndigraphEdge("V3", "V0");
  124. g.addUndigraphEdge("V2", "V0");
  125. System.out.println("输出无向图:");
  126. g.print();
  127. System.out.println("------------");
  128. Graph g1 = new Graph(4);
  129. g1.addVertex("V0");
  130. g1.addVertex("V1");
  131. g1.addVertex("V2");
  132. g1.addVertex("V3");
  133. //插入边:有向图
  134. g1.addDigraphEdge("V0", "V3");
  135. g1.addDigraphEdge("V1", "V0");
  136. g1.addDigraphEdge("V1", "V2");
  137. g1.addDigraphEdge("V2", "V0");
  138. g1.addDigraphEdge("V2", "V1");
  139. System.out.println("输出有向图:");
  140. g1.print();
  141. System.out.println("----------");
  142. Graph g2 = new Graph(4);
  143. g2.addVertex("V0");
  144. g2.addVertex("V1");
  145. g2.addVertex("V2");
  146. g2.addVertex("V3");
  147. //插入边:有向图
  148. g2.addWebEdge("V0", "V4",6);
  149. g2.addWebEdge("V1", "V0",9);
  150. g2.addWebEdge("V1", "V2",3);
  151. g2.addWebEdge("V2", "V0",2);
  152. g2.addWebEdge("V2", "V3",5);
  153. g2.addWebEdge("V3", "V4",1);
  154. System.out.println("输出带权值的网:");
  155. g2.print();
  156. }
  157. }
  158. //定义定点
  159. class Vertex{
  160. String value;//顶点的值
  161. Edge firstEdge;//第一条边
  162. public Vertex(String value, Edge firstEdge) {
  163. super();
  164. this.value = value;
  165. this.firstEdge = firstEdge;
  166. }
  167. }
  168. class Edge{
  169. String value;//该边对应的顶点
  170. int weight;//权重,无向图,有向图权重为0
  171. Edge next;//下一个边
  172. /**
  173. * 构建一条边 weight未0表示无向图或者有向图 不为0表示网
  174. * @param value
  175. * @param weight
  176. * @param next
  177. */
  178. public Edge(String value, int weight, Edge next) {
  179. super();
  180. this.value = value;
  181. this.weight = weight;
  182. this.next = next;
  183. }
  184. }

Copy即可运行,结果如下:

  1. 输出无向图:
  2. V0-->V2[0]-->V3[0]-->V1[0]-->∧
  3. V1-->V2[0]-->V0[0]-->∧
  4. V2-->V0[0]-->V3[0]-->V1[0]-->∧
  5. V3-->V0[0]-->V2[0]-->∧
  6. ------------
  7. 输出有向图:
  8. V0-->V3[0]-->∧
  9. V1-->V2[0]-->V0[0]-->∧
  10. V2-->V1[0]-->V0[0]-->∧
  11. V3-->
  12. ----------
  13. 输出带权值的网:
  14. V0-->V4[6]-->∧
  15. V1-->V2[3]-->V0[9]-->∧
  16. V2-->V3[5]-->V0[2]-->∧
  17. V3-->V4[1]-->∧

上面把添加变的操作抽取成了公共的方法!


完成!

 1968

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2