Задача о динамической связности — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Псевдокод)
м (rollbackEdits.php mass rollback)
 
(не показаны 23 промежуточные версии 3 участников)
Строка 12: Строка 12:
 
Существуют задачи, в которых граф не обязательно на протяжении нашей работы после каждой операции добавления ребра остаётся лесом. Для решения таких задач в каждой компоненте связности выделим [[Остовные деревья: определения, лемма о безопасном ребре|остовные деревья]], которые образуют остовный лес.  
 
Существуют задачи, в которых граф не обязательно на протяжении нашей работы после каждой операции добавления ребра остаётся лесом. Для решения таких задач в каждой компоненте связности выделим [[Остовные деревья: определения, лемма о безопасном ребре|остовные деревья]], которые образуют остовный лес.  
  
[[Файл:Graph.jpg|550px|thumb|left|Граф]]  [[Файл:Spanforest.jpg|550px|thumb|right|Остовный лес в графе]]
+
[[Файл:Graph.jpg|530px|thumb|left|Граф]]  [[Файл:Spanforest.jpg|530px|thumb|right|Остовный лес в графе]]
  
  
Строка 29: Строка 29:
  
  
 
+
===Проверка связности===
 
 
===connected(u,v)===
 
 
Граф и его остовный лес {{---}} одно и то же с точки зрения связности. Поэтому проверка связности в графе сводится к проверке связности в остовном лесе и решается за <tex>O(\log n)</tex>.<!--Добавление рёбер можно рассмотреть с точки зрения [[СНМ (реализация с помощью леса корневых деревьев)|системы непересекающихся множеств]], такой запрос будет работать за <tex>O(\log n)</tex>. Операция проверки сводится к проверке связности в остовном лесе и работает также за <tex>O(\log n)</tex>.-->
 
Граф и его остовный лес {{---}} одно и то же с точки зрения связности. Поэтому проверка связности в графе сводится к проверке связности в остовном лесе и решается за <tex>O(\log n)</tex>.<!--Добавление рёбер можно рассмотреть с точки зрения [[СНМ (реализация с помощью леса корневых деревьев)|системы непересекающихся множеств]], такой запрос будет работать за <tex>O(\log n)</tex>. Операция проверки сводится к проверке связности в остовном лесе и работает также за <tex>O(\log n)</tex>.-->
  
===add(u,v)===
+
===Добавление ребра===
 
Чтобы разобраться с тем, как изменится граф и остовный лес при добавлении и удалении ребра, введём функцию <tex>l(e):E{\rightarrow}[0;\log n]</tex> и назовём её ''уровнем ребра'' <tex>e</tex>. Уровни ребра можно распределить любым способом, но для всех <tex> i </tex> должно выполняться следующее свойство: размер каждой компоненты связности <tex>G_i</tex> не превосходит <tex>\dfrac{n}{2^i}</tex>. Здесь графы <tex>G_i</tex> определяются так: <tex>G_i=\langle V, E\rangle: \{e \in E \mid l(e) \geqslant i\}</tex>.
 
Чтобы разобраться с тем, как изменится граф и остовный лес при добавлении и удалении ребра, введём функцию <tex>l(e):E{\rightarrow}[0;\log n]</tex> и назовём её ''уровнем ребра'' <tex>e</tex>. Уровни ребра можно распределить любым способом, но для всех <tex> i </tex> должно выполняться следующее свойство: размер каждой компоненты связности <tex>G_i</tex> не превосходит <tex>\dfrac{n}{2^i}</tex>. Здесь графы <tex>G_i</tex> определяются так: <tex>G_i=\langle V, E\rangle: \{e \in E \mid l(e) \geqslant i\}</tex>.
  
Строка 43: Строка 41:
 
====Псевдокод====
 
====Псевдокод====
 
    
 
    
   '''function''' add ('''Node''' u, '''Node''' v):
+
   '''function''' <tex>\mathrm{add}</tex>('''Node''' u, '''Node''' v):
 
     '''Edge''' e = <tex>\langle </tex>u, v<tex>\rangle</tex>
 
     '''Edge''' e = <tex>\langle </tex>u, v<tex>\rangle</tex>
 
     e.level = 0
 
     e.level = 0
     insert(<tex>G_0</tex>, e)
