29 Stimmen

Das Finden vollständiger Rechtecke, die 0 umschließen

Es gibt verschiedene Rechtecke in den gegebenen 1000 x 1000 Arrays. In

wird die Seriennummer "1", die als gelbe Zelle dargestellt ist, als Muster des Rechtecks angezeigt. Die minimale Größe des Rechtecks in

beträgt 3 x 3 und wird als grüne Zelle angezeigt.

Es sollte mindestens eine '0' innerhalb des Rechtecks vorhanden sein.

In diesem Array gibt es jedoch auch ungeschlossene Formen oder gerade Linienmuster.

Bildbeschreibung hier eingeben

(Der Ausgangswert des Arrays ist '0' und die Muster werden als eine Serie von '1' dargestellt. Sie sind nicht übereinander oder ineinander enthalten.)

Was könnte ein effizienter Algorithmus sein, um die vollständigen Rechtecke im Array zu finden, außer den ungeschlossenen Formen oder geraden Linien? Zum Beispiel in der obigen Abbildung beträgt die Anzahl der vollständigen Rechtecke 3

21voto

Shahbaz Punkte 44492

Dies ist ziemlich einfach. Wenn Sie n Quadrate haben, können Sie die Rechtecke in O(n) zählen.

Annahmen:

  • Die Grenzen jedes gültigen Rechtecks teilen keine Zellen mit einem nicht gültigen Pfad.
  • Wenn ein Rechteck innerhalb eines anderen liegt, würden Sie sich freuen, sie zu finden.

Sie benötigen zusätzlichen Speicher so groß wie die Eingabe. Nennen wir dies besucht und initialisieren es mit 0.

Lassen Sie uns zuerst eine Hilfsfunktion erstellen:

is_rechteck(quadrat s)
    Von s aus nach rechts gehen, während Sie 1en sehen und besucht 0 ist
        Markieren Sie sie als 1 in besucht
    Wenn weniger als 3 Schritte
        gebe 0 zurück
    dann nach unten gehen, während Sie 1en sehen und besucht 0 ist
        Markieren Sie sie als 1 in besucht
    Wenn weniger als 3 Schritte
        gebe 0 zurück
    dann nach links gehen, während Sie 1en sehen und besucht 0 ist
        Markieren Sie sie als 1 in besucht
    Wenn weniger als 3 Schritte
        gebe 0 zurück
    dann nach oben gehen, während Sie 1en sehen und besucht 0 ist
        Markieren Sie sie als 1 in besucht
    Wenn dann eine darüber nicht s ist
        gebe 0 zurück
    gebe 1 zurück

Diese Funktion verfolgt im Wesentlichen die 1en in die Richtungen rechts-unten-links-oben und überprüft, ob die Bedingungen erfüllt sind (Länge mindestens 3 und Erreichen der Ausgangsposition). Es markiert auch die besuchten Quadrate.

Das Wichtige bei dieser Funktion ist zu beachten, dass sie nur korrekt funktioniert, wenn das Ausgangsquadrat in der oberen linken Ecke liegt.

Jetzt ist die Lösung des Problems einfach:

num_rechtecke = 0
besucht auf 0 initialisieren (Zeilen x Spalten)
für r in Zeilen
    für s in Spalten
        wenn visitiert[r][s] oder bild[r][s] == 0
            fortfahren
        num_rechtecke += is_rechteck()
gebe num_rechtecke zurück

So führt der Algorithmus aus:

Geben Sie hier eine Bildbeschreibung ein
1. Fehlgeschlagener (und markierter) Teil eines falschen Rechtecks

Geben Sie hier eine Bildbeschreibung ein
2. Gefundenes (und markiertes) Rechteck

Geben Sie hier eine Bildbeschreibung ein
3. Fehlgeschlagen an einem einzelnen Quadrat (einer vertikalen Linie)

Geben Sie hier eine Bildbeschreibung ein
4. Fehlgeschlagen an einem einzelnen Quadrat (einer vertikalen Linie)

Geben Sie hier eine Bildbeschreibung ein
5. Fehlgeschlagen an einem einzelnen Quadrat (einer vertikalen Linie)

Geben Sie hier eine Bildbeschreibung ein
6. Nach vielen ähnlichen Schritten das nächste Rechteck gefunden

Geben Sie hier eine Bildbeschreibung ein
7. Und das nächste Rechteck

Geben Sie hier eine Bildbeschreibung ein
8. Algorithmus Ende

