【问题标题】:Finding Contiguous Areas of Bits in 2D Bit Array在 2D 位数组中查找位的连续区域
【发布时间】:2013-07-31 18:58:05
【问题描述】:

问题

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

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

图块必须直接位于图块的 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

我不喜欢我的解决方案的地方

  • 只是为了运行,它需要两倍于它正在处理的位图数组的内存。
  • 在提取“区域”时,它使用的内存是位图数组的三倍。
  • “要检查的图块”堆栈中存在重复项 - 这看起来很难看,但不值得像我现在这样避免使用。

我想看什么

  • 更好的内存配置文件
  • 大面积处理速度更快

解决方案/跟进

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

这实现起来非常简单 - 并且完全消除了跟踪“访问过的图块”的需要。

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

这是我使用的三个规则的演示:

【问题讨论】:

  • 你的算法的运行时间是多少?
  • 还有一个问题,算法的最终结果是什么,这不是洪水填充权
  • +1 获取详细信息和图片。题外话,你是如何制作第三张图片的?
  • 好吧,进行洪水填充的最佳方法是使用广度优先搜索来避免递归
  • @Steve - 如果您只想要封闭的矩形,您可以沿着周边走直到回到起点,边走边记录 x 和 y 的最小值和最大值。这将需要很少的额外空间。

标签: algorithm graph-algorithm


【解决方案1】:

一种方法是外围步行。 给定形状边缘任意位置的起点,记住该点。

从该点开始边界框。

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

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

继续直到到达起点。

缺点:如果形状有很多单像素“细丝”,你会在步行回来时重新审视它们。

优点:如果形状具有大片的内部占用空间,则无需访问它们或记录它们,就像在填充填充中记录访问过的像素一样。

因此,节省空间,但在某些情况下会以时间为代价。

编辑

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

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

其他资源:

Moore Neighbor Contour Tracing Algorithm in C++

【讨论】:

  • 这是否说明了一个内部有空白空间的大纲?
  • @aaronman - 我考虑了一些可能会阻碍这一策略的案例。考虑和排除这种可能性很重要。我还没有彻底做到这一点。我认为封闭的空白空间是可以的。它们不会影响边界框,只要您将形状输入到外部周边点的起点,我认为它只会绕过空白空间。
  • 在那种情况下是好的解决方案 +1,唯一的问题是 OP 确实想要一个洪水填充变体,我不是 100% 确定
  • 我意识到我选择这个的决定完全基于我想要的结果 - 我可能没有在问题中完全解释...所以我不是说这将是对于任何希望“在二维位数组中查找连续位区域”的人的最佳答案......但是,对于我的特定目的,这似乎是理想的。封闭的空白空间是可以的,等等 - 另外,我将遇到的可能的“形状”最好通过这个想法来处理(大量占用的空间,周边有“锯齿状”边缘。谢谢!
  • @Steve - 我已经编辑了我的答案以提供一些额外的重要信息。
【解决方案2】:

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

您可以假设数组是一个图,而连接的节点是相邻的节点。我的算法将涉及向右移动 1 直到找到标记的节点。当您找到一个以 O(n) 运行并避免递归的广度优先搜索时。当 BFS 返回时,继续从您离开的地方搜索,并且如果该节点已被先前的 BFS 之一标记,您显然不需要搜索。我不确定你是否真的想返回找到的对象的数量,但是当你点击第一个标记的方块时,只需增加一个计数器就可以很容易地跟踪。

通常,当您执行洪水填充类型算法时,您会被放置在一个位置并要求您进行填充。由于这是找到所有已填充区域的一种方法,因此您希望对其进行优化,以避免重新检查以前 BFS 中已标记的节点,不幸的是,目前我想不出一种方法来做到这一点。

减少内存消耗的一种巧妙方法是存储 short[][] 而不是布尔值。然后使用这个方案来避免制作整个第二个二维数组

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

这样您可以通过值检查条目的状态,避免创建第二个数组。

【讨论】:

  • @Steve 谢谢,我认为运行时间仍然是 O(n),我希望我能想出一种方法来完全跳过我已经标记为对象的部分并稍微提高运行时间
  • 是的,我正在查看代码以尝试确定运行时,但我有点缺乏实践,我认为它可能是 O(n) 原样......但是在这一点上,我认为我更大的问题是内存配置文件——我可能愿意放弃一些处理效率来减少使用的内存量——尽管我现在想不出该怎么做并且有这是一个妥协。似乎我必须在糟糕的内存配置文件和糟糕的处理效率之间做出选择。
  • @Steve 这个解决方案有点 hacky,但是因为我猜布尔值与 java 中的整数占用相同的空间,所以使用原始数组作为第二个数组。未标记 = 0,已标记 = 1,已选中且未标记 = 3,已选中且已标记 = 3
  • 我实际上使用的是 C,并且一次将 32 位存储在一个 int 中......所以我的位确实是单个位。不幸的是,它们并没有比这更小:) 我非常感谢您的帮助,以及您富有洞察力的 cmets。
猜你喜欢
  • 1970-01-01
  • 2017-08-08
  • 1970-01-01
  • 1970-01-01
  • 2016-02-15
  • 1970-01-01
  • 2014-03-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多