Как найти число компонент связности графа

Компонентой связности неориентированного графа называется подмножество вершин, достижимых из какой-то заданной вершины. Как следствие неориентированности, все вершины компоненты связности достижимы друг из друга.

Граф с двумя компонентами связности

Дан неориентированный граф $G$ с $n$ вершинами и $m$ рёбрами. Требуется найти в нём все компоненты связности, то есть разбить вершины графа на несколько групп так, что внутри одной группы можно дойти от одной вершины до любой другой, а между разными группами путей не существует.

Для решения задачи модифицируем обход в глубину так, чтобы запустившись от вершины какой-то компоненты, от пометил все вершины этой компоненты — то есть все достижимые вершины — заданным номером этой компоненты. Для этого можно массив used заменить массивом номеров компонент для каждой вершины, изначально заполненный нулями:

const int maxn = 1e5;
int component[maxn]; // тут будут номера компонент

void dfs(int v, int num) {
    component[v] = num;
    for (int u : g[v])
        if (!component[u]) // если номер не присвоен, то мы там ещё не были
            dfs(u, num);
}

Теперь проведем серию обходов: сначала запустим обход из первой вершины, и все вершины, которые он при этом обошёл, образуют первую компоненту связности. Затем найдём первую из оставшихся вершин, которые ещё не были посещены, и запустим обход из неё, найдя тем самым вторую компоненту связности. И так далее, пока все вершины не станут помеченными.

Записывается это очень компактно:

int num = 0;
for (int v = 0; v < n; v++)
    if (!component[v])
        dfs(v, ++num);

После этого переменная num будет хранить число компонент связности, а массив component — номер компоненты для каждой вершины, который, например, можно использовать, чтобы быстро проверять, существует ли путь между заданной парой вершин.

Итоговая асимптотика составит $O(n + m)$, потому что такой алгоритм не будет запускаться от одной и той же вершины дважды, и каждое ребро будет просмотрено ровно два раза (с одного конца и с другого).

83

A =

0

2

1

=

0 1

.

1

1

0

и

0

1

0

A

1 4

Заметим еще, что если в матрице смежности мультиграфа отличны от нуля лишь элементы, стоящие на главной диагонали, то число компонент связности равно числу вершин мультиграфа, т. е. размеру матрицы, так как такой мультиграф содержит только петли. На основании этого сформулируем алгоритм подсчета числа компонент связности по матрице мультиграфа.

Шаг.1. Найти ненулевой элемент матрицы смежности, не стоящий на главной диагонали. Если он существует, перейти шагу.2, если нет, то перейти к шагу.3.

Шаг.2. Произвести над матрицей операцию, отвечающую склейке

вершин xi и x j перейти к шагу.1.

Шаг.3. Подсчитать количество р строк матрицы, содержащих ненулевые элементы на главной диагонали. Результат: количество компонент связности мультиграфа равно р.

Упражнения

1. В неорграфе G = (X, U), в котором ½X½= 10 и X = { x1, x2, x3, x4, x5, x6, x7, x8, x9, x10} и U ¹ Æ, были склеены вершины x3 и x7 . В результате выполнения этой операции был получен граф G = (X , U ) c новым числом вершин. Установить соответствие между номерами вершин графа G и графа G .

84

2. Определить число компонент связности в графе G, заданного матрицей смежности R =(ri,j) , элементы которой равны: r1,2= 2 , r2,3= 1, r4,11= 1, r5,9= 1, r6,7= 2 , r4,10= 2, r11,10= 2, r7,8 = 1 (значения симметричных элементов матрицы R получить самостоятельно; значения неуказанных элементов при-равнять «нулю»), с помощью алгоритма рассмотренного в данной теме.

3.Исследовать алгоритм определения количества компонент связности для классов орграфов, неориентированных графов и смешанных графов.

4.Разработать алгоритм определения числа компонент связности для компьютерной обработки.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Рассмотрим базовую задачу.
Условие:
Дан неориентированный граф 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)
Самое классическое решение. Даже комментировать особо нечего.

  1. const int SIZE = 1e3 + 10;
  2. vector<int> adj[SIZE];
  3. bool usd[SIZE];
  4. ...
  5. void dfs(int cur) {
  6.   usd[cur] = true;
  7.   for (int i=0;i<adj[cur].size();++i) {
  8.     int nxt = adj[cur][i];
  9.     if (!usd[nxt])
  10.       dfs(nxt);
  11.   }
  12. }
  13. int connected_components_amount_dfs() {
  14.   int cnt = 0;
  15.   for (int i=1; i<=n; ++i) {
  16.     if (!usd[i]) {
  17.       dfs(i);
  18.       ++cnt;
  19.     }
  20.   }
  21.   return cnt;
  22. }