4voto

j_random_hacker Punkte 49159

Der folgende O(n)-Algorithmus funktioniert mit jeder 2D-Matrix von 0/1-Werten (d. h., sich schneidende/überlappende Rechtecke sind erlaubt, ebenso wie beliebige nicht-rechteckige formlose Formen). Die Definition eines Rechtecks, die ich hier verwende, lautet "das Innere besteht ausschließlich aus 0-Zellen" (so werden z. B. wenn ein Rechteck vollständig ein anderes enthält, nur das innere Rechteck gefunden; wenn enthaltene Rechtecke auch berücksichtigt werden sollen, kann jedes gefundene Rechteck gelöscht und der Algorithmus neu gestartet werden). Es basiert auf der Beobachtung, dass jede 0-Zelle sich im Inneren höchstens ein 1-Rechteck befinden kann.

Ich verwende die Konvention, dass x = 0 die linke Position und y = 0 die oberste Position ist.

  1. Finde die obere linke Ecke. Beginnend bei der oberen linken Zelle und fortlaufend von links nach rechts und von oben nach unten, finde die nächste unbesuchte Zelle, die die obere linke Ecke eines soliden 0-Rechtecks sein könnte: Spezifisch muss es eine 0-Zelle sein, die 1-Zellnachbarn in den SW-, W-, NW-, N- und NE-Positionen hat, und 0-Zellen in den verbleibenden 3 benachbarten Positionen.
  2. Finde die obere rechte Ecke. Durchsuche Nachbarn rechts davon, während diese Zellen 0 sind und einen 1-Zell-Nachbarn N haben.
  3. Könnte dies die obere Reihe eines soliden 0-Rechtecks sein? Wenn die letzte Zelle, die von der obigen Schleife gefunden wurde, bevor sie endet, eine Zelle ist, die die obere rechte Zelle eines soliden 0-Rechtecks sein könnte (spezifisch eine 0-Zelle, die 1-Zellnachbarn in den NW-, N-, NE-, E- und SE-Zellen hat, und 0-Zellen an den verbleibenden 3 Positionen), dann haben wir sowohl die obere y-Koordinate als auch die genaue Breite des einzigen möglichen soliden 0-Rechtecks, das diese Zellen verwendet, festgelegt. Wenn die letzte Zelle diese Bedingungen der oberen rechten Ecke nicht erfüllt, können keine dieser Zellen Teil eines soliden 0-Rechtecks sein: markiere sie als besucht und gehe zu 1.
  4. Rufe die Start- und End-x-Koordinaten des Streifens mit 0-Zellen x1 und x2 auf; rufe die vertikale Position y1 auf.
  5. Scanne zeilenweise nach unten. Setze y2 = y1, und während die Linie zwischen x1 und x2 bei vertikaler Position y2 Teil des soliden 0-Rechtecks sein könnte, erhöhe y2. Spezifisch ist der Test an jeder vertikalen Position y2: Die Zellen bei (x1 - 1, y2) und (x2 + 1, y2) müssen beide 1 sein, und alle Zellen dazwischen müssen 0 sein.
  6. Könnte dies die untere Reihe eines soliden 0-Rechtecks sein? Wenn die letzte von der vorherigen Schleife gefundene Zeile, bevor sie endet, eine Zeile ist, die die untere Reihe eines soliden 0-Rechtecks sein könnte (spezifisch gibt es 1-Zellen den ganzen Weg von (x1 - 1, y2 + 1) bis (x2 + 1, y2 + 1)), dann haben wir ein vollständiges solides 0-Rechteck umgeben von 1-Zellen gefunden: Wenn seine Größe größer ist als das bisher größte entdeckte, dann zeichne es als das neue größte Rechteck auf. Andernfalls (wenn es keine solide Reihe von 1-Zellen in der nächsten Zeile gibt), können keine der untersuchten 0-Zellen Teil eines soliden 0-Rechtecks sein: markiere alle als besucht und gehe zu 1.

3voto

Bentoy13 Punkte 4746

Wenn Sie nur rechteckige Formen in Ihrem Array haben können, entspricht dies einem klassischen Problem der Berechnung von binären Bildern: wenden Sie einfach einen Standardalgorithmus für zusammenhängende Komponenten an. Sie beschriften nur zusammenhängende Komponenten von 0en und zählen sie.

