图的基本概念
graph:重在由一些基本元素构造而来的图,如点,线段等
图(graph)是比树更为一般的结构,也是由节点和边组成的,树可以说是一种特殊性质的图.
图可以用来表示现实中的很多事物如:道路交通系统,航班路线,互联网连接,大学中课程的先修次序
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210615220442361.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzUzMTgzNjA4,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210615220500474.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzUzMTgzNjA4,size_16,color_FFFFFF,t_70)
还有一个跟六度空间一样的玩意,什么任意两个人之间可以通过最多六个人联系起来,这违背了我的尝试,我是不信的,不过说不定哪天我就信了
一些听起来很专业的术语表
顶点是图的基本组成部分
而边分为有向边和无向边,相应的图被称为有向图和无向图
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210615220847290.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzUzMTgzNjA4,size_16,color_FFFFFF,t_70)
这个东西就是为了表达一个顶点到另一个顶点的代价,可以给权重也可以不给
图的定义
一个图的基本组成部分就是顶点vertex,顶点到顶点就构成了边,如果是赋权图就再加一个权重weight就可以了
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210615221313389.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzUzMTgzNjA4,size_16,color_FFFFFF,t_70)
举个例子
对于无权图路径是指从该顶点到末尾顶点的边的数量之和,有权图是指从该顶点到末尾顶点权重之和
圈是指一个闭环,如果有向图不存在任何圈,就是有向无权图
Graph的抽象数据类型定义
就是定义一个Graph的类,里面有各种各样的方法.
可以添加顶点:addVertex,有向边:addEdge,带权有向边:addEdge让他默认为一个数,查找顶点getVertex,getvertices返回图中所有顶点列表
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210615222329924.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzUzMTgzNjA4,size_16,color_FFFFFF,t_70)
具体的两种实现方法
邻接矩阵
这个东西比较好理解,就是列为fromvert,行为tovert,里面保存的是权重
主要的问题就是一般不会有那么紧的排列,所以利用的效率不高
邻接列表
可以看到邻接列表是一种稀疏图的更高效实现的方式,就是当前节点保存了对应的顶点和他们之间的边
具体的代码实现
1.首先创建一个顶点类
#首先是Vertex类
class Vertex:
def __init__(self,key):
self.id=key
self.connectedTo={} #用来保存相连顶点,里面有相连顶点,以及对应的权重
def addNeighbor(self,nbr,weight=0): #nbr是顶点对象的key
self.connectedTo[nbr]=weight #这个函数是用来 将与此顶点相连的顶点保存起来
def __str__(self):
return str(self.id)+' connectedTo:'\
+str([x.id for x in self.connectedTo]) #返回的是该顶点的名称和与其相连的顶点
def getConnections(self):
return self.connectedTo.keys() #获得与其相连的所有顶点
def getId(self):
return self.id #获得本顶点的名字
def getweight(self,nbr):
return self.connectedTo[nbr] #获得与nbr之间的权重
有必要提一下的是python的类方法中的__str__()方法,这个方法是python的内置方法使用如下
最开始的输出
class obj:
def __init__(self):
pass
p=obj()
print(p) <class '__main__.obj'>
class obj:
def __str__(self):
pass
p=obj()
print(p) 报错 我需要字符串而你什么都不干
class obj:
def __str__(self):
print(1)
p=obj()
print(p) 会输出1 同时也会报错因为没有返回字符串
但是当有了返回值之后就不一样了
class obj:
def __str__(self):
return 1
p=obj()
print(p) 报错 因为不是字符串
class obj:
def __str__(self):
return '1'
p=obj()
print(p) 1 因为有返回值,且为字符串
所以说只要是有正确的__str__()方法的时候打印对象就成了打印字符串
创建一个图类
class Graph:
def __init__(self):
self.verList={} #包含所有顶点
self.numVertices=0 #记录顶点个数
def addVertex(self,key):
self.numVertices=self.numVertices+1 #顶点个数加一
newVertex=Vertex(key) #创建一个新的顶点
self.verList[key]=newVertex #根据顶点的key保存顶点
def getvertex(self,key):
if key in self.verList:
#能进来说明有对应的key
return self.verList[key]
else:
return None
def __contains__(self, item): #这个方法是使 in 可以直接被使用,进行判断用的
return item in self.verList
def addEdge(self,f,t,weight=0):
#f为一个顶点的key
if f not in self.verList:
#能进来说明没有这个顶点需要创建一个
self.addVertex(f)
if t not in self.verList:
self.addVertex(t)
#添加这个顶点,以及与其相连的顶点
self.verList[f].addNeighbor(self.verList[t],weight)
#因为是有向图,每条边均为有向边,所以不考虑t的邻居添加f
def getVertices(self):
return self.verList.keys() #这个得到的是所有顶点所对应的键
def __iter__(self):
return iter(self.verList.values())
重点还是提一下__contains__()方法,他是需要去传入一个参数item的,主要就是判断该item是否在某个属性中
class obj:
def __init__(self):
self.list=[1,2,3]
def __contains__(self,item):
return item in self.list
p=obj()
print(1 in p) True
print(4 in p) False
如果删除了呢
class obj:
def __init__(self):
self.list=[1,2,3]
p=obj()
print(1 in p)
print(4 in p)
这是会报错的TypeError: argument of type 'obj' is not iterable
说对象是不可迭代的
并且contains中return后所跟不一定是 in
class obj:
def __init__(self):
self.list=[1,2,3]
def __contains__(self,item):
return 1
p=obj()
print(1 in p) True
print(4 in p) True 但返回值一定是bool类型
还有__iter__()方法:使对象本身可以被迭代
class obj:
def __init__(self):
self.list=[1,2,3]
p=obj()
for i in p:
print(i) TypeError: 'obj' object is not iterable
那么如果加上__iter__()方法呢?
class obj:
def __init__(self):
self.list=[1,2,3]
def __iter__(self):
return 1 TypeError: iter() returned non-iterator of type 'int'
p=obj()
for i in p:
print(i)
class obj:
def __init__(self):
self.list=[1,2,3]
def __iter__(self):
return [1,2,3]
p=obj()
for i in p:
print(i) TypeError: iter() returned non-iterator of type 'list'
上面两个报错大同小异,都是说返回的类型是什么什么而非迭代器,这时候怎么办呢?
请出我们的iter关键字可以将返回对象转化为迭代器
class obj:
def __init__(self):
self.list=[1,2,3]
def __iter__(self):
return iter([1])
p=obj()
for i in p:
print(i) 1
图的实际应用
词梯问题是一个关于最短路径的问题
如果每个单词在创建边时都进行一次比对的话需要n**2的次数,这是一个很大的计算复杂度
这里是一个优化,他建立了一个桶.一个桶中的所有单词之间都是可以建边的
这是老师写的代码文件