Algorithm

Зачем это нужно

Сэлли устраивает вечеринку.? Она пригласила Макса, Сью, Тома и Джейка. Потом Том позвал Райна, который пришел с Джесс, а Джесс позвала Лу, который знает друзей Тома. Джейк очень общительный, поэтому он тоже знаком с Джесс и Райаном. А ещё пришёл Джо. Джо знаком с Лу, но не знаком с Сэлли, и на самом деле не знает больше никого из гостей, но тем не менее, теоретически, он тоже приглашенный гость. Пришли ещё три случайных гостя — Лиз, Ти и Джей, — потому что вечеринка обещает быть шумной. В конце концов Эрин, которая как всегда очень элегантно опаздывает, приходит вместе с Эми и Джеком.

Наша задача — найти все максимальные группы людей, в которых все знают друг друга.

Давайте применим основной алгоритм Брона-Кербоша к данной системе социальных взаимоотношений, или, иными словами, давайте решим данную задачу, применив к вечеринке метод рекурсивного поиска с возвратом при O(3^n/3). NP-полные задачи решаются быстро, но сложно. Поэтому решим нашу задачу с помощью подграфов.
Посмотрите на вершины «Джек», «Эми», «Эрин» и «Сэлли».

Здесь мы видим две максимальные группы: (1) Эми, Джек и Эрин. (2) Сэлли и Эрин.

Наш мозг обладает большим потенциалом и способен быстро распознать данные группы. Компьютеры тоже имеют высокий потенциал и способны распознать эти группы, если правильно задать параметры.

Какие параметры мы должны задать компьютеру, чтобы он начал видеть то, что видим мы?


Начнем с выбора вершины. Допустим, это будет Джек.

Теперь необходимо запустить рекурсивный вызов на вершину «Джек» и рассмотреть только его соседей.

Далее выберите любую из соседних от Джека вершин.
Давайте возьмем вершину «Эми».

На следующем уровне нужно выбрать вершину, которая принадлежит и множеству соседних вершин для Джека, и множеству соседних вершин для Эми.

Это Эрин.

Для обозначения расположения «принадлежит и множеству соседних вершин для вершины «Джек», и множеству соседних вершин для вершины «Эми» существует термин «пересечение».


На третьем этапе, на уровне вершины «Эрин», пересекающихся соседних вершин нет.

Обратите внимание, что Сэлли не входит в данную группу, так как Сэлли не знакома с Джеком и Эми.
Основной сценарий можно считать верным. Группа найдена.


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

Поднимемся на один уровень.

Мы нашли максимальную группу в множестве с вершинами «Джек» и «Эми», может, в множестве с вершинами «Джек» и «Эрин».

Выбираем другого соседа Джека — Эрин. Исключаем Эми, и следуем по новому пути.

Делаем два рекурсивных вызова, проверяем, нет ли соседних вершин, которые пересекаются с вершинами «Эрин» и «Джек».

В данном случае это Эми, но вершина «Эми» находится в исключенном пересечении.

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

Поднимаемся на уровень выше.


Все соседи Джека были просмотрены. Снова поднимаемся на самый высокий уровень.

На верхнем уровне исключаем Джека и выбираем любую другую вершину.
Давайте в это раз выберем Эми.

Опустимся на уровень ниже и рассмотрим соседей Эми (которые не были исключены).

Эми знакома с Эрин.

Опустимся ещё на уровень, и проверим, есть ли общие соседи у Эми и Эрин.

Единственной такой вершиной в множестве является вершина «Джек», которая исключена, соответственно тут максимальных групп тоже нет.

Запускаем поиск с возвратом до верхнего уровня.


Эми следует за Джеком (и попадает в множество исключенных вершин), а мы начнем поиск с другой произвольной вершины.

Давайте возьмем вершину «Эрин».

Рассмотрим неисключенных соседей Эрин  — это Сэлли.

Вершина выбрана, рекурсия спускается на уровень ниже, поиск продолжается.

