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

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

Что такое быстрый алгоритм, который будет итеративно проверять двумерную сетку на связность?

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

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


person Bennett Bernardoni    schedule 07.08.2018    source источник
comment
Я нашел правильное название для этого — динамическое подключение. страница википедии содержит несколько алгоритмов для общих графиков, но приведенный ниже ответ лучше подходит для моего случая с сеткой. .   -  person Bennett Bernardoni    schedule 08.08.2018


Ответы (1)


Это вдохновлено алгоритмом Крускала для создания лабиринта.

Я определяю окрестность квадрата как его 8 окружающих квадратов, включая внешнюю часть сетки (окрестность углового квадрата - это 3 окружающих его квадрата плюс внешняя сторона, поэтому всего 4 «квадрата»).

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

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

Если у новой 1 нет соседней 1, то она принадлежит новому набору.

 0 0 0 0 0
 0 2 0 0 0
 0 0 0[3]0
 0 0 0 0 0
 0 0 1 0 0

Если у него есть одна соседняя 1, то он принадлежит тому же множеству.

 0 0 0 0 0
 0 2 0 0 0
 0 0[2]0 0
 0 0 0 0 0
 0 0 1 0 0

Если у него есть несколько соседних единиц, и все соседи, принадлежащие к одному и тому же набору, являются прямыми соседями, то вы можете объединить наборы, и новая единица будет принадлежать результирующему набору. Нет необходимости проверять отключение.

 0 0 0 0 0                0 0 0 0 0
 0 2 0 0 0                0 1 0 0 0
 0 0[3]1 0       ->       0 0[1]1 0
 0 0 1 1 0                0 0 1 1 0
 0 0 1 0 0                0 0 1 0 0

 0 0 0 0 0                0 0 0 0 0
 0 2 0 0 0                0 1 0 0 0
 0 2 0 1 0       ->       0 1 0 1 0
[3]0 0 1 0               [1]0 0 1 0
 1 1 1 0 0                1 1 1 0 0

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

 0 0 0 0 0                0 0 0 0 0 <- first group of 0s
 0 2 0 0 0                0 1 0 0 0
 0 0[3]1 0       ->       0 0[1]1 0
 0 1 0 1 1                0 1 0 1 1
 1 0 0 0 0                1 0 0 0 0 <- second group of 0s

 0 0 0 0 0 <- first group of 0s
 0 0 1 0 0
 0 1 0 1 1 
[1]1 0 0 0
 0 0 0 0 0 <- second group of 0s

 0 0 0 0 0                0 0 0 0 0
 0 2 0 0 0                0 1 0 0 0
 0 2 0 1 0       ->       0 1 0 1 0
[3]0 0 1 0               [1]0 0 1 0
 0{1}1 0 0      lone 0 -> 0{1}1 0 0

В этом последнем примере 1, отмеченная {1}, и внешнее технически являются соседями, но не с точки зрения вновь размещенной 1.

В общем случае при удалении 1, у которого есть несколько соседних 1, нужно проверить, связаны ли они после удаления (например, запустив между ними поиск пути). Если нет, разделите их на разные наборы.

Если вы знаете, что все 0 связаны, то вы можете проверить локально: удаление 1 не приведет к разделению набора, к которому он принадлежит, если все его соседи являются прямыми соседями (однако будьте осторожны с внешними). Это произойдет, если в его окрестностях есть несколько «пробелов».

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

person Nelfeal    schedule 07.08.2018
comment
Оптимизация заключается в том, что тщательная проверка необходима только тогда, когда единицы не являются смежными. Например, добавление 1 в середине предпоследнего регистра допустимо. - person Bennett Bernardoni; 08.08.2018
comment
Я не понимаю, что вы имеете в виду. Тщательная проверка необходима всякий раз, когда вы кладете 1 по соседству (в 8 соседних квадратах) по крайней мере с двумя другими 1, принадлежащими к тому же набору (включая внешние). - person Nelfeal; 08.08.2018
comment
Я отредактировал свой ответ, чтобы прояснить возможные случаи. На самом деле нет необходимости в затоплении в любом месте. - person Nelfeal; 08.08.2018
comment
Оптимизация, о которой я говорил, это то же самое, что вы добавили в своем редактировании. Хотя я не понимал, что это избавляет от необходимости тщательной проверки. - person Bennett Bernardoni; 08.08.2018
comment
При удалении единицы вы можете локально определить, будет ли ее удаление создавать один или несколько новых непересекающихся наборов. Если это произойдет, у его соседей будут пробелы. В этом случае вы можете использовать BFS для каждого соседнего региона, чтобы определить экстент каждого соседнего набора и переназначить все, кроме самого большого. - person Matt Timmermans; 08.08.2018
comment
Другой вариант удаления единиц — использование дерева ссылок/вырезов. Для этого нужно удалить единицы в порядке, обратном их добавлению (что верно для моего случая). - person Bennett Bernardoni; 10.08.2018
comment
@MattTimmermans Истинно, если все 0 связаны. Я не был уверен, что каждый раз так было с ОП, но теперь кажется, что так и должно быть. - person Nelfeal; 10.08.2018
comment
@BennettBernardoni Вам не нужна сложная структура, такая как дерево ссылок / разрезов. Просто следите за единицами, которые вы добавляете. Я отредактировал свой ответ. - person Nelfeal; 10.08.2018
comment
@Nelfeal Проблема в том, что я использовал непересекающиеся наборы, и если вы реализуете это со сжатием пути, нет быстрого способа отключить наборы. Некоторое время я пытался поместить новый 1 в корень дерева, а затем отключиться, удалив его дочерние элементы. Но это сделало find_set линейной временной сложностью, поскольку нет сжатия пути или объединения по рангу (что не сохранило бы дерево). Поэтому вместо этого я ускоряю это до O(log(n)) с помощью деревьев ссылок/вырезов. - person Bennett Bernardoni; 10.08.2018