递归函数进行深度优先搜索

时间:2015-11-02 07:40:29

标签: python python-3.x recursion

我正在尝试编写一个从命令行初始化的递归函数,该函数采用带有一系列起点,终点和这些点之间距离的文本文件,然后找到从特定起点到最短距离的最短距离一个特定的终点。

例如,文本文件看起来像这样:

a,b,5
a,c,8
b,d,6
c,d,2
d,e,12
d,f,2
e,g,3
f,g,7

将使用以下内容调用:

python program_name.py a b text_file_name.txt

这个想法是基于我即将开始的下一学期的课程项目,我希望能够从头开始。教授提供了一个广泛的“起始代码”here

我已经尝试了多种不同的方法来实现递归函数来遍历文本文件并记录和比较点之间的距离,但我似乎无法做到正确。现在我有一个(非常明显不正确的)代码:

if place not in distances:
    print('not in')
    distances[place] =roads[place]
    dist_so_far = distances[place]     
    dfs(place, 0.0, roads, distances)
elif place in distances and distances[place] <= dist_so_far:
    print('less than')
    #dfs(place,0.0, roads, distances)
elif place in distances and distances[place] > dist_so_far:
    print('greater than')
    distances[place] = dist_so_far
    dfs(place, 0.0, roads, distances)

我知道这是不对的,我只是认为它的格式是一个很好的起点。我似乎无法理解哪些字典包含要比较的内容和哪些索引。

1 个答案:

答案 0 :(得分:2)

  

我似乎无法理解哪些词典包含哪些内容和哪些索引要比较,这让我疯狂!

我会尝试解释starting-point code that your professor posted以及它正在做什么,而不是在银盘上给你解决方案(因为你将通过自己解决它来了解更多信息) )。

def read_distances(map_file):
    connections = dict()
    # ....
    return connections

此函数正在构建一个字典,您可以使用该字典查找是否有任何两个点相互连接,以及它们之间的距离。所以如果有一条线&#34; A,B,5&#34;在输入文本文件中,read_distances的结果将创建两个条目,一个用于值("B",5)的A,另一个用于值为("A",5)的B.另请注意,该词典中的每个条目都是列表的连接。换句话说:

# Assume that map_file contained one line: "A,B,5"
distances = read_distances(map_file)
print(distances["A"])  # Will print "[('B',5)]"
print(distances["B"])  # Will print "[('A',5)]"

如果有多个连接,例如如果文本文件包含:

A,B,5
A,C,3
B,C,4

然后你会得到类似的东西:

distances = read_distances(map_file)
print(distances["A"])  # Will print "[('B',5),('C',3)]"
print(distances["B"])  # Will print "[('A',5),('C',4)]"
print(distances["C"])  # Will print "[('A',3),('B',4)]"

因此,当您获得distances[starting_point]时,您会获得与您的起点有单一连接的所有点的列表。该列表由2元组(即具有2个元素的元组)组成,每个元组都具有结构(other_point,distance_as_int)。

