连接组件标签

21 algorithm image-processing neighbours area multidimensional-array

我几天前问了一个类似的问题,但我还没有找到解决问题的有效方法.我正在开发一个简单的控制台游戏,我有一个像这样的2D数组:

1,0,0,0,1
1,1,0,1,1
0,1,0,0,1
1,1,1,1,0
0,0,0,1,0
Run Code Online (Sandbox Code Playgroud)

我试图找到由相邻1(4路连接)组成的所有区域.因此,在此示例中,2个区域如下:

1
1,1
  1
1,1,1,1
      1
Run Code Online (Sandbox Code Playgroud)

并且:

       1
     1,1
       1
Run Code Online (Sandbox Code Playgroud)

我一直在研究的算法找到了一个单元的邻居的所有邻居,并且在这种矩阵上完美地工作.但是,当我使用更大的数组(如90*90)时,程序非常慢,有时使用的巨大数组会导致堆栈溢出.

我的另一个问题上的一个人告诉我关于连接组件标签是我问题的有效解决方案.

有人可以告诉我任何使用这种算法的C++代码,因为我对它如何与这个不相交的数据结构事物一起工作有点困惑......

非常感谢您的帮助和时间.

gus*_*gus 28

我先给你代码,然后解释一下:

// direction vectors
const int dx[] = {+1, 0, -1, 0};
const int dy[] = {0, +1, 0, -1};

// matrix dimensions
int row_count;
int col_count;

// the input matrix
int m[MAX][MAX];

// the labels, 0 means unlabeled
int label[MAX][MAX];

void dfs(int x, int y, int current_label) {
  if (x < 0 || x == row_count) return; // out of bounds
  if (y < 0 || y == col_count) return; // out of bounds
  if (label[x][y] || !m[x][y]) return; // already labeled or not marked with 1 in m

  // mark the current cell
  label[x][y] = current_label;

  // recursively mark the neighbors
  for (int direction = 0; direction < 4; ++direction)
    dfs(x + dx[direction], y + dy[direction], current_label);
}

void find_components() {
  int component = 0;
  for (int i = 0; i < row_count; ++i) 
    for (int j = 0; j < col_count; ++j) 
      if (!label[i][j] && m[i][j]) dfs(i, j, ++component);
}
Run Code Online (Sandbox Code Playgroud)

这是解决此问题的常用方法.

方向矢量只是查找相邻单元格的一种很好的方法(在四个方向中的每个方向上).

DFS功能执行深度优先搜索网格的.这仅仅意味着它将访问从起始单元可到达的所有单元.每个单元格都将标记为current_label

所述find_components函数穿过网格的所有小区,如果发现未标记的细胞(标有1)开始的成分标签.

这也可以使用堆栈迭代完成.如果用队列替换堆栈,则获取bfs广度优先搜索.


Duk*_*ing 9

这可以通过union find来解决(虽然DFS,如另一个答案所示,可能有点简单).

这种数据结构背后的基本思想是重复合并同一组件中的元素.这是通过将每个组件表示为树(节点跟踪其自己的父节点而不是相反的方式)来完成的,您可以通过遍历根节点来检查2个元素是否在同一个组件中,并且您可以合并节点通过简单地使一个根成为另一个根的父.

一个简短的代码示例演示了这一点:

const int w = 5, h = 5;
int input[w][h] =  {{1,0,0,0,1},
                    {1,1,0,1,1},
                    {0,1,0,0,1},
                    {1,1,1,1,0},
                    {0,0,0,1,0}};
int component[w*h];

void doUnion(int a, int b)
{
    // get the root component of a and b, and set the one's parent to the other
    while (component[a] != a)
        a = component[a];
    while (component[b] != b)
        b = component[b];
    component[b] = a;
}

void unionCoords(int x, int y, int x2, int y2)
{
    if (y2 < h && x2 < w && input[x][y] && input[x2][y2])
        doUnion(x*h + y, x2*h + y2);
}

int main()
{
    for (int i = 0; i < w*h; i++)
        component[i] = i;
    for (int x = 0; x < w; x++)
    for (int y = 0; y < h; y++)
    {
        unionCoords(x, y, x+1, y);
        unionCoords(x, y, x, y+1);
    }

    // print the array
    for (int x = 0; x < w; x++)
    {
        for (int y = 0; y < h; y++)
        {
            if (input[x][y] == 0)
            {
                cout << ' ';
                continue;
            }
            int c = x*h + y;
            while (component[c] != c) c = component[c];
            cout << (char)('a'+c);
        }
        cout << "\n";
    }
}
Run Code Online (Sandbox Code Playgroud)

现场演示.

以上将使用不同的字母表示每组.

p   i
pp ii
 p  i
pppp 
   p 
Run Code Online (Sandbox Code Playgroud)

应该很容易修改它以单独获取组件或获取与每个组件对应的元素列表.一种想法是,以取代cout << (char)('a'+c);与上述componentMap[c].add(Point(x,y))componentMap作为一个map<int, list<Point>>-然后在这个地图中的每个条目将对应于一个组件,给点的列表.

有各种优化来提高联合查找的效率,上面只是一个基本的实现.