Рассмотрим базовую задачу.
Условие:
Дан неориентированный граф G, имеющий N вершин и M ребер. Чтобы все рассмотренные подходы имели практических смысл, ограничим N<=1000.
Необходимо найти количество компонент связности данного графа.
Формат входных данных: В первой строке входного файла находятся N и M, разделенные пробелом. Далее идет M строк, содержащих пару вершин, между которыми находится ребро. Вершины нумеруются с 1.
Формат выходных данный: В единственной строке выдать количество компонент связности графа.
Пример:
input.txt
15 11
1 2
2 3
2 11
10 11
11 12
11 15
4 12
12 13
8 14
7 14
5 6
output.txt
4
Компонента связности неориентированного графа является подграф, в котором для любой пары вершин v и u существует путь. Между двумя различными компонентами связности не существует пути.
Задача стара, как мир. Но тем не менее сегодня покажу несколько подходов по ее решению.
1. Поиск в глубину(DFS)
Самое классическое решение. Даже комментировать особо нечего.
- const int SIZE = 1e3 + 10;
- vector<int> adj[SIZE];
- bool usd[SIZE];
- ...
- void dfs(int cur) {
- usd[cur] = true;
- for (int i=0;i<adj[cur].size();++i) {
- int nxt = adj[cur][i];
- if (!usd[nxt])
- dfs(nxt);
- }
- }
- int connected_components_amount_dfs() {
- int cnt = 0;
- for (int i=1; i<=n; ++i) {
- if (!usd[i]) {
- dfs(i);
- ++cnt;
- }
- }
- return cnt;
- }
* This source code was highlighted with Source Code Highlighter.
Граф храним в виде списка смежности(строка 2). Общая сложность решения $latex O(N + M)$.
Решение
2. DSU подобная структура(ленивый подход)
Будем делать конденсацию компоненты в одну вершину. Идея следующая: будем последовательно обрабатывать ребра. Каждая компонента связности будет представлена одной своей вершиной(титульная). При этом неважно какой. По ходу обработки ребер титульная вершина компонент может меняться.
В итоге для нахождения количества компонент связности нужно найти количество титульных вершин.
- const int SIZE = 1e3 + 10;
- int p[SIZE];
- bool usd[SIZE];
- ...
- int connected_components_amount_dsu() {
-
- scanf("%d %d", &n, &m);
-
- for (int i=1;i<=n;++i) {
- p[i] = i;
- }
-
- for (int i=0;i<m;++i) {
- scanf("%d %d", &f, &s);
- int par = p[f];
- for (int j=1;j<=n;++j) {
- if (p[j] == par)
- p[j] = p[s];
- }
- }
- for (int i=1;i<=n;++i)
- usd[p[i]] = true;
- int cnt = 0;
- for (int i=1;i<=n;++i) {
- if (usd[i]) ++cnt;
- }
- return cnt;
- }
* This source code was highlighted with Source Code Highlighter.
Заметим, что сам граф непосредственно вообще никак не хранится. Общая сложность $latex O(M*N)$
Решение
3. Система непересекающихся множеств (DSU)
Реализацию, представленную выше можно существенно усовершенствовать. При добавлении нового ребра будем “мерджить меньшее подмножество к большему” + сжимать пути. У emaxx’а рассматривается доказательство того, что ассимптотика обработки одного ребра равна $latex O(\alpha (N))$, где $latex \alpha (N)$ – обратная функция Аккермана.
- const int SIZE = 1e3 + 10;
-
- int p[SIZE];
- int size[SIZE];
-
- int par(int x) {
- return p[x] == x ? x : p[x] = par(p[x]);
- }
- int connected_components_amount_dsu() {
-
- scanf("%d %d", &n, &m);
-
- for (int i=1;i<=n;++i) {
- p[i] = i;
- size[i] = 1;
- }
-
- for (int i=0;i<m;++i) {
- scanf("%d %d", &f, &s);
- f = par(f); s = par(s);
- if (f != s) {
- if (size[f] > size[s])
- swap(f,s);
- p[f] = s;
- size[s] += size[f];
- }
- }
- bool usd[SIZE];
- memset(usd, 0, sizeof(usd));
- for (int i=1;i<=n;++i)
- usd[par(i)] = true;
- int cnt = 0;
- for (int i=1;i<=n;++i) {
- if (usd[i]) ++cnt;
- }
-
- return cnt;
- }
* This source code was highlighted with Source Code Highlighter.
Как и прежде сам граф не хранится в виде матрицы смежности. Общая сложность $latex O(M * \alpha (N))$
4. Алгоритм Флойда.
Этот подход для самых ленивых, правда он требует хранить граф явно в виде матрицы смежности.
Запускаем алгоритм Флойда и строим матрицу достижимости для каждой пары вершин. В результате если первоначально adj[u][v] указывало на наличие ребра между u и v, то после работы алгоритма adj[u][v] будет указывать на наличие пути между u и v. После чего можно написать DFS двумя вложенными циклами.
- const int SIZE = 1e3 + 10;
- int adj[SIZE][SIZE];
- bool usd[SIZE];
- ...
- int connected_components_amount_floyd() {
-
- for (int k=1;k<=n;++k){
- for (int i=1;i<=n;++i){
- for (int j=1;j<=n;++j){
- if (adj[i][k] + adj[k][j] == 2)
- adj[i][j] = 1;
- }
- }
- }
-
- int cnt = 0;
- for (int i=1;i<=n;++i) {
- if (!usd[i]) {
- ++cnt;
- usd[i] = true;
- for (int j=1;j<=n;++j) {
- if (adj[i][j] && !usd[j])
- usd[j] = true;
- }
- }
- }
- return cnt;
- }
* This source code was highlighted with Source Code Highlighter.
Алгоритм ужасно долгий, но зато пишется довольно просто. Общая сложность $latex O({ N }^{ 3 }) $ Решение
Собственно пока и все. Мы рассмотрели 3 принципиально разных подхода. На маленьких ограничениях можно выбрать тот из них, что больше по душе.
Подскажите, пожалуйста, с алгоритмом для поиска всё того же кол-ва компонент, но с условием, что граф ориентированный
ОтветитьУдалитьDid you hear there's a 12 word sentence you can tell your partner... that will induce deep emotions of love and instinctual attractiveness for you buried inside his heart?
ОтветитьУдалитьBecause deep inside these 12 words is a "secret signal" that triggers a man's instinct to love, look after and protect you with his entire heart...
===> 12 Words Who Fuel A Man's Love Response
This instinct is so hardwired into a man's genetics that it will drive him to try better than ever before to take care of you.
In fact, triggering this influential instinct is so important to getting the best ever relationship with your man that once you send your man a "Secret Signal"...
...You will soon notice him expose his soul and mind for you in a way he never experienced before and he'll distinguish you as the one and only woman in the galaxy who has ever truly attracted him.