Проблема маршрута в графике: минимизировать среднюю стоимость края, а не общую стоимость

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

Так, например, чтобы перейти от A к J, может быть, Дейкстра найдет этот путь (между скобкой весом)

A (4) D (6) J -> total cost: 10

и необходимый мне алгоритм, установив K = 10, найдет что-то вроде

A (1) B (2) C (2) D (1) E (3) F (2) G (1) H (3) J -> total cost: 15

Есть ли какой-либо известный алгоритм для этой проблемы?

Спасибо заранее.

Евгенио

Изменить как ответ на templatetypedef. Некоторые вопросы:

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

2) Можно ли использовать тот факт, что у меня нет отрицательных весов?

3) Когда вы сказали O (kE), что вы имели в виду весь алгоритм или только для дополнительной части?

Возьмем эту простую реализацию в C, где n = число узлов e = число ребер, d - вектор с расстояниями, вектор pa с предшественником и ребрами структуры (u, v, w) запоминают ребра в графики

for (i = 0; i < n; ++i)
    d[i] = INFINITY;

    d[s] = 0;

    for (i = 0; i < n - 1; ++i)
        for (j = 0; j < e; ++j)
            if (d[edges[j].u] + edges[j].w < d[edges[j].v]){
                d[edges[j].v] = d[edges[j].u] + edges[j].w;
                p[edges[j].v] = u;
                }

Я не уверен, как мне изменить код в соответствии с вашим ответом; принимать во внимание среднее значение вместо общей стоимости, если этого достаточно?

for (i = 0; i < n; ++i)
        d[i] = INFINITY;

    d[s] = 0;

    for (i = 0; i < n - 1; ++i)
        steps = 0;
        for (j = 0; j < e; ++j)
            if ( (d[edges[j].u]+ edges[j].w)/(steps+1) < d[edges[j].v]/steps){
                d[edges[j].v] = d[edges[j].u] + edges[j].w;
                p[edges[j].v] = u;
                steps++;
            }

Но в любом случае я не знаю, как принимать во внимание предел K в то же время... Еще раз заблаговременно за вашу помощь.

Edit Поскольку я могу позволить себе некоторые ошибки, я думаю об этом решении naif:

  • прекомпите все кратчайшие пути и запомните в A
  • прекомпутеровать все кратчайшие пути на модифицированном графике, где я режу края по определенному весу и запоминаю их в B

Когда мне нужен путь, я смотрю в A, например. от x до y это путь x- > z- > у то для каждого шага я смотрю в B, поэтому для x > z я вижу, есть ли соединение в B, если не хранить x > z, в противном случае я заполняю путь x > z подпутью, предоставленной B, которая может быть чем-то вроде x- > j- > h- > г; то я делаю то же самое для z- > y. Каждый раз, когда я также проверю, добавляю ли я циклический путь.

Возможно, у меня появятся какие-то странные пути, но он может работать в большинстве случаев. Если я продолжу решение, пытаясь использовать разные "пороговые значения", возможно, я также могу быть близким к уважению K constrain.

Ответ 1

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

Bellman-Ford основан на следующем динамическом повторе программирования, который пытается найти кратчайший путь от некоторого пуска node s друг к другу node с длиной не более m для некоторого m. В качестве базового случая, когда вы рассматриваете пути длины ноль, единственным достижимым node является s, а начальные значения

BF(s, t, 0) = infinity
BF(s, s, 0) = 0

Затем, если мы знаем значения для пути длины m, мы можем найти его для путей длины m + 1, заметив, что старый путь все еще может быть действительным или мы хотим расширить некоторый путь по длине один:

BF(s, t, m + 1) = min {
                     BF(s, t, m),
                     BF(s, u, m) + d(u, t) for any node u connected to t
                   }

Алгоритм в целом работает, отмечая, что любой кратчайший путь должен иметь длину не более n, а затем использовать указанное повторение и динамическое программирование для вычисления значения BF (s, t, n) для всех t. Его общая продолжительность выполнения - O (EV), так как на каждом шаге есть E ребер и V общих вершин.

Посмотрим, как мы можем изменить этот алгоритм, чтобы решить вашу проблему. Во-первых, чтобы ограничить это путями длины k, мы можем просто отключить итерацию Беллмана-Форда после нахождения всех кратчайших путей длины до k. Найти путь с наименьшей средней стоимостью немного сложнее. В каждой точке мы будем отслеживать две величины - длину кратчайшего пути, достигающую node t, и среднюю длину этого пути. При рассмотрении новых путей, которые могут достигать t, наши параметры должны либо сохранить найденный ранее путь (стоимость которого задана кратчайшим путем, пока разделенным на количество узлов в нем), либо расширить какой-либо другой путь на один шаг. Новая стоимость этого пути затем определяется общей стоимостью до плюс длина края, деленная на количество ребер в старом пути плюс одна. Если мы возьмем самый дешевый из них, а затем зафиксируем как его стоимость, так и количество ребер, в конце мы вычислим путь с наименьшей средней стоимостью длины, не превышающей k за время O (kE). В качестве инициализации мы скажем, что путь от начала node к себе имеет длину 0 и среднюю стоимость 0 (средняя стоимость не имеет значения, так как всякий раз, когда мы умножаем ее на число ребер, получаем 0). Мы также скажем, что каждый другой node находится на бесконечности расстояний, говоря, что средняя стоимость ребра бесконечна и число ребер равно единице. Таким образом, если мы когда-нибудь попробуем вычислить стоимость пути, образованного путем расширения пути, он будет иметь среднюю стоимость бесконечности и не будет выбран.

