【问题标题】:Finding complete rectangles enclosing 0查找包含 0 的完整矩形
【发布时间】:2012-07-11 20:55:45
【问题描述】:

在给定的 1000 x 1000 阵列中有不同的矩形。在<Figure 1>中,显示为黄色单元格的序列“1”是矩形的图案。 <Figure 1> 中矩形的最小尺寸为 3 x 3,显示为绿色单元格。

矩形内至少应该有一个“0”。

但是,在这个数组中,也存在未闭合的形状或直线图案。

(数组初始值为‘0’,图案由一系列‘1’表示,不重叠也不包含。)

除了未闭合的形状或直线之外,找到数组中完整矩形的有效算法是什么?比如上图中完整矩形的个数是3

【问题讨论】:

  • 我在该图像上看不到任何东西(在 IE 和 Opera 中尝试过)
  • 会不会出现其他非矩形封闭图形?
  • 其他形状可以存在吗? (例如,任意连接点、T 形等)有效矩形的内部必须全为零吗?矩形里面可以有矩形吗?
  • 输入是否可以包含您不想找到的填充矩形(全为 1)?部分矩形可以与有效矩形共享任何单元格吗?
  • 有效矩形内可以有任何 1 吗?一个有效的矩形可以在另一个有效的矩形内吗?

标签: c arrays algorithm math


【解决方案1】:

这很简单。如果你有n方块,你可以数O(n)中的矩形。

假设:

  • 每个有效矩形的边框不与无效路径共享任何单元格。
  • 如果一个矩形在另一个矩形内,您会很高兴找到它们

您需要与输入一样大的额外内存。让我们称之为visited 并用 0 进行初始化。

我们先构造一个辅助函数:

is_rectangle(square s)
    from s, go right while you see 1s and visited is 0
        mark them as 1 in visited
    if less than 3 steps
        return 0
    then, go down while you see 1s and visited is 0
        mark them as 1 in visited
    if less than 3 steps
        return 0
    then, go left while you see 1s and visited is 0
        mark them as 1 in visited
    if less than 3 steps
        return 0
    then, go up while you see 1s and visited is 0
        mark them as 1 in visited
    if then one above is not s
        return 0
    return 1

这个函数基本上沿着右下左上的方向跟踪1,并检查是否满足条件(长度至少为3并到达起始位置)。它还标记了访问过的方格。

关于此函数需要注意的重要一点是,它只有在初始正方形位于左上角时才能正常工作。

现在,问题的解决方案很简单:

num_rectangles = 0
initialize visited to 0 (rows x columns)
for r in rows
    for c in columns
        if visitied[r][c] or image[r][c] == 0
            continue
        num_rectangles += is_rectangle(<r,c>)
return num_rectangles

以下是算法的执行方式:


1. 坏矩形的失败(和标记)部分


2. 找到(并标记)一个矩形。


3.在一个正方形(垂直线)上失败


4.在一个正方形(垂直线)上失败


5.在一个正方形(垂直线)上失败


6.经过多次类似的步骤,找到下一个矩形。


7. 下一个矩形


8.算法结束

【讨论】:

  • 我喜欢这个,但是根据输入的糟糕程度,当您意识到一个矩形只是部分完成时,您可能需要返回并从访问的数组中删除其单元格。
  • @NateKohl,恰恰相反!考虑到问题的情况,保持它们被访问有助于跳过未来的非矩形。
  • @NateKohl,好的,我明白你的意思了。我添加了一个假设,即您对该问题的评论不可能发生(因此矩形不与非矩形共享边框)
  • 如果矩形的上边缘“延伸超过”它的右边缘,这将失败,这是一个有效的输入,因为它相当于有一个从正方形开始的水平线段立即到矩形上边缘的右侧。在此输入上不会失败的 O(n) 算法是可能的。
  • @j_random_hacker,由于 OP 没有回应我们关于此类情况是否存在的问题,我假设(我在帖子顶部的第一个假设)不会发生这种情况。
【解决方案2】:

以下 O(n) 算法将适用于任何 0/1 值的 2D 矩阵(即,允许相交/重叠的矩形,任意非矩形开放/封闭形状也是如此) .我在这里使用的矩形的定义是“内部完全由 0 个单元组成”(例如,如果一个矩形完全包含另一个矩形,则只会找到内部矩形;如果还应考虑包含矩形,则可以删除每个找到的矩形并重新启动算法)。这是基于观察到每个0-cell可以在最多一个1-rectangle的内部。

我使用的约定是 x = 0 是最左边的位置,y = 0 是最上面的位置。

  1. 找到左上角。 从左上角的单元格开始,从左到右和从上到下,找到下一个未访问的单元格,它可能是左上角solid-0 矩形:具体来说,它必须是一个 0-cell,在 SW、W、NW、N 和 NE 位置有 1 个相邻单元,在其余 3 个相邻位置有 0 个相邻单元。
  2. 找到右上角。当这些单元格为 0 并且有一个单元格为 N 的邻居时,扫描其右侧的邻居。
  3. 这可能是实心 0 矩形的顶行吗? 如果上述循环在结束之前找到的最后一个单元格是一个可能是实心 0 矩形中右上角的单元格的单元格(特别是在 NW、N、NE、E 和 SE 单元中具有 1 个相邻单元的 0 单元,在其余 3 个位置具有 0 单元),那么我们已经建立了顶部 y 坐标和确切的宽度唯一可能使用这些单元格的实心 0 矩形。如果最后一个单元格不满足这些右上角条件,则这些单元格都不能成为实心 0 矩形的一部分:将它们标记为已访问并转到 1。
  4. 调用 0 单元格 x1 和 x2 条带的开始和结束 x 坐标;调用垂直位置 y1。
  5. 向下扫描,一次一行。 设置 y2 = y1,而 x1 和 x2 在垂直位置 y2 之间的线可能是实心 0 矩形的一部分,增加 y2。具体来说,在每个垂直位置 y2 的测试是:(x1 - 1, y2) 和 (x2 + 1, y2) 处的单元格必须都为 1,并且其间的所有单元格必须为 0。
  6. 这可能是实心 0-矩形的底行吗? 如果上一个循环在结束之前找到的最后一行是可能是实心 0-矩形的底行的行(特别是在从 (x1 - 1, y2 + 1) 到 (x2 + 1, y2 + 1)) 一直是 1-cells,那么我们找到了一个由 1-cells 包围的完整实心 0-矩形:如果它的大小更大比目前发现的最大的长方形,然后记为新的最大长方形。否则(如果在下一行中没有实心行 1 单元格),所检查的 0 单元格都不能是任何实心 0 矩形的一部分:将它们全部标记为已访问并转到 1。

【讨论】:

    【解决方案3】:

    如果您的数组中只能有矩形形状,则相当于二进制图像上的经典计算问题:只需对连通分量应用标准算法即可。您只标记 0 的连通分量,并计算它们。

    例如见http://en.wikipedia.org/wiki/Connected-component_labeling。这种类型的算法在图像上非常简单,但会占用一些内存(与输入数组大小相同,类型为 short 或 int)。注意连通性:如果您选择 4 连通性,即使缺少某些角,您也会计算封闭的矩形。但该算法比 8 连接更简单。

    如果你可以有更复杂的封闭形状,只需添加一个后处理:对于每个连接的组件,计算组件边界框内的像素数(如果两个数字相等,你就有一个矩形)

    【讨论】:

    • +1,这行得通。但是“连接”呢? 颤抖请说“连接”(和“标签”而不是“标签化”)。
    • +1。当我阅读问题描述时,立即想到了连通分量算法。
    【解决方案4】:

    想了一会儿。我想出了这个方法:

    1) 消除边缘周围的所有零 - 将它们的值更改为 2

    2) 在 2s 左右填充矩阵

    这使您只剩下零岛,现在可以测试凸度。 所以对于每个岛屿:

    3) 在 X 和 Y 中寻找 0 值的范围 - 给你一个潜在的内部矩形

    4) 如果内部矩形包含 1 或外部矩形包含 0,则用 2s 填充这个岛,因为它不是凸的(因此不是矩形)

    假设您可以找到一个好的洪水填充算法(不像我的那样),这应该可以有效地快速切割搜索空间。

    现在是代码(对不起,它是升 C):

    using System;
    using System.Collections.Generic;
    
    namespace Test
    {
    class MainClass
    {
        static private int [,] matrix = new int[,] {
            {0,0,0,0,0,0,0,0,1,1,1,1,0,0,0},
            {0,1,1,1,1,1,1,0,1,0,0,1,0,1,0},
            {0,1,0,0,0,0,1,0,1,0,0,1,0,1,0},
            {0,1,0,0,0,0,1,0,1,0,0,1,0,1,0},
            {0,1,0,0,0,0,1,0,1,0,0,0,0,1,0},
            {0,1,0,0,0,0,1,0,1,0,0,0,0,1,0},
            {0,1,1,1,1,1,1,0,1,0,0,1,0,1,0},
            {0,0,0,0,0,0,0,0,1,1,1,1,0,0,0},
            {0,0,1,1,1,1,0,0,0,0,0,0,0,0,0},
            {0,0,1,0,0,1,0,0,1,1,1,0,1,1,0},
            {0,0,1,1,1,1,0,0,1,0,1,0,0,0,0},
            {0,0,0,0,0,0,0,0,1,1,1,0,0,0,0}
        };
    
        static private int width = matrix.GetLength(0);
        static private int height = matrix.GetLength(1);
    
        private const int DEAD = 2;
        private const int RECT = 3;
    
        public static void Main (string[] args)
        {
            //width = matrix.GetLength(0);
            //height = matrix.GetLength(1);
    
            PrintMatrix ();
            EliminateFromEdges (DEAD);
            PrintMatrix ();
            FloodFill (DEAD); // very inefficient - find a better floodfill algorithm
            PrintMatrix ();
    
            // test each island of zeros for convexness
            for (int i = 0; i < width; i++) {
                for (int j = 0; j < height; j++) {
                    if (matrix[i,j] == 0)
                    {
                        if (TestIsland(i,j) == false)
                        {
                            // eliminate this island as it is not convex
                            matrix[i,j] = DEAD;
                            FloodFill(DEAD);
                            PrintMatrix ();
                        }
                        else
                        {
                            // flag this rectangle as such
                            matrix[i,j] = RECT;
                            FloodFill(RECT);
                            PrintMatrix ();
                        }
                    }
                }
            }
    
            // We're done, anything flagged as RECT can be expanded to yield the rectangles
            PrintMatrix ();
        }
    
        // flag any zero at edge of matrix as 'dead'
        static private void EliminateFromEdges(int value)
        {
            for (int i = 0; i < width; i++) 
            {
                if (matrix [i, 0] == 0) 
                {
                    matrix [i, 0] = value;
                }
                if (matrix [i, height - 1] == 0)
                {
                    matrix [i, height - 1] = value;
                }
            }
            for (int j = 1; j < height - 1; j++)
            {
                if (matrix [0, j] == 0)
                {
                    matrix [0, j] = value;
                }
                if (matrix [width - 1, j] == 0)
                {
                    matrix [width - 1, j] = value;
                }
            }
        }
    
        // propagte a value to neighbouring cells
        static private void FloodFill (int value)
        {
            bool change_made = true; // set to true to start things off
            while (change_made) {
                change_made = false;
                for (int i = 1; i < width - 1; i++) {
                    for (int j = 1; j < height - 1; j++) {
                        if ((matrix [i, j] == 0) &&
                            ((matrix [i - 1, j] == value) || 
                            (matrix [i + 1, j] == value) ||
                            (matrix [i, j - 1] == value) || 
                            (matrix [i, j + 1] == value))) {
                            matrix [i, j] = value;
                            change_made = true;
                        }
                    }
                }
            }
        }
    
        static private bool TestIsland (int x, int y)
        {
            // find convex extend of island
            int x2 = x;
            int y2 = y;
            while (matrix[++x2, y] == 0);
            x2--;
            while (matrix[x,++y2] == 0);
            y2--;
    
            // check inner cells (want all zeroes)
            for (int i = x; i <= x2; i++) 
            {
                for (int j = y; j <= y2; j++) 
                {
                    if (matrix[i,y] != 0)
                    {
                        return false;
                    }
                }
            }
    
            // check surrounding cells (want all ones)
            x--; y--;
            x2++; y2++;
            for (int i = x; i <= x2; i++) 
            {
                if ((matrix[i,y] != 1) || (matrix[i,y2] != 1))
                {
                    return false;
                }
            }
            for (int j = y + 1; j <= y2 - 1; j++) 
            {
                if ((matrix[x,j] != 1) || (matrix[x2,j] != 1))
                {
                    return false;
                }
            }
    
            return true;
        }
    
        // for debug purposes
        static private void PrintMatrix ()
        {
            for (int i = 0; i < width; i++) {
                for (int j = 0; j < height; j++) {
                    switch(matrix[i,j])
                    {
                    case DEAD:
                        Console.Write("-");
                        break;
                    case RECT:
                        Console.Write("+");
                        break;
                    default:
                        Console.Write(matrix[i,j]);
                        break;
                    }
                }
                Console.WriteLine();
            }
            Console.WriteLine();
        }
    }
    }
    

    这段代码的输出

    000000001111000
    011111101001010
    010000101001010
    010000101001010
    010000101000010
    010000101000010
    011111101001010
    000000001111000
    001111000000000
    001001001110110
    001111001010000
    000000001110000
    
    --------1111---
    -1111110100101-
    -1000010100101-
    -1000010100101-
    -1000010100001-
    -1000010100001-
    -1111110100101-
    -0000000111100-
    -0111100000000-
    -0100100111011-
    -0111100101000-
    --------111----
    
    --------1111---
    -111111-1--1-1-
    -100001-1--1-1-
    -100001-1--1-1-
    -100001-1----1-
    -100001-1----1-
    -111111-1--1-1-
    --------1111---
    --1111---------
    --1001--111-11-
    --1111--101----
    --------111----
    
    --------1111---
    -111111-1--1-1-
    -1++++1-1--1-1-
    -1++++1-1--1-1-
    -1++++1-1----1-
    -1++++1-1----1-
    -111111-1--1-1-
    --------1111---
    --1111---------
    --1001--111-11-
    --1111--101----
    --------111----
    
    --------1111---
    -111111-1--1-1-
    -1++++1-1--1-1-
    -1++++1-1--1-1-
    -1++++1-1----1-
    -1++++1-1----1-
    -111111-1--1-1-
    --------1111---
    --1111---------
    --1++1--111-11-
    --1111--101----
    --------111----
    
    --------1111---
    -111111-1--1-1-
    -1++++1-1--1-1-
    -1++++1-1--1-1-
    -1++++1-1----1-
    -1++++1-1----1-
    -111111-1--1-1-
    --------1111---
    --1111---------
    --1++1--111-11-
    --1111--1+1----
    --------111----
    
    --------1111---
    -111111-1--1-1-
    -1++++1-1--1-1-
    -1++++1-1--1-1-
    -1++++1-1----1-
    -1++++1-1----1-
    -111111-1--1-1-
    --------1111---
    --1111---------
    --1++1--111-11-
    --1111--1+1----
    --------111----
    

    【讨论】:

    • "在 X 和 Y 中找到另一个至少 2 处的 1"?它可以是任何单元格,找到正确的一个有效确实是问题
    • 我没说随便选1个!您按行,然后按列查找候选人。您可以显着减少搜索空间,因为您知道矩形内不能有 1。每个失败的候选人 1 允许您跳过一行的其余部分。此外,如果 S2 > 0,您可以移动到主循环中的下一个单元格。
    • 我认为它可以更简单。你用一个淹没阵列。这样,剩余的零就标记了所需的区域。在后期处理中,您只需将边框增加 1 即可获得原始形状。
    【解决方案5】:

    这就是我的想法,可能是资源效率很低。不知道这个。

    1. 除非找到至少 3 个1s,否则沿行遍历。
    2. 向下遍历并对下面的行执行boolean & 操作 --> 如果它是一个有效的矩形,它们的格式应该是100..001。 (假设你可以完成所有boolean 操作)
    3. 当您在第 2 步中找到至少一行时,您已经找到了一个矩形,最后是所有1s。
    4. 对行的下一个元素重复!

    【讨论】:

    • 为什么使用布尔与?为什么不将每一行与100..001 进行比较?另外,如果有一条水平线段延伸到矩形上边缘的左侧呢?
    • @j_random_hacker 是的。也可以进行比较。反正都是一样的。而且我认为没有“扩展”之类的东西。
    • “扩展”相当于从矩形顶部边缘右侧开始的水平线段,OP 的约束允许 AFAICT。
    • @j_random_hacker 嗯。明白你的意思了。在这种情况下,我们需要更多的检查点和约束。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-03
    相关资源
    最近更新 更多