在二维比特阵列中寻找比特的连续区域

时间:2013-07-31 18:58:05

标签: algorithm graph-algorithm

问题

我有一个位数组,代表“瓷砖”的二维“地图”。此图像提供了位数组中位的图形示例:

Bit Array Example

我需要确定数组中存在多少个连续的“区域”。在上面的例子中,有两个这样的连续“区域”,如下所示:

Contiguous Areas

瓷砖必须直接位于瓷砖的N,S,E或W上才能被视为“连续”。对角线触摸的瓷砖不计算在内。

我到目前为止

因为这些位数组可能变得相对较大(大小为几MB),所以我故意避免在我的算法中使用任何类型的递归。

伪代码如下:

LET S BE SOURCE DATA ARRAY
LET C BE ARRAY OF IDENTICAL LENGTH TO SOURCE DATA USED TO TRACK "CHECKED" BITS
FOREACH INDEX I IN S
    IF C[I] THEN 
        CONTINUE 
    ELSE
        SET C[I]
        IF S[I] THEN
            EXTRACT_AREA(S, C, I)

EXTRACT_AREA(S, C, I):
    LET T BE TARGET DATA ARRAY FOR STORING BITS OF THE AREA WE'RE EXTRACTING
    LET F BE STACK OF TILES TO SEARCH NEXT
    PUSH I UNTO F
    SET T[I]
    WHILE F IS NOT EMPTY
        LET X = POP FROM F
        IF C[X] THEN 
            CONTINUE
        ELSE
            SET C[X]
            IF S[X] THEN
                PUSH TILE NORTH OF X TO F
                PUSH TILE SOUTH OF X TO F
                PUSH TILE WEST OF X TO F
                PUSH TILE EAST OF X TO F
                SET T[X]
    RETURN T

Animation of My Algorithm

我不喜欢我的解决方案

  • 只是要运行,它需要两倍于它正在处理的位图数组的内存。
  • 在提取“区域”时,它使用位图数组的三倍内存。
  • 复制品存在于“要检查的瓷砖”堆栈中 - 这看起来很丑陋,但不值得避开我现在拥有的东西。

我想看什么

  • 更好的记忆力资料
  • 更快地处理大片区域

解决方案/后续行动

我重新编写解决方案来探索边缘(根据@hatchet的建议)。

实施起来非常简单 - 并且无需完全跟踪“访问过的瓷砖”。

基于三个简单的规则,我可以遍历边缘,跟踪最小值/最大值x& y值,并在我再次到达开始时完成。

以下是我使用的三条规则的演示:

Solution

2 个答案:

答案 0 :(得分:10)

一种方法是周边步行。 给定形状边缘的起点,记住这一点。

启动边界框作为该点。

使用顺时针规则集行走周边 - 如果用于到达当前点的点在上方,则首先向右看,然后向下看,然后向左找到形状周长上的下一个点。这有点像解决迷宫的简单策略,你不断跟随墙壁并始终向右倾斜。

每次访问新的外围点时,如果新点在其外部,则展开边界框(即跟踪最小和最大x和y。

继续,直到达到起点。

缺点:如果形状有很多单像素“细丝”,那么当游走时,你会重新访问它们。

优点:如果形状具有大面积的内部占用空间,您就不必像在洪水填充中记录访问过的像素那样访问它们或记录它们。

因此,节省空间,但在某些情况下会牺牲时间。

修改

正如经常出现的情况一样,这个问题已知,命名并且具有多种算法解决方案。您描述的问题称为最小边界矩形。解决此问题的一种方法是使用Contour Tracing。我上面描述的方法属于该类,称为Moore-Neighbor TracingRadial Sweep。我为他们提供的链接详细讨论了它们,并指出了一个我没有抓到的问题。有时你会在遍历整个边界之前重新审视的起点。如果您的起点位于单个像素“灯丝”的某个位置,您将在完成之前重新访问它,除非您考虑到这种可能性,否则您将过早停止。我链接的网站讨论了解决这个停止问题的方法。该网站的其他页面还讨论了另外两种算法:Square Tracing和Theo Pavlidis的算法。有一点需要注意的是,这些对角线都是连续的,而你却没有,但这应该只是对基本算法进行微小修改后可以处理的事情。

问题的另一种方法是Connected-component labeling。但是,根据您的需要,这可能是一个比您需要的更昂贵的解决方案。

其他资源:

Moore Neighbor Contour Tracing Algorithm in C++

答案 1 :(得分:4)

我实际上曾在一次采访中得到过这样的问题。

您可以假装数组是图形,连接的节点是相邻的节点。我的算法将涉及向右移1,直到找到标记的节点。当你发现一个人在O(n)中运行广泛的第一次搜索并避免递归。当BFS返回时,继续从您离开的位置进行搜索,如果该节点已经被之前的BFS之一标记,您显然不需要搜索。我不确定你是否想要实际返回找到的物体数量,但是当你击中第一个标记的方块时,只需增加一个计数器就可以很容易地跟踪。

通常,当您执行填充填充类型算法时,您将被放置在某个位置并要求填充。由于这是找到所有填充区域的一种方法,你想要优化它是避免重新检查以前BFS的已经标记的节点,不幸的是,此刻我想不出办法做到这一点。

减少内存消耗的一种hacky方法是存储short[][]而不是布尔值。然后使用此方案避免制作整个第二个2d阵列

  

未标记= 0,标记= 1,已检查且未标记= 3,已检查并标记为= 3

通过这种方式,您可以按其值检查条目的状态,并避免生成第二个数组。