图的存储结构与实现总结

目录
图的存储结构 图的存储结构主要分两种,一种是邻接矩阵,一种是邻接表。
邻接矩阵 【图的存储结构与实现总结】图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。
设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
图的存储结构与实现总结
文章图片

看一个实例,下图左就是一个无向图。
图的存储结构与实现总结
文章图片

从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。
从这个矩阵中,很容易知道图中的信息。
(1)要判断任意两顶点是否有边无边就很容易了;
(2)要知道某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和;
(3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点;
而有向图讲究入度和出度,顶点vi的入度为1,正好是第i列各数之和。顶点vi的出度为2,即第i行的各数之和。
若图G是网图,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
图的存储结构与实现总结
文章图片

邻接表 邻接矩阵是不错的一种图存储结构,但是,对于边数相对顶点较少的图,这种结构存在对存储空间的极大浪费。因此,找到一种数组与链表相结合的存储方法称为邻接表。
邻接表的处理方法是这样的:
(1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。
(2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。
例如,下图就是一个无向图的邻接表的结构。
图的存储结构与实现总结
文章图片

从图中可以看出,顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。
对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可。如下图所示。
图的存储结构与实现总结
文章图片

两者区别 对于一个具有n个顶点e条边的无向图
它的邻接表表示有n个顶点表结点2e个边表结点
对于一个具有n个顶点e条边的有向图
它的邻接表表示有n个顶点表结点e个边表结点
**如果图中边的数目远远小于n^2称作稀疏图,这是用邻接表表示比用邻接矩阵表示节省空间;
如果图中边的数目接近于n^2,对于无向图接近于n*(n-1)称作稠密图,考虑到邻接表中要附加链域,采用邻接矩阵表示法为宜。**
图的java实现 这个实现是基于邻接矩阵的
顶点 使用label作为顶点的标识
edgelist作为linkedlist,存储以这个顶点为起点的边
后面3个属性是为了应对其他操作(比如深度遍历等),特意保留的变量

package datastructure.graph.adjacencymatrixgraph; import java.util.Iterator; import java.util.LinkedList; import java.util.List; /**邻接矩阵的顶点类 * @author xusy * * @param */ public class Vertex {/** * 能够标识这个定点的属性,可以用不同类型来标识顶点如String,Integer.... */ private T label; /** * 这个定点对应的边
* 如果为有向图,则代表以这个定点为起点的边 */ private List edgeList; /** * 表示这个顶点是否已被访问,在bfs和dfs中会被使用到 */ private boolean visited; /** * 该顶点的前驱节点
* 在求图中某两个顶点之间的最短路径时,在从起始顶点遍历过程中,需要记录下遍历到某个顶点时的前驱顶点 */ private Vertex previousVertex; /** * 这个定点的权值(注意不是边的权值) */ private double cost; /**创建顶点 * @param label这个顶点的标识 * @param cost这个顶点的权值 */ public Vertex(T label,double cost){ this.label=label; //用链表存储边 edgeList=new LinkedList<>(); visited=false; previousVertex=null; this.cost=cost; }//下面与顶点的标识相关/**返回顶点的标识 * @return */ public T getLabel() { return label; }/** * 根据顶点的标识确定是否是同一个顶点 */ @Override public boolean equals(Object otherVertex) { boolean result; //如果otherVertex为空或者类不同,直接返回false if(otherVertex==null||getClass()!=otherVertex.getClass()){ return false; } Vertex other=(Vertex)otherVertex; //根据label确定是否是同一个顶点 result=label.equals(other.getLabel()); return result; }//下面与顶点的边相关/** 返回边的迭代器 * @return */ public Iterator getEdgeIterator(){ return edgeList.iterator(); }/**返回是否有以这个顶点为出发点的边数 * @return */ public int getEdgeCount(){ return edgeList.size(); }/**将这个顶点与endVertex连接,边的权值为weight * @param endVertex * @param weight * @return 如果顶点已经与endVertex连接,那么将会更新权值,返回false
* 如果顶点没有与endVertex相连,则互相连接,返回true */ public boolean connect(Vertex endVertex,double weight){ Iterator iterator=getEdgeIterator(); Edge edge=null; Vertex vertex=null; while(iterator.hasNext()){ edge=iterator.next(); vertex=edge.getEndVertex(); if(vertex.equals(endVertex)){ //如果顶点已经与endVertex连接,那么将会更新权值,返回false edge.setWeight(weight); return false; } } //如果顶点没有与endVertex相连,则互相连接,返回true edge=new Edge(this,endVertex, weight); edgeList.add(edge); return true; }/**将这个顶点与endVertex连接的边删除 * @param endVertex * @return如果顶点已经与endVertex连接,那么将会删除这条边,返回true
* 如果顶点没有与endVertex连接,则啥都不做,返回false */ public boolean disconnect(Vertex endVertex){ Iterator iterator=getEdgeIterator(); Edge edge=null; Vertex vertex=null; while(iterator.hasNext()){ edge=iterator.next(); vertex=edge.getEndVertex(); if(vertex.equals(endVertex)){ //如果顶点已经与endVertex连接,那么将会删除这条边,返回true //edgeList.remove(edge); iterator.remove(); return true; } } //如果顶点没有与endVertex连接,则啥都不做,返回false return false; }/**返回是否有以这个顶点为出发点,以endVertex为结束点的边 * @return 如果有,返回那条边
* 如果没有,返回null */ public Edge hasNeighbourVertex(Vertex endVertex){ Iterator iterator=getEdgeIterator(); Edge edge=null; Vertex vertex=null; while(iterator.hasNext()){ edge=iterator.next(); vertex=edge.getEndVertex(); if(vertex.equals(endVertex)){ //如果顶点已经与endVertex连接,那么将返回这个边 return edge; } } //没有则返回null return null; }//下面是与顶点是否被访问相关/**返回顶点是否被访问 * @return */ public boolean isVisited() { return visited; }/** * 访问这个顶点 */ public void visit(){ visited=true; }/** * 不访问这个顶点,或者说是清除访问状态 */ public void unVisit(){ visited=false; }/**获得以这个顶点为出发点,相邻的第一个没有被访问的顶点 * @return 如果没有,返回null
* 如果有,返回对应的顶点 */ public Vertex getUnvisitedVertex(){ Iterator iterator=getEdgeIterator(); Edge edge=null; Vertex vertex=null; while(iterator.hasNext()){ edge=iterator.next(); vertex=edge.getEndVertex(); if(vertex.isVisited()==false){ return vertex; } } //没有则返回null return null; }//下面与前驱节点相关/**返回顶点的前驱节点 * @return */ public Vertex getPreviousVertex() { return previousVertex; }/**设置顶点的前驱节点 * @param previousVertex */ public void setPreviousVertex(Vertex previousVertex) { this.previousVertex = previousVertex; }//下面与顶点的权值相关/**返回顶点的权值 * @return */ public double getCost() { return cost; }/** 设置顶点的权值 * @param cost */ public void setCost(double cost) { this.cost = cost; }}


-
beginVertex是开始点
endVertex是结束点
weight为边的权值
package datastructure.graph.adjacencymatrixgraph; /** 连接两个顶点的边 * @author xusy * */ public class Edge {/** * beginVertex是边的起始顶点
* 普通情况是不用显示地存储beginVertex,但是生成最小生成树时需要 */ private Vertex beginVertex; /** * 由于Edge是存储在Vertex中的,所以包含这个边的vertex是开始点 * endVertex是结束点 */ private Vertex endVertex; /** * 边的权值 */ private double weight; /**创建边 * @param beginVertex 边的开始点 * @param endVertex 边的结束点 * @param weight边的权值 */ public Edge(Vertex beginVertex,Vertex endVertex, double weight) { this.beginVertex = beginVertex; this.endVertex = endVertex; this.weight = weight; }/**返回边的开始点 * @return */ public Vertex getBeginVertex() { return beginVertex; }/** 返回边的结束点 * @return */ public Vertex getEndVertex() { return endVertex; }/**返回边的权值 * @return */ public double getWeight() { return weight; }/**设置边的权值 * @param weight */ public void setWeight(double weight) { this.weight = weight; }}


-
isDirect用来区分有向图,区别就是加入边的时候,无向图会加入两条,有向图只会加入一条
如果不需要边和顶点的权值,加入时,设置为0即可
package datastructure.graph.adjacencymatrixgraph; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; /**邻接矩阵的图类 * @author xusy * * @param */ public class Graph {/** * 用来存储顶点 * T做为标识,vertext作为实际的顶点 */ private Map> vertexMap; /** * 图中边的数目
* 顶点的数目可以用vertexMap.size() */ private int edgeCount; /** * 图是否为有向图
* 如果是有向图,则为true */ boolean isDirect; /**图的构造函数 * @param isDirect图是否为有向图
* 如果是有向图,则为true */ public Graph(boolean isDirect){ vertexMap=new LinkedHashMap<>(); edgeCount=0; this.isDirect=isDirect; }//下面与图的顶点相关/**返回图中的顶点个数 * @return */ public int getVertexCount(){ return vertexMap.size(); }/** 返回图的顶点的迭代器 * @return */ public Iterator> getVertexIterator(){ return vertexMap.values().iterator(); }/**在图中插入节点,节点的标识为label,节点的权值为cost * @param label * @param cost如果不需要节点的权值,则设0即可 * @return 如果图中不存在该节点,则插入,返回true
* 如果图中已经存在该节点,则更新权值,返回false */ public boolean addVertex(T label,double cost){ Vertex vertex=vertexMap.get(label); if(vertex!=null){ //如果图中已经存在该节点,则更新权值,返回false vertex.setCost(cost); return false; } //如果图中不存在该节点,则插入,返回true vertex=new Vertex(label, cost); vertexMap.put(label, vertex); return true; }//下面与图的边相关/** 返回图中所有的边的个数
* 如果为有向图,则是所有的有向边的个数
* 如果为无向图,则视一条边为两条相反的有向边,相当于返回无向边的个数*2 * @return */ public int getEdgeCount(){ Iterator> iterator=getVertexIterator(); int count=0; while(iterator.hasNext()){ Vertex vertex=iterator.next(); count=count+vertex.getEdgeCount(); } return count; }/** 返回图中标识为label的顶点作为出发点的边的个数 * @param label * @return 如果为有向图,则返回标识为label的顶点作为出发点的边的个数 * 如果为无向图,则返回标识为label的顶点相连接的边的个数 * 如果图中没有这个顶点,返回-1 */ public int getEdgeCount(T label){ Vertex vertex=vertexMap.get(label); if(vertex==null){ //如果图中没有这个顶点,返回-1 return -1; } //返回途中标识为label的顶点作为出发点的边的个数 return vertex.getEdgeCount(); }/** 返回图中标识为label的顶点作为出发点的边的迭代器 * @param label * @return 如果没有这个顶点,返回null */ public Iterator getEdgeIterator(T label){ Vertex vertex=vertexMap.get(label); if(vertex==null){ //如果图中没有这个顶点,返回null return null; } return vertex.getEdgeIterator(); }/**在图中加入一条边,如果isDirect为true,则为有向图,则
* 建立一条以begin作为标识的节点开始的边,以end作为标识的节点结束,边的权值为weight
* 如果isDirect为false,则为无向图,则
* 建立两条边,一条以begin开始,到end ,一条以end开始,到begin * @param begin * @param end * @param weight 如果不需要边的权值,可以设为0 * @return 如果没有对应的边,则加入对应的边,返回true
* 如果有对应的边,则更新weight,返回false * 如果没有以begin或者end标识的顶点,则直接返回false */ public boolean addEdge(T begin,T end,double weight){ Vertex beginVertex=vertexMap.get(begin); Vertex endVertex=vertexMap.get(end); if(beginVertex==null||endVertex==null){ //如果没有以begin或者end标识的顶点,则直接返回false return false; } //有向图和无向图都要建立begin到end的边 //如果顶点已经与endVertex连接,那么将会更新权值,result=false //如果顶点没有与endVertex相连,则互相连接,result=true boolean result=beginVertex.connect(endVertex, weight); if(result){ edgeCount++; } if(!isDirect){ //如果不是有向图,则建立两条边,一条以end开始,到begin endVertex.connect(beginVertex, weight); if(result){ edgeCount++; } } return result; }/**在图中删除一条边,如果isDirect为true,则为有向图,则
* 删除一条以begin作为标识的节点开始的边,以end作为标识的节点结束
* 如果isDirect为false,则为无向图,则
* 删除两条边,一条以begin开始,到end ,一条以end开始,到begin * @param begin * @param end * @return 如果有对应的边,则删除对应的边,返回true
* 如果没有有对应的边,则直接返回false * 如果没有以begin或者end标识的顶点,则直接返回false */ public boolean removeEdge(T begin,T end){ Vertex beginVertex=vertexMap.get(begin); Vertex endVertex=vertexMap.get(end); if(beginVertex==null||endVertex==null){ //如果没有以begin或者end标识的顶点,则直接返回false return false; } //有向图和无向图都要删除begin到end的边 //如果顶点已经与endVertex连接,那么将会删除这条边,返回true //如果顶点没有与endVertex连接,则啥都不做,返回false boolean result=beginVertex.disconnect(endVertex); if(result){ edgeCount--; } if(!isDirect){ //如果不是有向图,则删除两条边,一条以end开始,到begin endVertex.disconnect(beginVertex); if(result){ edgeCount--; } } return result; }//下面与打印相关/** * 打印图的概况,所有顶点,所有边 */ public void printGraph(){ Iterator> iteratorVertex=getVertexIterator(); Iterator iteratorEdge; Vertex vertex; Edge edge; T label; System.out.println("图是否为有向图:"+isDirect+",图的顶点个数:"+getVertexCount()+",图的总边个数:"+getEdgeCount()); while(iteratorVertex.hasNext()){ vertex=iteratorVertex.next(); label=vertex.getLabel(); iteratorEdge=vertex.getEdgeIterator(); System.out.println("顶点:"+label+",以这个顶点为出发点的边的个数:"+getEdgeCount(label)+",该顶点的权值为:"+vertex.getCost()); while(iteratorEdge.hasNext()){ edge=iteratorEdge.next(); System.out.print("边:从 "+label+" 到 "+edge.getEndVertex().getLabel()+" ,权值:"+edge.getWeight()+""); } System.out.println(); } System.out.println(); }}

测试
package datastructure.graph.adjacencymatrixgraph; public class Main {public static void main(String[] args) { Graph graph=new Graph<>(false); graph.addVertex("first", 0); graph.addVertex("second", 0); graph.addVertex("third", 1); graph.addEdge("first", "second", 1); graph.addEdge("first", "third", 2); graph.printGraph(); graph.removeEdge("first", "second"); graph.printGraph(); }}

    推荐阅读