Деревья и графы. Система непересекающихся множеств. Поиск наименьшего общего предка.

Система непересекающихся множеств

Структура данных "система непересекающихся множеств" (на английском "disjoint-set-union", или просто "DSU").

Эта структура данных предоставляет следующие возможности. Изначально имеется несколько элементов, каждый из которых находится в отдельном (своём собственном) множестве. За одну операцию можно объединить два каких-либо множества, а также можно запросить, в каком множестве сейчас находится указанный элемент. Также, в классическом варианте, вводится ещё одна операция — создание нового элемента, который помещается в отдельное множество.

Таким образом, базовый интерфейс данной структуры данных состоит всего из трёх операций:

Описываемая ниже структура данных позволяет делать каждую из этих операций почти за $O(1)$ в среднем (более подробно об асимптотике см. ниже после описания алгоритма).

Реализация на Python

# DSU (система непересекающихся множеств): сжатие путей и объединение по размеру, O(α(n)) амортизированно
def make_set(parent, set_size, x):
    """Добавляет элемент x в новое множество из одного элемента."""
    if x in parent:
        return
    parent[x] = x
    set_size[x] = 1


def find_set(parent, x):
    """Возвращает представителя множества, в котором лежит x; по пути сжимает цепочку."""
    if parent[x] != x:
        parent[x] = find_set(parent, parent[x])
    return parent[x]


def union_sets(parent, set_size, x, y):
    """Объединяет множества элементов x и y: меньшее подвешивается к большему."""
    root_x = find_set(parent, x)
    root_y = find_set(parent, y)
    if root_x == root_y:
        return
    if set_size[root_x] < set_size[root_y]:
        root_x, root_y = root_y, root_x
    parent[root_y] = root_x
    set_size[root_x] += set_size[root_y]


if __name__ == "__main__":
    parent, set_size = {}, {}
    for x in range(1, 7):
        make_set(parent, set_size, x)
    union_sets(parent, set_size, 1, 2)
    union_sets(parent, set_size, 3, 4)
    union_sets(parent, set_size, 2, 4)
    print(find_set(parent, 1) == find_set(parent, 3))  # True
    print(find_set(parent, 5) == find_set(parent, 1))  # False
    print(set_size[find_set(parent, 1)])  # 4

Реализация на Rust

// DSU (система непересекающихся множеств): сжатие путей и объединение по размеру, O(α(n)) амортизированно
use std::collections::HashMap;

#[derive(Default)]
pub struct Dsu {
    parent: HashMap<usize, usize>,
    set_size: HashMap<usize, usize>, // размер множества хранится только у его представителя
}

impl Dsu {
    // Добавляет элемент x в новое множество из одного элемента: O(1) в среднем
    pub fn make_set(&mut self, x: usize) {
        if !self.parent.contains_key(&x) {
            self.parent.insert(x, x);
            self.set_size.insert(x, 1);
        }
    }

    // Возвращает представителя множества x; по пути сжимает цепочку родителей
    pub fn find_set(&mut self, x: usize) -> usize {
        let up = self.parent[&x];
        if up == x {
            return x;
        }
        let root = self.find_set(up);
        self.parent.insert(x, root);
        root
    }

    // Объединяет множества элементов x и y: меньшее подвешивается к большему
    pub fn union_sets(&mut self, x: usize, y: usize) {
        let (mut root_x, mut root_y) = (self.find_set(x), self.find_set(y));
        if root_x == root_y {
            return;
        }
        if self.set_size[&root_x] < self.set_size[&root_y] {
            std::mem::swap(&mut root_x, &mut root_y);
        }
        self.parent.insert(root_y, root_x);
        let added = self.set_size[&root_y];
        *self.set_size.get_mut(&root_x).unwrap() += added;
    }

    // Размер множества, в котором лежит x
    pub fn size_of(&mut self, x: usize) -> usize {
        let root = self.find_set(x);
        self.set_size[&root]
    }
}

fn main() {
    let mut dsu = Dsu::default();
    for x in 1..=6 { dsu.make_set(x); }
    dsu.union_sets(1, 2);
    dsu.union_sets(3, 4);
    dsu.union_sets(2, 4);
    println!("{}", dsu.find_set(1) == dsu.find_set(3)); // true
    println!("{}", dsu.find_set(5) == dsu.find_set(1)); // false
    println!("{}", dsu.size_of(1)); // 4
}

Реализация на C++

// DSU (система непересекающихся множеств): сжатие путей и объединение по размеру, O(α(n)) амортизированно
#include <iostream>
#include <unordered_map>
#include <utility>

struct Dsu {
  std::unordered_map<int, int> parent;
  std::unordered_map<int, int> set_size;  // размер множества хранится только у его представителя

  // Добавляет элемент x в новое множество из одного элемента: O(1) в среднем
  void make_set(int x) {
    if (parent.count(x)) return;
    parent[x] = x;
    set_size[x] = 1;
  }

  // Возвращает представителя множества x; по пути сжимает цепочку родителей
  int find_set(int x) {
    int up = parent.at(x);
    if (up == x) return x;
    int root = find_set(up);
    parent[x] = root;
    return root;
  }

  // Объединяет множества элементов x и y: меньшее подвешивается к большему
  void union_sets(int x, int y) {
    int root_x = find_set(x);
    int root_y = find_set(y);
    if (root_x == root_y) return;
    if (set_size.at(root_x) < set_size.at(root_y)) std::swap(root_x, root_y);
    parent[root_y] = root_x;
    set_size[root_x] += set_size[root_y];
  }

  // Размер множества, в котором лежит x
  int size_of(int x) { return set_size.at(find_set(x)); }
};

int main() {
  Dsu dsu;
  for (int x = 1; x <= 6; ++x) dsu.make_set(x);
  dsu.union_sets(1, 2);
  dsu.union_sets(3, 4);
  dsu.union_sets(2, 4);
  int root1 = dsu.find_set(1);
  int root3 = dsu.find_set(3);
  std::cout << std::boolalpha << (root1 == root3) << '\n';          // true
  std::cout << (dsu.find_set(5) == root1) << '\n';                  // false
  std::cout << dsu.size_of(1) << '\n';                              // 4
  return 0;
}