Перейти до основного вмісту

Пошук циклу від'ємної ваги у графі

Нам задано орієнтований зважений граф GG з NN вершинами та MM ребрами. Потрібно знайти в ньому будь-який цикл від'ємної ваги, якщо такий цикл існує.

В іншому формулюванні задачі потрібно знайти всі пари вершин, між якими існує шлях скільки завгодно малої ваги.

Для розв'язання цих двох варіацій задачі зручно використовувати різні алгоритми, тож тут ми розглянемо обидва.

Коли підходить цей алгоритм?
  • Граф зважений і потрібно знайти цикл саме від'ємної ваги? (якщо шукають будь-який цикл у незваженому графі → Пошук циклу)
  • Достатньо знайти один від'ємний цикл (досяжний з джерела чи будь-де)? (використовуйте Беллман-Форд)
  • Потрібні всі пари вершин зі шляхом скільки завгодно малої ваги? (тоді → варіант на основі Флойда-Воршелла нижче)

Використання алгоритму Беллмана-Форда

Алгоритм Беллмана-Форда дозволяє перевірити, чи існує у графі цикл від'ємної ваги, і, якщо такий існує, знайти один із цих циклів.

Подробиці алгоритму описано у статті про алгоритм Беллмана-Форда. Тут ми опишемо лише його застосування до цієї задачі.

Стандартна реалізація алгоритму Беллмана-Форда шукає цикл від'ємної ваги, досяжний з деякої стартової вершини vv; однак алгоритм можна модифікувати так, щоб він шукав просто будь-який цикл від'ємної ваги у графі. Для цього нам потрібно встановити всі відстані d[i]d[i] рівними нулю, а не нескінченності — ніби ми шукаємо найкоротший шлях одночасно з усіх вершин; на коректність виявлення циклу від'ємної ваги це не впливає.

Виконаємо NN ітерацій алгоритму Беллмана-Форда. Якщо на останній ітерації не було жодних змін, то у графі немає циклу від'ємної ваги. В іншому разі візьмемо вершину, відстань до якої змінилася, і будемо рухатися від неї через її предків, доки не знайдемо цикл. Цей цикл і буде шуканим циклом від'ємної ваги.

Реалізація

struct Edge {
int a, b, cost;
};

int n;
vector<Edge> edges;
const int INF = 1000000000;

void solve() {
vector<int> d(n, 0);
vector<int> p(n, -1);
int x;

for (int i = 0; i < n; ++i) {
x = -1;
for (Edge e : edges) {
if (d[e.a] + e.cost < d[e.b]) {
d[e.b] = max(-INF, d[e.a] + e.cost);
p[e.b] = e.a;
x = e.b;
}
}
}

if (x == -1) {
cout << "No negative cycle found.";
} else {
for (int i = 0; i < n; ++i)
x = p[x];

vector<int> cycle;
for (int v = x;; v = p[v]) {
cycle.push_back(v);
if (v == x && cycle.size() > 1)
break;
}
reverse(cycle.begin(), cycle.end());

cout << "Negative cycle: ";
for (int v : cycle)
cout << v << ' ';
cout << endl;
}
}

Використання алгоритму Флойда-Воршелла

Алгоритм Флойда-Воршелла дозволяє розв'язати другу варіацію задачі — знайти всі пари вершин (i,j)(i, j), між якими немає найкоротшого шляху (тобто існує шлях скільки завгодно малої ваги).

Знову ж таки, подробиці можна знайти у статті про Флойда-Воршелла, а тут ми опишемо лише його застосування.

Запустимо алгоритм Флойда-Воршелла на графі. Спочатку d[v][v]=0d[v][v] = 0 для кожної vv. Але після виконання алгоритму d[v][v]d[v][v] стане меншим за 00, якщо існує шлях від'ємної довжини з vv до vv. Це можна використати, щоб також знайти всі пари вершин, між якими немає найкоротшого шляху. Ми перебираємо всі пари вершин (i,j)(i, j) і для кожної пари перевіряємо, чи існує між ними найкоротший шлях. Для цього перебираємо всі можливості для проміжної вершини tt. Пара (i,j)(i, j) не має найкоротшого шляху, якщо для однієї з проміжних вершин tt виконується d[t][t]<0d[t][t] < 0 (тобто tt є частиною циклу від'ємної ваги), tt досяжна з ii, а jj досяжна з tt. Тоді шлях від ii до jj може мати скільки завгодно малу вагу. Ми будемо позначати це як -INF.

Реалізація

for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
for (int t = 0; t < n; ++t) {
if (d[i][t] < INF && d[t][t] < 0 && d[t][j] < INF)
d[i][j] = - INF;
}
}
}

Задачі для практики

Відеоматеріали