python 算法 day11 图 词梯

顶点 vertex:是图的基础部分

边 edge:如果一个边连接两个点,则表示两者具有联系,边可以是单向的也可以是双向的,如果一个图中的边都是单向的,我们就说这个图是有向图

权重 weight:一个顶点到另一个顶点的“代价”,可以给边赋权

路径 path:由边依次连接起来的顶点序列

圈 cycle:有向图里的圈是首尾顶点相同的路径

邻接矩阵

用邻接矩阵表示图的优点是简单,很容易看出节点之间的联系状态,然而会有大量的矩阵分量是空的

邻接表

一个更高效的方法是使用邻接表,我们采用维护一个包含所有顶点的主列表,再关联一个与自身有边连接的所以顶点的列表,我们采用字典来实现顶点类,字典中的key对应连接的顶点标示,value对应与自身连接的点的权重

class Vertex:
    def __init__(self,key):
        self.id = key
        self.connectedTo = {}
    def addNeighbor(self,nbr,weight=0):
        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]

 

图的类是把刚才的顶点类和顶点重新构造成一个字典

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
    def getVertex(self,n):
        if n in self.vertList:
            return self.vertList[n]
        else:
            return None
    def __contains__(self, item):   重载in
        return item 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())

 

 词梯问题

  • 以图的形式描绘出单词间的关系
  • 利用广度优先搜索(BFS)的图算法找到一条从开始单词到目标单词的最短路径

首先解决如何将大量单词组成的集合转变成图,我们先假设我们有一个单词列表,其中的单词都一样长,我们可以为列表里的每个单词在图中创建一个顶点,而为将这些单词连接起来,我们可以将列表中的每个词与所有其他单词比较,当我们比较的时候,我们要看到单词中有多少字母是不同的,如果两个词中只有一个字母不同,我们就可以在图中创建一条连接他们的边 算法复杂度是O(n2)

 

优化方法:假设我们有非常多的桶,每个桶外都贴有一个四字字母的单词标签,并且标签上有且只有一个字母被通配符-代替

我们可以通过一个字典来实现 桶上的标签作为一个key值  value对应桶里单词的列表

from pythonds.graphs import Graph
def buildGraph(wordFile):
    d = {}
    g = Graph()
    w_file = open(wordFile,'r')  这里文件每行只有一个单词
    for line in w_file:
        word = line[:-1] 去除行结尾的回车字符
        for i in range(len(word)):
            bucket = word[:i]+'_'+word[i+1:]  将第i个字符去除作为通配符
            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)

 

 实现广度优先搜索(BFS)

已知一个图G和它的一个起始节点s,广度优先搜索(BFS)通过搜索图中的边来找到图G中所有和s有路径相连的顶点,其显著特点是在搜索达到距离k+1的顶点之前,BFS会找全部距离为k的顶点 为了追踪这一过程,会给每一个搜索过的顶点染成灰色 每一个顶点在被构建时都初始化为白色,当完全搜索完一个层级的节点时 它会被染成黑色,这意味着一旦一个节点染成黑色,它就没有邻近的白色节点,而另一方面,如果一个顶点被标示成了灰色,这就意味这附近还可能存在为探索的顶点等待被探索

 

 节点类中还需添加三个新的实例变量:距离 父顶点和颜色

对于起始节点 设置0和None 随后起始节点加入一个队列中,下一步便是系统地搜索队首顶点,这个过程通过迭代遍历队首顶点的邻接列表来完成,每检查邻接表中的一个顶点,便会维护这个顶点的颜色变量:

  1. 新的未探索的顶点nbr 标记成灰色
  2. nbr的父顶点被设置成当前节点currentVert
  3. nbr的距离被设置成当前节点的距离+1
  4. nbr被加入队尾,这一操作使得直到nbr在当前顶点的邻接列表中所有节点被搜索完后,才能进行下一层次的探索操作

 

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)  每次标记完节点 再把下一层节点 为白色的加入队列  直到最后没有元素

 

 对BFS的分析:

当while循环被执行时,图的顶点集合|v|的每个顶点最多被访问一次,因而while循环拥有O(V)的复杂度 while中的for循环语句,对于图的边集|E|中的每一条边至多会被执行一次,这是因为每个顶点只会最多被出队一次,对于从顶点u到顶点v的边,只有当顶点u出队的时候我们才会检查,所以每条边最多被检查一次 算法复杂度为O(V+E)

 

posted @ 2019-02-28 13:42  碎纸屑  阅读(392)  评论(0编辑  收藏  举报