Проблема маршрута на графике: минимизируйте среднюю стоимость границы вместо общей стоимости - PullRequest
14 голосов
/ 25 августа 2011

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

Так, например, чтобы перейти от А к 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

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

Заранее спасибо.

Eugenio

Редактировать как ответ на templatetypedef. Некоторые вопросы:

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

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 ... Еще раз спасибо за вашу помощь.

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

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

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

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

Ответы [ 3 ]

6 голосов
/ 25 августа 2011

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

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

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

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, поскольку для минимизации средней стоимости может потребоваться, чтобы выВозьмите цикл с низкой (положительной или отрицательной) стоимостью несколько раз, чтобы снизить среднее значение.Например, если на графике есть цикл с нулевой стоимостью, вы должны просто брать его столько раз, сколько сможете.

РЕДАКТИРОВАТЬ : В ответ на ваши новые вопросы этот подход выигралне работает, если вы не хотите дублировать узлы на пути.Как указал @comestibles, эта версия проблемы является NP-трудной, поэтому, если P = NP, вы не должны ожидать, что найдете какой-либо хороший алгоритм с полиномиальным временем для этой проблемы.

Что касается времени выполнения,алгоритм, который я описал выше, работает за общее время O (кЕ).Это связано с тем, что каждая итерация вычисления повторения занимает время 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++;
        }
    }
}

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

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

2 голосов
/ 26 августа 2011

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

Не могли бы вы рассказать нам, почему вам нужны эти пути, чтобы мы могли посоветовать вам разумную стратегию приближения?

0 голосов
/ 25 августа 2011

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

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

...