Effiziente Algorithmen für vertikale Sichtbarkeitsprobleme


18

Während ich über ein Problem nachdachte, wurde mir klar, dass ich einen effizienten Algorithmus erstellen muss, der die folgende Aufgabe löst:

Das Problem: Wir erhalten einen zweidimensionalen quadratischen Kasten der Seite dessen Seiten parallel zu den Achsen sind. Wir können es von oben untersuchen. Es gibt jedoch auch horizontale Segmente. Jedes Segment hat eine ganzzahlige Koordinate ( ) und Koordinaten ( ) und verbindet die Punkte und (siehe Bild unten).m y 0 y n x 0 x 1 < x 2n ( x 1 , y ) ( x 2 , y )nmy0ynx0x1<x2n(x1,y)(x2,y)

Wir möchten für jedes Einheitensegment oben auf der Box wissen, wie tief wir vertikal in die Box schauen können, wenn wir durch dieses Segment schauen.

Formal möchten wir für .x{0,,n1}maxi: [x,x+1][x1,i,x2,i]yi

Beispiel: Bei und Segmenten, die sich wie im Bild unten befinden, ist das Ergebnis . Schauen Sie sich an, wie tiefes Licht in die Box gelangen kann.n=9m=7(5,5,5,3,8,3,7,8,7)

Sieben Segmente;  Der schattierte Teil gibt den Bereich an, der durch Licht erreicht werden kann

Zum Glück für uns beide und sind recht klein und wir können die Berechnungen off-line tun.nm

Der einfachste Algorithmus zur Lösung dieses Problems ist Brute-Force: Durchlaufen Sie für jedes Segment das gesamte Array und aktualisieren Sie es bei Bedarf. Es gibt uns jedoch nicht sehr beeindruckend .O(mn)

Eine große Verbesserung besteht darin, einen Segmentbaum zu verwenden, der in der Lage ist, Werte auf dem Segment während der Abfrage zu maximieren und die Endwerte zu lesen. Ich werde es nicht weiter beschreiben, aber wir sehen, dass die Zeitkomplexität .O((m+n)logn)

Ich habe mir jedoch einen schnelleren Algorithmus ausgedacht:

Gliederung:

  1. Sortieren Sie die Segmente in absteigender Reihenfolge der Koordinate (lineare Zeit unter Verwendung einer Variation der Zählsortierung). Es ist nun zu beachten, dass, wenn ein Einheits-Segment zuvor von einem Segment abgedeckt wurde, kein nachfolgendes Segment den Lichtstrahl mehr durch dieses Einheits-Segment binden kann. Dann fegen wir eine Linie von oben nach unten.yxx

  2. Nun führen sie einige Definitionen: -Einheit Segment ist ein imaginäres horizontales Segment auf der Schleife , deren - Koordinaten ganze Zahlen sind und deren Länge ist 1. Jedes Segment während des Durchlaufprozesses kann entweder unmarkiert (das heißt, ein Lichtstrahl geht aus dem Der obere Rand der Box kann dieses Segment erreichen) oder markiert sein (gegenüberliegender Fall). Stellen Sie sich ein Einheitensegment vor, bei dem , immer unmarkiert ist. wir uns auch die Mengen . Jeder Satz enthält eine ganze Sequenz von aufeinanderfolgenden markierten Einheitensegmenten (falls vorhanden) mit folgenden nicht markiertenxxxx1=nx2=n+1S0={0},S1={1},,Sn={n} x Segment.

  3. Wir brauchen eine Datenstruktur, die in der Lage ist, diese Segmente und Mengen effizient zu bearbeiten. Wir werden eine Find-Union-Struktur verwenden, die um ein Feld erweitert ist, das den maximalen unit-Segmentindex (Index des nicht markierten Segments) enthält.x

  4. Jetzt können wir die Segmente effizient handhaben. Nehmen wir an, wir betrachten jetzt das te Segment in der Reihenfolge (nennen es "Abfrage"), das in beginnt und in endet . Wir müssen alle nicht markierten Einheitssegmente finden, die innerhalb des ten Segments enthalten sind (dies sind genau die Segmente, auf denen der Lichtstrahl seinen Weg beenden wird). Wir werden Folgendes tun: Erstens finden wir das erste nicht markierte Segment in der Abfrage ( Finden Sie den Repräsentanten der Menge, in der enthalten ist, und erhalten Sie den maximalen Index dieser Menge, die per Definition das nicht markierte Segment ist ). Dann wird dieser Indexix1x2 xix1xWenn Sie sich in der Abfrage befinden, fügen Sie sie zum Ergebnis hinzu (das Ergebnis für dieses Segment ist ) und markieren Sie diesen Index ( Union- Mengen mit und ). Dann wiederholen Sie diesen Vorgang , bis wir alle finden nicht markierten Segmente, das heißt, neben Suche Abfrage gibt uns Index .yxx+1xx2

