Количество Островов



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



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



Каким-то образом нам нужно запоминать маршрут, чтобы не приходить туда, где уже были и одновременно с этим приходить в самые удаленные части острова. Первое решается изменением 1 на 0. То есть, проходя по клетке, мы ее затапливаем и соотвественно больше не можем по ней проходить. С помощью этого приема кстати мы не сможем посчитать один остров 2 раза. Второе решается применением ключевого подхода для задачи - обход графа. А точнее сказать в нашем случае - дерева.



Как из острова получить дерево?



Для начала осознаем, что остров - это граф. Не Монтекристо, а математическая абстракция реальной системы любой природы, объекты которой обладают парными связями. Кусочек суши - вершина. С ней могут быть смежны другие кусочки суши, которые тоже являются вершинами. А переходы между ними - ребра.



Теперь про дерево. Дерево - связный ациклический граф. То что остров - связный граф, думаю, вопросов нет. А вот требование ацикличности не удовлетворяется для острова в базе. Остров из 4-х клеток квадратиком уже будут циклическим графом. Но тут вступают в игру наши изменения - когда мы топим кусочки суши, мы разрываем циклы. Мы не сможем прийти на тот же кусочек суши второй раз, потому что он потоплен.



С этим разобрались. Теперь про обход дерева.



У нас есть несколько алгоритмов обходов деревьев, но базовых два - поиск в глубину и поиск в ширину. Поиск в глубину в данных условиях реализовать проще, поэтому буду использовать его. Его суть состоит в том, чтобы идти «вглубь» графа, насколько это возможно. Алгоритм поиска описывается рекурсивно: перебираем все исходящие из рассматриваемой вершины рёбра. Если ребро ведёт в вершину, которая не была рассмотрена ранее, то запускаем алгоритм от этой нерассмотренной вершины, а после возвращаемся и продолжаем перебирать рёбра. Возврат происходит в том случае, если в рассматриваемой вершине не осталось рёбер, которые ведут в нерассмотренную вершину.



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



Таким образом впервые встретив единичку, мы потопим весь остров и пойдем дальше искать острова. Счетчик будем увеличивать каждый раз, когда встретили единичку.



Вот и все. Довольно длинное объяснение получилось, но само решение простое.



Учитывая, что мы пробегаем всю матрицу по строкам, дважды не проходим по одной и той же клетке суши, а на одну и ту же клетку смотрим не более 4-х раз, то сложность этого алгоритма по времени O(n*m)



По поводу сложности по памяти не так все однозначно, как кажется на первый взгляд. Дело в том, что рекурсия - это по факту наслоение функции на стек вызовов. И хоть мы не используем явной дополнительной памяти в виде контейнеров, мы используем стек вызовов программы в качестве такой дополнительной памяти. В худшем случае, когда вся карта - это один большой остров нам нужно O(n*m) фреймов стека, чтобы хранить информацию о текущем распространении путей обхода.



Solve problems. Stay cool.