+
     <tex>G_0</tex> = <tex>G_0</tex> <tex>\cup</tex> e<!---insert(<tex>G_0</tex>, e)-->
     '''if not''' connected(u, v)
+
     '''if not''' <tex>\mathrm{connected(u,v)}</tex>
       insert(<tex>F_0</tex>, e)
+
       <tex>F_0</tex> = <tex>F_0</tex> <tex>\cup</tex> e<!---insert(<tex>F_0</tex>, e)-->
  
===remove(u,v)===
+
===Удаление ребра===
 
{{Утверждение
 
{{Утверждение
 
|statement=Если ребро, которое мы хотим удалить, не принадлежит остовному лесу, то связность между любой парой вершин сохранится.
 
|statement=Если ребро, которое мы хотим удалить, не принадлежит остовному лесу, то связность между любой парой вершин сохранится.
Строка 90: Строка 88:
 
====Псевдокод====
 
====Псевдокод====
  
   '''function''' remove ('''Node''' u, '''Node''' v):
+
   '''function''' <tex>\mathrm{remove}</tex>('''Node''' u, '''Node''' v):
 
     '''Edge''' e = <tex>\langle </tex>u, v<tex>\rangle</tex>
 
     '''Edge''' e = <tex>\langle </tex>u, v<tex>\rangle</tex>
     i = e.level
+
     '''for''' i = e.level '''downto''' 0
    '''while''' i >= 0
+
      <tex>G_i</tex> = <tex>G_i\setminus</tex>e<!---delete(<tex>G_i</tex>, e)--->
 
       <tex>F_i</tex> = <tex>F_i\setminus</tex>e<!---delete(<tex>F_i</tex>, e)--->
 
       <tex>F_i</tex> = <tex>F_i\setminus</tex>e<!---delete(<tex>F_i</tex>, e)--->
 
       '''Edge''' e2
 
       '''Edge''' e2
       '''for''' e2 = <tex>\langle </tex>x, y<tex>\rangle</tex> : e.level == i '''and''' x <tex>\in T_u</tex>
+
       '''for''' e2 = <tex>\langle </tex>x, y<tex>\rangle</tex> : e2.level == i '''and''' x <tex>\in T_u</tex>
 
         '''if''' y <tex>\in T_v</tex>  
 
         '''if''' y <tex>\in T_v</tex>  
           '''while''' i >= 0
+
           '''for''' j = i '''downto''' 0
             <tex>F_i</tex> = <tex>F_i\bigcup</tex> e<!---insert(<tex>F_i</tex>, e2)-->
+
             <tex>F_j</tex> = <tex>F_j</tex> <tex>\cup</tex> e2<!---insert(<tex>F_i</tex>, e2)-->
            i--
+
           '''return'''
           '''break'''
+
         '''else'''  
         '''else''' e2.level++
+
          e2.level++
      i--
+
          <tex>G_{i+1}</tex> = <tex>G_{i+1}</tex> <tex>\cup</tex> e2<!---insert(<tex>F_i</tex>, e2)-->
  
 
== См. также ==
 
== См. также ==

Текущая версия на 19:15, 4 сентября 2022

Задача:
Есть неориентированный граф из [math]n[/math] вершин, изначально не содержащий рёбер. Требуется обработать [math]m[/math] запросов трёх типов:
  • [math]\mathrm{add(u,v)}[/math] — добавить ребро между вершинами [math]u[/math] и [math]v[/math];
  • [math]\mathrm{remove(u,v)}[/math] — удалить ребро между вершинами [math]u[/math] и [math]v[/math];
  • [math]\mathrm{connected(u,v)}[/math] — проверить, лежат ли вершины [math]u[/math] и [math]v[/math] в одной компоненте связности.

Динамическая связность в лесах

Если задача такова, что в графе нет и не может быть циклов, то она сводится к задаче о связности в деревьях эйлерова обхода. Время работы каждого запроса для упрощённой задачи — [math]O(\log n)[/math], где [math]n[/math] — количество вершин в графе.

Обобщение задачи для произвольных графов

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

Граф
Остовный лес в графе









Проверка связности

Граф и его остовный лес — одно и то же с точки зрения связности. Поэтому проверка связности в графе сводится к проверке связности в остовном лесе и решается за [math]O(\log n)[/math].

Добавление ребра

Чтобы разобраться с тем, как изменится граф и остовный лес при добавлении и удалении ребра, введём функцию [math]l(e):E{\rightarrow}[0;\log n][/math] и назовём её уровнем ребра [math]e[/math]. Уровни ребра можно распределить любым способом, но для всех [math] i [/math] должно выполняться следующее свойство: размер каждой компоненты связности [math]G_i[/math] не превосходит [math]\dfrac{n}{2^i}[/math]. Здесь графы [math]G_i[/math] определяются так: [math]G_i=\langle V, E\rangle: \{e \in E \mid l(e) \geqslant i\}[/math].

Очевидно, что [math]G_{\log n} \subseteq G_{\log n-1} \subseteq \ldots \subseteq G_1 \subseteq G_0 = G[/math]. Выделим в графах остовные леса таким образом, что [math]F_{\log n} \subseteq F_{\log n-1} \subseteq \ldots \subseteq F_1 \subseteq F_0[/math], где [math]F_i[/math] — остовный лес графа [math]G_i[/math].

Удобнее всего новому ребру давать уровень [math]0[/math]. В этом случае изменится только [math]G_0[/math], так как в остальные подграфы [math]G_i[/math] рёбра нулевого уровня не входят. После вставки нового ребра нам нужно проверить, были ли вершины [math]u[/math] и [math]v[/math] в одной компоненте связности до того, как мы вставили ребро. Если они лежали в разных компонентах, то необходимо новое ребро добавить и в остовный лес [math]F_0[/math].

Псевдокод

 function [math]\mathrm{add}[/math](Node u, Node v):
   Edge e = [math]\langle [/math]u, v[math]\rangle[/math]
   e.level = 0
   [math]G_0[/math] = [math]G_0[/math] [math]\cup[/math] e
   if not [math]\mathrm{connected(u,v)}[/math]
     [math]F_0[/math] = [math]F_0[/math] [math]\cup[/math] e

Удаление ребра

Утверждение:
Если ребро, которое мы хотим удалить, не принадлежит остовному лесу, то связность между любой парой вершин сохранится.
[math]\triangleright[/math]

Докажем от противного. Допустим, что это не так. Понятно, что при разрезании ребра нового пути между вершинами не появится.

Предположим, что нарушилась связность для каких-то двух вершин. Значит, мы убрали мост. А любой мост принадлежит всем остовным деревьям его компоненты. Противоречие.
[math]\triangleleft[/math]
Компонента связности T.

Таким образом, если мы удалили ребро не из остовного леса, то нам не придётся перестраивать лес и пересчитывать значение [math]\mathrm{connected(u,v)}[/math].

Рассмотрим случаи, когда мы берём ребро из леса. Тогда необходимо выяснить, является ли данное ребро мостом в графе, и выполнить соответствующие действия.

Проверим, является ли ребро мостом. У ребра [math]uv[/math] известен уровень, пусть он равен [math]i[/math]. Попробуем найти другое ребро ([math]xy[/math]), соединяющее поддеревья [math]T_u[/math] и [math]T_v[/math], на которые распалось остовное дерево исследуемой компоненты [math]T[/math].

Утверждение:
Если ребро [math]xy[/math] существует, то его уровень не больше [math]i[/math].
[math]\triangleright[/math]
От противного. Пусть [math]l(xy)=j[/math], где [math]j \gt i[/math]. Тогда вершины [math]x[/math] и [math]y[/math] каким-то образом связаны в [math]F_j[/math] (либо непосредственно ребром [math]xy[/math], либо каким-то другим путём). Но [math]F_j \subseteq F_i[/math]. Значит, в [math]F_i[/math] между [math]x[/math] и [math]y[/math] сохранился путь из рёбер уровня не меньше [math]j[/math] и появился другой путь через [math]uv[/math]. Приходим к противоречию, так как в [math]F_i[/math] все компоненты должны быть деревьями.
[math]\triangleleft[/math]

Чтобы найти [math]xy[/math], выберем из поддеревьев [math]T_u[/math] и [math]T_v[/math] наименьшее. Не умаляя общности, будем считать, что [math]|T_u|\leqslant|T_v|[/math]. Так как всегда из двух слагаемых можно выбрать одно такое, что оно не превосходит половины их суммы, имеем важное свойство: [math]|T_u|\leqslant\dfrac{|T_u|+|T_v|}{2}=\dfrac{|T|}{2}[/math]. Также нам известно, что [math]T \subseteq F_i[/math], а значит, [math]|T|\leqslant\dfrac{n}{2^i}[/math]. Отсюда [math]|T_u|\leqslant\dfrac{n}{2^{i+1}}[/math]. Это неравенство позволит нам увеличивать уровни рёбер при необходимости.

Будем искать ребро [math]xy[/math] следующим образом:

  1. Выбираем любое ребро уровня [math]i[/math], выходящее из вершины, принадлежащей [math]T_u[/math].
  2. Если выбранное ребро ведёт в [math]T_v[/math], выходим из цикла и добавляем ребро [math]xy[/math] в остовные леса [math]F_i[/math], для которых [math]i\leqslant l(xy)[/math] и выходим из цикла;
  3. Если выбранное ребро ведёт в другую вершину поддерева [math]T_u[/math], увеличиваем его уровень на [math]1[/math];
  4. Если есть непроверенные рёбра на интересующем нас уровне [math]i[/math], переходим к пункту [math]1[/math];
  5. Если таких рёбер уровня [math]i[/math] не осталось и [math]i\gt 0[/math], рассматриваем уровень на единицу меньший и переходим к пункту [math]1[/math];
  6. Если все рёбра просканированы и [math]i=0[/math], то [math]uv[/math] является мостом.

Замечание. Увеличив уровень ребра на единицу, нужно не забыть обновить [math]G_{i+1}[/math] и [math]F_{i+1}[/math].

Оценка времени работы

Пункт [math]2[/math] работает за [math]O(\log^2 n)[/math], так как после выхода из цикла мы добавляем ребро за [math]O(\log n)[/math] на каждом уровне, а количество уровней не больше [math]\log n[/math].

Пусть до момента, когда мы нашли нужное ребро, мы сделали [math]S[/math] неудачных сканирований. После каждого такого сканирования нам приходится добавлять новые рёбра в [math]G_{i+1}[/math], что стоит [math]O(\log n)[/math]. Получаем сложность удаления одного ребра [math]O(\log^2{n}+S\cdot\log n)[/math].

Выразим сложность одной операции [math]\mathrm{remove}[/math] другим способом. Для [math]n[/math] вершин и [math]m[/math] вызовов процедуры сложность равна [math]O(\log^2{n}\cdot m+\log n\cdot\displaystyle \sum_{i=1}^m S_i)[/math], что не превосходит [math]O(\log^2{n} \cdot m+\log n\cdot\log n\cdot m)[/math], так как уровень ребра [math]m[/math] раз рос максимум до [math]\log n[/math]. Отсюда суммарная сложность всех запросов равна [math]O(\log^2{n}\cdot m)[/math], а для одного запроса мы решаем задачу за [math]O(\log^2{n})[/math].

Псевдокод

 function [math]\mathrm{remove}[/math](Node u, Node v):
   Edge e = [math]\langle [/math]u, v[math]\rangle[/math]
   for i = e.level downto 0
     [math]G_i[/math] = [math]G_i\setminus[/math]e
     [math]F_i[/math] = [math]F_i\setminus[/math]e
     Edge e2
     for e2 = [math]\langle [/math]x, y[math]\rangle[/math] : e2.level == i and x [math]\in T_u[/math]
       if y [math]\in T_v[/math] 
         for j = i downto 0
           [math]F_j[/math] = [math]F_j[/math] [math]\cup[/math] e2
         return
       else 
         e2.level++
         [math]G_{i+1}[/math] = [math]G_{i+1}[/math] [math]\cup[/math] e2

См. также

Источники информации