Python 寻找欧拉之旅

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), (

我正在尝试解决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), (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))