背景:词梯问题
从一个单词演变为另一个单词,其中的过程可以经过多个中间单词,要求是相邻两个单词之间差异只能是1个字母。如FOOL变为SAGE:FOOL >> POOL >> POLL >> POLE >> PALE >> SALE >> SAGE。
我们的目标是找到最短的单词变换序列,采用图来解决这个问题的步骤如下:将可能的单词之间的演变关系表达为图,采用广度优先搜索BFS,来搜寻从开始单词到结束单词之间的所有有效路径,选择其中最快到达目标单词的路径。
从FOOL到SAGE的词梯解,所用的图是无向图,边没有权重,FOOL到SAGE的每条路径都是一个解。
注意:构建单词关系图,若包含较多单词时,采用邻接矩阵来表示时会是一个非常稀疏的图,单元利用率仅为百分之几。
采用字典建立词梯桶
def buildGraph(wordFile):
d = {}
g = Graph()
wfile = open(wordFile,'r')
for line in wfile:
word = line[:-1]
for i in range(len(word)): # 4字母单词可属于4个桶
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
广度优先算法(BFS)
BFS是搜索图的最简单算法之一,也是其它一些重要的图算法的基础。
算法思路:给定图G,以及开始搜索的起始顶点s。BFS搜索所有从s可到达顶点的边,而且在达到更远的距离k+1的顶点之前,BFS会找到全部距离为k的顶点。可以想象为以s为根,构建一棵树的过程,从顶部向下逐步增加层次。广度优先搜索能保证在增加层次之前,添加了所有兄弟节点到树中。
以词梯问题为例:说明广度优先算法的过程:
从起始顶点s开始,作为刚发现的顶点,标注为灰色(已发现),距离为0,前驱为None,加入队列,接下来是个循环迭代过程:从队首取出一个顶点作为当前顶点;遍历当前顶点的邻接顶点,如果是尚未发现的白色顶点,则将其颜色改为灰色(已发现),距离增加1,前驱顶点为当前顶点,加入到队列中;遍历完成后,将当前顶点设置为黑色(已探索过),循环回到步骤1的队首取当前顶点。
### 广度优先算法
def bfs(g,start):
start.setdistance(0)
start.setpred(None)
vertqueue=Queue()
vertqueue.enqueue(start)
while (vertqueue.size()>0): #取队首为当前节点
currentvert=vertqueue.dequeue() #遍历临接顶点
for nbr in currentvert.getConnections():
if (nbr.getcolor()=='white'):
nbr.setcolor('gray')
nbr.setdistance(currentvert.getdistance()+1)
nbr.setpred(currentvert)
vertqueue.enqueue(nbr)
currentvert.setcolor('black') #设置当前顶点为黑色
def tranverse(y): #回溯函数来确定特定单词到任何单词顶点的最短词梯
x=y
while (x.getpred()):
print(x.getId())
x=x.getpred()
print (x.getId())
wordgraph=buildGraph(fourletterwords.txt)
bfs(wordgraph,wordgraph.getVertex('fool'))
tranverse(wordgraph.getvertex('sale'))
算法分析:
(1)BFS算法主体是两个循环的嵌套,while循环对每个顶点访问一次,所以是O(|V|),而嵌套在while中的for,由于每条边只有在其起始顶点u出队的时候才会被检查一次,而每个顶点最多出队一次,所以边最多被检查1次,一共是0(|E|),综合起来BFS的时间复杂度为0(|V|+|E|)。
(2)词梯问题还包括两个部分算法:建立BFS树之后,回溯顶点到起始顶点的过程,最多为0(|V|);创建单词关系图也需要时间,最多为O(|V|^2)。