赞
踩
参考自 MOOC数据结构与算法Python版
本章代码: https://github.com/HuiFang-hub/-/tree/main.
一旦我们对图相关问题进行了准确的描述,就可以采用处理图的标准算法来解决那些
看起来很艰深的问题
函数 | 含义 |
---|---|
Graph() | 创建一个空图 |
addVertex(vert) | 将顶点vert加入图中 |
addEdge(fromVert, toVert) | 添加有向边 |
addEdge(fromVert, toVert, weight) | 添加带权的有向边 |
getVertex(vKey) | 查找名称为vKey的顶点 |
getVertices() | 返回图中所有顶点列表 |
in | 按照vert in graph的语句形式,返回顶点是否存在图中True/False |
两种方法各有优劣,需要在不同应用中加以选择
矩阵的每行和每列都代表图中的顶点,如果两个顶点之间有边相连,设定行列值
例如下面的带权图:
邻接矩阵顶实现法的优点是简单,可以很容易得到顶点是如何相连
但如果图中的边数很少则效率低下,成为“稀疏sparse”矩阵,而大多数问题所对应的图都是稀疏的,边远远少于|V|2这个量级,从而出现邻接列表。
例如上面的图转为邻接列表,与V0有关的有V1和V5,权重分别是5和2:
下面展示了 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() #返回key def getId(self): return self.id #返回顶点边的权重。 def getWeight(self,nbr): return self.connectedTo[nbr]
下面展示了 Graph 类的代码,包含将顶点名称映射到顶点对象的字典。Graph 还提供了将顶点添加到图并将一个顶点连接到另一个顶点的方法。getVertices方法返回图中所有顶点的名称。此外,我们实现了__iter__ 方法,以便轻松地遍历特定图中的所有顶点对象。 这两种方法允许通过名称或对象本身在图形中的顶点上进行迭代。
class Graph: def __init__(self): self.vertList = {} self.numVertices = 0 #新加顶点 def addVertex(self,key): self.numVertices = self.numVertices + 1 newVertex = Vertex(key) self.vertList[key] = newVertex return newVertex #通过key查找顶点 def getVertex(self,n): if n in self.vertList: return self.vertList[n] else: return None def __contains__(self,n): return n in self.vertList def addEdge(self,f,t,cost=0): if f not in self.vertList: #不存在的顶点先添加 nv = self.addVertex(f) if t not in self.vertList: nv = self.addVertex(t) self.vertList[f].addNeighbor(self.vertList[t], cost) def getVertices(self): return self.vertList.keys() def __iter__(self): return iter(self.vertList.values())
g = Graph()
for i in range(6):
g.addVertex(i)
g.addEdge(0,1,5)
g.addEdge(0,5,2)
g.addEdge(1,2,4)
g.addEdge(2,3,9)
g.addEdge(3,4,7)
g.addEdge(3,5,3)
g.addEdge(4,0,1)
g.addEdge(5,4,8)
g.addEdge(5,2,1)
for v in g: #遍历输出
for w in v.getConnections():
print("( %s , %s )" % (v.getId(), w.getId()))
Out: {0: <__main__.Vertex at 0x2459cdf65f8>, 1: <__main__.Vertex at 0x2459cdf6630>, 2: <__main__.Vertex at 0x2459cdf6668>, 3: <__main__.Vertex at 0x2459cdf66a0>, 4: <__main__.Vertex at 0x2459cdf66d8>, 5: <__main__.Vertex at 0x2459cdf6710>} ( 0 , 1 ) ( 0 , 5 ) ( 1 , 2 ) ( 2 , 3 ) ( 3 , 4 ) ( 3 , 5 ) ( 4 , 0 ) ( 5 , 4 ) ( 5 , 2 )
由 “ 爱 丽 丝 漫 游 奇 境 ” 的 作 者 LewisCarroll在1878年所发明的单词游戏。
从一个单词演变到另一个单词, 其中的过程可以经过多个中间单词,要求是相邻两个单词之间差异只能是1个字母,如FOOL变SAGE:
FOOL >> POOL >> POLL >> POLE >> PALE>> SALE >> SAGE
我们的目标是找到最短的单词变换序列,采用图来解决这个问题的步骤如下:
首先是如何将大量的单词集放到图中:将单词作为顶点的标识Key,如果两个单词之间仅相差1个字母,就在它们之间设一条边
下图是从FOOL到SAGE的词梯解, 所用的图是无向图, 边没有权重
单词关系图可以通过不同的算法来构建(以4个字母的单词表为例)
def buildGraph(wordfile): d = {} g = Graph() wfile = open(wordfile,'r') for line in wfile: word = line[:-1] for i in range(len(word)): bucket = word[:i] + '_' + word[i+1:] if bucket in d: d[bucket].append(word) else: d[bucket] = [word] #同一个桶的单词建立边 for bucket in d.keys(): for word1 in d[bucket]: for word2 in d[bucket]: if word1 != word2: g.addEdge(word1, word2) return g
注意,如果采用邻接矩阵表示这个单词关系图,则需要2,600万个矩阵单元,单词关系图是一个非常稀疏的图
很多问题都可转化为图, 利用图算法解决,例如早餐吃薄煎饼的过程,以动作为顶点,以先后次序为有向边。(有先后次序和依赖关系)
从工作流程图得到工作次序排列的算法,称为“拓扑排序”
拓扑排序广泛应用在依赖事件的排期上,还可以用在项目管理、 数据库查询优化和矩阵乘法的次序优化上。拓扑排序可以采用DFS很好地实现:
我们关注一下互联网相关的非常巨大图:
我们可以猜想, Web的底层结构可能存在某些同类网站的聚集
强连通分支, 定义为图G的一个子集C:C中的任意两个顶点v,w之间都有路径来回,即(v,w)(w,v)都是C的路径,而且C是具有这样性质的最大子集
下图是具有3个强连通分支的9顶点有向图,一旦找到强连通分支, 可以据此对图的顶点进行分类, 并对图进行化简(9个顶点变为3个顶点,相当于聚类)
在用深度优先搜索来发现强连通分支之前, 先熟悉一个概念: Transposition转置
一个有向图G的转置GT,定义为将图G的所有边的顶点交换次序,如将(v,w)转换为(w,v)。可以观察到图和转置图在强连通分支的数量和划分上,是相同的
下图互为转置,即
第一趟DFS
转置后第二趟DFS, 取结束时间最大的第一个开始
结果形成三个强连通分支:
Kosaraju算法最好理解,但是效率最差,因此另外的常用强连通分支算法有:
参考阅读:https://baike.baidu.com/item/tarjan算法.
当我们通过网络浏览网页、 发送电子邮件、 QQ消息传输的时候, 数据会在联网设备之间流动。
所以我们可以将互联网路由器体系表示为
一个带权边的图:
解决带权最短路径问题的经典算法是以发明者命名的“Dijkstra算法”( /ˈdɛɪkstra)
这是一个迭代算法, 得出从一个顶点到其余所有顶点的最短路径, 很接近于广度优先搜索算法BFS的结果
具体实现上, 在顶点Vertex类中的成员dist用于记录从开始顶点到本顶点的最短带权路径长度(权重之和) , 算法对图中的每个顶点迭代一次
步骤:
以下图为例:
form pythonds.graphs import Graph,PriorityQueue,Vertex
def dijkstra(aGraph, start):
pq = PriorityQueue()
start.setDistance(0)#开始点的距离设为0
pq.buildHeap([(v.getDistance(), v) for v in aGraph]) #对所有顶点建堆,形成优先队列
while not pq.isEmpty():
currentVert = pq.delMin() #优先队列出队
for nextVert in currentVert.getConnections():
newDist = currentVert.getDistance() + currentVert.getWeight(nextVert)
if newDist < nextVert.getDistance():#修改出队顶点所邻接顶点的dist,并逐个顶点的dist,并逐个
nextVert.setDistance(newDist)
nextVert.setPred(currentVert)
pq.decreaseKey(nextVert, newDist)
需要注意的是, Dijkstra算法只能处理大于0的权重,如果图中出现负数权重,则算法会陷入无限循环。
虽然Dijkstra算法完美解决了带权图的最短路径问题, 但实际上Internet的路由器中采用的是其它算法,其中最重要的原因是, Dijkstra算法需要具备整个图的数据, 但对于Internet的路由器来说, 显然无法将整个Internet所有路由器及其连接信息保存在本地,路由器的选径算法(或“路由算法”) 对于互联网极其重要, 有兴趣可以进一步参考“距离向量路由算法“。
上 面 三 个 加 在 一 起 , 数 量 级 就 是 O ( ( ∣ V ∣ + ∣ E ∣ ) l o g ∣ V ∣ ) O((|V|+|E|)log|V|) O((∣V∣+∣E∣)log∣V∣)
本算法涉及到在互联网中网游设计者和网络收音机所面临的问题:信息广播问题
图G(V,E)的最小生成树T, 定义为包含所有顶点V,以及E的无圈子集,并且边权重之和最小。
图为一个最小生成树,这样信息广播就只需要从A开始, 沿着树的路径层次向下传播就可以达到每个路由器只需要处理1次消息,同时总费用最小。
解决最小生成树问题的Prim算法, 属于“贪心算法”, 即每步都沿着最小权重的边向前搜索。
构造最小生成树的思路很简单, 如果T还不是生成树, 则反复做:
from pq import PriorityQueue, Graph, Vertex # 最小生成树prim算法 # G - 无向赋权图 # start - 开始节点 # 返回从开始节点创建最小生成树 def prim(G, start): pq = PriorityQueue() # 创建优先队列 start.setDistance(0) # 起点最小权重代价设置为0,其它节点最小权重代价默认maxsize # 将节点排入优先队列,start在最前面 pq.buildHeap([(v.getDistance(), v) for v in G]) while not pq.isEmpty(): # 取距离*已有树*最小权重代价的节点出队列,作为当前节点 # 当前节点已解出最小生成树的前驱pred和对应最小权重代价dist currentVert = pq.delMin() # 遍历节点的所有邻接节点 for nextVert in currentVert.getConnections(): # 从当前节点出发,逐个检验到邻接节点的权重 newCost = currentVert.getWeight(nextVert) # 如果邻接节点是"安全边",并且小于邻接节点原有最小权重代价dist,就更新邻接节点 if nextVert in pq and newCost < nextVert.getDistance(): # 更新最小权重代价dist nextVert.setPred(currentVert) # 更新返回路径 nextVert.setDistance(newCost) # 更新优先队列 pq.decreaseKey(nextVert, newCost) G = Graph() ndedge = [('A', 'B', 2), ('A', 'C', 3), ('B', 'C', 1), ('B', 'D', 1), ('B', 'E', 4), ('C', 'F', 5), ('D', 'E', 1), ('E', 'F', 1), ('F', 'G', 1)] for nd in ndedge: G.addEdge(nd[0], nd[1], nd[2]) G.addEdge(nd[1], nd[0], nd[2]) start = G.getVertex('A') prim(G, start) print(G) G = Graph() ndedge = [('v1', 'v2', 6), ('v1', 'v3', 1), ('v1', 'v4', 5), ('v2', 'v3', 5), ('v3', 'v4', 5), ('v2', 'v5', 3), ('v3', 'v5', 6), ('v3', 'v6', 4), ('v4', 'v6', 2), ('v5', 'v6', 6)] for nd in ndedge: G.addEdge(nd[0], nd[1], nd[2]) G.addEdge(nd[1], nd[0], nd[2]) start = G.getVertex('v1') prim(G, start) print(G)
pq.py
import sys class Graph: def __init__(self): self.vertices = {} self.numVertices = 0 def addVertex(self, key): self.numVertices = self.numVertices + 1 newVertex = Vertex(key) self.vertices[key] = newVertex return newVertex def getVertex(self, n): if n in self.vertices: return self.vertices[n] else: return None def __contains__(self, n): return n in self.vertices def addEdge(self, f, t, cost=0): if f not in self.vertices: nv = self.addVertex(f) if t not in self.vertices: nv = self.addVertex(t) self.vertices[f].addNeighbor(self.vertices[t], cost) def getVertices(self): return list(self.vertices.keys()) def __iter__(self): return iter(self.vertices.values()) def __str__(self): s = "" for v in self: s += f"[{v.id},{v.dist},{v.pred.id if v.pred else None}] " return s class Vertex: def __init__(self, num): self.id = num self.connectedTo = {} self.color = 'white' self.dist = sys.maxsize self.pred = None self.disc = 0 self.fin = 0 # def __lt__(self,o): # return self.id < o.id def addNeighbor(self, nbr, weight=0): self.connectedTo[nbr] = weight def setColor(self, color): self.color = color def setDistance(self, d): self.dist = d def setPred(self, p): self.pred = p def setDiscovery(self, dtime): self.disc = dtime def setFinish(self, ftime): self.fin = ftime def getFinish(self): return self.fin def getDiscovery(self): return self.disc def getPred(self): return self.pred def getDistance(self): return self.dist def getColor(self): return self.color def getConnections(self): return self.connectedTo.keys() def getWeight(self, nbr): return self.connectedTo[nbr] def __str__(self): return str(self.id) + ":color " + self.color + ":disc " + str(self.disc) + ":fin " + str( self.fin) + ":dist " + str(self.dist) + ":pred \n\t[" + str(self.pred) + "]\n" def __repr__(self): return str(self.id) def getId(self): return self.id class PriorityQueue: def __init__(self): self.heapArray = [(0, 0)] self.currentSize = 0 def buildHeap(self, alist): self.currentSize = len(alist) self.heapArray = [(0, 0)] for i in alist: self.heapArray.append(i) i = len(alist) // 2 while (i > 0): self.percDown(i) i = i - 1 def percDown(self, i): while (i * 2) <= self.currentSize: mc = self.minChild(i) if self.heapArray[i][0] > self.heapArray[mc][0]: tmp = self.heapArray[i] self.heapArray[i] = self.heapArray[mc] self.heapArray[mc] = tmp i = mc def minChild(self, i): if i * 2 > self.currentSize: return -1 else: if i * 2 + 1 > self.currentSize: return i * 2 else: if self.heapArray[i * 2][0] < self.heapArray[i * 2 + 1][0]: return i * 2 else: return i * 2 + 1 def percUp(self, i): while i // 2 > 0: if self.heapArray[i][0] < self.heapArray[i // 2][0]: tmp = self.heapArray[i // 2] self.heapArray[i // 2] = self.heapArray[i] self.heapArray[i] = tmp i = i // 2 def add(self, k): self.heapArray.append(k) self.currentSize = self.currentSize + 1 self.percUp(self.currentSize) def delMin(self): retval = self.heapArray[1][1] self.heapArray[1] = self.heapArray[self.currentSize] self.currentSize = self.currentSize - 1 self.heapArray.pop() self.percDown(1) return retval def isEmpty(self): if self.currentSize == 0: return True else: return False def decreaseKey(self, val, amt): # this is a little wierd, but we need to find the heap thing to decrease by # looking at its value done = False i = 1 myKey = 0 while not done and i <= self.currentSize: if self.heapArray[i][1] == val: done = True myKey = i else: i = i + 1 if myKey > 0: self.heapArray[myKey] = (amt, self.heapArray[myKey][1]) self.percUp(myKey) def __contains__(self, vtx): for pair in self.heapArray: if pair[1] == vtx: return True return False def __str__(self): return str(self.heapArray)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。