简体   繁体   English

您如何验证2d位图是连续的?

[英]How do you verify that a 2d bitmap is contiguous?

Let's say you have the following structure in C#: 假设您在C#中具有以下结构:

struct Piece : IEquatable<Piece> {
    public readonly int size;
    public readonly bool[,] data;

    public Piece(Piece p) {
        size = p.size;

        data = (bool[,])p.data.Clone();
    }
    public Piece(int s, bool[,] d) {
        size = s;
        if (d.GetLength(0) != s || d.GetLength(1) != s) throw new ArgumentException();

        data = (bool[,])d.Clone();
    }

    public bool Equals(Piece other) {
        if (size != other.size) return false;

        return (data.Equals(other.data));
    }
}

The idea is that it represents a size x size set of bits which represents a shape (a bit map, if you will). 这个想法是,它代表size x size的位集合,位集合表示形状(如果需要,可以绘制位图)。

Now, not all possible combinations of bits are valid. 现在,并非所有可能的位组合都有效。 I have a few requirements: 我有一些要求:

  1. There must be only size bits total. 总共只能有size位。 (This is easy, I have already implemented this) (这很容易,我已经实现了)
  2. All bits must be contiguous. 所有位必须是连续的。

So, again assuming size==4 , the following is good: 因此,再次假设size==4 ,以下内容是好的:

..#.
..#.
.##.
....

While the following is not: 虽然以下不是:

...#
...#
#...
#...

How do I determine whether all the bits are contiguous or not? 如何确定所有位是​​否连续?

Edit: Here is the full code, incorporating Eric Lippert's answer. 编辑:这是完整的代码,其中包含Eric Lippert的答案。 This can definitely be tightened up, performance-wise, but it's very readable. 从性能角度来看,这绝对可以加强,但是它非常可读。

struct Point : IEquatable<Point> {
    public int X, Y;

    public Point(int x, int y) {
        X = x; Y = y;
    }

    public bool Equals(Point obj) {
        return X == obj.X && Y == obj.Y;
    }

    public override bool Equals(object obj) {
        if (obj == null) return false;

        if(obj is Point)
            return Equals((Point)obj);

        return false;
    }

    public override int GetHashCode() {
        return X ^ ~Y;
    }

    public static bool operator == (Point p1, Point p2) {
        return p1.X == p2.X && p1.Y == p2.Y;
    }

    public static bool operator !=(Point p1, Point p2) {
        return p1.X != p2.X || p1.Y != p2.Y;
    }

    public static readonly Point Empty = new Point(int.MinValue, int.MinValue);
}

struct Piece : IEquatable<Piece> {
    public readonly int size;
    public readonly bool[,] data;
    private bool valid;

    public Piece(Piece p) {
        size = p.size;
        valid = p.valid;
        data = (bool[,])p.data.Clone();
    }
    public Piece(int s, bool[,] d) {
        size = s;
        if (d.GetLength(0) != s || d.GetLength(1) != s) throw new ArgumentException();

        data = (bool[,])d.Clone();
        valid = false;

        CalcValidity();
    }

    public bool IsValid {
        get {
            return valid;
        }
    }


    private enum Square {
        White,
        Black,
        Red,
        Blue,
    }

    private int NumSquares(Square[,] map, Square q) {
        int ret = 0;
        for (int y = 0; y < size; y++) {
            for(int x = 0; x < size; x++) {
                if (map[x, y] == q) ret++;
            }
        }
        return ret;
    }

    private Point PickSquare(Square[,] map, Square q) {
        for (int y = 0; y < size; y++) {
            for (int x = 0; x < size; x++) {
                if (map[x, y] == q) return new Point(x, y);
            }
        }
        return Point.Empty;
    }

    private void MakeNeighboursRed(Square[,] map, Point p) {
        if (p.X > 0 && map[p.X - 1, p.Y] == Square.Black) map[p.X - 1, p.Y] = Square.Red;
        if (p.X < size - 1 && map[p.X + 1, p.Y] == Square.Black) map[p.X + 1, p.Y] = Square.Red;

        if (p.Y > 0 && map[p.X, p.Y - 1] == Square.Black) map[p.X, p.Y - 1] = Square.Red;
        if (p.Y < size - 1 && map[p.X, p.Y + 1] == Square.Black) map[p.X, p.Y + 1] = Square.Red;
    }

    private void CalcValidity() {
        Square[,] map = new Square[size, size];

        int count = 0;
        Point square = Point.Empty;


        for (int y = 0; y < size; y++) {
            for (int x = 0; x < size; x++) {
                if (data[x, y]) {
                    map[x, y] = Square.Black;
                    square = new Point(x, y);
                } else {
                    map[x, y] = Square.White;
                }
            }
        }

        if (square != Point.Empty) {
            map[square.X, square.Y] = Square.Red;
        }

        while (true) {
            if (NumSquares(map, Square.Red) == 0) {
                if (NumSquares(map, Square.Black) == 0) {
                    valid = count == size;
                    return;
                } else {
                    valid = false;
                    return;
                }
            } else {
                square = PickSquare(map, Square.Red);

                MakeNeighboursRed(map, square);
                map[square.X, square.Y] = Square.Blue;
                count++;
            }
        } 
    }

    #region IEquatable<Piece> Members

    public bool Equals(Piece other) {
        if (size != other.size) return false;

        for (int y = 0; y < size; y++) {
            for (int x = 0; x < size; x++) {
                if (data[x, y] != other.data[x, y]) return false;
            }
        }
        return true;
    }

    #endregion
}

Here's a way to think about the flood fill algorithm that does not use recursion. 这是一种考虑不使用递归的泛洪填充算法的方法。

Start with every square set to either white (blank) or black (filled). 首先将每个正方形设置为白色(空白)或黑色(实心)。 The question is "are the black regions contiguous or not?" 问题是“黑色区域是否连续?”

You can use this algorithm: 您可以使用以下算法:

  1. If there are no black squares then it is true that there are no discontiguous black regions, so you're done. 如果没有黑色方块,那么确实没有不连续的黑色区域,那么就完成了。
  2. Otherwise, there is at least one black square. 否则,至少有一个黑色正方形。 Pick any black square and turn it red. 选择任何黑色正方形并将其变为红色。
  3. If there are no red squares and no black squares then you're done, and the black regions are contiguous . 如果没有红色方块和黑色方块,则说明操作完成,并且黑色区域是连续的
  4. If there are no red squares but one or more black squares then you're done. 如果没有红色方块,但有一个或多个黑色方块,那么您就完成了。 The black regions are not contiguous . 黑色区域不连续 The region that is still black is discontiguous from the region that is blue. 仍然是黑色的区域与蓝色的区域是不连续的。
  5. Otherwise, there must be at least one red square. 否则,必须至少有一个红色正方形。 Pick any red square. 选择任何红色正方形。 Turn all its black neighbours to red, and then turn it blue. 将其所有黑色邻居变为红色,然后将其变为蓝色。
  6. Go back to step 3. 返回步骤3。

See how that works? 看看如何运作? The red squares are the "edges" of the region that is not flood-filled. 红色方块是未充满洪水的区域的“边缘”。 The blue squares are the flooded region. 蓝色方块是水淹区域。 If the blue floods all the black then it must have been contiguous. 如果蓝色泛滥了所有黑色,那么它一定是连续的。

UPDATE: Re, your comment: 更新:回复,您的评论:

Thank you so much! 非常感谢! This is perfect. 太棒了。 I love your blog, especially the articles on LINQ and "writing what you mean", and I tried to apply those principles here 我喜欢您的博客,尤其是有关LINQ的文章和“写出您的意思”,我尝试在此处应用这些原则

Thanks for the kind words. 感谢您的客气话。 If what you like is very "LINQ-y" solutions to these sorts of problems then I would not use the solution that I sketched out here. 如果您想要的是针对此类问题的非常“ LINQ-y”解决方案,那么我将使用在此概述的解决方案。 Notice that the algorithm is basically "mutate a data structure to learn facts about it". 请注意,该算法基本上是“对数据结构进行变异以了解其事实”。 That's not a very LINQ-ish thing to do. 这不是LINQ式的事情。 LINQ is all about querying data structures without modifying them. LINQ只是关于查询数据结构而不修改它们。

