Algoritmi efficienti per problemi di visibilità verticale


18

Durante la riflessione su un problema, mi sono reso conto che ho bisogno di creare un algoritmo efficiente che risolva il seguente compito:

Il problema: ci viene data una scatola quadrata bidimensionale del lato cui lati sono paralleli agli assi. Possiamo esaminarlo attraverso la cima. Tuttavia, ci sono anche segmenti orizzontali. Ogni segmento ha un numero intero coordinato ( ) e -coordinate ( ) e collega i punti e (guarda il foto sotto).m y 0 y n x 0 x 1 < x 2n ( x 1 , y ) ( x 2 , y )nmy0ynx0x1<x2n(x1,y)(x2,y)

Vorremmo sapere, per ogni segmento di unità nella parte superiore della scatola, quanto possiamo guardare in profondità all'interno della scatola se guardiamo attraverso questo segmento.

Formalmente, per , vorremmo trovare .max i : [ x , x + 1 ] [ x 1 , i , x 2 , i ] y ix{0,,n1}maxi: [x,x+1][x1,i,x2,i]yi

Esempio: dati n=9 e m=7 segmenti posizionati come nella figura sotto, il risultato è (5,5,5,3,8,3,7,8,7) . Guarda come la luce profonda può entrare nella scatola.

Sette segmenti;  la parte ombreggiata indica la regione che può essere raggiunta dalla luce

Fortunatamente per noi, sia n che m sono piuttosto piccoli e possiamo fare i calcoli off-line.

L'algoritmo più semplice per risolvere questo problema è la forza bruta: per ogni segmento attraversare l'intero array e aggiornarlo dove necessario. Tuttavia, ci dà O (mn) non molto impressionante O(mn).

Un grande miglioramento consiste nell'utilizzare un albero dei segmenti in grado di massimizzare i valori sul segmento durante la query e di leggere i valori finali. Non lo descriverò ulteriormente, ma vediamo che la complessità temporale è .O((m+n)logn)

Tuttavia, ho trovato un algoritmo più veloce:

Schema:

  1. Ordinare i segmenti in ordine decrescente di coordinate (tempo lineare usando una variazione dell'ordinamento di conteggio). Ora nota che se un segmento di unità è stato coperto da un segmento in precedenza, nessun segmento successivo può più limitare il raggio di luce che attraversa questo segmento di unità . Quindi eseguiremo uno sweep di linea dall'alto verso il basso della casella.x xyxx

  2. Ora introduciamo alcune definizioni: il segmento -unit è un segmento orizzontale immaginario sulla sweep i cui coordinate sono numeri interi e la cui lunghezza è 1. Ogni segmento durante il processo di spazzamento può essere o non contrassegnato (ovvero, un raggio di luce che va dal la parte superiore della scatola può raggiungere questo segmento) o contrassegnata (caso opposto). Si consideri un segmento -Unità con , sempre marcato. Introduciamo anche i set . Ogni set conterrà un'intera sequenza di segmenti di unità contrassegnati consecutivamente (se presenti) con un seguente non contrassegnatox x x 1 = n x 2 = n + 1 S 0 = { 0 } , S 1 = { 1 } , , S n = { n } xxxxx1=nx2=n+1S0={0},S1={1},,Sn={n} x segmento.

  3. Abbiamo bisogno di una struttura di dati in grado di operare su questi segmenti e impostare in modo efficiente. Useremo una struttura find-union estesa da un campo contenente l' indice massimo del segmento -unit (indice del segmento non contrassegnato ).x

  4. Ora possiamo gestire i segmenti in modo efficiente. Supponiamo che ora stiamo considerando l' -segmento in ordine (chiamalo "query"), che inizia in e termina in . Dobbiamo trovare tutti i segmenti di unità non contrassegnati che sono contenuti all'interno -segmento (questi sono esattamente i segmenti su cui il raggio luminoso finirà la sua strada). Faremo quanto segue: in primo luogo, troviamo il primo segmento non contrassegnato all'interno della query ( trova il rappresentante del set in cui è contenuto e ottiene l'indice massimo di questo set, che è il segmento non contrassegnato per definizione ). Quindi questo indicex 1 x 2 x i x 1 x y x x + 1 x x 2ix1x2 xix1xè all'interno della query, aggiungerlo al risultato (il risultato per questo segmento è ) e contrassegnare questo indice ( insiemi di unione contenenti e ). Quindi ripetere questa procedura fino a quando non troviamo tutti i segmenti non contrassegnati , ovvero la successiva ricerca Trova ci fornisce l'indice .yxx+1xx2

