Поиск цикла Эйлера алгоритмом backtracking

Алгоритм поиска с возвратом является хоть и затратным, но зато достаточно универсальным методом. Связан он с перебором вершин графа. Возникает вопрос: можно ли интерпретировать ребра в качестве вершин? Вот эта идея и реализуется.

Код позволяет найти, с учетом стартовой вершины, цикл Эйлера или хотя бы его маршрут (полу-Эйлер). По сути, за вершину в такой интерпретации берется множество из двух соседних вершин. Реализовано в dict_to_setEdge. Что касается dfs, то это стандартная реализация обхода в глубину с возвратом.

рис. из интернета

рис. из интернета

# не Эйлер
G1 = {
    1: [2, 3, 5],
    2: [1, 4, 5],
    3: [1, 4, 5],
    4: [2, 3, 5],
    5: [1, 2, 3, 4]
}

# полу-Эйлер
G2 = {
    1: [2, 3, 5],
    2: [1, 4, 5, 6],
    3: [1, 4, 5],
    4: [2, 3, 5, 6],
    5: [1, 2, 3, 4],
    6: [2, 4]
}

# Эйлер
G3 = {
    1: [2, 3, 5, 7],
    2: [1, 4, 5, 6],
    3: [1, 4, 5, 7],
    4: [2, 3, 5, 6],
    5: [1, 2, 3, 4],
    6: [2, 4],
    7: [1, 3]
}

def dfs(g, v, path):
    #print("path =", path)
    if len(path)-1 == len(edgeSet): 
        return path
    for w in g[v]:
        if not {v, w} in path: 
            path.append({v, w})
            newpath = dfs(g, w, path) 
            if newpath: 
                return newpath 
    path.pop()
   
def dict_to_setEdge(G):
    s = []
    for v in G:
        for z in G[v]:
            if not {v, z} in s:
                s.append({v, z})
    return s

   
v = [1, 2]

for i,g in enumerate([G1, G2, G3]):
    edgeSet = dict_to_setEdge(g)
    print("==== граф ===", i+1)
    for s in v:
        print(" для вершины =", s)
        p = dfs(g, s, [{s, None}])
        if not p:
            print("Эйлер и полу-Эйлер не найдены")
        elif len({s, None}.intersection(p[-1])):
            print("Эйлер найден", p[1:])
        else: print("Полу-Эйлер найден", p[1:])
        print()

d5ba236e08fc96c5cf17341e2f085558.jpg

Теперь модифицируем формат данных так, чтобы была возможность обработки мульти-графов. Для этого, во-первых, добавим именованные ребра. Во-вторых, к двум смежным вершинам (3,7) добавим еще одно ребро (для G5) или еще два ребра (для G4). Соответственно, G4 Эйлеровым графом останется.

# Эйлер
G4 = {
    1: [(2,1), (3,1), (5,1), (7,1)],
    2: [(1,1), (4,1), (5,1), (6,1)],
    3: [(1,1), (4,1), (5,1), (7,1), (7,2), (7,3)],
    4: [(2,1), (3,1), (5,1), (6,1)],
    5: [(1,1), (2,1), (3,1), (4,1)],
    6: [(2,1), (4,1)],
    7: [(1,1), (3,1), (3,2), (3,3)]
}

# не Эйлер
G5 = {
    1: [(2,1), (3,1), (5,1), (7,1)],
    2: [(1,1), (4,1), (5,1), (6,1)],
    3: [(1,1), (4,1), (5,1), (7,1), (7,2)],
    4: [(2,1), (3,1), (5,1), (6,1)],
    5: [(1,1), (2,1), (3,1), (4,1)],
    6: [(2,1), (4,1)],
    7: [(1,1), (3,1), (3,2)]
}

def dfs(g, v, path):
    #print("path =", path)
    if len(path)-1 == len(edgeSet):
        return path
    
    for w in g[v]:
        if not ({v, w[0]}, w[1]) in path: 
            path.append(({v, w[0]}, w[1]))
            newpath = dfs(g, w[0], path) 
            if newpath: 
                return newpath 
    path.pop()
   
def dict_to_setEdge(G):
    s = []
    for v in G:
        for z in G[v]:
            if not ({v, z[0]}, z[1]) in s:
                s.append(({v, z[0]}, z[1]))
    return s

    
v = [1, 2]

for i,g in enumerate([G4, G5]):
    edgeSet = dict_to_setEdge(g)
    #print(edgeSet); input()
    print("\n==== граф ===", i+1)
    for s in v:
        print("для вершины =", s)
        p = dfs(g, s, [{s, None}])
        if not p:
            print("Эйлер и полу-Эйлер не найден")
        elif len({s, None}.intersection(p[-1][0])):
            print("Эйлер найден", p[1:])
        else: print("Полу-Эйлер найден", p[1:])
        print()

6361930a54e3ca1ff79bd62d82054899.jpg

Вот и все.

© Habrahabr.ru