Пошук у ширину 0-1#10056

Пошук у ширину 0-1 (Розбір)

0 - 1 пошук в ширину (або 0 - 1 BFS) — це модифікація класичного алгоритму пошуку в ширину (BFS), який використовується для знаходження найкоротших шляхів у графах. Відмінність цього алгоритму в тому, що ваги ребер у графі можуть приймати лише значення або , що дозволяє значно оптимізувати пошук.

Постановка задачі. Уявімо, що у нас є зважений граф, у якому ваги ребер — це або , або . Наприклад, такий граф можна зустріти, коли ми працюємо з містом, в якому деякі дороги безкоштовні (вага ), а за інші доводиться платити (вага ). Ми хочемо знайти найкоротший шлях від однієї вершини графа до іншої з урахуванням цих ваг.

Проблема стандартного алгоритму пошуку в ширину. Класичний алгоритм пошуку в ширину (BFS) має оцінку складності O(V + E) і використовується для пошуку найкоротших шляхів у графі, але він підходить лише для графів, де всі ребра мають однакову вагу. У випадку, якщо ваги ребер різні, такий підхід вже неефективний — для цих цілей зазвичай застосовують алгоритм Дейкстри з оцінкою складності O(V^2 + E) або O(E \cdot log_2V). Але для графів з вагами лише і існує швидший підхід — 0 - 1 BFS.

0-1 BFS Алгоритм

У 0 - 1 BFS ми використовуємо двосторонню чергу (deque). Ідея полягає в наступному:

  1. Ми починаємо з початкової вершини і додаємо її до черги з початковою відстанню, що дорівнює .

  2. При обробці кожної вершини (яка витягується з голови черги) розглядаємо її сусідів :

    • Якщо у ребра вага , то сусідня вершина поміщається на початок черги з поточною відстанню (dist[v] = dist[u]).

    • Якщо у ребра вага , то сусідня вершина поміщається в кінець черги зі збільшенням відстані на одиницю (dist[v] = dist[u] + 1).

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

Таким чином, використання двосторонньої черги гарантує, що ми завжди маємо доступ до вершини з мінімальною відстанню на початку черги, а складність роботи алгоритму залишається O(V + E).

Релаксація ребер при 0 - 1 пошуку в ширину схожа на класичну релаксацію в алгоритмі Дейкстри, але відрізняється використанням двосторонньої черги (deque) замість черги з пріоритетами (priority queue) для впорядкування вершин на основі ваги ребер.

Алгоритм 0-1 BFS (на відміну від класичного BFS) не гарантує, що кожна вершина буде відвідана з мінімальною відстанню з першого разу, особливо якщо до однієї й тієї самої вершини можна дістатися кількома шляхами. Тому релаксація ребер обов'язкова.

Розглянемо наступний приклад. Ініціалізуємо масив найкоротших відстаней та чергу :

Розглянемо ребра, що виходять з вершини . Релаксуємо ребра та . Вершина буде занесена на початок черги, а вершина — у кінець черги.

Однак значення dist[2] = 1 не є остаточним. Пройдемо по ребру 3 - 4 і встановимо dist[4] = 0, queue = (4, 2). Потім розглянемо ребро 4 - 2 і значення встановиться рівним . Таким чином, приймало два різних значення в результаті релаксації (спочатку , потім ).

Приклад

Граф, наведений у прикладі, має вигляд:

Довжина найкоротшого шляху з вершини у дорівнює , а сам найкоротший шлях має вигляд: 1 - 2 - 3.

Реалізація алгоритму

Оголосимо константу нескінченність.

#define INF 0x3F3F3F3F

Оголосимо масив найкоротших відстаней і список суміжності графа .

vector\u003cint\u003e dist;
vector\u003cvector\u003cpair\u003cint, int\u003e\u003e\u003e g;

Функція bfs реалізує пошук у ширину з вершини .

void bfs(int start)
{

Ініціалізуємо масив найкоротших відстаней .

  dist = vector\u003cint\u003e(n + 1, INF);
  dist[start] = 0;

Оголосимо чергу і занесемо в неї стартову вершину .

  deque\u003cint\u003e q;
  q.push_back(start);

Продовжуємо алгоритм пошуку в ширину, поки черга не порожня.

  while (!q.empty())
  {

Витягуємо з голови черги вершину .

    int v = q.front(); q.pop_front();

Перебираємо ребра, що виходять з вершини .

    for (auto edge : g[v])
    {
      int to = edge.first;
      int w = edge.second;

Поточне ребро (v, to) має вагу . Проводимо його релаксацію.

      if (dist[to] \u003e dist[v] + w)
      {

Якщо ребро релаксує, то перераховуємо найкоротшу відстань dist[to].

        dist[to] = dist[v] + w;

Залежно від ваги ребра додаємо вершину в кінець або на початок черги.

        if (w == 1)
          q.push_back(to);
        else
          q.push_front(to);
      }
    }
  }
}

Основна частина програми. Читаємо вхідні дані.

scanf("%d %d %d %d", \u0026n, \u0026m, \u0026s, \u0026d);
g.resize(n + 1);

Читаємо граф.

for (i = 0; i \u003c m; i++)
{
  scanf("%d %d %d", \u0026a, \u0026b, \u0026w);
  g[a].push_back({ b, w });
  g[b].push_back({ a, w });
}

Запускаємо пошук у ширину зі стартової вершини .

bfs(s);

Виводимо найкоротшу відстань до вершини .

printf("%d\n", dist[d]);