Один источник — кратчайший путь с одним пунктом назначения (DFS + DP) — временная сложность?

Контекст:

Я хочу написать алгоритм поиска кратчайшего (наименьшей стоимости) пути от фиксированного источника к фиксированному узлу назначения во взвешенном ориентированном графе с неотрицательными весами (может иметь циклы).

Следующий алгоритм DFS + DP делает вышеописанное.

  1. DO DFS — на каждом узле спросить, какова минимальная стоимость от текущего узла до узла DST.
  2. Это делается путем опроса - из каждого соседа кто даст минимум (cost_to_dst + edgecost_to_neighbour)
  3. После того, как мы найдем эту минимальную стоимость для DST, мы сохраним ее в массиве dp, чтобы нам не приходилось вычислять ее снова для этого узла.
  4. Чтобы избежать бесконечного цикла рекурсивных вызовов dfs, мы ограничиваем глубину вызовов до V (количество всех узлов), поскольку максимальная длина кратчайшего пути от SRC до DST может быть V.
  5. Итак, базовые условия для dfs/рекурсивных вызовов: i) если вы достигли летнего времени, верните 0. ii) если вы исчерпали вызовы V dfs, верните INT_MAX. iii) если узел уже обработан, верните dp[node]

Псевдокод:

int dfs(int u, int stops = V + 1, int &dst, vector<vector<pair<int, int>>> &adj, vector<int> &dp)
    {
        if (u == dst) // reached dst
            return 0; 
        if (stops == 0) // can't go on anymore
            return INT_MAX; 
        if (dp[u]!= -1)
            return dp[u];

        int res = INT_MAX;
        for(auto &[v, edgecost]: adj[u])
        {
            int vdstcost = dfs(v, stops - 1, dst, adj, dp); // min cost from v to dst
            if (vdstcost != INT_MAX)
                res = min(res, vdstcost + edgecost);
        }
        return dp[u] = res;
    }

Запросы:

  1. Какова будет временная сложность этого кода с точки зрения E (количество ребер) и V (количество узлов)?

  2. Это логически правильно? т. е. даст ли этот алгоритм правильный ответ для каждого сценария в рамках ограничений?

Четвертый момент введения ограничения глубины V в рекурсивные вызовы затрудняет анализ временной сложности. Но я уверен, что это будет где-то между O(V+E) и O(VE).

Представьте себе путь --> A --->...----> B ---->...----> A. В таком случае рекурсивные вызовы будут зацикливаться до тех пор, пока глубина (V) не будет исчерпана. Таким образом, TC не может быть просто O(V+E)

Изменить - исправлен псевдокод

ДП также необходимо рассмотреть вопрос «нет». осталось остановок, как указано в ответе ниже. код ниже обновлен для использования 2D DP


int dfs(int u, int stops, int dst, vector<vector<pair<int, int>>> &adj, vector<vector<int>> &dp)
{
    if (u == dst) // reached dst
        return 0; 
    if (stops == 0) // can't go on anymore
        return INT_MAX; 
    if (dp[u][stops]!= -1)
        return dp[u][stops];

    int res = INT_MAX;
    for(auto &[v, edgecost]: adj[u])
    {
        int vdstcost = dfs(v, stops - 1, dst, adj, dp); // min cost from v to dst
        if (vdstcost != INT_MAX)
            res = min(res, vdstcost + edgecost);
    }
    return dp[u][stops] = res;
}

«Это логически правильно?» Нет, ваш алгоритм застрянет в графовом цикле и никогда не найдет ответа.

n. m. could be an AI 03.07.2024 21:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
95
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если я правильно понимаю вопрос, вы намерены иметь работающий алгоритм, который полагается только на запоминание и обход в глубину, без понятия посещенных/непосещенных или приоритетных очередей. Но это не всегда работает:

Проблемы

Указанный вами алгоритм не всегда находит путь, поскольку он может хранить INT_MAX расстояния в dp[u], когда на самом деле существует путь от u до цели:

  • Мемоизация не должна осуществляться только с помощью u. Результат зависит от количества оставшихся остановок. Возможно, имея всего две остановки, невозможно достичь цели, но с пятью путь есть. Поэтому вам нужно запомнить результаты для каждого узла и каждой остановки.

Не критическая проблема, но:

  • в вашем псевдокоде значение по умолчанию (начальное) для stops может быть 𝑉−1, поскольку вы уже передаете ему узел в качестве аргумента, который считается за единицу, оставляя вам только 𝑉−1 для дальнейшего расширения (т. е. число ребер, которым нужно следовать).

Анализ сложности

Как только вышеизложенное исправлено, мы можем рассмотреть этот «плохой» случай:

У нас есть узлы 𝑛, каждый из которых имеет два исходящих ребра, одно к самому себе и одно к «следующему» узлу, и где все веса равны 1. Пусть 𝑣1 будет источником, а 𝑣𝑛 целью. Предположим, что в худшем случае в первую очередь всегда посещаются ребра цикла. Тогда, если мы на мгновение проигнорируем мемоизацию, DFS, начинающаяся с 𝑣1, будет пробовать эти пути, все длины 𝑉−1 (т. е. с 𝑉 узлами):

  • 𝑣1 → ... → 𝑣1 → 𝑣1 → 𝑣1
  • 𝑣1 → ... → 𝑣1 → 𝑣1 → 𝑣2
  • 𝑣1 → ... → 𝑣1 → 𝑣2 → 𝑣2
  • 𝑣1 → ... → 𝑣1 → 𝑣2 → 𝑣3
  • 𝑣1 → ... → 𝑣2 → 𝑣2 → 𝑣2
  • 𝑣1 → ... → 𝑣2 → 𝑣2 → 𝑣3
  • ...
  • 𝑣1 → 𝑣2 → ... → 𝑣𝑛−1 → 𝑣𝑛