Оцениваем основной сценарий.

(1) Есть ли дополнительные вершины, которые пересекаются с соседними множествами? Нет.

(2) Есть ли вершины, которые входят в число исключенных пересечений с соседними множествами? Нет.

Обратите внимание, что вершина «Сэлли» соединена с «Эрин», а не с Джеком или Эми, и именно по этой причине исключенные вершины «Джек» и «Эми» не входят в это множество пересечений.


Мы нашли группу.

А теперь запускаем поиск с возвратом.

У Эрин больше не осталось вершин, с которыми она могла бы образовать группу, поэтому она следует за Джеком и Эми (и попадает в множество исключенных).

У Сэлли остается только связь с Эрин, а Эрин уже исключена.

Исключаем и Сэлли тоже.

Больше исключать некого. Алгоритм выполнен.

Пример из веб-приложения, здесь.

Как это работает

Алгоритм Брона-Кербоша работает на трех множествах: R, P и X.

R := это множество вершин максимальной группы.
P := множество возможных вершин максимальной группы.
X := множество исключенных вершин.

Алгоритму Брон-Кербоша запускает следующие операции с множествами P, R и X:

R ⋃ {v} := соединение R с единичным множеством (singleton) v.
P ⋂ N(v) := пересечение множества P с соседями v.
X ⋂ N(v) := пересечение множества X с соседями v.
P \ {v} := относительное соотнесение P единичного множества v.
X ⋃ {v} := соединение множества X и единичного набора v.

Псевдокод:

BronKerbosch1(R, P, X):
  Если и P, и X окажутся пустыми,
   то R — максимальная группа
  для каждой вершины v в P:
   BronKerbosch1(
     R ⋃ {v}, 
     P ⋂ N(v), 
     X ⋂ N(v)
   )
   P := P \ {v}
   X := X ⋃ {v}

Более подробное описание множеств:

ПРИМЕР СОЕДИНЕНИЯ:
Вершины принадлежат к или к множеству А, или к множеству В.
A = {Джек, Эми}
B = {Джек, Сэлли, Эрин}
A ⋃ B = {Jack, Amy, Sally, Erin}
ПРИМЕР ПЕРЕСЕЧЕНИЯ:
Вершины принадлежат множествам А и B.
A = {Джек, Эми}
B = {Джек, Сэлли, Эрин}
A ⋂ B = {Джек}
ПРИМЕР ОТНОСИТЕЛЬНОГО СООТНЕСЕНИЯ:
Вершины принадлежат множеству А, а не B.
A = {Джек, Эми}
B = {Джек, Сэлли, Эрин}
A \ B = {Эми}

Переведем псевдокод в язык С++:

void bronKerbosch(set<node*> R, set<node*> P, set<node*> X) {
  if(P.empty() && X.empty()) {
    cout<<"Clique found: ";
    set_print(R);
  }
  set<node*>::iterator v = P.begin();
  while(!P.empty()  && v!=P.end()){
    set<node*> singleton = { (*v) };
    bronKerbosch(
      set_union(R,singleton), 
      set_intersection(P,(*v)->friends),
      set_intersection(X,(*v)->friends));
    P = set_difference(P,singleton);
    X = set_union(X,singleton);
    if(!P.empty())
      v = P.begin();
  }
}

1. Скопируйте код отсюда в текстовый редактор и сохраните как файл main.cpp
2. Составьте в командной строке g++ main.cpp -std=c++11 -o Bronker.exe
3. Потом запустите код из командной строки ./Bronker.exe.

После составления и выполнения исходного кода, программа «организует вечеринку»? и предоставит список всех максимальных групп.

Код

Посмотреть полный код на языке С++ можно здесь.

Перевод статьи David PynesGraphs & paths: Bron-Kerbosch, maximal cliques

Предыдущая статьяДелаем Node.js быстрым: инструменты, техники и советы для создания эффективных серверов на Node.js Часть первая
Следующая статьяЛучший способ для привязки обработчиков событий в React