БлогNot. Размеры "областей" из одинаковых элементов в матрице

Размеры "областей" из одинаковых элементов в матрице

Представим себе, что обычная матрица целых чисел содержит сколько-то повторяющихся элементов, стоящих в соседних строках или столбцах, например, на рисунке показано пять таких больших областей, образованных разными значениями элементов:

Находясь в некоторой стартовой точке, я хочу узнать "размер области" (количество элементов), заполненной тем же значением, например, для тройки в левом верхнему углу это значение равно 5, а для нуля в правом нижнем 41.

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

Примем, что стартовая точка, с которой начинается определение размера области, в программе задана парой координат строки и столбца матрицы seed_row и seed_col.

Если мы позволяем себе "портить" данные матрицы (например, закрашивать контур), решение сводится к тривиальной рекурсии, реализуемой функцией s:

#include <stdio.h>

const int n =10;
int data[n][n] = {
 {3,3,3,3,1,1,4,4,4,4},
 {3,1,1,1,1,1,4,4,0,4},
 {2,2,1,1,1,1,4,4,4,4},
 {0,2,1,1,1,1,4,4,0,0},
 {0,2,2,2,2,2,2,4,4,4},
 {0,0,0,0,0,2,2,2,4,4},
 {0,0,0,0,0,0,2,2,4,0},
 {0,0,0,0,0,0,4,4,4,0},
 {0,0,0,0,0,0,0,0,0,0},
 {0,0,0,0,0,0,0,0,0,0}
};

int s (int seed,int r,int c) {
 if (r<0 || r>n-1 || c<0 || c>n-1 || data[r][c]<0) return 0;
 else if (data[r][c]==seed) {
  data[r][c]=-data[r][c];
  return (1 +
  (r>0 ? s(seed,r-1,c) : 0) +
  (r<n-1 ? s(seed,r+1,c) : 0) +
  (c>0 ? s(seed,r,c-1) : 0) +
  (c<n-1 ? s(seed,r,c+1) : 0));
 }
 else return 0;
}

int main(){
 int seed_row = 3, seed_col = 1;
 printf ("\nSeed data = %d",data[seed_row][seed_col]);
 printf ("\nN=%d",s(data[seed_row][seed_col],seed_row,seed_col));
 fflush (stdin);
 getchar ();
 return 0;
}

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

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

За добавление элемента в путь будет отвечать метод push_in_way, а за проверку того, есть ли узел в ранее обработанной части матрицы - метод in_way. Сам "путь" - это обычный динамический массив way со счётчиком элементов way_cnt. В массиве way чередуются номера обработанных строк и столбцов матрицы данных. Так как мы выходим на один элемент "за границы" матрицы, контролируя элементы крайних столбцов и строк, размерность вектора "пути" будет разумно установить в значение (n*n+4*n+4)*2 - записанное в скобках число элементов матрицы размерностью (n+2)*(n+2) умножается на 2, так как сохраняются номер строки и номер столбца каждого элемента, где мы побывали, а в "худшем" случае исходная матрица n*n состоит из всех одинаковых элементов.

Нули в данных теперь коду не страшны, и матрицу он не меняет.

#include <stdio.h>

const int n =10;
int data[n][n] = {
 {3,3,3,3,1,1,4,4,4,4},
 {3,1,1,1,1,1,4,4,0,4},
 {2,2,1,1,1,1,4,4,4,4},
 {0,2,1,1,1,1,4,4,0,0},
 {0,2,2,2,2,2,2,4,4,4},
 {0,0,0,0,0,2,2,2,4,4},
 {0,0,0,0,0,0,2,2,4,0},
 {0,0,0,0,0,0,4,4,4,0},
 {0,0,0,0,0,0,0,0,0,0},
 {0,0,0,0,0,0,0,0,0,0}
};

int dirs[4][2] = { {-1,0},{0,1},{1,0},{0,-1} };

int *way=NULL;
int way_cnt = 0;

int in_way (int r,int c) {
 for (int i=0; i<way_cnt; i+=2) if (way[i]==r && way[i+1]==c) return 1;
 return 0;
}

void push_in_way (int r,int c) { way[way_cnt++] = r; way[way_cnt++] = c; }

int s (int seed,int r,int c) {
 if (r<0 || r>n-1 || c<0 || c>n-1 || data[r][c]!=seed) return 0;
 else {
  int cnt = 1;
  for (int dir=0; dir<4; dir++)
  if (!in_way(r+dirs[dir][0],c+dirs[dir][1])) {
   push_in_way (r+dirs[dir][0],c+dirs[dir][1]);
   cnt+=s(seed,r+dirs[dir][0],c+dirs[dir][1]);
  }
  return cnt;
 }
}

int main(){
 int seed_row = 3, seed_col = 1;

 way = new int [(n*n+4*n+4)*2];
 if (way == NULL) return -1;
 for (int i=0; i<n*n*2; i++) way[i]=-1;
 way_cnt = 0;

 printf ("\nSeed data = %d",data[seed_row][seed_col]);

 push_in_way (seed_row,seed_col);
 printf ("\nN=%d",s(data[seed_row][seed_col],seed_row,seed_col));
 fflush (stdin);
 getchar ();
 return 0;
}

Обратите внимание, что перед вызовом функции s мы "положили" в путь исходную точку функцией push_in_way.

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

30.11.2014, 19:45 [11030 просмотров]


теги: c++ графика алгоритм

К этой статье пока нет комментариев, Ваш будет первым