图的存储结构

图的存储结构

顶点位置与邻接点位置只是一个相对概念。

任何一个顶点都可以看成是第一个顶点,任一顶点的邻接点之间也不存在次序关系

image

上图为同一个图,顶点位置不同,造成表象不一样。

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

如果采用多重链表的方式,以一个数据域和多个指针组成的结点表示图中的一个顶点,可以实现,但存在问题。

各个顶点的度数相差很大,按度数最大的顶点设计结点结构会造成很多存储单元的浪费;而按照每个顶点自己的度数设计不同的顶点结构,又带来操作的不便

五种不同的存储结构

邻接矩阵

图是由顶点和边或弧两部分组成。自然地考虑分两个结构分别存储。

  • 顶点不分大小、主次,用一维数组来存储
  • 边或弧由于是顶点与顶点之间的关系,一维搞不定,所以用二维数组存储

以上就是邻接矩阵的方案。

图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。

无向图邻接矩阵

设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
image

无向图邻接矩阵实例如下:
image

arc[1][3] = 0 是因为v1到v3的边不存在。因为是无向图,所以v3到v1的边也不存在。

所以无向图的边数组是一个对称矩阵。

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

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

  • 非常容易判断任意两顶点是否有边无边
  • 某个顶点的度,就是顶点vi在邻接矩阵中第i行(或第i列)的元素之和。比如v1的度就是1 + 0 + 1 + 0 = 2
  • 求顶点vi的所有邻接点,就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点。

有向图邻接矩阵

有向图邻接矩阵实例如下:
image

有向图讲究入度和出度。顶点v1的入度为1,正好是第v1列各数之和。顶点v2的出度为2,即第v1行的各数之和。

判断vi到vj是否存在弧,只需要查找矩阵中arc[i][j]是否为1即可。

要求vi的所有邻接点就是将矩阵第i行元素扫描一遍,查找arc[i][j]为1的顶点。

网的邻接矩阵

回顾:带权的图叫做网。

网的邻接矩阵需要存下权。

设G为网,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
image

  • Wij表示(vi,vj)或<vi,vj>上的权值。
  • ∞是计算机允许的,大于所有边上权值的值,也是一个不可能的极限值

有向网邻接矩阵

image

无向图邻接矩阵创建实现

public class GraphTest {
	String[] scanfVertex = {"A","B","C","D","E","F","G"};
	//输入权值,默认使用0
	int scanfValue = 0;
	//i,j为输入边的下标
	int i,j = 0;
	
	void createMGraph(Graph graph){
		graph.numVertexes = 7;
		graph.numEdges = 9;
		//输入顶点
		for(int i = 0;i<scanfVertex.length;i++){
			graph.verxs[i] = scanfVertex[i];
		}
		//用顶点初始化邻接矩阵 默认复制无穷大
		for(int i = 0;i<graph.verxs.length;i++){
			for(int j = 0;j<graph.verxs.length;j++){
				//初始化无穷
				graph.arc[i][j] = graph.INFINITY;
			}
		}
		//读入numEdges条边,建立邻接矩阵
		for(int k = 0;k<graph.numEdges;k++){
			graph.arc[i][j] = scanfValue;
			//无向图矩阵对称
			graph.arc[i][j] = graph.arc[j][i];
		}
	}
	
}
//无向图数据结构
class Graph{
	String vertexType;//顶点类型
	int edge;//边上权值
	static final int MAXVEX = 10;//最大顶点数
	static final int INFINITY = 65535;//65535代表无穷
	
	String[] verxs = new String[MAXVEX];//顶点表
	int[][] arc = new int[MAXVEX][MAXVEX];//邻接矩阵,可看做边表
	int numVertexes,numEdges;//图中当前的顶点数和边数
}

总结:邻接矩阵a[i][j],i是矩阵纵向下标,j是矩阵横向下标。

邻接表

邻接矩阵缺点:对于边数相对顶点较少的图,这种结构是存在对存储空间的极大浪费。

image

上图邻接矩阵中除了arc[1][0]有权值外,没有其他弧,其他的存储空间都浪费掉了。

回顾:线性表中,顺序存储结构存在预先分配内存可能造成存储空间浪费的问题,于是引出了链式存储的结构。

同样可以考虑对边或弧使用链式存储的方式来避免空间浪费的问题。

同样适用于图的存储。我们把数组与链表相结合的存储方法称为邻接表(Adjacency List)。

邻接表原理:

  • 顶点用一维数组存储。顶点也可以用单链表存储,不过数组更容易读取顶点信息,更加方便。在顶点数组中,每个数据元素还需要存储指向第一个邻接点的指针,便于查找该顶点的边信息。
  • 图中每个顶点v1的所有邻接点构成一个线性表,邻接点的个数不定,所以用单链表存储;无向图称为顶点v1的边表,有向图则称为顶点v1作为弧尾的出边表。

无向图邻接表

image

如上就是一个无向图的邻接表结构

边表结点由adjvex和next两个域组成。

  • adjvex邻接点域,存储某顶点的邻接点在顶点表中的下标
  • next存储指向边表中下一个结点的指针

比如v1顶点与v0、v2互为邻接点,则在v1的边表中,adjvex分别为v0的0和v2的2。

  • 某个顶点的度,查找这个顶点边表中结点的个数
  • 判断顶点vi到vj是否存在边,只需要测试顶点vi的边表中adjvex是否存在vj的下标j就可以了
  • 求顶点的所有邻接点,对顶点边表进行遍历

有向图邻接表

image

以上为有向图邻接表

可以算出某顶点的入度和出度,判断两顶点是否存在弧也很容易实现。

带权的网图,在边表结点定义中增加一个weight的数据域,存储权信息即可。
image

/**
 * 邻接表实现
 * @author jiaxinxiao
 *
 */
public class GraphAdjList {
	static final int MAX = 10;
	static final int INIT_VALUE = 5;
	static final String INIT_STRING_VALUE = "string";
	VertexNode[] vertexNodes = new VertexNode[MAX];
	int numVertexes,numEdges;//顶点数和边数
	
	void createAlgraph(GraphAdjList graphAdjList){
		//初始化
		graphAdjList.numVertexes = INIT_VALUE;
		graphAdjList.numEdges = INIT_VALUE;
		//读入顶点信息,建立顶点表
		for(int i=0;i<numVertexes;i++){
			vertexNodes[i].data = INIT_STRING_VALUE;//输入顶点信息
			vertexNodes[i].firstEdge = null;//边表置为空表
		}
		
		//回顾:此处用到了单链表创建中讲解到的头插法
		for(int k=0;k<numEdges;k++){
			//输入边上顶点序号
			int i  = INIT_VALUE;
			int j = INIT_VALUE;
			EdgeNode eNode = new EdgeNode();
			
			eNode.adjvex = j;//邻接序号为j
			eNode.next = vertexNodes[i].firstEdge;
			vertexNodes[i].firstEdge = eNode;
			
			eNode = new EdgeNode();
			eNode.adjvex = i;//邻接序号为j
			eNode.next = vertexNodes[j].firstEdge;
			vertexNodes[j].firstEdge = eNode;
		}
	}
}
//边表结点
class EdgeNode{
	int adjvex;//邻接域
	int weight;//权值,非网图不需要
	EdgeNode next;//链域,指向下一个邻接点
}
//顶点表结构
class VertexNode{
	String data;//顶点域,存储顶点信息,数据暂时使用String类型
	EdgeNode firstEdge;//边表头指针
}

重点:单链表头插法。

十字链表

时差 安保问题告诉我们,充分利用现有资源,正向思维、逆向思维、整合思维可以创造更大价值。

邻接表缺陷:关心了出度问题,了解入度必须遍历整个图。反之,逆邻接表解决了入度确不了解出度的情况。

有没有能把邻接表和逆邻接表结合起来呢?

有:有向图的一种存储方法:十字链表(Orthogonal List)。

顶点结构
image

  • firstin:入边表头指针,指向该顶点的入边表中第一个结点
  • firstout:出边表头指针,指向该顶点的出边表中的第一个结点

边表结构
image

  • tailvex:弧起点在顶点表的下标
  • headvex:弧终点在顶点表的下标
  • headlink:指入边表指针域,指向终点相同的下一条边
  • taillink:出边表指针域,指向起点相同的下一条边

十字链表结构
image

上图中taillink应该为headlink

十字链表优点:

  • 将邻接表与逆邻接表整合到一起,既容易找到以vi为尾的弧,也容易找到以vi为头的弧,因而容易求得顶点的出度和入度。
  • 除了结构复杂以外,创建的时间复杂度和邻接表相同

在有向图应用中,十字链表是非常好的数据结构

邻接多重表

无向邻接表存在的弊端:边的操作麻烦,比如对边做标记,删除某一条边等。那就意味着,需要找到这条边的两个边表结点进行操作,比较麻烦

image

上图若要删除(v0,v2)这条边,需要对邻接表结构中红框标识的两个结点删掉,比较麻烦

边表结点结构:
image

ivex和jvex是与某条边依附的两个顶点在顶点表中下标。ilink指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边。这就是邻接多重表结构。

image

ilink指向的结点的jvex一定要和它本身的ivex的值相同

邻接多重表与邻接表的差别,仅仅是在于同一条边在邻接表中用两个jie

边集数组

边集数组是由两个一维数组构成。一个是存储顶点的信息;另一个是存储边的信息,这个边数组每个数据元素由一条边的起点下标(begin)、终点下标(end)和权(weight)组成。

边集数组关注的是边的集合,但是查找一个顶点的度需要扫描整个边数组,效率比较低。

image

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值