* This source code was highlighted with Source Code Highlighter.

Граф храним в виде списка смежности(строка 2). Общая сложность решения $latex O(N + M)$.
Решение

2. DSU подобная структура(ленивый подход)
Будем делать конденсацию компоненты в одну вершину. Идея следующая: будем последовательно обрабатывать ребра. Каждая компонента связности будет представлена одной своей вершиной(титульная). При этом неважно какой. По ходу обработки ребер титульная вершина компонент может меняться.
В итоге для нахождения количества компонент связности нужно найти количество титульных вершин.

  1. const int SIZE = 1e3 + 10;
  2. int p[SIZE];
  3. bool usd[SIZE];
  4. ...
  5. int connected_components_amount_dsu() {
  6.  
  7.   scanf("%d %d", &n, &m);
  8.  
  9.   for (int i=1;i<=n;++i) {
  10.     p[i] = i;
  11.   }
  12.  
  13.   for (int i=0;i<m;++i) {
  14.     scanf("%d %d", &f, &s);
  15.     int par = p[f];
  16.     for (int j=1;j<=n;++j) {
  17.       if (p[j] == par)
  18.         p[j] = p[s];
  19.     }
  20.   }
  21.   for (int i=1;i<=n;++i)
  22.     usd[p[i]] = true;
  23.   int cnt = 0;
  24.   for (int i=1;i<=n;++i) {
  25.     if (usd[i]) ++cnt;
  26.   }
  27.   return cnt;
  28. }

* This source code was highlighted with Source Code Highlighter.

Заметим, что сам граф непосредственно вообще никак не хранится. Общая сложность $latex O(M*N)$ 
Решение

3. Система непересекающихся множеств (DSU)
Реализацию, представленную выше можно существенно усовершенствовать. При добавлении нового ребра будем “мерджить меньшее подмножество к большему” + сжимать пути. У emaxx’а рассматривается доказательство того, что ассимптотика обработки одного ребра равна $latex O(alpha (N))$, где $latex alpha (N)$ – обратная функция Аккермана.

  1. const int SIZE = 1e3 + 10;
  2.  
  3. int p[SIZE];
  4. int size[SIZE];
  5.  
  6. int par(int x) {
  7.   return p[x] == x ? x : p[x] = par(p[x]);
  8. }
  9. int connected_components_amount_dsu() {
  10.  
  11.   scanf("%d %d", &n, &m);
  12.  
  13.   for (int i=1;i<=n;++i) {
  14.     p[i] = i;
  15.     size[i] = 1;
  16.   }
  17.  
  18.   for (int i=0;i<m;++i) {
  19.     scanf("%d %d", &f, &s);
  20.     f = par(f); s = par(s);
  21.     if (f != s) {
  22.       if (size[f] > size[s])
  23.         swap(f,s);
  24.       p[f] = s;
  25.       size[s] += size[f];
  26.     }
  27.   }
  28.   bool usd[SIZE];
  29.   memset(usd, 0, sizeof(usd));
  30.   for (int i=1;i<=n;++i)
  31.     usd[par(i)] = true;
  32.   int cnt = 0;
  33.   for (int i=1;i<=n;++i) {
  34.     if (usd[i]) ++cnt;
  35.   }
  36.  
  37.   return cnt;
  38. }

* 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 двумя вложенными циклами.

  1. const int SIZE = 1e3 + 10;
  2. int adj[SIZE][SIZE];
  3. bool usd[SIZE];
  4. ...
  5. int connected_components_amount_floyd() {
  6.  
  7.   for (int k=1;k<=n;++k){
  8.     for (int i=1;i<=n;++i){
  9.       for (int j=1;j<=n;++j){
  10.         if (adj[i][k] + adj[k][j] == 2)    
  11.           adj[i][j] = 1;
  12.       }
  13.     }
  14.   }
  15.  
  16.   int cnt = 0;
  17.   for (int i=1;i<=n;++i) {
  18.     if (!usd[i]) {
  19.       ++cnt;
  20.       usd[i] = true;
  21.       for (int j=1;j<=n;++j) {
  22.         if (adj[i][j] && !usd[j])
  23.           usd[j] = true;
  24.       }
  25.     }
  26.   }
  27.   return cnt; 
  28. }

* This source code was highlighted with Source Code Highlighter.

Алгоритм ужасно долгий, но зато пишется довольно просто. Общая сложность $latex O({ N }^{ 3 }) $
Решение

Собственно пока и все. Мы рассмотрели 3 принципиально разных подхода. На маленьких ограничениях можно выбрать тот из них, что больше по душе.

Определение компоненты связности

