Дешевые авиабилеты с более K останавливается


Это дешевые авиабилеты в K остановок проблема с leetcode.com:

Есть \ФП\$ городов, Соединенных перелета \$м\$. Каждый бой начинается с города \$щ\$ и прибывает в \$\ В$ с ценой \$ш\$.

Теперь с учетом всех городов и драк, вместе с началом города \$ГРЦ\$ и пунктом \$ДСТ\$, ваша задача-найти самую дешевую цену от \$ГРЦ\ долларов \$ДСТ\$ до \$к\$ остановок. Если нет такого маршрута, выход \-1$\$.

Я наивной реализации, что не на эффективность. Я чувствовал, что это случится, но я взял этот подход, чтобы реализовать мое первое когда-либо итеративное углубление глубиной первого поиска (IDDFS). Поскольку он не на время выполнения, я не знаю, если мой код-это 100% действует с точки зрения его реализации

Мой алгоритм:

  1. Перечислить все пути, глубины меньше, чем \$к + 1\$ через IDDFS, добавить в приоритетной очереди на пути стоимость
  2. Если очередь не пуста, а путь существует, что делает в большинстве \$к\$ остановок, извлечения мин из приоритетной очереди.

Очевидно, Шаг 1 растет в геометрической прогрессии и вовсе не эффективным.

Что я хотел бы помочь рецензирование ли мой итеративного углубления глубиной первого поиска с точки зрения его реализации правильно.

def findCheapestPrice(self, n, flights, src, dst, K):
        """
        :type n: int
        :type flights: List[List[int]]
        :type src: int
        :type dst: int
        :type K: int, K + 1 = max search depth, K + 2 = amount of visited nodes
        :rtype: int
        """
        from collections import defaultdict
        import heapq
        adj = defaultdict(list) # u : (v, cost)

        for u,v,c in flights:
            adj[u].append((v,c))

        paths = [] # pqueue

        def enumerateAllPaths(u, goal, visited, path, pathcost, depthLimit):
            visited[u] = True
            path.append(u)

            if u == goal:
                heapq.heappush(paths, (pathcost, len(path), path))
            elif depthLimit == 0:
                path.pop()
                visited[u] = False
                return
            else:
                for v,c in adj[u]:
                    if not visited[v]:
                        enumerateAllPaths(v, goal, visited, path, pathcost + c, depthLimit - 1)
            path.pop()
            visited[u] = False

        visited = [False for s in range(len(n))]
        path = []

        enumerateAllPaths(src, dst, visited, path, 0, K + 1)

        # (length of path, pathcost, path)
        bestcost = 2**63 - 1 # best cost decreases if a better path is found

        if paths:
            pathcost, pathlen, path = heapq.heappop(paths)
            bestcost = pathcost

        if bestcost != 2**63 - 1:
            return bestcost
        else:
            return -1


Комментарии
1 ответ

1. Комментарий


  1. Там, кажется, не быть любой итеративный углубление происходит. Код просто реализует обыкновенным поиском в глубину для всех простых путей в графе. Итеративное углубление потребует вызова enumerateAllPaths сначала depthLimit=1тогда с depthLimit=2 и так далее.

  2. Строкой документации должен разъяснить, какие параметры означают.

  3. Импорт лучше сделать один раз на верхнем уровне модуля, а не каждый раз, когда функция вызывается.

  4. Комментарии paths говорит "pqueue", который я не понимаю (это аббревиатура для "приоритетной очереди"?). Было бы лучше, чтобы быть явным, например я бы написал что-то вроде "кучи (стоимость, длина, путь) Кортежи на пути достижения цели".

  5. Код для получения наилучшего пути выхода из кучи излишне многословно, и есть ошибка (если лучший путь стоил \$2^{63}-1\$ он не будет найден). Лучше написать:

    if paths:
    cost, _, _ = heapq.heappop(paths)
    return cost
    else:
    return -1

  6. Если вам нужен номер, который больше, чем любой возможной цене, воспользуйтесь бесконечности, То есть float('inf') вместо того, чтобы произвольное число.

  7. Цель задачи-найти только один путь к месту назначения (самый дешевый), так это разорительно для поддержания коллекций все пути к месту назначения. Лучше просто помнить, лучший путь найти до сих пор.

  8. Нет необходимости передавать переменные goal и visited и path в enumerateAllPaths функция. Эти не меняются в ходе обыска.

  9. Эти строки кода повторяются:

    path.pop()
    visited[u] = False

    Путем тестирования depthLimit != 0 вместо depthLimit == 0 вы могли бы избежать этого повторения.


  10. Вместо реализации visited в качестве словаря сопоставление узлов True или Falseиспользуйте набор узлов вместо.

  11. В path список не нужен: это используется только для того, чтобы найти длину пути, но это является излишним, так как len(path) всегда равна K + 1 - depthLimit. И поскольку вы не заботитесь о фактической длине пути, только ее относительную длину по сравнению с другими путями, вы можете упасть K + 1 (так это же для всех путей), а просто использовать -depthLimit.

