Оптимизация для проблемы с длинным пути в циклическом графе

Какие существуют оптимизации для поиска самого длинного пути в циклическом графе?

Самый длинный путь в циклических графах, как известно, является NP-полным. Какие оптимизации или эвристики могут сделать поиск самого длинного пути быстрее, чем DFSing всего графика? Существуют ли какие-либо вероятностные подходы?

У меня есть график с определенными качествами, но я ищу ответ на этот вопрос в общем случае. Связывание с документами было бы фантастическим. Вот частичный ответ:

  • Подтвердите, что он цикличен. Самый длинный путь в ациклических графах легко вычисляется с помощью динамического программирования.

  • Узнайте, является ли граф плоским (какой алгоритм лучше?). Если это так, вы можете увидеть, является ли это графом блоков, графиком ptolemaic или cacti, и примените методы, найденные в в этой статье.

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

  • Если вам нужен DFS весь график, вы можете обрезать некоторые пути, предварительно вычислив "достижимость" каждого node. Эта достижимость, которая в основном применима к ориентированным графам, - это количество узлов, каждое из которых node может достигать без повторений. Это самый длинный путь от этого node. С этой информацией, если ваш текущий путь плюс достижимость дочернего элемента node меньше, чем самый длинный, который вы уже нашли, нет смысла принимать эту ветку, поскольку невозможно, чтобы вы нашли более длинный путь.

Ответ 1

Вот подход динамического программирования O (n * 2 ^ n), который должен быть осуществлен до 20 вершин:

m(b, U)= максимальная длина любого пути, заканчивающегося на b, и посещение только (некоторых) вершин в U.

Сначала установите m(b, {b}) = 0.

Затем m(b, U)= максимальное значение m(x, U - x) + d(x, b) по всем x в U такое, что x не b и существует ребро (x, b). Возьмите максимум этих значений для всех конечных точек b, с U= V (полный набор вершин). Это будет максимальная длина любого пути.

Следующий код C принимает матрицу расстояний в d[N][N]. Если ваш график невзвешен, вы можете изменить каждый доступ на чтение к этому массиву на константу 1. Трассировка, показывающая оптимальную последовательность вершин (может быть больше одной), также вычисляется в массиве p[N][NBITS].

#define N 20
#define NBITS (1 << N)

int d[N][N];       /* Assumed to be populated earlier.  -1 means "no edge". */
int m[N][NBITS];   /* DP matrix.  -2 means "unknown". */
int p[N][NBITS];   /* DP predecessor traceback matrix. */

/* Maximum distance for a path ending at vertex b, visiting only
   vertices in visited. */
int subsolve(int b, unsigned visited) {
    if (visited == (1 << b)) {
        /* A single vertex */
        p[b][visited] = -1;
        return 0;
    }

    if (m[b][visited] == -2) {
        /* Haven't solved this subproblem yet */
        int best = -1, bestPred = -1;
        unsigned i;
        for (i = 0; i < N; ++i) {
            if (i != b && ((visited >> i) & 1) && d[i][b] != -1) {
                int x = subsolve(i, visited & ~(1 << b));
                if (x != -1) {
                    x += d[i][b];
                    if (x > best) {
                        best = x;
                        bestPred = i;
                    }
                }
            }
        }

        m[b][visited] = best;
        p[b][visited] = bestPred;
    }

    return m[b][visited];
}

/* Maximum path length for d[][].
   n must be <= N.
   *last will contain the last vertex in the path; use p[][] to trace back. */
int solve(int n, int *last) {
    int b, i;
    int best = -1;

    /* Need to blank the DP and predecessor matrices */
    for (b = 0; b < N; ++b) {
        for (i = 0; i < NBITS; ++i) {
            m[b][i] = -2;
            p[b][i] = -2;
        }
    }

    for (b = 0; b < n; ++b) {
        int x = subsolve(b, (1 << n) - 1);
        if (x > best) {
            best = x;
            *last = b;
        }
    }

    return best;
}

На моем ПК это разрешает полный график 20x20 с весами краев, случайным образом выбранным в диапазоне [0, 1000] примерно за 7 секунд, и ему требуется около 160 Мб (половина из них для трассы предшественника).

(Пожалуйста, никаких комментариев об использовании массивов фиксированного размера. Используйте malloc() (или еще лучше, С++ vector<int>) в реальной программе. Я просто написал это так, чтобы все было понятнее.)