If I wanted to make a more LINQ-like solution to your problem then I would take a very different approach. 如果我想为您的问题提供更像LINQ的解决方案,那么我将采用一种非常不同的方法。 Here's what I'd do. 这就是我要做的。

First off, do you know what an "equivalence class" or an "equivalence relation" is? 首先,您知道什么是“等价类”或“等价关系”吗? I'll briefly define them if you don't know. 如果您不知道,我将简要定义它们。 A relation is a function that takes two things and returns a bool. 关系是需要两件事并返回布尔值的函数。 For example, "less than", "equal to", "have the same last digit in base ten" and "sum to an even number" are all relations on integers. 例如,“小于”,“等于”,“以十进制表示的最后一位相同”和“总和为偶数”都是整数上的关系。 An equivalence relation (A~B) is a relation which is reflexive (X~X is always true), symmetric (X~Y and Y~X are the same) and transitive (if X~Y and Y~Z are both true then so is X~Z). 等价关系(A〜B)是自反X〜X始终为真), 对称 (X〜Y和Y〜X相同)和传递 (如果X〜Y和Y〜Z都为真)的关系X〜Z也是如此)

In our examples, "less than" is transitive but not reflexive or symmetric. 在我们的示例中,“小于”是传递的,但不是反身的或对称的。 The other three are equivalence relations. 其他三个是等价关系。

An equivalence relation partitions the set into equivalence classes. 等价关系将集合划分为等价类。 For example, the equivalence relation "sum to an even number" partitions the integers into two equivalence classes: the even numbers and the odd numbers. 例如,等价关系“总和为偶数”将整数分为两个等价类:偶数和奇数。 Pick any two odd numbers and X~Y is true. 选择任意两个奇数,X〜Y为真。 Pick any two even numbers and X~Y is true. 选择任意两个偶数,X〜Y为真。 But an odd number and an even number, X~Y is false. 但是X〜Y是奇数和偶数,是错误的。 All the even numbers are "equivalent" for the purposes of this relation. 就此关系而言,所有偶数都是“等效的”。

Now consider the relation "is a neighbour on this tile set" for one of your tile sets. 现在考虑其中一个图块集的关系“此图块集上的邻居”。 Clearly that is not an equivalence relation; 显然,这不是等价关系; it is symmetric but not reflexive or transitive. 它是对称的,但不是反身或传递的。 But any relation can be extended to be an equivalence relation by defining a new relation that is the reflexive and transitive closure of the relation. 但是,通过定义新的关系,即关系的自反和及物闭合 ,可以将任何关系扩展为等价关系。 This is the "is reachable from" relation. 这是“可以从”到达的关系。

Your question is therefore essentially "how many equivalence classes are there for the equivalence relation of reachability"? 因此,您的问题本质上是“可达性的等价关系有多少个等价类”? If the answer is zero then there are no black regions. 如果答案为零,则没有黑色区域。 If the answer is one then there is a single contiguous region. 如果答案是一个,则只有一个连续的区域。 If it is more than one then there must be discontiguous regions. 如果不止一个,则必须有不连续的区域。

Therefore another way to characterize your question is "given that there is at least one black tile, is the entire set of black tiles identical with the reflexive and transitive closure of the neighbour relation on an arbitrary tile?" 因此,表征您的问题的另一种方式是“假设存在至少一个黑色图块,那么整个黑色图块集合是否与任意图块上的邻居关系的反射性和传递性闭合相同?” If the answer is "yes" then there is a single contiguous region. 如果答案为“是”,则存在单个连续区域。 If "no" then there must be a region that is not reachable. 如果为“否”,则必须存在一个无法到达的区域。

Since you have a way to count tiles, and since the numbers are finite integers, we can do even better than that. 由于您可以计算图块,并且数字是有限整数,因此我们可以做得更好。 We can just ask "is the size of the reflexive and transitive closure of the neighbour relation on an arbitrary black tile identical with the count of all black tiles?" 我们可以问:“任意黑色图块上邻居关系的反射性和传递性闭合的大小是否与所有黑色图块的数量相同?” to solve your problem. 解决您的问题。

So how to answer that question? 那么如何回答这个问题呢? Suppose you have a method that takes a tile and returns a sequence of its neighbours: 假设您有一个方法,该方法接受一个tile并返回其邻居的序列:

public static IEnumerable<Tile> GetNeighbours(Tile tile)
{
     ... yield return the north, south, east and west neighbours
     ... if they exist and they are on
}

Basically this method is "given a tile, give me all the tiles that have the neighbour relation with it". 基本上,这种方法是“给一个瓷砖,给我所有与之有邻居关系的瓷砖”。 This works great if you can compute which members have a relation with a given member, which clearly in this case you can do so cheaply. 如果您可以计算出哪些成员与给定成员有关系,那么这样做非常有用,在这种情况下,显然可以便宜地做到这一点。

Now you can compute the transitive and reflexive closure of that relation using the code I posted here: 现在,您可以使用我在此处发布的代码来计算该关系的传递和自反闭合:

http://blogs.msdn.com/b/ericlippert/archive/2010/02/08/making-the-code-read-like-the-spec.aspx http://blogs.msdn.com/b/ericlippert/archive/2010/02/08/making-the-code-read-like-the-spec.aspx

And now your entire algorithm becomes very short indeed: 现在,您的整个算法确实变得非常短:

bool HasExactlyOneRegion()
{
    return (tilesTurnedOn.Count == 0) ? 
        false : 
        TransitiveAndReflexiveClosure(GetNeighbours, tilesTurnedOn.First()).Count == tilesTurnedOn.Count;
}

If you have the right tools at your disposal then the solution is a single statement! 如果您拥有合适的工具,那么该解决方案就是一个简单的陈述!

Notice that the two solutions I've given (and Albin's sketch as well) are identical in their operation . 注意,我给出的两个解决方案(以及Albin的草图) 在操作上相同的 In my second solution the "red tiles" of the first solution are the tiles in the "stack" data structure, and the "blue tiles" are the tiles in the "closure" data structure of the code in the link I gave. 在我的第二个解决方案中,第一个解决方案的“红色图块”是“堆栈”数据结构中的图块,而“蓝色图块”是我给出的链接中的代码“闭合”数据结构中的图块。

The difference between the solutions is only in how you think about the solution. 解决方案之间的区别仅在于您对解决方案的看法 I like to think mathematically, so I would prefer the second solution. 我喜欢用数学的方式思考,所以我更喜欢第二种解决方案。 It's all about sets and relations and closures, very abstract ideas. 这都是关于集合,关系和闭包,非常抽象的想法。 If you're more of a visual thinker then my first solution, where you can visualize a red-edged wave spreading into a black area until it is full, might be easier to understand and reason about. 如果您更像是一个视觉思想家,那么我的第一个解决方案可能更容易理解和推论,在该解决方案中,您可以看到一个红色边缘的波,该波扩散到黑色区域,直到充满为止。

You start at a random "true" bit. 您从随机的“真”位开始。 Then you "walk" north, south, east and west one at a time. 然后您一次“走”北,南,东和西。 If you find a "true" bit that is not "visited", mark that node as "visited" in a separate structure and "walk" recursively in all directions from there. 如果找到未“访问”的“真”位,请在单独的结构中将该节点标记为“已访问”,然后从那里在所有方向上递归“行走”。 If the bit is "false" or "visited", do nothing and return to the previous "level". 如果该位是“ false”或“ visited”,则不执行任何操作并返回到先前的“级别”。 When you can not find any more non "visited" nodes, count the number of visited nodes and compare with the total number of "true" nodes. 当您找不到其他未访问的节点时,请计算访问的节点数,然后与“真实”节点的总数进行比较。

Edit: Note that recursion will run out of stack space if the bitmaps are large. 编辑:请注意,如果位图很大,则递归将耗尽堆栈空间。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM