【问题标题】:Traversing through all the available paths in a diagraph遍历图中所有可用路径
【发布时间】:2018-06-05 15:26:34
【问题描述】:

有一个带有如下数字的图形结构。

在 python 的图形对象中加载这个结构。我把它做成了一个多行字符串,如下所示。

myString='''1
2 3
4 5 6
7 8 9 10
11 12 13 14 15'''

将其表示为列表列表。

>>> listofLists=[ list(map(int,elements.split())) for elements in myString.strip().split("\n")]
>>> print(listofLists)
[[1], [2, 3], [4, 5, 6], [7, 8, 9, 10], [11, 12, 13, 14, 15]]

在 python 中使用下面的节点和边类创建图形结构

节点类,它需要位置作为一个元组和一个值 示例:元素及其位置、值

1 --- position (0,0) and value is 1
2 --- position (1,0) and value is 2
3 --- position (1,1) and value is 3

节点类

class node(object):
    def __init__(self,position,value):
        '''
        position : gives the position of the node wrt to the test string as a tuple
        value    : gives the value of the node
        '''
        self.value=value
        self.position=position

    def getPosition(self):
        return self.position

    def getvalue(self):
        return self.value

    def __str__(self):
        return 'P:'+str(self.position)+' V:'+str(self.value)

edge 类在两个节点之间创建一条边。

class edge(object):
    def __init__(self,src,dest):
        '''src and dest are nodes'''
        self.src = src
        self.dest = dest

    def getSource(self):
        return self.src

    def getDestination(self):
        return self.dest
    #return the destination nodes value as the weight
    def getWeight(self):
        return self.dest.getvalue()

    def __str__(self):
        return (self.src.getPosition(),)+'->'+(self.dest.getPosition(),)

有向图类如下。图结构构建为字典邻接列表。 {node1:[node2,node3],node2:[node3,node4]......}

class Diagraph(object):

    '''the edges is a dict mapping node to a list of its destination'''
    def __init__(self):
        self.edges = {}

    '''Adds the given node as a key to the dict named edges ''' 
    def addNode(self,node):
        if node in self.edges:
            raise ValueError('Duplicate node')
        else:
            self.edges[node]=[]

    '''addEdge accepts and edge class object checks if source and destination node are present in the graph '''     
    def addEdge(self,edge):
        src = edge.getSource()
        dest = edge.getDestination()
        if not (src in self.edges and dest in self.edges):
            raise ValueError('Node not in graph')
        self.edges[src].append(dest)

    '''getChildrenof returns  all the children of the node'''   
    def getChildrenof(self,node):
        return self.edges[node]

    '''to check whether a node is present in the graph or not'''    
    def hasNode(self,node):
        return node in self.edges

    '''rootNode returns the root node i.e node at position (0,0)''' 
    def rootNode(self):
        for  keys in self.edges:
            return keys if keys.getPosition()==(0,0) else 'No Root node for this graph'

一个创建和返回图形对象的函数。

def createmygraph(testString):
    '''input is a multi-line string'''

    #create a list of lists from the string
    listofLists=[ list(map(int,elements.split())) for elements in testString.strip().split("\n")]
    y = Diagraph()
    nodeList = []

    # create all the nodes and store it in a list nodeList
    for i in range(len(listofLists)):
        for j in range(len(listofLists)):
            if i<=j:
                mynode=node((j,i),listofLists[j][i])
                nodeList.append(mynode)
                y.addNode(mynode)

    # create all the edges
    for srcNode in nodeList:
    # iterate through all the nodes again and form a logic add the edges
        for destNode in nodeList:
            #to add the immediate down node eg : add 7 (1,0) to 3 (0,0) , add 2 (2,0) to 7 (1,0)
            if srcNode.getPosition()[0]==destNode.getPosition()[0]-1 and srcNode.getPosition()[1]==destNode.getPosition()[1]-1:
                y.addEdge(edge(srcNode,destNode))
            #to add the bottom right node eg :add 4 (1,1) to 3 (0,0) 
            if srcNode.getPosition()[0]==destNode.getPosition()[0]-1 and srcNode.getPosition()[1]==destNode.getPosition()[1]:
                y.addEdge(edge(srcNode,destNode))

    return y