Понятие компоненты связности вытекает из понятия связности графа. Попросту
говоря, компонента связности — часть графа (подграф), являющаяся связной.
Формально, компонента связности — набор вершин графа, между любой парой
которых существует путь.

Граф с тремя компонентами связности

Граф на иллюстрации содержит три компоненты связности, закрашенные разными
цветами. Можно заметить, что даже одна вершина, изолированная от остального
графа, составляет компоненту связности.

Общее понятие связности распространяется только на неориентированные графы.
Для описания ориентированных графов используются понятия сильной и слабой
связности, но они выходят за границы материала этой лекции.

Алгоритм поиска компонент связности

Для поиска компонент связности используется обычный DFS практически без
модификаций (можно использовать и BFS). При запуске обхода из одной вершины, он
гарантированно посетит все вершины, до которых возможно добраться, то есть,
всю компоненту связности, к которой принадлежит начальная вершина. Для нахождения
всех компонент просто попытаемся запустить обход из каждой вершины по очереди,
если мы ещё не обошли её компоненту ранее.

Реализация

Приведём два варианта реализации с разным способом хранения компонент связности.

Простейший вариант: просто заполнить массив (comp), где (comp[i]) — номер
компоненты связности, к которой принадлежит вершина (i).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <bits/stdc++.h>

using namespace std;

vector<int> graph[100000];
bool used[100000];
int comp[100000];

void dfs(int v, int c_num) {    //c_num - номер текущей компоненты связности.
    used[v] = true;
    comp[v] = c_num;

    for (int u: graph[v]) {
        if (!used[u]) {
            dfs(u, c_num);
        }
    }
}

int main() {
    //Ввод графа...

    int c_num = 1;  //Номер очередной компоненты.
                    //Нумеровать можно как с 0, так и с 1, как вам удобнее.

    for (int i = 0; i < n; i++) {
        if (!used[i]) {     //если мы ещё не посетили эту вершину, обходя одну из предыдущих
            dfs(i, c_num);
            c_num++;
        }
    }

    for (int i = 0; i < n; i++) {
        cout << "Vertex " << i + 1 << " belongs to component " << comp[i] << endl;
    }
}

Другой вариант: явно хранить для каждой компоненты вектор из вершин, входящих
в неё. Компоненты (векторы) хранить в векторе (из векторов).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <bits/stdc++.h>

using namespace std;

vector<int> graph[100000];
bool used[100000];

vector<vector<int>> comps;

void dfs(int v) {   //информацию о компоненте в DFS больше передавать не нужно,
                    //он просто будет добавлять вершины в последний вектор в comps.
    used[v] = true;
    comps.back().push_back(v);

    for (int u: graph[v]) {
        if (!used[u]) {
            dfs(u);
        }
    }
}

int main() {
    //Ввод графа...

    for (int i = 0; i < n; i++) {
        if (!used[i]) {
            comps.push_back(vector<int>());   //добавляем в comps новый пустой вектор,
                                              //в который DFS будет записывать посещённые вершины.
            dfs(i);
        }
    }

    for (vector<int>& comp: comps) {
        cout << "Component: ";
        for (int v: comp) {
            cout << v + 1 << ", ";
        }
        cout << endl;
    }
}

Как найти количество компонент связности графа?

for (int i = 0; i < n; i++)
    {
        if (was[i] != -1)
           continue;
        q.push(i);
        while (!q.empty())
        {
              int v = q.front();
              q.pop();
              if (was[v] != -1)
                 continue;
              was[v] = cur;
              for (int j = 0; j < n; j++)
                  if (mas[i][j] != 0 && was[j] == -1)
                     q.push(j);
        }
        cur++;
    }

Данный код считает максимальную компоненту связности. Как найти их количество?
Например,
3
0 0 0
0 0 1
0 1 0
Эта программа выводит 3, а количество будет равно 2.


  • Вопрос задан

    более трёх лет назад

  • 4111 просмотров

Пригласить эксперта

Выбираете первую вершину графа, отмечаете её и все связанные с ней точки (прямо или чрез другие вершины). Получаете первую компоненту. Выбираете следующую непомеченную вершину графа, от неё получаете вторую компоненту и т.д., пока не останется непомеченных вершин.


  • Показать ещё
    Загружается…

29 мая 2023, в 09:18

30000 руб./за проект

29 мая 2023, в 08:59

500 руб./за проект

29 мая 2023, в 08:57

20000 руб./за проект

Минуточку внимания

Понравилась статья? Поделить с друзьями:
  • Как можно найти новую работу
  • Треугольник как найти сторану
  • Каталог цен как составить
  • Ошибка d3d11 dll как исправить ошибку
  • Как найти размер одномерного массива паскаль