Как найти кратчайший путь из набора списков, представляющих железнодорожные линии?

Итак, у меня есть четыре линии поезда, представленные в виде списков:

line1 = ["a", "b", "f", "d", "e"]
line2 = ["c", "e", "j", "g", "i"]
line3 = ["c", "j", "k", "l", "m"]
line4 = ["h", "b", "e", "a", "n"]

По сути, каждая буква служит станцией. Если станция появляется на нескольких линиях, вы можете переходить с одной линии на другую, подобно многим подземным городским транспортным системам. Например, кратчайший путь от a до h будет [a, b, h], потому что вы можете перейти от a к b в строке 1, перейти к строке 4, а затем перейти от b к h.

Я хочу найти простой способ найти кратчайший путь с учетом пункта отправления и пункта назначения. Мое текущее решение - преобразовать линии в график, найдя соседние станции станции и связав их с этой станцией.

stations = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n"]
allLines = [line1, line2, line3, line4]
nodeGraph = {}
def getList(letter):
  neighbors = []
  for i in allLines:
     if letter in i:
        pos = i.index(letter)
        if pos == 0:
            neighbors.append(i[pos+1])
        elif pos == len(i) - 1:
            neighbors.append(i[pos-1])
        elif pos > 0 and pos < len(i) - 1:
            neighbors.append(i[pos-1])
            neighbors.append(i[pos+1])
  return neighbors

for station in stations:
   nodeGraph[station] = getList(station)

Затем я нашел функцию кратчайшего пути на этом веб-сайте, которая выводит кратчайший путь из ввод графа.

def SP(graph, start, end, path=[]):
  path = path + [start]
  if start == end:
      return path
  shortest = None
  for node in graph[start]:
      if node not in path:
          newpath = SP(graph, node, end, path)
          if newpath:
              if not shortest or len(newpath) < len(shortest):
                  shortest = newpath
  return shortest

Я хочу полностью избежать этапа создания графика и получить кратчайший путь только из четырех списков. Кто-нибудь может мне помочь?


person arduinoman    schedule 26.01.2021    source источник
comment
самым коротким от a до h должно быть a → h на строке 4, верно?   -  person Yang Yushi    schedule 26.01.2021
comment
Только в строке 4 это будет длина 4 ([a, e, b, h]), тогда как [a, b, h] имеет длину 3   -  person arduinoman    schedule 26.01.2021
comment
Я понял! Извините, я не полностью понял вопрос, и мой ответ неверен. (Он находит решение, предполагая, что вы можете телепортироваться в каждую линию.)   -  person Yang Yushi    schedule 26.01.2021
comment
Все нормально. Спасибо за помощь!   -  person arduinoman    schedule 26.01.2021
comment
Не стоит беспокоиться. Я изменил свой код и думаю, что теперь он дает правильные результаты. Ваше здоровье.   -  person Yang Yushi    schedule 26.01.2021


Ответы (2)


Я реализовал эвристический алгоритм грубой силы для решения проблемы с чистыми функциями Python.

from itertools import combinations, permutations

stations = [
    "a", "b", "c", "d", "e",
    "f", "g", "h", "i", "j",
    "k", "l", "m", "n"
]

line1 = ["a", "b", "f", "d", "e"]
line2 = ["c", "e", "j", "g", "i"]
line3 = ["c", "j", "k", "l", "m"]
line4 = ["h", "b", "e", "a", "n"]
lines = [line1, line2, line3, line4]


def validate_step(x, y, lines):
    """
    check if we can change fron x to y in a single line
    """
    for i, line in enumerate(lines):
        if (x in line) and (y in line):
            if abs(line.index(x) - line.index(y)) == 1:
                return True, (i, (line.index(x), line.index(y)))
    else:
        return False, None


def find_shortest(x, y, lines, max_step=12):
    # check if x and y are in the same line
    valid = validate_step(x, y, lines)
    if valid[0]:
        return 0, [valid[1]]
    # iterating over all the possibilities
    possible_inter = [s for s in stations if s not in (x, y)]
    for im_step in range(1, max_step):  # intermediate step
        inter_steps = combinations(possible_inter, im_step)
        for i_step in inter_steps:
            for steps in permutations(i_step):
                solution = []
                is_path_valid = True
                full_path = [x] + list(steps) + [y]
                
                for p1, p2 in zip(full_path[:-1], full_path[1:]):
                    valid = validate_step(p1, p2, lines)
                    is_path_valid *= valid[0]
                    solution.append(valid[1])

                if is_path_valid:
                    return im_step, solution
    print("Did not find a solution")
    return None, None


x = "d"
y = "n"

result = find_shortest(x, y, lines)

print(f"with {result[0]} changes, the path from '{x}' to '{y}' is find")
for step in result[1]:
    s1 = lines[step[0]][step[1][0]]
    s2 = lines[step[0]][step[1][1]]
    print(f"- Taking line {step[0]+1}, go from '{s1}' to '{s2}'")

Безусловно, следует отдавать предпочтение графовым алгоритмам, когда сложность проблемы возрастает ....

P.S. Мои результаты идентичны результатам @Alain T.

person Yang Yushi    schedule 26.01.2021
comment
Просто запустил код, работает отлично. Спасибо! - person arduinoman; 26.01.2021
comment
честно говоря, код @Alian T. лучше и в 10 раз быстрее ... но радует, чувак - person Yang Yushi; 26.01.2021

Вы можете использовать подход «сначала в ширину», создав список путей, которые вы продолжите на одну станцию, пока не достигнете пункта назначения. Если сначала продлить более короткие пути, вы гарантированно окажетесь на одном из кратчайших путей, когда достигнете пункта назначения:

def shortPath(origin,destination,*lines):
    paths    = [[origin]]        # start from origin
    visited  = set()             # only extend once per station
    while paths:                 # until no more extensions
        path = paths.pop(0)                   # shortest paths first
        if path[-1]==destination: return path # arrived!
        for line in lines:                    # find a transfer 
            if path[-1] not in line:continue  # no transfer on line
            i = line.index(path[-1])          # from station's location
            for station in line[i-1:i]+line[i+1:i+2]: # previous/next stations
                if station in visited : continue # already been there
                paths.append(path + [station])   # add longer path
                visited.add(station)
    return [] # no path to destination

выход:

line1 = ["a", "b", "f", "d", "e"]
line2 = ["c", "e", "j", "g", "i"]
line3 = ["c", "j", "k", "l", "m"]
line4 = ["h", "b", "e", "a", "n"]

print(shortPath("a","h",line1,line2,line3,line4))
# ['a', 'b', 'h']

print(shortPath("d","n",line1,line2,line3,line4))
# ['d', 'e', 'a', 'n']

print(shortPath("h","m",line1,line2,line3,line4))
# ['h', 'b', 'e', 'j', 'k', 'l', 'm']  
person Alain T.    schedule 26.01.2021