Математически решение выглядит так. В каждой точке мы сохраняем среднюю цену края и общее количество ребер на каждом node:

BF(s, t, 0).edges = 1
BF(s, t, 0).cost  = infinity

BF(s, s, 0).edges = 0
BF(s, s, 0).cost  = 0

BF(s, t, m + 1).cost = min {
    BF(s, t, m).cost,
    (BF(s, u, m).cost * BF(s, u, m).edges + d(u, t)) / (BF(s, u, m).edges + 1)
}

BF(s, t, m + 1).edges = {
    BF(s, t, m).edges         if you chose the first option above. 
    BF(s, u, m).edges + 1     else, where u is as above
}

Обратите внимание, что это может не найти простой путь длины k, так как для минимизации средней стоимости вам может потребоваться несколько циклов с низкой (положительной или отрицательной) стоимостью, чтобы снизить среднее значение. Например, если граф имеет цикл с нулевым значением, вы должны просто использовать его столько раз, сколько сможете.

EDIT. В ответ на ваши новые вопросы этот подход не будет работать, если вы не хотите дублировать узлы на пути. Как отметил @comestibles, эта версия проблемы NP-hard, поэтому, если P = NP, вы не должны ожидать найти подходящий алгоритм полиномиального времени для этой проблемы.

Как и во время выполнения, описанный выше алгоритм работает в общем времени O (kE). Это связано с тем, что каждая итерация вычисления рекуррентности принимает O (E) время и есть всего k итераций.

Наконец, давайте посмотрим на ваш предлагаемый код. Я перепечатал его здесь:

for (i = 0; i < n - 1; ++i) {
    steps = 0;
    for (j = 0; j < e; ++j) {
        if ( (d[edges[j].u]+ edges[j].w)/(steps+1) < d[edges[j].v]/steps){
            d[edges[j].v] = d[edges[j].u] + edges[j].w;
            p[edges[j].v] = u;
            steps++;
        }
    }
}

Ваш первый вопрос заключался в том, как учитывать k. Это можно сделать легко, переписав внешний цикл для подсчета до k, а не n - 1. Это дает нам этот код:

for (i = 0; i < k; ++i) {
    steps = 0;
    for (j = 0; j < e; ++j) {
        if ( (d[edges[j].u]+ edges[j].w)/(steps+1) < d[edges[j].v]/steps){
            d[edges[j].v] = d[edges[j].u] + edges[j].w;
            p[edges[j].v] = u;
            steps++;
        }
    }
}

Одна из проблем, которую я замечаю, заключается в том, что модифицированный алгоритм Беллмана-Форда должен иметь оптимальный путь каждого кандидата, чтобы сохранить его количество ребер независимо, так как каждый оптимальный путь node может быть достигнут с помощью другого количества ребер. Чтобы исправить это, я предложил бы, чтобы массив d хранил два значения - количество ребер, необходимое для достижения node и средняя стоимость node вдоль этого пути. Затем вы обновите свой код, заменив переменную steps в этих уравнениях на длину кэшированных путей.

Надеюсь, это поможет!

Ответ 2

Для новой версии вашей проблемы есть сокращение от пути Гамильтона (что делает вашу проблему неразрешимой). Возьмем экземпляр пути Гамильтона (т.е. Граф, ребра которого имеют единичный вес), добавьте вершины источника и стопки и ребра веса 2 от источника ко всем остальным и от приемника ко всем остальным. Множество K = | V | + 2 и запросить путь от источника к потоку. Существует путь Гамильтона тогда и только тогда, когда оптимальная средняя длина ребра равна (| V | + 3)/(| V | + 2).

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

Ответ 3

Вы можете немного изменить алгоритм Bellman-Ford, чтобы найти минимальный путь, используя не более K ребер/узлов. Если количество ребер фиксировано, чем вам нужно минимизировать общую стоимость, поскольку средняя стоимость будет TotalCost/NumberOfEdges.

Одним из решений было бы перебрать NumberOfEdges от 1 до K, найти минимальную общую стоимость и выбрать минимальный TotalCost/NumberOfEdges.