Только при последней попытке цель будет найдена и все узлы получат небесконечное присвоение в массиве dp.

Если мы рассмотрим мемоизацию для обрезки дерева, то в узле 𝑣1 мы будем иметь 𝑛 разные значения stops, которые могут возникнуть в этом узле. Для 𝑣2 у нас будет на одну возможность меньше для stops, ... и т. д., вплоть до For 𝑣𝑛, где stops не может быть ничем иным, как 0. Итак, чтобы записать все эти dp записи (т. е. в худшем случае), нам понадобится 𝑛 + 𝑛−1 + 𝑛−2 + ... + 1 посещение. Все остальные посещения узла, поступающие через альтернативный край к тому же узлу, выиграют от мемоизации. Таким образом, они рассчитаны на одинаковое количество операций.

Таким образом, мы получаем сложность этого сценария O(𝐸²).

Если вы ограничитесь графами без циклов, то вы можете провести аналогичные рассуждения с графом, который имеет четное количество узлов 𝑛 и где узлы в паре нечет-чет ссылаются друг на друга, например:

Эффективные алгоритмы

В Википедии перечислены некоторые популярные алгоритмы поиска кратчайшего пути в графе. Хотя алгоритм Дейкстры может решить проблему поиска кратчайших путей от заданной вершины ко всем вершинам графа, его можно использовать и для одного пункта назначения, куда он просто возвращается, когда эта вершина найдена. Его временная сложность в худшем случае равна O(𝐸 + 𝑉log𝑉).

«Тогда DFS, начинающаяся с 𝑣1, будет пробовать эти пути длиной 𝑉−1». Это не то, что произойдет. Алгоритм просто не найдет правильный ответ. Он не продвинется дальше v2.

n. m. could be an AI 03.07.2024 21:25

Верно. В начале я добавил пункт, чтобы выделить проблему в их коде, которую необходимо исправить, прежде чем можно будет использовать этот анализ.

trincot 03.07.2024 21:59

Зависит от. Ваше изменение может сработать, а может и не сработать, но если вы исправите его правильно, сложность тоже будет устранена. Только не посещайте один и тот же узел на одном и том же пути-кандидате дважды (отметьте его при входе в функцию, снимите отметку при выходе, не обрабатывайте, если отмечено).

n. m. could be an AI 04.07.2024 05:35

Даже если бы мы добавили в их алгоритм концепцию посещений, сложность в худшем случае все равно была бы экспоненциальной (но для других графов, чем я предоставил). У меня сложилось впечатление, что целью автора вопроса было представить работающий алгоритм, отличающийся от более известных алгоритмов. То, что это не работает, сводится к легко исправимой оплошности при их запоминании.

trincot 04.07.2024 09:06

Я не уверен, как в этом случае сложность может быть экспоненциальной, можете ли вы показать пример графика?

n. m. could be an AI 04.07.2024 09:23

Я ошибаюсь в этом. При посещенной маркировке временная сложность тогда равна O(𝐸).

trincot 04.07.2024 13:14

Во-первых, спасибо огромное за такое подробное объяснение @trincot. Во-вторых, @n.m.couldbeanAI правильно указал, что мой код не найдет правильный ответ - исправление @trincot использования [u, stops] в качестве ключей для dp должно исправить мой код. В-третьих, мы не можем использовать посещаемый массив, как предложил @n.m.couldbeanAI. Это также приведет к тому, что алгоритм будет давать неверные ответы, поскольку пути с более низкой стоимостью могут быть пропущены. Пытался объяснить [здесь]. Lasty @ trincot, как вы создавали эти диаграммы?

Lupin 05.07.2024 14:07

Спасибо за хороший и развернутый комментарий. Схемы я создал в PowerPoint.

trincot 05.07.2024 14:15

@trincot Было бы очень полезно, если бы вы могли дать мне анализ худшего случая для обновленного кода, используя [u, stop] в качестве ключей для DP. Используя этот псевдокод, я вижу, что в вашем примере 2^n не пройдено несколько путей. Но не могу точно определить, насколько точно сократится временная сложность.

Lupin 05.07.2024 16:43

Я обновил свой ответ, чтобы сосредоточиться на этом случае. Мне пришлось исправить свое предыдущее решение исключить мемоизацию из анализа, поэтому теперь я включил ее, и она выходит на уровне O(𝐸²) — что то же самое, что O(𝑉²) для этого конкретного примера плохого случая.

trincot 05.07.2024 17:26

«Это также приведет к тому, что алгоритм будет давать неправильные ответы, поскольку пути с более низкой стоимостью могут быть пропущены». Нет, ваше объяснение неправильно использует посещенный массив. Вам необходимо отметить u перед входом в цикл for и снять отметку после выхода из цикла. Вы проверяете, посещается ли v в цикле. График без циклов никогда не вызовет эту проверку.

n. m. could be an AI 07.07.2024 20:06

Другие вопросы по теме