Si noti che ogni operazione di ricerca unione verrà eseguita solo in due casi: o iniziamo a considerare un segmento (che può accadere volte) o abbiamo appena contrassegnato un segmento di unità (ciò può accadere volte). Quindi la complessità complessiva è ( è una funzione inversa di Ackermann ). Se qualcosa non è chiaro, posso approfondire questo aspetto. Forse potrò aggiungere qualche foto se avrò del tempo.mxnO((n+m)α(n))α

Ora ho raggiunto "il muro". Non riesco a trovare un algoritmo lineare, anche se sembra che dovrebbe essercene uno. Quindi, ho due domande:

  • Esiste un algoritmo a tempo lineare (ovvero ) che risolve il problema di visibilità del segmento orizzontale?O(n+m)
  • In caso contrario, qual è la prova che il problema di visibilità è ?ω(n+m)

Quanto velocemente selezioni i tuoi segmenti m ?
babou,

@babou, la domanda specifica l'ordinamento di conteggio, che come dice la domanda, viene eseguito in un tempo lineare ("tempo lineare usando una variazione dell'ordinamento di conteggio").
DW

Hai provato a spazzare da sinistra a destra? Tutto ciò di cui hai bisogno è l'ordinamento su e x 2 entrambi nei passaggi O ( m ) e O ( m ) per camminare verso destra. Quindi in totale O ( m ) . x1x2O(m)O(m)O(m)
invalid_id

@invalid_id Sì, ci ho provato. Tuttavia, in questo caso la linea di sweep deve reagire in modo appropriato quando incontra l'inizio del segmento (in altre parole, aggiungi il numero uguale al coordinato del segmento al multiset), incontra la fine del segmento (rimuovi un'occorrenza di y -coordinate) e genera il segmento attivo più alto (genera il valore massimo nel multiset). Non ho sentito parlare di alcuna struttura di dati che ci consenta di farlo in tempo (ammortizzato) costante. yy
mnbvmar,

@mnbvmar forse un suggerimento stupido, ma che ne dici di un array di dimensioni , ti spazzare e fermare ogni cella O ( n ) . Per ogni cella che conosci max y e puoi inserirla nella matrice, inoltre puoi tenere traccia del massimo complessivo con una variabile. nO(n)y
invalid_id

Risposte:


1
  1. Primo sort sia e x 2 coordinate delle linee in due array separati A e B . O ( m )x1x2ABO(m)
  2. Manteniamo anche una dimensione di array di bit ausiliaria per tenere traccia dei segmenti attivi.n
  3. Inizia a spazzare da sinistra a destra:
  4. per (i=0,i<n,i++)
  5. {
  6. ..if con valore y c O ( 1 )x1=iyc O(1)
  7. .. {
  8. .... trova ( )max
  9. .... store ( ) O ( 1 )maxO(1)
  10. ..}
  11. ..if con valore y c O ( 1 )x2=iyc O(1)
  12. .. {
  13. .... trova ( )max
  14. .... store ( ) O ( 1 )maxO(1)
  15. ..}
  16. }

find ( ) può essere implementato usando un array di bit con n bit. Ora, ogni volta che rimuoviamo o aggiungiamo un elemento a L , possiamo aggiornare questo numero intero impostando un bit rispettivamente su true o false. Ora si hanno due opzioni a seconda del linguaggio utilizzato e l'assunzione n è relativamente piccolo esempio minore di l o n g l o n g i n t che è almeno 64 bit o una quantità fissa di questi numeri interi:maxnLnlonglongint

  • Ottenere il bit meno significativo a tempo costante è supportato da alcuni hardware e gcc.
  • Convertendo in un numero intero O ( 1 ) otterrai il massimo (non direttamente ma puoi derivarlo).LO(1)

