数据结构与算法Python版之北大慕课笔记(五)
一、图Graph
1. 图的基础知识
-
图Graph是比树更为一般的结构,也是由节点和边构成的,实际上树是一种具有特殊性质的图。
-
顶点Vertex(也称“节点Node”)是图的基本组成部分,顶点具有名称标识Key,也可以携带数据项payload。
-
边Edge(也称“弧Arc”),作为2个顶点之间关系的表示,边连接两个顶点;边可以是无向或者有向的,相应的图称作“无向图”和“有向图”。
-
权重Weight,为了表达从一个顶点到另一个顶点的代价,可以给边赋权。例如公交网络中两个站点之间的距离、通行时间和票价都可以作为权重。
2. 图的定义
- 一个图G可以定义为G = (V,E),其中V是顶点的集合,E是边的集合,E中的每条边e = (v, w),v和w都是V中的顶点;如果是赋权图,则可以在e中添加权重分量子图:V和E的子集。
- 路径Path,图中的路径,是由边依次连接起来的顶点序列;无权路径的长度为边的数量;带权路径的长度为所有边权重的和。
- 圈Cycle,圈是首尾顶点相同的路径。如果有向图不存在任何圈,则称作“有向无圈图 directed acyclic graph: DAG”。如果一个问题能表示成DAG,就可以用图算法很好地解决。
二、ADT Graph
ADT Graph的实现方法有两种主要形式:邻接矩阵adjacency matrix;邻接表adjacency list,两种方法各有优劣,需要在不同应用中加以选择。
1. 邻接矩阵
-
矩阵的每行和每列都代表图中的顶点。
-
如果两个顶点之间有边相连,设定行列值。无权边则将矩阵分量有就标注为1,没有就标注为0;带权边则将权重保存为矩阵分量值。
-
邻接矩阵实现法的优点是简单,可以很容易得到顶点是如何相连的。
-
但如果图中的边数很少则效率低下,成为稀疏sparse矩阵。而大多数问题所对应的图都是稀疏的,边远远少于|v|^2 这个量级。
2. 邻接列表
-
邻接列表可以成为稀疏图的更高效实现方案。维护一个包含所有顶点的主列表(master list),主列表中的每个顶点,再关联一个与自身有边连接的所有顶点的列表。
-
邻接列表法的存储空间紧凑高效,很容易获得顶点所连接的所有顶点,以及连接边的信息。
3. ADT Graph的实现
-
顶点Vertex类:Vertex包含了顶点信息,以及顶点连接边信息。
class Vertex: def __init__(self, key): self.id = key self.connectedTo = { } def addNeighbor(self, nbr, weight=0): self.connectedTo[nbr] = weight # nbr是顶点对象的key 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]
-
图Graph类:Graph保存了包含所有顶点的主表。
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 return newVertex def getVertex(self, n): # 通过key查找顶点 if n in self.verList: return self.verList[n] else: return None def __contains__(self, n): return n in self.verList def addEdge(self, f, t, cost=0): if f not in self.verList: nv = self.addVertex(f) # 不存在的顶点先添加 if t not in self.verList: nv = self.addVertex(t) self.verList[f].addNeighbor(self.verList[t], cost) # 调用起始顶点的方法添加临街边 def getVertices(self): return self.verList.keys() def __iter__(self): return iter(self.verList.values())
三、图的应用
1. 词梯Word Ladder问题
- 由爱丽丝漫游奇境的作者Lewis Carroll在1878年所发明的单词游戏。
- 从一个单词演变为另一个单词,其中的过程可以经过多个中间单词,要求是相邻两个单词之间差异只能是1个字母。如FOOL变为SAGE:FOOL >> POOL >> POLL >> POLE >> PALE >> SALE >> SAGE
- 我们的目标是找到最短的单词变换序列,采用图来解决这个问题的步骤如下:将可能的单词之间的演变关系表达为图,采用广度优先搜索BFS,来搜寻从开始单词到结束单词之间的所有有效路径,选择其中最快到达目标单词的路径。
构建单词关系图:
-
首先是如何将大量的单词集放到图中,将单词作为顶点的标识key,如果两个单词之间仅相差1个字母,就在它们之间设一条边。
-
下图是从FOOL到SAGE的词梯解,所用的图是无向图,边没有权重