web-dev-qa-db-ja.com

2Dビット配列でビットの連続領域を見つける

問題

「タイル」の2次元「マップ」を表すビット配列があります。この画像は、ビット配列内のビットのグラフィカルな例を示しています。

Bit Array Example

配列内にビットの連続する「領域」がいくつ存在するかを判別する必要があります。上記の例では、次のように、このような連続した2つの「領域」があります。

Contiguous Areas

「隣接」と見なされるには、タイルをタイルのN、S、E、またはWに直接配置する必要があります。斜めに触れるタイルはカウントされません。

私がこれまでに得たもの

これらのビット配列は比較的大きくなる可能性があるため(サイズが数MB)、アルゴリズムで再帰を使用することは意図的に避けています。

擬似コードは次のとおりです。

LET S BE SOURCE DATA ARRAY
LET C BE ARRAY OF IDENTICAL LENGTH TO SOURCE DATA USED TO TRACK "CHECKED" BITS
FOREACH INDEX I IN S
    IF C[I] THEN 
        CONTINUE 
    ELSE
        SET C[I]
        IF S[I] THEN
            EXTRACT_AREA(S, C, I)

EXTRACT_AREA(S, C, I):
    LET T BE TARGET DATA ARRAY FOR STORING BITS OF THE AREA WE'RE EXTRACTING
    LET F BE STACK OF TILES TO SEARCH NEXT
    Push I UNTO F
    SET T[I]
    WHILE F IS NOT EMPTY
        LET X = POP FROM F
        IF C[X] THEN 
            CONTINUE
        ELSE
            SET C[X]
            IF S[X] THEN
                Push TILE NORTH OF X TO F
                Push TILE SOUTH OF X TO F
                Push TILE WEST OF X TO F
                Push TILE EAST OF X TO F
                SET T[X]
    RETURN T

Animation of My Algorithm

私のソリューションについて嫌いなこと

  • 実行するには、処理するビットマップ配列の2倍のメモリが必要です。
  • 「領域」を抽出する際、ビットマップ配列の3倍のメモリを使用します。
  • 重複は「チェックするタイル」スタックに存在します。これは醜いように見えますが、現在の方法を回避する価値はありません。

見たいもの

  • より良いメモリプロファイル
  • 広い領域のより高速な処理

解決策/フォローアップ

私は(@hatchetの提案に従って)エッジのみを探索するようにソリューションを書き直しました。

これは実装が非常に簡単で、「訪問したタイル」を完全に追跡する必要がなくなりました。

3つの簡単なルールに基づいて、エッジをトラバースし、最小/最大のxとyの値を追跡し、再び開始点に到達したときに完了することができます。

これが私が使用した3つのルールのデモです:

Solution

30
Steve

1つのアプローチは、境界ウォークです。シェイプのエッジに沿った任意の場所に開始点がある場合は、その点を覚えておいてください。

そのポイントとしてバウンディングボックスを開始します。

時計回りのルールセットを使用して周囲を歩きます。現在のポイントに到達するために使用されたポイントが上にあった場合は、最初に右を見て、次に下を見て、次に左を見て、形状の周囲の次のポイントを見つけます。これは、迷路を解くという単純な戦略のようなもので、壁をたどり続け、常に右を向いています。

新しい境界点にアクセスするたびに、新しい点が境界ボックスの外側にある場合は境界ボックスを展開します(つまり、最小および最大のxとyを追跡します。

開始点に到達するまで続けます。

短所:形状に単一ピクセルの「フィラメント」がたくさんある場合は、散歩が戻ってきたときにそれらを再訪することになります。

長所:形状に内部占有スペースが大きく広がっている場合、塗りつぶしで訪問したピクセルを記録する場合のように、訪問したり記録したりする必要はありません。

したがって、スペースを節約しますが、場合によっては時間を犠牲にします。

編集

よくあることですが、この問題は既知であり、名前が付けられており、複数のアルゴリズムによる解決策があります。あなたが説明した問題は、最小境界長方形と呼ばれます。これを解決する1つの方法は、 等高線トレース を使用することです。上で説明したメソッドはそのクラスにあり、 Moore-Neighbor Tracing または Radial Sweep と呼ばれます。私が彼らのために含めたリンクはそれらを詳細に議論し、私が捕らえなかった問題を指摘します。開始点に再度アクセスする場合があります境界全体を横断しました。たとえば、開始点が単一ピクセルの「フィラメント」に沿った場所にある場合は、完了する前に再検討します。この可能性を考慮しない限り、途中で停止します。私がリンクしたウェブサイトは、この停止の問題に対処する方法について話します。そのWebサイトの他のページでも、SquareTracingとTheoPavlidisのアルゴリズムという2つの他のアルゴリズムについて説明しています。注意すべき点の1つは、これらは対角線を連続していると見なしますが、そうではないということですが、基本的なアルゴリズムを少し変更するだけで処理できるものでなければなりません。

問題に対する別のアプローチは 連結成分ラベリング です。ただし、ニーズに対しては、これは必要以上に時間のかかるソリューションになる可能性があります。

追加リソース:

C++のムーア近傍輪郭追跡アルゴリズム

10
hatchet

実は一度のインタビューでこんな質問がありました。

配列がグラフであり、接続されたノードが隣接するノードであると偽ることができます。私のアルゴリズムでは、マークされたノードが見つかるまで1を右に移動します。 1つを見つけたら、O(n)で実行され、再帰を回避する幅優先探索を実行します。BFSが戻ったら、中断したところから検索を続け、ノードがすでに1つでマークされているかどうかを確認します。以前のBFSの場合、明らかに検索する必要はありません。見つかったオブジェクトの数を実際に返したいかどうかはわかりませんでしたが、最初にマークされた正方形に到達したときにカウンターをインクリメントするだけで簡単に追跡できます。

一般に、フラッドフィルタイプのアルゴリズムを実行すると、スポットに配置され、フィルするように求められます。これは、最適化する1つの方法ですべての塗りつぶされた領域を見つけることなので、以前のBFSからすでにマークされたノードを再チェックしないようにすることです。残念ながら、現時点ではそれを行う方法を考えることはできません。

メモリ消費を減らすためのハックな方法の1つは、ブール値ではなくshort[][]を格納することです。次に、このスキームを使用して、2番目の2Dアレイ全体を作成しないようにします。

マークなし= 0、マーク付き= 1、チェック済みおよびマークなし= 3、チェック済みおよびマーク付き= 3

このようにして、エントリのステータスをその値で確認し、2番目の配列を作成しないようにすることができます。

4
aaronman