2

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));
    }
}

アイデアは、形状を表すsizexsizeビットのセットを表すということです(必要に応じて、ビットマップ)。

現在、ビットのすべての可能な組み合わせが有効であるとは限りません。私にはいくつかの要件があります:

  1. size合計はビットのみである必要があります。(これは簡単です、私はすでにこれを実装しました)
  2. すべてのビットは連続している必要があります。

したがって、再び仮定size==4すると、次のことが適切です。

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

以下はそうではありませんが:

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

すべてのビットが隣接しているかどうかを判断するにはどうすればよいですか?

編集:これがEricLippertの答えを組み込んだ完全なコードです。これは、パフォーマンスの面で確実に強化できますが、非常に読みやすくなっています。

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
}
4

2 に答える 2

6

再帰を使用しないフラッドフィルアルゴリズムについて考える方法は次のとおりです。

すべての正方形を白(空白)または黒(塗りつぶし)に設定して開始します。問題は、「黒い領域が隣接しているかどうか」です。

このアルゴリズムを使用できます。

  1. 黒い四角がない場合は、隣接する黒い領域がないのは事実なので、これで完了です。
  2. それ以外の場合は、少なくとも1つの黒い四角があります。黒い四角を選んで赤くします。
  3. 赤い四角と黒い四角がない場合は完了です。黒い領域は隣接しています。
  4. 赤い四角がなく、1つ以上の黒い四角があれば、これで完了です。黒い領域は隣接していません。まだ黒い領域は、青い領域とは不連続です。
  5. それ以外の場合は、少なくとも1つの赤い四角が必要です。赤い四角を選んでください。黒の隣人をすべて赤に変えてから、青に変えます。
  6. 手順3に戻ります。

それがどのように機能するか見てみましょう。赤い四角は、洪水で満たされていない地域の「エッジ」です。青い四角は浸水した地域です。青がすべての黒に溢れている場合、それは隣接していたに違いありません。

更新:Re、あなたのコメント:

どうもありがとう!これは完璧です。私はあなたのブログ、特にLINQと「あなたが意味することを書く」に関する記事が大好きです、そして私はここでそれらの原則を適用しようとしました

優しい言葉をありがとう。この種の問題に対する非常に「LINQ-y」ソリューションが好きな場合は、ここでスケッチしたソリューションは使用しません。アルゴリズムは基本的に「データ構造を変更して、それに関する事実を学習する」ことに注意してください。これは、LINQのようなことではありません。LINQは、データ構造変更せずにクエリすることを目的としています。

私があなたの問題に対してよりLINQのような解決策を作りたいのなら、私は非常に異なるアプローチを取るでしょう。これが私がすることです。

まず、「同値類」または「同値関係」とは何か知っていますか?わからない場合は簡単に定義します。リレーションは、2つのことを取り、ブール値を返す関数です。たとえば、「未満」、「等しい」、「基数10の最後の桁が同じ」、「偶数の合計」はすべて整数の関係です。同値関係(A〜B)は、反射(X〜Xは常に真)、対称的(X〜YとY〜Xは同じ)、推移的(X〜YとY〜Zが両方とも真の場合)の関係です。 X〜Zもそうです)。

この例では、「未満」は推移的ですが、反射的または対称的ではありません。他の3つは同値関係です。

同値関係は、セットを同値類に分割します。たとえば、「合計から偶数へ」の同値関係は、整数を2つの同値類(偶数と奇数)に分割します。任意の2つの奇数を選択すると、X〜Yが真になります。任意の2つの偶数を選択すると、X〜Yが真になります。しかし、奇数と偶数、X〜Yは偽です。この関係では、すべての偶数は「同等」です。

ここで、タイルセットの1つについて、「このタイルセットの隣人である」という関係について考えてみます。明らかに、それは同値関係ではありません。対称的ですが、反射的または推移的ではありません。しかし、関係の反射的および推移閉包である新しい関係を定義することにより、任意の関係を同値関係に拡張することができます。これは「到達可能」の関係です。

したがって、あなたの質問は本質的に「到達可能性の同値関係にはいくつの同値類があるか」ということです。答えがゼロの場合、黒い領域はありません。答えが1の場合、単一の連続した領域があります。複数ある場合は、不連続な領域が存在する必要があります。

したがって、質問を特徴付ける別の方法は、「少なくとも1つの黒いタイルがある場合、黒いタイルのセット全体が、任意のタイル上の隣接関係の反射的および推移的な閉包と同一ですか?」です。答えが「はい」の場合、隣接する領域が1つあります。「いいえ」の場合、到達できない領域が存在する必要があります。

タイルを数える方法があり、数は有限の整数であるため、それよりもさらにうまくいくことができます。「任意の黒タイル上の隣接関係の反射的および推移閉包のサイズは、すべての黒タイルの数と同じですか?」と尋ねることができます。あなたの問題を解決するために。

では、その質問にどのように答えるのですか?タイルを取得し、その隣接するシーケンスを返すメソッドがあるとします。

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;
}

適切なツールを自由に使用できる場合、解決策は1つのステートメントです。

私が提供した2つのソリューション(およびAlbinのスケッチも)は、操作が同じであることに注意してください。2番目のソリューションでは、最初のソリューションの「赤いタイル」は「スタック」データ構造のタイルであり、「青いタイル」は、指定したリンクのコードの「クロージャー」データ構造のタイルです。

ソリューションの違いは、ソリューションについての考え方だけです。私は数学的に考えるのが好きなので、2番目の解決策を好みます。それはすべて、セットと関係とクロージャ、非常に抽象的なアイデアについてです。あなたが視覚的な思想家であれば、私の最初の解決策は、黒い領域に広がる赤いエッジの波がいっぱいになるまで視覚化できるので、理解しやすく、推論しやすいかもしれません。

于 2010-09-10T21:18:13.873 に答える
0

ランダムな「真の」ビットから始めます。次に、一度に1つずつ北、南、東、西を「歩きます」。「訪問済み」ではない「真の」ビットを見つけた場合は、そのノードを別の構造で「訪問済み」としてマークし、そこからすべての方向に再帰的に「ウォーク」します。ビットが「false」または「visited」の場合は、何もせずに前の「レベル」に戻ります。「訪問済み」でないノードがこれ以上見つからない場合は、訪問済みノードの数を数え、「真の」ノードの総数と比較します。

編集:ビットマップが大きい場合、再帰はスタックスペースを使い果たすことに注意してください。

于 2010-09-10T20:08:40.077 に答える