Найменше топологічне сортування#10651

Найменше топологічне сортування (Розбір)

Послідовність a = (a_1, a_2, ..., a_n) лексикографічно менша за послідовність b = (b_1, b_2, ..., b_n), якщо на першій позиції , де вони відрізняються, виконується a_i < b_i. Таким чином, щоб отримати лексикографічно найменшу топологічну сортування, на перше місце потрібно поставити найменшу можливу вершину, на друге — найменшу з можливих при вже вибраній першій, і так далі.

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

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

Алгоритм виглядає наступним чином:

Виявлення циклу. Якщо граф містить цикл, то жодна з вершин циклу ніколи не отримає нульовий вхідний степінь і не потрапить до черги. Тому, якщо по завершенні алгоритму до відповіді потрапило менше n вершин, топологічна сортування неможлива — виводимо .

Складність. Кожна вершина додається до черги з пріоритетом і витягується з неї рівно один раз, кожне ребро переглядається один раз. Час роботи становить O(n~log~n + m).

Приклад

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

Даний граф допускає кілька варіантів топологічної сортування. Наприклад:

Лексикографічно найменшою топологічною сортуванням є

1, 3, 4, 2, 6, 5

Лексикографічно найбільшою топологічною сортуванням є

4, 6, 3, 1, 2, 5

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

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

vector<vector<int> > g;
vector<int> InDegree, Order;
set<int> minHeap;

Читаємо вхідний граф.

scanf("%d %d", &n, &m);
g.resize(n + 1);
InDegree.resize(n + 1);

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

for (i = 0; i < m; i++)
{
  scanf("%d %d", &a, &b);
  g[a].push_back(b);
  InDegree[b]++;
}

Усі вершини з нульовим вхідним степенем додаємо до множини .

for (i = 1; i < InDegree.size(); i++)
  if (InDegree[i] == 0) minHeap.insert(i);

Продовжуємо роботу алгоритму топологічної сортування, поки множина не стане порожньою.

while (!minHeap.empty())
{

Витягуємо з вершину з найменшим номером і додаємо її в кінець топологічного порядку.

  v = *minHeap.begin();
  minHeap.erase(minHeap.begin());
  Order.push_back(v);

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

 for (int to : g[v])
 {
   InDegree[to]--;
   if (InDegree[to] == 0) minHeap.insert(to);
 }
}

Якщо після виконання алгоритму в масив додано не всі вершин, то граф містить цикл, і топологічна сортування неможлива.

if (Order.size() < n)
  printf("-1");
else

Виводимо вершини графа в лексикографічно найменшому топологічному порядку.

  for (i = 0; i < Order.size(); i++)
    printf("%d ", Order[i]);
printf("\n");

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

#include <cstdio>
#include <queue>
#include <vector>
using namespace std;

vector<vector<int> > g;
vector<int> InDegree, Order;
priority_queue<long long, vector<long long>, greater<long long> > pq;
int i, n, m, a, b, v, to;

int main(void)
{
  scanf("%d %d", &n, &m);
  g.resize(n + 1);
  InDegree.resize(n + 1);

  for (i = 0; i < m; i++)
  {
    scanf("%d %d", &a, &b);
    g[a].push_back(b);
    InDegree[b]++;
  }

  for (i = 1; i < InDegree.size(); i++)
    if (InDegree[i] == 0) pq.push(i);

  while (!pq.empty())
  {
    v = pq.top(); pq.pop();
    Order.push_back(v);

    for (int to : g[v])
    {
      InDegree[to]--;
      if (InDegree[to] == 0) pq.push(to);
    }
  }

  if (Order.size() < n)
    printf("-1");
  else
    for (int x : Order)
      printf("%d ", x);
  printf("\n");
  return 0;
}

Java реалізація — priority queue

import java.util.*;
import java.io.*;

public class Main
{
  static ArrayList<ArrayList<Integer>> g;
  static ArrayList<Integer> top;
  static int InDegree[];
  static int n, m, flag = 0;
  
  public static void main(String[] args)
  {
    FastScanner con = new FastScanner(System.in);
    n = con.nextInt();
    m = con.nextInt();
    g = new ArrayList<ArrayList<Integer>>();
    InDegree = new int[n+1];
      
    for (int i = 0; i <= n; i++)
      g.add(new ArrayList<Integer>());

    for (int i = 0; i < m; i++)
    {
      int a = con.nextInt();
      int b = con.nextInt();     
      g.get(a).add(b);
      InDegree[b]++;
    }

    PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
    for(int i = 1; i <= n; i++)
      if (InDegree[i] == 0) pq.add(i);

    top = new ArrayList<Integer>();
    while (!pq.isEmpty())
    {
      int v = pq.poll();
      top.add(v);
      for (int to : g.get(v))
      {
        InDegree[to]--;
        if (InDegree[to] == 0) pq.add(to);
      }
    }

    if (top.size() < n)
      System.out.println("-1");
    else
    {
      for (int x : top) System.out.print(x + " ");
        System.out.println();
    }
  }
}	

class FastScanner
{
  BufferedReader br;
  StringTokenizer st;
 
  public FastScanner(InputStream inputStream)
  {
    br = new BufferedReader(new InputStreamReader(inputStream));
    st = new StringTokenizer("");
  }
 
  public String next()
  {
    while (!st.hasMoreTokens())
    {
      try
      {
        st = new StringTokenizer(br.readLine());
      } catch (Exception e)
      {
        return null;
      }
    }
    return st.nextToken();
  }
 
  public int nextInt()
  {
    return Integer.parseInt(next());
  }
}

Python реалізація — priority queue

Імпортуємо функції heappush і з модуля . — це вбудована бібліотека Python, яка надає реалізацію черги з пріоритетами на основі купи.

from heapq import heappush, heappop

Читаємо кількість вершин і ребер у графі.

n, m = map(int, input().split())

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

g = [[] for _ in range(n + 1)]
InDegree = [0] * (n + 1)

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

for _ in range(m):
  a, b = map(int, input().split())
  g[a].append(b)
  InDegree[b] += 1

Усі вершини з нульовим вхідним степенем додаємо до списку .

minHeap = []
for i in range(1, len(InDegree)):
  if InDegree[i] == 0:
    heappush(minHeap, i)

Топологічно відсортовані вершини графа заносимо в масив .

Order = []

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

while minHeap:

Витягуємо з вершину з найменшим номером і додаємо її в кінець топологічного порядку.

  v = heappop(minHeap)
  Order.append(v)

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

  for to in g[v]:
    InDegree[to] -= 1
    if InDegree[to] == 0:
      heappush(minHeap, to)

Якщо після виконання алгоритму в масив додано не всі вершин, то граф містить цикл, і топологічна сортування неможлива.

if len(Order) < n:
  print(-1)

Виводимо вершини графа в лексикографічно найменшому топологічному порядку.

else:
  print(*Order)