如何列出两个节点之间所有可用的路径。特别是 1---->11 , 1---->12 , 1---->13 , 1---- >14 , 1---->15 对于这种情况,我尝试了左优先深度优先方法。但它无法获得路径。

def leftFirstDepthFirst(graph,start,end,path,valueSum):
    #add input start node to the path
    path=path+[start]
    #add the value to the valueSum variable
    valueSum+=start.getvalue()
    print('Current Path ',printPath(path))
    print('The sum is ',valueSum)
    # return if start and end node matches.
    if start==end:
        print('returning as start and end have matched')
        return path

    #if there are no further destination nodes, move up a node in the path and remove the current element from the path list.
    if not graph.getChildrenof(start):
        path.pop()
        valueSum=valueSum-start.getvalue()
        return leftFirstDepthFirst(graph,graph.getChildrenof(path[-1])[1],end,path,valueSum)
    else:
        for aNode in graph.getChildrenof(start):
            return leftFirstDepthFirst(graph,aNode,end,path,valueSum)
    print('no further path to explore')

测试代码。

#creating a graph object with given string
y=createmygraph(myString)

函数返回终端节点,如 11、12、13、14、15。

def fetchTerminalNode(graph,position):
    terminalNode=[]
    for keys in graph.edges:
        if not graph.edges[keys]:
            terminalNode.append(keys)
    return terminalNode[position]

运行深度优先左前函数。

source=y.rootNode() # element at position (0,0)
destination=fetchTerminalNode(y,1) #ie. number 12
print('Path from ',start ,'to ',destination)
xyz=leftFirstDepthFirst(y,source,destination,[],0)

为元素 11 和 12 获取路径,但不是为 13、14 或 15 获取路径。即 destination=fetchTerminalNode(y,2) 不起作用。请任何人提出解决此问题的方法。

