Виконаємо серію перетворень графа в глибину. Для цього з кожної вершини, в якій ми ще не були, запустимо пошук в глибину. При вході в вершину будемо зафарбовувати її в сірий колір, а при виході — в чорний. Якщо під час пошуку ми спробуємо перейти в вершину, вже зафарбовану в сірий колір, це означатиме, що знайдений цикл.
Для відновлення циклу необхідно зберігати послідовність вершин в порядку їх обходу. Наприклад, якщо масив обходу містить вершини (2, 6, 3, 5, 4, 3), то шуканим циклом буде (3, 5, 4).
Приклад
Графи, наведені в умовах задачі, мають наступний вигляд:
Біля кожної вершини вказано поточний стан масиву .
Реалізація алгоритму
Список суміжності графа зберігається в векторі . У масиві зберігаємо послідовність вершин у порядку обходу в глибину.
vector<vector<int> > g;
vector<int> used, path;Функція dfs реалізує пошук в глибину з вершини . При вході в вершину зафарбовуємо її в сірий колір (used[v] = 1), а при виході — в чорний (used[v] = 2). Глобальна змінна вказує на наявність циклу: flag = 1 означає, що цикл знайдено, flag = 0 означає, що циклу немає.
void dfs(int v)
{Якщо цикл вже був знайдений (flag = 1), продовжувати пошук сенсу немає.
if (flag == 1) return;Вершину відзначаємо сірою та додаємо в масив .
used[v] = 1;
path.push_back(v);Перебираємо вершини , суміжні з .
for (int to : g[v])Цикл вважається знайденим, якщо пошук вглиб намагається піти у сіру вершину (у вершину , для якої used[to] = 1).
if (used[to] == 1)
{
path.push_back(to);
flag = 1;
return;
}Якщо вершина ще біла, продовжуємо з неї пошук в глибину.
else
{
if (used[to] == 0) dfs(to);
if (flag == 1) return;
}Після завершення обробки вершини відзначаємо її чорним кольором. Видаляємо вершину з масиву .
used[v] = 2;
path.pop_back();
}Основна частина програми. Читаємо список ребер та будуємо список суміжності графа.
scanf("%d %d", &n, &m);
g.resize(n + 1); used.resize(n + 1, 0);
for(i = 0; i < m; i++)
{
scanf("%d %d",&a,&b);
g[a].push_back(b);
}Запускаємо серію обходів в глибину. Якщо вершина ще не оброблена (used[i] = 0), запускаємо з неї пошук в глибину. Якщо при виконанні чергового виклику функції dfs значення змінної стає рівною (виявлено цикл в графі), припиняємо подальший пошук.
for (i = 1; i <= n; i++)
if (used[i] == 0)
{
dfs(i);
if (flag == 1) break;
}Якщо flag = 1, це означає, що цикл був знайдений. В такому випадку виводимо його, виходячи з інформації в масиві .
if (flag == 1)
{Цикл знайдено і завершується в вершині cfin = path[path.size() - 1].
cfin = path.back();Рухаємося змінною назад по масиву , щоб знайти ту ж вершину — точку, в якій починається цикл.
cstart = path.size() - 2;
while (path[cstart] != cfin) cstart--;Виводимо повідомлення про наявність циклу та сам цикл.
printf("YES\n");
for (i = cstart; i < path.size() - 1; i++)
printf("%d ", path[i]);
printf("\n");
}Інакше (flag = 0) циклів у графі немає.
else printf("NO\n");Java реалізація
import java.util.*;
public class Main
{
static ArrayList<Integer>[] g;
static int used[], flag, n, m;
static Vector<Integer> path = new Vector<Integer>();
static void dfs(int v)
{
if (flag == 1) return;
used[v] = 1;
path.add(v);
for (int i = 0; i < g[v].size(); i++)
{
int to = g[v].get(i);
if (used[to] == 1)
{
path.add(to);
flag = 1;
return;
}
else dfs(to);
if (flag == 1) return;
}
used[v] = 2;
path.remove(path.size() - 1);
}
public static void main(String[] args)
{
Scanner con = new Scanner(System.in);
n = con.nextInt();
m = con.nextInt();
g = new ArrayList[n+1];
for (int i = 0; i <= n; i++)
g[i] = new ArrayList<Integer>();
used = new int[n+1];
for(int i = 0; i < m; i++)
{
int a = con.nextInt();
int b = con.nextInt();
g[a].add(b);
}
for (int i = 1; i <= n; i++)
if (used[i] == 0)
{
dfs(i);
if (flag == 1) break;
}
if (flag == 1)
{
int i = path.size() - 2;
int to = path.lastElement();
while (path.get(i) != to) i--;
System.out.println("YES");
for (; i < path.size() - 1; i++)
System.out.print(path.get(i) + " ");
System.out.println();
}
else System.out.println("NO");
con.close();
}
}