Python 寻找欧拉之旅
我正在尝试解决Udacity上的一个问题,如下所述:Python 寻找欧拉之旅,python,algorithm,graph,discrete-mathematics,Python,Algorithm,Graph,Discrete Mathematics,我正在尝试解决Udacity上的一个问题,如下所述: # Find Eulerian Tour # # Write a function that takes in a graph # represented as a list of tuples # and return a list of nodes that # you would follow on an Eulerian Tour # # For example, if the input graph was # [(1, 2), (
# Find Eulerian Tour
#
# Write a function that takes in a graph
# represented as a list of tuples
# and return a list of nodes that
# you would follow on an Eulerian Tour
#
# For example, if the input graph was
# [(1, 2), (2, 3), (3, 1)]
# A possible Eulerian tour would be [1, 2, 3, 1]
我提出了以下解决方案,虽然没有一些递归算法那么优雅,但在我的测试用例中似乎确实有效
def find_eulerian_tour(graph):
tour = []
start_vertex = graph[0][0]
tour.append(start_vertex)
while len(graph) > 0:
current_vertex = tour[len(tour) - 1]
for edge in graph:
if current_vertex in edge:
if edge[0] == current_vertex:
current_vertex = edge[1]
elif edge[1] == current_vertex:
current_vertex = edge[0]
else:
# Edit to account for case no tour is possible
return False
graph.remove(edge)
tour.append(current_vertex)
break
return tour
graph = [(1, 2), (2, 3), (3, 1)]
print find_eulerian_tour(graph)
>> [1, 2, 3, 1]
然而,当我提交这篇文章时,我被评分员拒绝了。我做错了什么?我看不到任何错误。这里有一种情况是您的算法无法处理的:4个顶点上的完整图形。将
打印教程
粘贴在其中,您将获得:
>>> cg4 = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
>>> find_eulerian_tour(cg4)
[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 0]
[0, 1, 2, 0, 3]
[0, 1, 2, 0, 3, 1]
[0, 1, 2, 0, 3, 1]
[0, 1, 2, 0, 3, 1]
[etc.]
我会让你去寻找你的方法的问题——你可以很容易地在谷歌上搜索一个完整的实现,所以既然你没有,我假设你想自己找到解决这个问题的乐趣。:^)
编辑:
嗯。我承认,一开始我认为这只是一个失败案例。总之,@WolframH给了我一个更新的示例,但是您也可以查看5个顶点上的完整图,其中代码给出了
[0, 1, 2, 0, 3, 1, 4, 0]
并忽略边缘(2,3)、(2,4)和(3,4)。以下是算法失败的有效情况:
graph = [(1, 2), (2, 3), (3, 1), (3, 4), (4, 3)]
使用print
的功能,找出图形
和当前顶点
的情况
另一个提示:向下移动else
,使其属于for
的,并在for
循环未中断时执行。现在,它永远无法执行校正之后,算法当然仍然失败。
当然,算法仍然失败。
当然,算法仍然失败。
请不要评论说代码不起作用。没有。算法仍然失败,即使下面的代码符合OP的想法。关键是要证明OP的算法是错误的,OP无法确定。为此,需要OP算法的正确实现(见下文)。错误算法的正确实现仍然不是正确的解决方案
我很抱歉写了这么多冗长的解释,让这个答案变得更糟,但人们继续抱怨代码不起作用(当然,关键是要表明它是错误的)。他们也否决了这个答案,可能是因为他们希望能够复制代码作为解决方案。但这不是重点,重点是向OP显示他的算法有错误
下面的代码找不到欧拉旅行。寻找其他地方复制代码,以便通过考试强>
def find_eulerian_tour(graph):
tour = []
current_vertex = graph[0][0]
tour.append(current_vertex)
while len(graph) > 0:
print(graph, current_vertex)
for edge in graph:
if current_vertex in edge:
if edge[0] == current_vertex:
current_vertex = edge[1]
else:
current_vertex = edge[0]
graph.remove(edge)
tour.append(current_vertex)
break
else:
# Edit to account for case no tour is possible
return False
return tour
graph = [(1, 2), (2, 3), (3, 1), (3, 4), (4, 3)]
print(find_eulerian_tour(graph))
输出:
[(1, 2), (2, 3), (3, 1), (3, 4), (4, 3)] 1
[(2, 3), (3, 1), (3, 4), (4, 3)] 2
[(3, 1), (3, 4), (4, 3)] 3
[(3, 4), (4, 3)] 1
False
使用简单的递归,这个问题比上述解决方案更容易解决
def find_eulerian_tour(graph):
tour=[]
find_tour(graph[0][0],graph,tour)
return tour
def find_tour(u,E,tour):
for (a,b) in E:
if a==u:
E.remove((a,b))
find_tour(b,E,tour)
elif b==u:
E.remove((a,b))
find_tour(a,E,tour)
tour.insert(0,u)
此代码适用于任何元组输入列表,并返回一个巡更列表。
请发送建议和更改(如有)。
谢谢
@WolframH:如果图形中存在任何循环,并且输入元组只是为了让代码失败,那么代码就不起作用。我在Udacity上也学过同样的课程。我在维基百科上读了Hierholzer的算法后实现了它。这是指向算法的链接
下面是我的代码。毫无疑问,它被评分员接受了(在做了一些从Python3到Python2的更改之后)
希望这有帮助。我也在同一个讲座中,沃尔夫拉姆的答案对我不适用。以下是我的解决方案(已被评分员接受):
将所有可能的下一个节点
推入一个堆(搜索
),然后在录制时搜索其中的每一个节点
def next_node(edge, current):
return edge[0] if current == edge[1] else edge[1]
def remove_edge(raw_list, discard):
return [item for item in raw_list if item != discard]
def find_eulerian_tour(graph):
search = [[[], graph[0][0], graph]]
while search:
path, node, unexplore = search.pop()
path += [node]
if not unexplore:
return path
for edge in unexplore:
if node in edge:
search += [[path, next_node(edge, node), remove_edge(unexplore, edge)]]
if __name__ == '__main__':
graph = [(1, 2), (2, 3), (3, 1), (3, 4), (4, 3)]
print find_eulerian_tour(graph)
[1,3,4,3,2,1]
虽然代码对无向图无效,但对有向图运行得非常好。然而,从Udacity的角度来看,它仍然不能解决手头的问题,但可以被视为同一问题的较低版本。请不要介意使用糟糕的Python,因为我对该语言还是新手
在底部添加了两个相当复杂的测试场景
initialNode=''
波长=0
in_图形长度=0
normalizedGraph=list()
路径=[]
node_dict={}
mod_标志=“”
def查找欧拉之旅(图表):
全局输入图形长度
in_graphLength=len(图形)
图=规格化图(图,[],-1,len(图))
打印(路径)
返回路径
def规格化图(图、nG、remNode、长度):
计数器=0
全局路径
全局初始节点
全局输入图形长度
全球波长
全局标准化边图
localGraph=list()
路径=[]
路径列表=[]
如果(remNode!=-1):
标准化边图=nG
baseNode=0
如果(len(normalizedGraph)!=0):
ini1,ini2=标准化边图[0]
initialNode=ini1
a1,b1=标准化边图[len(标准化边图)-1]
baseNode=b1
如果(remNode!=-2):
graph.pop(remNode)
如果(remNode==-1):
a、 b=图[0]
baseNode=b
normalizedGraph.append(图[0])
initialNode=a
波长=1
graph.pop(0)
i=0
如果(len(图)!=0):
对于图中的n1和n2:
i=i+1
如果(n1==baseNode):
localGraph=graph[:]
如果(isJunction((n1,n2),localGraph,nglength)):
图1.pop(i-1)
附加图((n1,n2))
规格化图形(图形,规格化图形,-2,英寸图形长度)
打破
其他:
规范化edgraph.append((n1,n2))
nglength=nglength+1
归一化图(图,归一化图,i-1,in_图长度)
打破
其他:
如果(计数器==0):
计数器=计数器+1
a0,b0=标准化边图[0]
对于图中的n1、n2:
path.append(n1)
路径追加(a0)
路径=路径
返回路径
def isJunction((n1,n2),图形,长度:
全局节点
计数=0
如果(len(图)>1):
对于图中的a1、a2:
如果(n1==a1):
计数=计数+1
如果(计数>1):
如果(str(n1)不在节点目录中):
ke
def next_node(edge, current):
return edge[0] if current == edge[1] else edge[1]
def remove_edge(raw_list, discard):
return [item for item in raw_list if item != discard]
def find_eulerian_tour(graph):
search = [[[], graph[0][0], graph]]
while search:
path, node, unexplore = search.pop()
path += [node]
if not unexplore:
return path
for edge in unexplore:
if node in edge:
search += [[path, next_node(edge, node), remove_edge(unexplore, edge)]]
if __name__ == '__main__':
graph = [(1, 2), (2, 3), (3, 1), (3, 4), (4, 3)]
print find_eulerian_tour(graph)
initialNode = ''
nglength = 0
in_graphLength = 0
normalizedGraph = list()
path = []
node_dict = {}
mod_flag = ''
def find_eulerian_tour(graph):
global in_graphLength
in_graphLength = len(graph)
graph = normalize_graph(graph,[],-1,len(graph))
print (path)
return path
def normalize_graph(graph,nG,remNode,length):
counter = 0
global path
global initialNode
global in_graphLength
global nglength
global normalizedGraph
localGraph = list()
path = []
pathList = []
if(remNode != -1):
normalizedGraph = nG
baseNode = 0
if(len(normalizedGraph) != 0):
ini1, ini2 = normalizedGraph[0]
initialNode = ini1
a1,b1 = normalizedGraph[len(normalizedGraph) - 1]
baseNode = b1
if(remNode != -2):
graph.pop(remNode)
if(remNode == -1):
a,b = graph[0]
baseNode = b
normalizedGraph.append(graph[0])
initialNode = a
nglength = 1
graph.pop(0)
i = 0
if(len(graph) != 0):
for n1, n2 in graph:
i = i + 1
if(n1 == baseNode):
localGraph = graph[:]
if(isJunction((n1,n2),localGraph, nglength)):
graph.pop(i-1)
graph.append((n1,n2))
normalize_graph(graph, normalizedGraph, -2,in_graphLength)
break
else:
normalizedGraph.append((n1, n2))
nglength = nglength + 1
normalize_graph(graph, normalizedGraph, i - 1,in_graphLength)
break
else:
if( counter == 0):
counter = counter + 1
a0, b0 = normalizedGraph[0]
for n1, n2 in normalizedGraph:
path.append(n1)
path.append(a0)
path = path
return path
def isJunction((n1,n2), graph, nglength):
global node_dict
count = 0
if(len(graph) > 1):
for a1, a2 in graph:
if (n1 == a1):
count = count + 1
if (count > 1):
if(str(n1) not in node_dict):
key = str(n1)
node_dict[key] = count
else:
return handle_degree(n1)
return modification_needed((n1, n2), graph, nglength)
else:
return False
else:
return False
def handle_degree(n1):
global node_dict
key = str(n1)
if(node_dict.get(key) == 2):
return False
def modification_needed((n1,n2),graph, tmplength):
i = 0
global mod_flag
if( n2 == initialNode):
return True
if(len(graph) > 1):
for b1, b2 in graph:
if(n2 == b1):
i = i + 1
tmplength = tmplength + 1
if (b1,b2) in normalizedGraph:
mod_flag = True
continue
if(tmplength < in_graphLength and b2 == initialNode):
mod_flag = True
continue
else:
graph.pop(i-1)
modification_needed((b1,b2),graph,tmplength)
return mod_flag
#find_eulerian_tour([(1,2),(2,6),(7,2),(6,1),(2,3),(3,5),(3,4),(4,5),(5,7),(7,3),(5,6),(6,7)])
#find_eulerian_tour([(0,4),(1,0),(4,2),(4,8),(2,5),(9,5),(8,9),(5,4),(5,1),(7,1),(3,7),(1,6),(6,3)])
def find_eulerian_tour(graph):
def freqencies():
# save all nodes of edges to my_list
# e.g. [3,4,5,1,2,2,3,5]
my_list = [x for (x, y) in graph]
# get the max num of nodes-->create a list
# set all to 0
# for i in range(5) = 0 1 2 3 4
# so range("5" +1) means
# len=6, result=[0,0,0,0,0,0]
# so that the index = the number itself
result = [0 for i in range(max(my_list) + 1)]
# nodes in my_list, increment
# e.g. [0,1,2,2,1,2]
# 3appears 2times.
for i in my_list:
result[i] += 1
return result
# this is Frequencies of each nodes.
def find_node(tour):
for i in tour:
if freq[i] != 0:
return i
return -1
def helper(tour, next):
find_path(tour, next)
u = find_node(tour)
while sum(freq) != 0:
sub = find_path([], u)
# get the sub_path
# add them together
# when draw to u, turn to sub, and then come back to go on the original tour path
# [:a], start to a; [a+1:] a+1 to end
tour = tour[:tour.index(u)] + sub + tour[tour.index(u) + 1:]
u = find_node(tour)
return tour
def find_path(tour, next):
for (x, y) in graph:
if x == next:
# from "double-graph"
# pop out the current one and its respondent one
# actually it means we delete this edge
current = graph.pop(graph.index((x,y)))
graph.pop(graph.index((current[1], current[0])))
# now add this "next" node into the tour
tour.append(current[0])
# decrement in frequency
freq[current[0]] -= 1
freq[current[1]] -= 1
return find_path(tour, current[1])
# if this "next" node is not connected to any other nodes
# single one
tour.append(next)
return tour
# in graph, all edges get reversed one and be added to graph
# can call it "double-graph"
# it helps to calculate the frequency in find_path
# actually we can regard frequency as degrees for each node
graph += [(y, x) for (x, y) in graph]
freq = freqencies()
# set graph[0][0] as starting point
return helper([], graph[0][0])
graph = [(1, 2), (2, 3), (3, 1)]
print find_eulerian_tour(graph)
# eulerian_tour.py by cubohan
# circa 2017
#
# Problem statement: Given a list of edges, output a list of vertices followed in an eulerian tour
#
# complexity analysis: O(E + V) LINEAR
def find_eulerian_tour(graph):
edges = graph
graph = {}
degree = {}
start = edges[0][0]
count_e = 0
for e in edges:
if not e[0] in graph:
graph[e[0]] = {}
if not e[0] in degree:
degree[e[0]] = 0
if not e[1] in graph:
graph[e[1]] = {}
if not e[1] in degree:
degree[e[1]] = 0
graph[e[0]][e[1]] = 1
graph[e[1]][e[0]] = 1
degree[e[0]] += 1
degree[e[1]] += 1
count_e += 1
max_d = 0
this_ = 0
for v, d in degree.items():
if not d%2 == 0:
# Eulerian tour not possible as odd degree found!
return False
if d>max_d:
this_ = v
max_d = d
visited_e = {}
def is_visited(i, j):
key = str(sorted([i,j]))
if key in visited_e:
return True
else:
visited_e[key] = True
return False
start = this_
route = [start]
indexof = {}
indexof[start] = 0
while count_e>0:
flag = False
for to_v in graph[this_]:
if not is_visited(to_v, this_):
route.append([to_v])
indexof[to_v] = len(route)-1
degree[to_v] -= 1
if degree[to_v] == 0:
del degree[to_v]
degree[this_] -= 1
if degree[this_] == 0:
del degree[this_]
this_ = to_v
flag = True
count_e -= 1
break
if not flag:
break
for key, v in degree.items():
if v <=0:
continue
try:
ind = indexof[key]
except Exception as e:
continue
this_ = key
while count_e>0:
flag = False
for to_v in graph[this_]:
if not is_visited(to_v, this_):
route[ind].append(to_v)
degree[to_v] -= 1
degree[this_] -= 1
this_ = to_v
flag = True
count_e -= 1
break
if not flag:
break
route_ref = []
for r in route:
if type(r) == list:
for _r in r:
route_ref.append(_r)
else:
route_ref.append(r)
return route_ref
if __name__ == "__main__":
print find_eulerian_tour([(0, 1), (1, 5), (1, 7), (4, 5),(4, 8), (1, 6), (3, 7), (5, 9),(2, 4), (0, 4), (2, 5), (3, 6), (8, 9)])
def find_eulerian_tour(graph):
nodes = set()
for i in graph:
if not i[0] in nodes:
nodes.add(i[0])
if not i[1] in nodes:
nodes.add(i[1])
tour = []
tempstack = []
graphtemp = []
current_vertex = graph[0][0]
tour.append(current_vertex)
tempstack.append(current_vertex)
last_edge = ()
count = 0
while len(set(tempstack)) + 1 < len(nodes) or (count == 0 or tour[0] != tour[len(tour) - 1]):
count += 1
flag = False
for edge in graph:
if current_vertex in edge and edge != last_edge:
if current_vertex == edge[0]:
current_vertex = edge[1]
else:
current_vertex = edge[0]
last_edge = edge
graphtemp.append(edge)
graph.remove(edge)
tour.append(current_vertex)
tempstack.append(current_vertex)
flag = True
break
if flag == False:
tour.remove(current_vertex)
current_vertex = tempstack[0]
tempstack.remove(tempstack[0])
graph.append(graphtemp[0])
graphtemp.remove(graphtemp[0])
return tour
print find_eulerian_tour([(1,2), (2,3), (3,1)])
print(find_eulerian_tour([(0, 1), (1, 5), (1, 7), (4, 5), (4, 8), (1, 6), (3, 7), (5, 9), (2, 4), (0, 4), (2, 5), (3, 6), (8, 9)]))
print(find_eulerian_tour([(1, 13), (1, 6), (6, 11), (3, 13), (8, 13), (0, 6), (8, 9),(5, 9), (2, 6), (6, 10), (7, 9), (1, 12), (4, 12), (5, 14), (0, 1), (2, 3), (4, 11), (6, 9), (7, 14), (10, 13)]))
print(find_eulerian_tour([(8, 16), (8, 18), (16, 17), (18, 19), (3, 17), (13, 17), (5, 13),(3, 4), (0, 18), (3, 14), (11, 14), (1, 8), (1, 9), (4, 12), (2, 19),(1, 10), (7, 9), (13, 15), (6, 12), (0, 1), (2, 11), (3, 18), (5, 6), (7, 15), (8, 13), (10, 17)]))
import itertools
def create_tour(alist):
return list(itertools.permutations(alist,2))