图的存储结构与实现总结
目录
图的存储结构
图的存储结构主要分两种,一种是邻接矩阵,一种是邻接表。
邻接矩阵
图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。
设图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 <T>
*/
public class Vertex<T> {
/**
* 能够标识这个定点的属性,可以用不同类型来标识顶点如String,Integer....
*/
private T label;
/**
* 这个定点对应的边<br>
* 如果为有向图,则代表以这个定点为起点的边
*/
private List<Edge> edgeList;
/**
* 表示这个顶点是否已被访问,在bfs和dfs中会被使用到
*/
private boolean visited;
/**
* 该顶点的前驱节点<br>
* 在求图中某两个顶点之间的最短路径时,在从起始顶点遍历过程中,需要记录下遍历到某个顶点时的前驱顶点
*/
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<Edge> getEdgeIterator(){
return edgeList.iterator();
}
/**返回是否有以这个顶点为出发点的边数
* @return
*/
public int getEdgeCount(){
return edgeList.size();
}
/**将这个顶点与endVertex连接,边的权值为weight
* @param endVertex
* @param weight
* @return 如果顶点已经与endVertex连接,那么将会更新权值,返回false<br>
* 如果顶点没有与endVertex相连,则互相连接,返回true
*/
public boolean connect(Vertex endVertex,double weight){
Iterator<Edge> 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<br>
* 如果顶点没有与endVertex连接,则啥都不做,返回false
*/
public boolean disconnect(Vertex endVertex){
Iterator<Edge> 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 如果有,返回那条边<br>
* 如果没有,返回null
*/
public Edge hasNeighbourVertex(Vertex endVertex){
Iterator<Edge> 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<br>
* 如果有,返回对应的顶点
*/
public Vertex getUnvisitedVertex(){
Iterator<Edge> 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是边的起始顶点<br>
* 普通情况是不用显示地存储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 <T>
*/
public class Graph<T> {
/**
* 用来存储顶点
* T做为标识,vertext作为实际的顶点
*/
private Map<T, Vertex<T>> vertexMap;
/**
* 图中边的数目<br>
* 顶点的数目可以用vertexMap.size()
*/
private int edgeCount;
/**
* 图是否为有向图<br>
* 如果是有向图,则为true
*/
boolean isDirect;
/**图的构造函数
* @param isDirect 图是否为有向图<br>
* 如果是有向图,则为true
*/
public Graph(boolean isDirect){
vertexMap=new LinkedHashMap<>();
edgeCount=0;
this.isDirect=isDirect;
}
//下面与图的顶点相关
/**返回图中的顶点个数
* @return
*/
public int getVertexCount(){
return vertexMap.size();
}
/** 返回图的顶点的迭代器
* @return
*/
public Iterator<Vertex<T>> getVertexIterator(){
return vertexMap.values().iterator();
}
/**在图中插入节点,节点的标识为label,节点的权值为cost
* @param label
* @param cost 如果不需要节点的权值,则设0即可
* @return 如果图中不存在该节点,则插入,返回true<br>
* 如果图中已经存在该节点,则更新权值,返回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<T>(label, cost);
vertexMap.put(label, vertex);
return true;
}
//下面与图的边相关
/** 返回图中所有的边的个数<br>
* 如果为有向图,则是所有的有向边的个数<br>
* 如果为无向图,则视一条边为两条相反的有向边,相当于返回无向边的个数*2
* @return
*/
public int getEdgeCount(){
Iterator<Vertex<T>> iterator=getVertexIterator();
int count=0;
while(iterator.hasNext()){
Vertex<T> vertex=iterator.next();
count=count+vertex.getEdgeCount();
}
return count;
}
/** 返回图中标识为label的顶点作为出发点的边的个数
* @param label
* @return 如果为有向图,则返回标识为label的顶点作为出发点的边的个数
* 如果为无向图,则返回标识为label的顶点相连接的边的个数
* 如果图中没有这个顶点,返回-1
*/
public int getEdgeCount(T label){
Vertex<T> vertex=vertexMap.get(label);
if(vertex==null){
//如果图中没有这个顶点,返回-1
return -1;
}
//返回途中标识为label的顶点作为出发点的边的个数
return vertex.getEdgeCount();
}
/** 返回图中标识为label的顶点作为出发点的边的迭代器
* @param label
* @return 如果没有这个顶点,返回null
*/
public Iterator<Edge> getEdgeIterator(T label){
Vertex<T> vertex=vertexMap.get(label);
if(vertex==null){
//如果图中没有这个顶点,返回null
return null;
}
return vertex.getEdgeIterator();
}
/**在图中加入一条边,如果isDirect为true,则为有向图,则<br>
* 建立一条以begin作为标识的节点开始的边,以end作为标识的节点结束,边的权值为weight<br>
* 如果isDirect为false,则为无向图,则<br>
* 建立两条边,一条以begin开始,到end ,一条以end开始,到begin
* @param begin
* @param end
* @param weight 如果不需要边的权值,可以设为0
* @return 如果没有对应的边,则加入对应的边,返回true<br>
* 如果有对应的边,则更新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,则为有向图,则<br>
* 删除一条以begin作为标识的节点开始的边,以end作为标识的节点结束<br>
* 如果isDirect为false,则为无向图,则<br>
* 删除两条边,一条以begin开始,到end ,一条以end开始,到begin
* @param begin
* @param end
* @return 如果有对应的边,则删除对应的边,返回true<br>
* 如果没有有对应的边,则直接返回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<Vertex<T>> iteratorVertex=getVertexIterator();
Iterator<Edge> iteratorEdge;
Vertex<T> 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<String> 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();
}
}
```;