2. Пересмотренный кодекс

from collections import defaultdict

def cheapest_path_cost(edges, src, dst, k):
"""Return the cost of the cheapest path in the graph from src to dst
visiting no more than k other nodes. The graph is specified as a
sequence of tuples (u, v, c) meaning that there is an edge from u
to v with cost c. If there is no path, return infinity.

"""
graph = defaultdict(list) # {u: (v, cost)}
for u, v, c in edges:
graph[u].append((v, c))
infinity = float('inf')
best = infinity, 0 # (cost, -depth) of best path found.
visited = set() # Set of nodes visited on current path.

def enumerate_paths(u, cost, depth):
# Enumerate simple paths of length up to depth, starting at u.
nonlocal best
visited.add(u)
if u == dst:
best = min((cost, -depth), best)
elif depth:
for v, c in graph[u]:
if v not in visited:
enumerate_paths(v, cost + c, depth - 1)
visited.remove(u)

enumerate_paths(src, 0, k + 1)
return best[0]

3. Тестирование

Вы пишете в посте:


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

Это наводит меня на мысль, что вы не тестировали свой код. Это верно? Вы говорите в комментариях, что вы "уже решил эту проблему с Беллмана–Форда" в каком случае вы могли бы использовать другое решение в качестве тестового оракула.

Вот тот тест, что вы можете быстро реализовать, используя тестовый оракул. Здесь я использую сжатый скудная графика процедур в scipy.sparse.csgraphно любой другой графической библиотеки будет делать.

import numpy as np
from scipy.sparse.csgraph import csgraph_from_dense, dijkstra
from unittest import TestCase

class TestCheapestPathCost(TestCase):
def test_cheapest_path_cost(self):
# Random graph on n nodes with given density and costs up to maxcost.
n = 10
density = 0.3
maxcost = 5
graph = np.random.randint(1, maxcost + 1, (n, n))
graph[np.random.random((n, n)) > density] = 0

# Convert to array of edges.
i, j = np.mgrid[:n, :n]
edges = np.dstack((i, j, graph))
edges = edges[edges[..., 2] != 0]

# Find shortest paths and compare.
shortest = np.dstack((i, j, dijkstra(graph))).reshape(-1, 3)
for src, dst, expected in shortest:
self.assertEqual(cheapest_path_cost(edges, src, dst, n), expected)

4. Производительности

Есть несколько простых оптимизаций, которые вы можете сделать в глубину поисков.

Во-первых, чернослив пути, не лучше, чем самый лучший путь пока такой:

def enumerate_paths(u, cost, depth):
# Enumerate simple paths of length up to depth, starting at u.
nonlocal best
visited.add(u)
if u == dst:
best = cost, -depth
elif depth:
for v, c in graph[u]:
if v not in visited and (cost + c, 1 - depth) < best:
enumerate_paths(v, cost + c, depth - 1)
visited.remove(u)

Обратите внимание, что мы не нуждаемся в вызове min в этой версии кода, так как мы уже проверили, что стоимость была усовершенствована, прежде чем посетить этот узел поиска.

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

4
ответ дан 15 марта 2018 в 11:03 Источник Поделиться