【问题讨论】:

    标签: python-3.x algorithm dictionary graph graph-algorithm


    【解决方案1】:

    给定一个tree

    tree = \
      [ [1]
      , [2, 3]
      , [4, 5, 6]
      , [7, 8, 9, 10]
      , [11, 12, 13, 14, 15]
      ]
    

    还有一个traverse 函数

    def traverse (tree):
      def loop (path, t = None, *rest):
        if not rest:
          for x in t:
            yield path + [x]
        else:
          for x in t:
            yield from loop (path + [x], *rest)
      return loop ([], *tree)
    

    遍历所有路径...

    for path in traverse (tree):
      print (path)
    
    # [ 1, 2, 4, 7, 11 ]
    # [ 1, 2, 4, 7, 12 ]
    # [ 1, 2, 4, 7, 13 ]
    # [ 1, 2, 4, 7, 14 ]
    # [ 1, 2, 4, 7, 15 ]
    # [ 1, 2, 4, 8, 11 ]
    # [ 1, 2, 4, 8, 12 ]
    # ...
    # [ 1, 3, 6, 9, 15 ]
    # [ 1, 3, 6, 10, 11 ]
    # [ 1, 3, 6, 10, 12 ]
    # [ 1, 3, 6, 10, 13 ]
    # [ 1, 3, 6, 10, 14 ]
    # [ 1, 3, 6, 10, 15 ]
    

    或者将所有路径收集到一个列表中

    print (list (traverse (tree)))
    # [ [ 1, 2, 4, 7, 11 ]
    # , [ 1, 2, 4, 7, 12 ]
    # , [ 1, 2, 4, 7, 13 ]
    # , [ 1, 2, 4, 7, 14 ]
    # , [ 1, 2, 4, 7, 15 ]
    # , [ 1, 2, 4, 8, 11 ]
    # , [ 1, 2, 4, 8, 12 ]
    # , ...
    # , [ 1, 3, 6, 9, 15 ]
    # , [ 1, 3, 6, 10, 11 ]
    # , [ 1, 3, 6, 10, 12 ]
    # , [ 1, 3, 6, 10, 13 ]
    # , [ 1, 3, 6, 10, 14 ]
    # , [ 1, 3, 6, 10, 15 ]
    # ]
    

    在上面,我们使用了生成器,它是 Python 中的高级功能。也许您想了解如何使用更原始的功能实现解决方案...

    我们在这里寻找的通用机制是 list monad,它捕捉了模糊计算的概念;一些可能返回多个值的过程。

    Python 已经提供了列表以及使用[] 构造它们的方法。我们只需要提供绑定操作,下面命名为flat_map

    def flat_map (f, xs):
      return [ y for x in xs for y in f (x) ]
    
    def traverse (tree):
      def loop (path, t = None, *rest):
        if not rest:
          return map (lambda x: path + [x], t)
        else:
          return flat_map (lambda x: loop (path + [x], *rest), t)
      return loop ([], *tree)
    
    print (traverse (tree))
    # [ [ 1, 2, 4, 7, 11 ]
    # , [ 1, 2, 4, 7, 12 ]
    # , [ 1, 2, 4, 7, 13 ]
    # , ... same output as above ...
    # ]
    

    哦,Python 有一个内置的product 函数,它恰好可以按照我们的需要工作。唯一的区别是路径将输出为元组(),而不是列表[]

    from itertools import product
    
    tree = \
      [ [1]
      , [2, 3]
      , [4, 5, 6]
      , [7, 8, 9, 10]
      , [11, 12, 13, 14, 15]
      ]
    
    for path in product (*tree):
      print (path)
    
    # (1, 2, 4, 7, 11)
    # (1, 2, 4, 7, 12)
    # (1, 2, 4, 7, 13)
    # (1, 2, 4, 7, 14)
    # (1, 2, 4, 7, 15)
    # ... same output as above ...
    

    在您的程序中,您尝试通过各种类nodeedgediagraph 来抽象这种机制。最终,您可以随心所欲地构建您的程序,但要知道它不需要比我们在此处编写的更复杂。


    更新

    正如@user3386109 在 cmets 中指出的那样,我上面的程序会生成路径,就好像每个父节点都连接到 所有 子节点一样。然而,这是一个错误,因为您的图表显示父母只连接到 相邻 孩子。我们可以通过修改我们的程序来解决这个问题——以下更改为粗体

    def traverse (tree):
      def loop (path, i, t = None, *rest):
        if not rest:
          for (j,x) in enumerate (t):
            if i == j or i + 1 == j:
              yield path + [x]
        else:
          for (j,x) in enumerate (t):
            if i == j or i + 1 == j:
              yield from loop (path + [x], j, *rest)
      return loop ([], 0, *tree)

    在上面,我们使用索引ij 来确定哪些节点是“相邻的”,但它把我们的loop 弄得乱七八糟。另外,新代码看起来像是引入了一些重复。给这个意图一个名字adjacencies 让我们的函数更干净

    def traverse (tree):
      def adjacencies (t, i):
        for (j, x) in enumerate (t):
          if i == j or i + 1 == j:
            yield (j, x)
    
      def loop (path, i, t = None, *rest):
        if not rest:
          for (_, x) in adjacencies (t, i):
            yield path + [x]
        else:
          for (j, x) in adjacencies (t, i):
            yield from loop (path + [x], j, *rest)
    
      return loop ([], 0, *tree)

    使用是一样的,但是这次我们得到的是原题中指定的输出

    for path in traverse (tree):
      print (path)
    
    # [1, 2, 4, 7, 11]
    # [1, 2, 4, 7, 12]
    # [1, 2, 4, 8, 12]
    # [1, 2, 4, 8, 13]
    # [1, 2, 5, 8, 12]
    # [1, 2, 5, 8, 13]
    # [1, 2, 5, 9, 13]
    # [1, 2, 5, 9, 14]
    # [1, 3, 5, 8, 12]
    # [1, 3, 5, 8, 13]
    # [1, 3, 5, 9, 13]
    # [1, 3, 5, 9, 14]
    # [1, 3, 6, 9, 13]
    # [1, 3, 6, 9, 14]
    # [1, 3, 6, 10, 14]
    # [1, 3, 6, 10, 15]
    

    这个简单的adjancies 函数之所以起作用,是因为您的输入数据是统一且有效的。您可以通过对图像中的路径进行颜色编码来清楚地显示索引ii + 1。我们永远不必担心 index-out-of-bounds 错误,因为您可以看到 i + 1 永远不会在没有子节点的节点上计算(即最后一行)。如果您要指定无效数据,traverse 不保证有效结果。

    【讨论】:

    • 此答案中的每个输出都包含1, 2, 4, 7, 13 行。从问题顶部的图像来看,这是不可能的,因为 7 与 13 没有优势。
    • 我好像忽略了这个细节!我的想法的核心是相同的,但是需要进行更改以仅绑定相邻的孩子而不是所有孩子。如果我有时间,我稍后会重新访问。
    • @user3386109 我进行了更新以解决您的评论:D
    • 感谢您的详细解释,上面的代码提供了从开始节点1到结束节点11、12、13、14、15的所有可能路径。但是,我一直在寻找一个使用图论来回答问题的答案。我同意but know that it doesn't need to be more complex than we have written it here. 我正在学习具有图算法的 edx 课程 6.00.2x,尽管我可以在这里应用它。如果您能建议一种使用图表来解决这个问题的方法,那将会很有帮助。
    【解决方案2】:

    我想出的 PFB 函数使用 breathfirstSearch 打印根节点和端节点之间的所有可用路径。

    谷歌Colab/github链接

    def breathfirstalgo(graph,tempPaths,finalPath):
    ## iterates over all the lists inside the tempPaths and checks if there are child nodes to its last node.
    condList=[graph.getChildrenof(apartList[-1]) for apartList in tempPaths if graph.getChildrenof(apartList[-1])]
    
    tempL=[]    
    if condList:
    
        for partialList in tempPaths:
            #get the children of the last element of partialList
            allchild=graph.getChildrenof(partialList[-1])
    
            if allchild:
                noOfChild=len(allchild)
                #create noOfChild copies of the partialList
                newlist=[partialList[:] for _ in range(noOfChild)]      
                #append the a child element to the new list
                for i in range(noOfChild):
                    newlist[i].append(allchild[i])
    
                #append each list to the temp list tempL
                for alist in newlist:
                    tempL.append(alist)
    
            else:
                pass
    
        #after completion of the for loop i.e iterate through 1 level
        return breathfirstalgo(graph,tempL,finalPath)
    else:
        #append all the lists from tempPaths to finalPath that will be returned
        for completePath in tempPaths:
            finalPath.append(completePath)
        return finalPath
    

    如下测试呼吸优先搜索解决方案。

    mygraph=createmygraph(myString)
    print('The graph object is ',mygraph)
    print('The root node is ',mygraph.rootNode())
    #print(mygraph)
    all_list=breathfirstalgo(mygraph,tempPaths=[[mygraph.rootNode()]],finalPath=[])
    
    print('alllist is ')
    for idx,partlist in enumerate(all_list):
        print(printPath(partlist))
    

    将节点类的__str__修改为str(self.value)后输出如下

    The graph object is  <__main__.Diagraph object at 0x7f08e5a3d128>
    The root node is  1
    alllist is 
    -->1-->2-->4-->7-->11
    -->1-->2-->4-->7-->12
    -->1-->2-->4-->8-->12
    -->1-->2-->4-->8-->13
    -->1-->2-->5-->8-->12
    -->1-->2-->5-->8-->13
    -->1-->2-->5-->9-->13
    -->1-->2-->5-->9-->14
    -->1-->3-->5-->8-->12
    -->1-->3-->5-->8-->13
    -->1-->3-->5-->9-->13
    -->1-->3-->5-->9-->14
    -->1-->3-->6-->9-->13
    -->1-->3-->6-->9-->14
    -->1-->3-->6-->10-->14
    -->1-->3-->6-->10-->15
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-10-09
      • 2020-07-20
      • 2015-04-19
      • 2019-06-03
      • 1970-01-01
      • 2019-04-25
      • 1970-01-01
      相关资源
      最近更新 更多