我想我会停在这里,因为这可能只是足以帮助你解决当前的问题,而无需为你解决问题。 (我刚刚删除了一段我写的&#34;这里是我建议如何解决这个问题&#34;,因为我意识到除非你要求,否则我不应该给你帮助如果您需要更多帮助,请对此答案(或您的问题)发表评论,我应该收到通知。我很乐意为此提供更多帮助,特别是因为您在课堂开始之前采取正确的方法尝试自己解决问题。

更新1:

好的,还有一个小贴士不会为你解决问题。当您查找distances[starting_point]并获取元组列表时,您将希望遍历该列表,对每个元组执行某些操作。如,

connections = distances[start_point]
for connection in connections:
    end_point = connection[0]
    distance = connection[1]
    # Now do something with start_point, end_point, and distance
    # Precisely *what* you'll do with them is up to you

该代码可以简化一点,因为Python有一个很好的&#34;元组解包&#34;功能:如果你在产生元组的东西上循环,比如你的&#34;连接&#34;列表,你可以这样做:

connections = distances[start_point]
for end_point, distance in connections:
    # Now do something with start_point, end_point, and distance
    # Precisely *what* you'll do with them is up to you

这将自动&#34;解包&#34;你的元组。请注意,只有当您知道所有获得的元组具有相同数量的项目(在这种情况下为2)时,此方法才有效。还有一件事我们可以做,就是要注意我们 循环。消除它,代码变为:

connections

那是最清晰,最恐怖的&#34; Pythonic&#34;编写特定循环的方法。

注意:以上代码实际上不会按原样运行,因为Python要求任何循环必须至少包含一个语句,并且注释不会计数作为陈述。要使代码实际运行,您必须在循环中包含for end_point, distance in distances[start_point]: # Now do something with start_point, end_point, and distance # Precisely *what* you'll do with them is up to you 语句。 pass语句是一个特殊的语句,它只是一个&#34; no-op&#34;,也就是说,它什么都不做。所以要实际运行上面的代码,在循环中什么都不做,你要写:

pass

将允许该循环,而没有单词for end_point, distance in distances[start_point]: # Now do something with start_point, end_point, and distance # Precisely *what* you'll do with them is up to you pass 的代码将产生pass。为简单起见,我在上面的所有示例中都将IndentationError留了出来,但我认为值得一提的是为什么该代码不会按原样运行。

更新2:

根据要求,这是一个可以解决此问题的功能,因此您可以逐步完成并了解正在进行的操作。我已经发表了大量的评论,但没有评论,这只是八行代码。

pass

是的,真的是这样。我针对你提供的样本距离文件运行它,它找到每对点的最短距离。尝试逐步完成算法,看看你是否理解为什么会这样。

一个关键概念供您理解,但是,当您第一次看到它时,可能不会立即显现出来。这个关键概念是: Python字典是持久且可变的。也就是说,如果您将字典对象(如本示例中的def dfs(place, dist_so_far, roads, distances): """Depth-first search, which may continue from from_place if dist_so_far is the shortest distance at which it has yet been reached. Args: place: Currently searching from here dist_so_far: Distance at which from_place has been reached this time (which may not be the shortest path to from_place) roads: dict mapping places to lists of hops of the form (place, hop-distance) distances: dict mapping places to the shortest distance at which they have been reached so far (up to this time). """ #FIXME # Consider cases: # - We've never been at place before (so it's not in distances) # - We've been at place before, on a path as short as this one (in distances) # - We've been here before, but this way is shorter (dist_so_far) # Consider which are base cases, and which require recursion. # For the cases that require recursion, what is the progress step? # First scenario: we've never reached this place before if place not in distances: # Right now we only know one way to get to this place, # so that's automatically the shortest known distance. distances[place] = dist_so_far # Second scenario: we've been here before, via a route # that was shorter than dist_so_far. If so, then any # roads from here lead to places we've also already # visited via a shorter route. Any distance we calculate # right now would just be longer than the distance we've # already found, so we can just stop right now! if dist_so_far > distances[place]: return # Third scenario: dist_so_far is actually the shortest # path we've found yet. (The first scenario is actually # a special case of this one!) We should record the # shortest distance to this place, since we'll want to # use that later. Then we'll look at all the roads from # this place to other places, and for each of those # other places, we'll make a recursive call to figure # out more paths. # Note no "if" statement needed: because of the return # statement earlier, if we get here, we know that the # current route is the best one yet known. distances[place] = dist_so_far # Now for some recursion: for other_place, hop_distance in roads[place]: dist_to_other_place = dist_so_far + hop_distance dfs(other_place, dist_to_other_place, roads, distances) # That's it! 字典)传递给函数,并且该函数修改字典,您传递给函数的字典将被修改

顺便说一句,专业程序员倾向于认为这是 BAD 的事情,因为调用函数不应该意外地修改你的参数。这往往会导致程序中的细微,难以追踪的错误,通常应该避免。 (请注意该句中的意外一词。如果您调用的函数名为distances,那么您可能期待你要修改的词典。)

但是,在这种特殊情况下,通常被认为是错误副作用的字典修改效果是编写高效代码的关键。 add_value_to_dict字典用于跟踪我们到目前为止所发现的内容,并查看是否还有任何工作要做。由于您希望通过递归调用distances来修改它,因此您不会为自己创建微妙的错误。但是我不想让你知道这里使用的技术,修改一个作为参数传递给你的函数的字典,总是一个好主意。大多数情况下,它会导致你在几个月后才会发现的细微错误。

好吧,这可能是足够的解释。看看你是否可以逐步掌握这些代码并理解它。如果有任何令你困惑的事情,请告诉我。