So che questo è abbastanza un hack perché assume un valore massimo per e quindi n può essere visto come una costante quindi ...nn


Come vedo, supponendo che tu abbia un processore x86 a 64 bit, sei in grado di gestire solo . Cosa succede se n è nell'ordine di milioni? n64n
mnbvmar,

Quindi avrai bisogno di più numeri interi. Con due numeri interi puoi gestire fino a 128, ecc. Quindi il passo di ricerca massima di O ( m ) è nascosto nel numero di numeri interi richiesti, che potresti comunque ottimizzare se m è piccolo. Nella tua domanda hai detto che n è relativamente piccolo, quindi ho pensato che non fosse nell'ordine di milioni. A proposito long long int è sempre almeno 64 bit per definizione anche su un processore a 32 bit. nO(m)mn
invalid_id

Ovviamente è vero, lo standard C ++ definisce long long intcome tipo intero almeno a 64 bit. Tuttavia, non sarà che se è enorme e denotiamo la dimensione della parola come w (di solito w = 64 ), allora ciascuno prenderà O ( nnww=64findtempo? Quindi finiremmo conOtotale(mnO(nw). O(mnw)
mnbvmar,

Sì, sfortunatamente per grandi valori di è il caso. Quindi ora mi chiedo quanto sarà grande n nel tuo caso e se è limitato. Se è davvero nell'ordine di milioni questo hack-around non funzionerà più, ma se c w n per valori di c bassi sarà veloce e praticamente O ( n + m ) . Quindi la migliore scelta dell'algoritmo dipende, come al solito, dall'input. Ad esempio per n 100 l'ordinamento per inserzione è normalmente più veloce quindi unisci l'ordinamento, anche con un tempo di esecuzione di O ( n 2 ) rispetto anncwncO(n+m)n100O(n2) . O(nlogn)
invalid_id

3
Sono confuso dalla tua scelta di formattazione. Sai che puoi comporre il codice qui, giusto?
Raffaello

0

Non ho un algoritmo lineare, ma questo sembra essere O (m log m).

Ordina i segmenti in base alla prima coordinata e altezza. Ciò significa che (x1, l1) viene sempre prima (x2, l2) ogni volta che x1 <x2. Inoltre, (x1, l1) all'altezza y1 precede (x1, l2) all'altezza y2 ogni volta che y1> y2.

Per ogni sottoinsieme con la stessa prima coordinata, facciamo quanto segue. Lascia che il primo segmento sia (x1, L). Per tutti gli altri segmenti nel sottoinsieme: se il segmento è più lungo del primo, cambiarlo da (x1, xt) a (L, xt) e aggiungerlo al sottoinsieme L nell'ordine corretto. Altrimenti rilasciarlo. Infine, se il sottoinsieme successivo ha una prima coordinata inferiore a L, dividere quindi (x1, L) in (x1, x2) e (x2, L). Aggiungi (x2, L) al sottoinsieme successivo nell'ordine corretto. Possiamo farlo perché il primo segmento nel sottoinsieme è più alto e copre l'intervallo da (x1, L). Questo nuovo segmento può essere quello che copre (L, x2), ma non lo sapremo finché non esamineremo il sottoinsieme che ha la prima coordinata L.

Dopo aver eseguito tutti i sottoinsiemi, avremo un insieme di segmenti che non si sovrappongono. Per determinare qual è il valore Y per una data X, dobbiamo solo passare attraverso i segmenti rimanenti.

Quindi qual è la complessità qui: l'ordinamento è O (m log m). Il ciclo tra i sottoinsiemi è O (m). Una ricerca è anche O (m).

Quindi sembra che questo algoritmo sia indipendente da n.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.