代码之家  ›  专栏  ›  技术社区  ›  Mike Caron

如何验证二维位图是连续的?

  •  2
  • Mike Caron  · 技术社区  · 14 年前

    假设在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));
        }
    }
    

    它代表了一种 size 表示形状的一组位(如果愿意的话,可以是位图)。

    现在,并非所有可能的位组合都是有效的。我有几个要求:

    1. 一定只有 比特总数。(这很简单,我已经实现了)
    2. 所有位必须是连续的。

    所以,再次假设 size==4 ,以下是好的:

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

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

    如何确定所有位是否连续?

    编辑:这是完整的代码,包含了埃里克·利珀特的答案。从性能角度来看,这一点肯定是可以加强的,但它的可读性非常强。

    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
    }
    
    2 回复  |  直到 14 年前
        1
  •  6
  •   Eric Lippert    14 年前

    下面是一种不使用递归的泛洪填充算法。

    从每个正方形设置为白色(空白)或黑色(填充)开始。问题是“黑色区域是否相邻?”

    您可以使用此算法:

    1. 如果没有黑色正方形,那么确实没有不连续的黑色区域,那么就完成了。
    2. 否则,至少有一个黑色正方形。选一个黑色的正方形,然后把它变成红色。
    3. 如果没有红色方块和黑色方块,那么你就完成了,黑色区域 相邻的 .
    4. 如果没有红色方块,只有一个或多个黑色方块,那么你就完了。黑色区域 . 仍然是黑色的区域与蓝色的区域不连续。
    5. 否则,必须至少有一个红色正方形。随便选一个红方块。把它所有的黑色邻居变成红色,然后变成蓝色。

    更新:回复,您的评论:

    使用我在这里草拟的解决方案。请注意,该算法基本上是“改变数据结构以了解有关它的事实”。那不是一件很危险的事。LINQ是关于查询数据结构的 修改它们。

    如果我想对你的问题提出一个更像LINQ的解决方案,那么我会采取完全不同的方法。我会这么做。

    首先,你知道什么是“等价类”或“等价关系”吗?如果你不知道的话,我会简单地给他们下定义。A 是一个接受两件事并返回布尔值的函数。例如,“小于”、“等于”、“以十为底的最后一位相同”和“和为偶数”都是整数的关系。安 等价 关系(A~B)是 自反的 (X~X总是正确的), 对称的 (X~Y和Y~X是一样的)和 传递的 (如果X~Y和Y~Z都是真的,那么X~Z也是真的)。

    等价关系将集合划分为等价类。例如,等价关系“和为偶数”将整数划分为两个等价类:偶数和奇数。选取任意两个奇数,X~Y为真。选择任意两个偶数,X~Y为真。但是一个奇数和一个偶数,X~Y是假的。就这种关系而言,所有偶数都是“等价的”。

    现在考虑一个图块集的关系“是这个图块集上的邻居”。显然,这不是一个等价关系;它是对称的,但不是自反的或传递的。但任何关系都可能 扩展 自反与传递闭包 关系的一部分。这是“可从”关系。

    因此,描述你的问题的另一种方法是“假设至少有一个黑瓦片,那么整个黑瓦片集合是否与任意瓦片上的相邻关系的自反和传递闭包相同?”如果答案是“是”,那么就有一个单一的连续区域。如果“否”,则必须存在无法到达的区域。

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

    基本上这种方法是“给我一个瓷砖,给我所有与它有邻居关系的瓷砖”。如果您可以计算出哪些成员与给定的成员有关系,那么这种方法非常有效,很明显,在本例中,您可以便宜地进行计算。

    现在您可以使用我在这里发布的代码计算该关系的传递闭包和自反闭包:

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

    现在整个算法变得非常简短:

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

    注意,我给出的两个解决方案(以及Albin的草图)是 . 在我的第二个解决方案中,第一个解决方案的“红瓷砖”是“stack”数据结构中的瓷砖,“蓝瓷砖”是我给出的链接中代码的“closure”数据结构中的瓷砖。

    解决方案之间的区别只在于你如何 认为 关于解决方案。我喜欢数学思考,所以我更喜欢第二种解决方案。都是关于集合、关系和闭包,非常抽象的概念。如果你更像是一个视觉思考者,那么我的第一个解决方案,你可以想象一个红边波扩散到一个黑色区域,直到它充满,可能更容易理解和推理。

        2
  •  0
  •   Albin Sunnanbo    14 年前

    你从一个随机的“真”开始。然后你一次“走”一个北,一个南,一个东,一个西。如果您发现一个“true”位不是“visited”,则在一个单独的结构中将该节点标记为“visited”,并从此处向所有方向递归“walk”。如果位为“假”或“已访问”,则不执行任何操作并返回到上一个“级别”。当您找不到更多的非“已访问”节点时,请计算已访问节点的数量,并与“真实”节点的总数进行比较。

    编辑: