图是一种非线性的关系型数据结构,关于图的概念以及性质网络上已有很多资料,我不再多说。本文主要介绍两种基于python的图结构的实现方法。
邻接矩阵实现
邻接矩阵是表示图中顶点间邻接关系的方阵。对于n个顶点的图G=(V,E),其邻接矩阵是一个的方阵,图中每个顶点(按顺序)对应于矩阵里的一行和一列,矩阵元素表示图中的邻接关系。一般对于不带权值的图,用1表示节点有边,用0表示节点无边。
对于有权图,矩阵元素值即为边的权值,可用0或表示两顶点不相连。显然,对于无向图,其邻接矩阵是一个对称矩阵。
如上图所示连个图的邻接矩阵分别为
其中a,b,c的行下标分别为0,1,2。
在python中实现的代码:
class Graph:
def __init__(self,mat,unconn=0):
vnum=len(mat)
for x in mat:
if len(x)!=vnum:
raise ValueError("参数错误")
self._mat=[mat[i][:] for i in range(vnum)] #做拷贝
self._unconn=unconn
self._vnum=vnum
#顶点个数
def vertex_num(self):
return self._vnum
#顶点是否无效
def _invalid(self,v):
return v<0 or v>=self._vnum
#添加边
def add_edge(self,vi,vj,val=1):
if self._invalid(vi) or self._invalid(vj):
raise ValueError(str(vi)+" or "+str(vj)+"不是有效的顶点")
self._mat[vi][vj]=val
#获取边的值
def get_edge(self,vi,vj):
if self._invalid(vi) or self._invalid(vj):
raise ValueError(str(vi)+" or "+str(vj)+"不是有效的顶点")
return self._mat[vi][vj]
#获得一个顶点的各条出边
def out_edges(self,vi):
if self._invalid(vi):
raise ValueError(str(vi)+"不是有效的顶点")
return self._out_edges(self._mat[vi],self._unconn)
@staticmethod
def _out_edges(row,unconn):
edegs=[]
for i in range(len(row)):
if row[i]!=unconn:
edegs.append((i,row[i]))
return edegs
def __str__(self):
return "[\n"+",\n".join(map(str,self._mat))+"\n]"+"\nUnconnected: "+str(self._unconn)
邻接矩阵存储在mat中,是一个二层嵌套的列表。注意到,该图类没有定义添加顶点的操作,主要是因为添加顶点时,要横向和纵向扩充邻接矩阵,操作不便。
可见用邻接矩阵表示图的方式所需的空间开销是和图的顶点数的平方成正比的,如果图的顶点数比较多,并且图的每个顶点的度并不多,这样邻接矩阵就是一个稀疏矩阵,其中大部分元素都是无用的,反而耗费了大量的存储空间。另外,也不便添加顶点。
邻接表实现
所谓邻接表,就是为图中每个顶点关联一个边表,其中记录这个顶点的所有邻接边。这样,一个顶点的表,其中每个顶点又关联一个边表,就构成了图的一种表示。如下图,右图是左图的邻接表表示,但没有表示权值。
以下是在python中的实现:
class GraphAL(Graph):
def __init__(self,mat=[],unconn=0):
vnum = len(mat)
for x in mat:
if len(x) != vnum:
raise ValueError("参数错误")
self._mat=[Graph._out_edges(mat[i],unconn) for i in range(vnum)]
self._unconn = unconn
self._vnum = vnum
#添加顶点
#返回该顶点编号
def add_vertex(self):
self._mat.append([])
self._vnum+=1
return self._vnum-1
#添加边
def add_edge(self,vi,vj,val=1):
if self._vnum==0:
raise ValueError("不能向空图添加边")
if self._invalid(vi) or self._invalid(vj):
raise ValueError(str(vi)+" or "+str(vj)+"不是有效的顶点")
row=self._mat[vi]
i=0
while i<len(row):
if row[i][0]==vj:
self._mat[vi][i]=(vj,val) #如果原来有到vj的边,修改mat[vi][vj]的值
return
if row[i][0]>vj: #原来没有到vj的边,退出循环后加入边
break
i+=1
self._mat[vi].insert(i,(vj,val))
#获取边的值
def get_edge(self,vi,vj):
if self._invalid(vi) or self._invalid(vj):
raise ValueError(str(vi)+" or "+str(vj)+"不是有效的顶点")
for i,val in self._mat[vi]:
if i==vj:
return val
return self._unconn
# 获得一个顶点的各条出边
def out_edges(self,vi):
if self._invalid(vi):
raise ValueError(str(vi)+"不是有效的顶点")
return self._mat[vi]
可以,这种表示图的方式更加节省空间,且插入顶点操作比较简单。但由于每个顶点的边表的元素是按终点的编号排序的,因此在插入边时,需要从头遍历该列表,在一定程度上增加了算法的复杂度。