Siehe http://en.wikipedia.org/wiki/Connected-component_labeling für ein Beispiel. Dieser Typ von Algorithmus ist auf Bildern ziemlich einfach, benötigt jedoch eine gewisse Menge an Speicher (gleiche Größe wie Ihr Eingabearray, vom Typ short oder int). Achten Sie auf die Konnektivität: Wenn Sie eine 4-Konnektivität wählen, zählen Sie eingeschlossene Rechtecke, auch wenn einige Ecken fehlen. Aber der Algorithmus ist einfacher als bei 8-Konnektivität.

Wenn Sie komplexere eingeschlossene Formen haben können, fügen Sie einfach eine Nachbearbeitung hinzu: Zählen Sie für jede zusammenhängende Komponente die Anzahl der Pixel innerhalb der Begrenzungsbox der Komponente (wenn die beiden Zahlen gleich sind, haben Sie ein Rechteck)

2voto

zeFrenchy Punkte 6493

Ich habe eine Weile darüber nachgedacht. Ich habe folgende Methode gefunden:

1) Beseitigen Sie alle Nullen am Rand - ändern Sie ihren Wert auf 2

2) Fluten Sie die Matrix um die 2er herum

Dies hinterlässt nur eine Insel von Nullen, die nun auf Konvexität getestet werden kann. Also für jede Insel:

3) Suchen Sie nach dem Bereich des 0-Werts in X und Y - geben Sie ein potenzielles inneres Rechteck

4) Wenn das innere Rechteck eine 1 enthält ODER das äußere Rechteck eine 0 enthält, fluten Sie diese Insel mit 2en, da sie nicht konvex (und daher kein Rechteck) ist

Unter der Annahme, dass Sie einen guten "Flood Fill"-Algorithmus finden können (nicht wie meinen), sollte dies effizient sein, um den Suchraum schnell zu reduzieren.

Und jetzt für den Code (sorry, es ist 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); // sehr ineffizient - finden Sie einen besseren "Flood-Fill"-Algorithmus
        PrintMatrix ();

        // Testen Sie jede Insel mit Nullen auf Konvexität
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                if (matrix[i,j] == 0)
                {
                    if (TestIsland(i,j) == false)
                    {
                        // Diese Insel ausschließen, da sie nicht konvex ist
                        matrix[i,j] = DEAD;
                        FloodFill(DEAD);
                        PrintMatrix ();
                    }
                    else
                    {
                        // Dieses Rechteck als solches kennzeichnen
                        matrix[i,j] = RECT;
                        FloodFill(RECT);
                        PrintMatrix ();
                    }
                }
            }
        }

        // Wir sind fertig, alles, was als RECT gekennzeichnet ist, kann erweitert werden, um die Rechtecke zu erhalten
        PrintMatrix ();
    }

    // Markieren Sie alle Nullen am Rand der Matrix als 'tot'
    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;
            }
        }
    }

    // Verbreiten Sie einen Wert auf benachbarte Zellen
    static private void FloodFill (int value)
    {
        bool change_made = true; // auf true setzen, um anzufangen
        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)
    {
        // Finde den konvexen Bereich der Insel
        int x2 = x;
        int y2 = y;
        while (matrix[++x2, y] == 0);
        x2--;
        while (matrix[x,++y2] == 0);
        y2--;

        // Überprüfe die inneren Zellen (möchte alles Nullen haben)
        for (int i = x; i <= x2; i++) 
        {
            for (int j = y; j <= y2; j++) 
            {
                if (matrix[i,y] != 0)
                {
                    return false;
                }
            }
        }

        // Überprüfe die umliegenden Zellen (möchte alles Einsen haben)
        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;
    }

    // Zum Debuggen
    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();
    }
}
}

Die Ausgabe dieses Codes

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

0voto

gaganbm Punkte 2511

Das ist meiner Meinung nach ziemlich ressourcenineffizient. Weiß nicht genau darüber Bescheid.

  1. Gehe entlang einer Zeile, es sei denn, du findest mindestens 3 1en.
  2. Gehe nach unten und führe eine boolean &-Operation mit den darunterliegenden Zeilen durch --> sie sollten im Format 100..001 resultieren, wenn es sich um ein gültiges Rechteck handelt. (Vorausgesetzt, du kannst alle boolean Operationen durchführen)
  3. Du hast ein Rechteck gefunden, wenn du mindestens eine Zeile in Schritt 2 gefunden hast und schließlich alle 1en.
  4. Wiederhole mit dem nächsten Element der Reihe!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X