glem1337
@glem1337

Как обойти граф не зацикливаясь на связи одного узла с другим?

Доброго времени суток!

Задача:
Существует набор электростанций и набор домов. Каждый дом может быть подключен к любому количеству электростанций. Электростанция питает дом электричеством. Дом имеет электричество, если он подключен к одному или нескольким электростанциям.

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

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

Моя реализация: https://codesandbox.io/s/electricity-4it7g?file=/s...

Проблема:
Дом1 я связываю со Станцией1. При присоединении дома к дому, я записываю в объект дома – id присоединенного дома. Таким образом получаю, что в Дом1 лежит ссылка на Дом2, а в Дом2 лежит ссылка на Дом1. Передо мной стоит задача, как правильно узнать, если ли электричество в Дом2 (электричество подключено к Дом1).
// Как это выглядит в тестах
var world = new World();
var household1 = world.createHousehold();
var household2 = world.createHousehold();
var powerPlant = world.createPowerPlant();
world.connectHouseholdToPowerPlant(household1, powerPlant);
world.connectHouseholdToHousehold(household1, household2);
assert.equal(world.householdHasEletricity(household1), true);
assert.equal(world.householdHasEletricity(household2), true); // тут false

Проблема заключается в том, что я не могу придумать, как правильно пробегаться по присоединенным к друг другу домам в методе householdHasEletricity не зацикливаясь на проверке одного и того же ребра и не вызывая попутно maximum call stack size exceeded. В теории я понимаю, что нужно создать массив в который я должен записывать проверенные дома, но такое бы сработало, если бы у меня сразу был полный граф. А в данный момент происходит так: в Дом1 лежит ссылка на Дом2, иду в Дом2, а там ссылка на Дом1.

Есть идеи как это можно решить?
  • Вопрос задан
  • 233 просмотра
Решения вопроса 1
wataru
@wataru Куратор тега Алгоритмы
Разработчик на С++, экс-олимпиадник.
Вам обязательно отвечать на вопрос об электричесвте в конкретном доме по мере добавления ребер/вершин в граф?
Если вам только тесты надо делать, то можно сначала создать весь мир, а потом поспрашивать его о статусах домов. При чем сами вычисления (обход графа) можно сделать только один раз, получить данные по всем домам и потом их проверить.

В этом случае надо, как вы и предположили, хранить массив "посещенности" домов. Можно совместить его с массивом-ответом или статусами домов. Ваш метод уже не принимает конкретный дом как параметр, а просто считатет для всех домов. Он изначально заполняет массив для всех домов и электростанций нулями, что значит, что электричества в доме нет. Потом выполняется DFS (обзод в глубину) или BFS (обход в ширину) от электростанций, которые активны. При этом все посещенные вершины помечаются 1 в массиве. В уже помеченные вершины вы не ходите, поэтому алгоритм незациклиться.

Если же вам обязательно надо узнавать статус домов после каждого изменения графа, то можно делать все тоже самое, но это будет медленно, ведь после каждого изменения вы будете обходить весь граф.

Можно сделать гораздо быстрее. Если связи между домами только добавляются (но ни в коем случае не удаляются), то можно использовать систему непересекающихся множеств (DSU). Изначально каждый дом и электростанция - сами себе множество. В каждом множестве поддерживайте счетчик активных электростанций (изначально у домов - 0, у электростанций - 1). Это просто в реализации: у вас есть какой-то массив ссылок root, элеметы которого указывают сами на себя только в корне множества. Заведите второй массив, который будет по тем же индексам хранить счетчики для элементов. Точно также в вики по ссылке выше поддерживатеся rank.

При добавлении провода вы должны слить 2 множества. При этом пересчитайте счетчик (прибавьте счетчик нижнего множества к счетчику верхнего множества).

При запросе на статус дома возьмите счетчик активных станций для его множества и проверьте, что он положителен. При выключении/включении электростанции измените счетчик соответствующего множества на +1 или на -1.

Это решение будет фактически работать за O(1) при каждом запросе и добавлении ребра (если использовать эвристику сжатия путей).
Наивное же решение с полным обходом графа каждый раз было бы O(1) при добавлении ребра и O(n) при запросе.
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@Karpion
Алгоритм зависит от ситуации и от организации графа.

Если нам надо выяснить наличие электричества с нуля для уже построенной сети:
  1. Все работающие электростанции считаются - имеют электричество.
  2. Перебираем линии, подключённые к работающим электростанциям; их надо собирать в отдельную очередь. Каждый дом на конце линии, который ещё не в списке - заносим в список имеющих электричество.
  3. При добавлении каждого дома - в очередь заносятся линии, подключённые к этим домам.
  4. Когда очередь стала пустой - список построен окончательно.


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

Если включается электростанция - то запускаем поиск от неё.

А вот если рвётся линия или отключается электростанция - то придётся строить список с нуля.

PS: Есть алгоритмы динамической маршрутизации пакетов в сетях передачи данных (в т.ч. в Internet). Они умеют чётко обрабатывать добавление и удаление узлов и связей между узлами. Но они хранят намного больше информации - т.е. такие алгоритмы требуют больше памяти.
Ну и надо добавить, что эти алгоритмы предназначены для распределённого выполнения на узлах, причём связь между узлами может отсутствовать.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы