找到包含0的完整矩形

时间:2012-07-12 06:36:08

标签: c arrays algorithm math

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

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

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

enter image description here

(数组的初始值为'0',模式表示一系列'1'。它们不重叠或相互包含。)

除了未闭合的形状或直线外,什么可能是一个有效的算法来找到阵列中的完整的整数?例如,在上图中,完整矩形的数量是3

5 个答案:

答案 0 :(得分:21)

这很简单。如果您有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

以下是算法的执行方式:

enter image description here
1.失败(并标记)坏矩形的一部分

enter image description here
2.找到(并标记)一个矩形。

enter image description here
3.单个方格(垂直线)失败

enter image description here
4.单个方格(垂直线)失败

enter image description here
5.单个方格(垂直线)失败

enter image description here
6.经过许多类似的步骤,找到了下一个矩形。

enter image description here
7.下一个矩形

enter image description here
8.算法结束

答案 1 :(得分:4)

以下O(n)算法将在任何具有0/1值的2D矩阵上工作(即,允许交叉/重叠矩形,以及任意非矩形打开/关闭形状) 。我在这里使用的矩形的定义是“内部完全由0个单元格组成”(例如,如果一个矩形完全包含另一个矩形,则只能找到内部矩形;如果还要考虑包含矩形,那么可以删除每个找到的矩形并重新启动算法)。它基于观察结果,即每个0-单元格可以位于内部,最多只有一个1-矩形

我使用的惯例是x = 0是最左边的位置而y = 0是最顶端的位置。

  1. 找到左上角。从左上角开始,从左到右,从上到下,找到下一个未访问的单元格,可能是左上角实心0矩形的一角:具体来说,它必须是一个0格,在SW,W,NW,N和NE位置有1个单元的邻居,剩下的3个相邻位置有0个单元。
  2. 找到右上角。扫描右边的邻居,而这些单元格为0并且有一个1个单元格的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,而在垂直位置y2的x1和x2之间的线可以是实心0矩形的一部分,增加y2。具体来说,每个垂直位置y2的测试是:(x1 - 1,y2)和(x2 + 1,y2)的单元格必须都是1,其间的所有单元格必须为0。
  6. 这可能是实心0矩形的底行吗?如果前一个循环在结束之前找到的最后一行是一行,可能是一个实心0矩形的底行(特别是从(x1 - 1,y2 + 1)到(x2 + 1,y2 + 1)都有1个单元格,然后我们找到了一个由1个单元格包围的完整的实心0矩形:如果是尺寸大于迄今为止发现的最大尺寸,然后将其记录为新的最大矩形。否则(如果下一行中没有一行实体的1个单元格),则所检查的0个单元都不能成为任何实心0矩形的一部分:将它们全部标记为已访问并转到1。

答案 2 :(得分:3)

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

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

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

答案 3 :(得分:2)

考虑一下。我想出了这个方法:

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

2)洪水填充2s周围的矩阵

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

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

4)如果内部矩形包含1个OR外部矩形包含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----

答案 4 :(得分:0)

我认为这可能是资源效率低下的问题。不知道那个。

  1. 除非您找到至少3 1 s。
  2. ,否则沿着一行遍历
  3. 走下来做boolean&amp;操作下面的行 - &gt;如果它是有效的矩形,它们应该导致100..001的格式。 (假设您可以执行所有boolean操作)
  4. 当您在步骤2中找到至少一行时,您找到了一个矩形,最后找到了所有1 s。
  5. 重复该行的下一个元素!