Beachten Sie, dass jede Find-Union-Operation nur in zwei Fällen ausgeführt wird: Entweder beginnen wir mit der Betrachtung eines Segments (was mal vorkommen kann), oder wir haben gerade ein unit-Segment markiert (dies kann mal vorkommen). Die Gesamtkomplexität ist also ( ist eine inverse Ackermann-Funktion ). Wenn etwas nicht klar ist, kann ich darauf näher eingehen. Vielleicht kann ich ein paar Bilder hinzufügen, wenn ich Zeit habe.mxnO((n+m)α(n))α

Jetzt erreichte ich "die Mauer". Ich kann mir keinen linearen Algorithmus ausdenken, obwohl es einen geben sollte. Ich habe also zwei Fragen:

  • Gibt es einen linearen Zeitalgorithmus (d. H. ), der das Sichtbarkeitsproblem für horizontale Segmente löst?O(n+m)
  • Wenn nicht, was ist der Beweis, dass das Sichtbarkeitsproblem ?ω(n+m)

Wie schnell sortieren Sie Ihre m Segmente?
Babou

@babou, die Frage gibt die Zählsortierung an, die, wie die Frage sagt, in linearer Zeit abläuft ("lineare Zeit unter Verwendung einer Variation der Zählsortierung").
DW

Haben Sie versucht, von links nach rechts zu kehren? Sie müssen in Schritten von und nach und sortieren , um nach rechts zu gehen. Also insgesamt . x1x2O(m)O(m)O(m)
invalid_id

@invalid_id Ja, ich habe es versucht. In diesem Fall muss die Sweep-Linie jedoch angemessen reagieren, wenn sie auf den Beginn des Segments trifft (mit anderen Worten, addieren Sie die Zahl, die der Koordinate des Segments entspricht , zum Multiset) und auf das Ende des Segments (entfernen Sie ein Vorkommen von -Koordinate) und geben das höchste aktive Segment aus (Maximalwert im Multiset ausgeben). Ich habe nichts von Datenstrukturen gehört, die uns dies in (amortisierter) konstanter Zeit erlauben. yy
Mnbvmar

@mnbvmar vielleicht ein blöder Vorschlag, aber wie wäre es mit einem Array der Größe , das Sie fegen und stoppen jede Zelle . Für Evry Zelle wissen Sie max und Sie können es in der Matrix eingeben, außerdem Sie den Überblick über die Gesamt maximal mit einer Variablen halten können. nO(n)y
invalid_id

Antworten:


1
  1. Erste nach sowohl und Koordinaten der Linien in zwei getrennten Arrays und . x1x2ABO(m)
  2. Wir behalten auch eine Hilfs-Bit-Array-Größe , um die aktiven Segmente zu verfolgen.n
  3. Fange an von links nach rechts zu kehren:
  4. für(i=0,i<n,i++)
  5. {
  6. ..wenn mit Wertx1=iyc O(1)
  7. .. {
  8. .... find ( )max
  9. .... store ( )maxO(1)
  10. ..}
  11. ..wenn mit Wertx2=iyc O(1)
  12. .. {
  13. .... find ( )max
  14. .... store ( )maxO(1)
  15. ..}
  16. }

find ( ) kann mit einem Bit-Array mit Bits implementiert werden . Wenn wir nun ein Element zu hinzufügen oder entfernen, können wir diese Ganzzahl aktualisieren, indem wir ein Bit auf true bzw. false setzen. Nun haben Sie zwei Möglichkeiten, abhängig von der verwendeten Programmiersprache und der Annahme, dass relativ klein ist, dh kleiner als was mindestens 64 Bit oder eine feste Anzahl dieser ganzen Zahlen ist:maxnLnlonglongint

  • Das niedrigstwertige Bit in konstanter Zeit zu erhalten, wird von einigen Hardware- und GCC-Komponenten unterstützt.
  • Durch Konvertieren von in eine Ganzzahl Sie das Maximum (nicht direkt, aber Sie können es ableiten).LO(1)

Ich weiß, dass dies ein ziemlicher Hack ist, da es einen Maximalwert für annimmt und daher dann als Konstante angesehen werden kann ...nn


Angenommen, Sie haben einen 64-Bit-x86-Prozessor, dann können Sie nur . Was ist, wenn in der Größenordnung von Millionen liegt? n64n
Mnbvmar

Dann brauchen Sie mehr ganze Zahlen. Mit zwei Ganzzahlen können Sie mit bis zu 128 usw. umgehen. Der Schritt zum Auffinden des -Maximums ist also in der Anzahl der erforderlichen Ganzzahlen verborgen, die Sie möglicherweise noch optimieren, wenn klein ist. Sie haben in Ihrer Frage erwähnt, dass relativ klein ist, also habe ich angenommen, dass es nicht in der Größenordnung von Millionen liegt. Übrigens ist long long int auch auf einem 32-Bit-Prozessor per Definition immer mindestens 64 Bit. nO(m)mn
invalid_id

Natürlich ist es wahr, dass der C ++ - Standard long long intmindestens einen 64-Bit-Integer-Typ definiert. Allerdings wird es nicht sein , dass , wenn sehr groß ist und bezeichnen wir die Wortgröße als ( in der Regel ), dann wird jeder nehmen Zeit? Dann hätten wir insgesamt . nww=64findO(nw)O(mnw)
Mnbvmar

Ja, leider ist das für große Werte von der Fall. Jetzt frage ich mich also, wie groß in Ihrem Fall sein wird und ob es begrenzt ist. Wenn es tatsächlich in der Größenordnung von Millionen liegt, funktioniert dieses Herumhacken nicht mehr, aber wenn für niedrige Werte ist, ist es schnell und praktisch . Die beste Algorithmusauswahl ist also wie üblich eingabeabhängig. Zum Beispiel ist für Insertion Sort normalerweise schneller als Merge Sort, sogar mit einer Laufzeit von Vergleich zu . nncwncO(n+m)n100O(n2)O(nlogn)
invalid_id

3
Ihre Wahl der Formatierung verwirrt mich. Sie wissen, dass Sie hier Code eingeben können, oder?
Raphael

0

Ich habe keinen linearen Algorithmus, aber dieser scheint O (m log m) zu sein.

Sortieren Sie die Segmente nach der ersten Koordinate und Höhe. Dies bedeutet, dass (x1, l1) immer vor (x2, l2) steht, wenn x1 <x2 ist. Auch (x1, l1) in Höhe y1 kommt vor (x1, l2) in Höhe y2, wenn y1> y2.

Für jede Teilmenge mit derselben ersten Koordinate gehen wir wie folgt vor. Das erste Segment sei (x1, L). Für alle anderen Segmente in der Teilmenge: Wenn das Segment länger als das erste ist, ändern Sie es von (x1, xt) in (L, xt) und fügen Sie es in der richtigen Reihenfolge zur L-Teilmenge hinzu. Sonst lass es fallen. Wenn die erste Koordinate der nächsten Teilmenge kleiner als L ist, teilen Sie (x1, L) in (x1, x2) und (x2, L) auf. Addieren Sie die (x2, L) zur nächsten Teilmenge in der richtigen Reihenfolge. Wir können dies tun, weil das erste Segment in der Teilmenge höher ist und den Bereich von (x1, L) abdeckt. Dieses neue Segment kann dasjenige sein, das (L, x2) abdeckt, aber das werden wir nicht wissen, bis wir uns die Teilmenge ansehen, die die erste Koordinate L hat.

Nachdem wir alle Teilmengen durchlaufen haben, werden wir eine Reihe von Segmenten haben, die sich nicht überlappen. Um den Y-Wert für ein gegebenes X zu bestimmen, müssen wir nur die verbleibenden Segmente durchlaufen.

Was ist die Komplexität hier: Die Sortierung ist O (m log m). Das Durchlaufen der Teilmengen ist O (m). Ein Lookup ist auch O (m).

Es scheint also, dass dieser Algorithmus unabhängig von n ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.