Inizializza l'array in un tempo costante ammortizzato - come si chiama questo trucco?


13

Esiste questa struttura di dati che scambia le prestazioni dell'accesso alla matrice contro la necessità di iterare su di essa quando viene cancellata. Mantenete un contatore di generazione con ogni voce e anche un contatore di generazione globale. L'operazione "cancella" aumenta il contatore di generazione. Ad ogni accesso, si confrontano i contatori di generazione locale rispetto a quelli globali; se differiscono, il valore viene trattato come "pulito".

Questo è emerso in questa risposta su Stack Overflow di recente, ma non ricordo se questo trucco ha un nome ufficiale. Vero?

Un caso d'uso è l'algoritmo di Dijkstra se solo un piccolo sottoinsieme dei nodi deve essere rilassato e se ciò deve essere fatto ripetutamente.


2
Trucco interessante, ma ha un bel sovraccarico. Quindi mi chiedo quali usi hanno cancellato l'array come un'operazione così comune che paga il prezzo? (Domanda sincera!)
Joachim Sauer,

@JoachimSauer: modificato.
krlmlr,

Sembra molto costoso, in generale, sia per l'utilizzo della memoria che per il costo degli accessi. Il caso d'uso di questa tecnica deve essere molto specifico.
Martin York,

3
@Joachim: è utilizzato per cancellare rapidamente i buffer per il rendering, approssimativamente. Hanno solo un "bit chiaro" per 64kb o qualcosa del genere.
DeadMG

3
@ user946850 "ammortizzato" significa che puoi dimostrare che un'operazione costosa si verifica abbastanza raramente nell'immagine complessiva da non contribuire più di quanto O (1)

Risposte:


2

L'approccio sopra menzionato richiede che ogni cella sia in grado di contenere un numero abbastanza grande da contenere il numero di volte in cui l'array potrebbe dover essere reinizializzato, il che è una sostanziale penalità di spazio. Se uno slot è in grado di contenere almeno un valore che non sarà mai scritto in modo legittimo, si può evitare di avere qualsiasi altra penalità di spazio (non costante) a scapito dell'aggiunta di una O(Wlg(N))penalità di tempo, dove Wè il numero di slot di array distinti scritti tra operazioni di cancellazione ed Nè la dimensione dell'array. Ad esempio, supponiamo che uno memorizzi numeri interi da -2.147.483.647 a 2.147.483.647 (ma mai -2.147.483.648) e che si desideri che gli elementi di array vuoti vengano letti come zero. Inizia riempiendo l'array con -2.147.483.648 (chiama quel valoreB). Durante la lettura di uno slot di array per l'applicazione, segnalare un valore pari Ba zero. Prima di scrivere scanalatura matrice I, controlla se si tiene Bin caso affermativo ed Iè maggiore di uno, memorizzare uno zero a fessura I/4dopo aver eseguito un controllo simile a quella posizione (e, se ritenuto B, I/16ecc).

Per cancellare l'array, iniziare con Iuguale a 0 o 1, a seconda della base dell'array (l'algoritmo come descritto funzionerà per entrambi). Quindi ripetere la seguente procedura: Se l'elemento Iè B, incrementare Ie, in tal caso, si ottiene un multiplo di quattro, dividere per quattro (terminare se la divisione produce un valore di 1); in caso Icontrario B, archiviare Blì e moltiplicare Iper quattro (se Iinizia da zero, la moltiplicazione per quattro lo lascerà zero, ma poiché l'articolo 0 sarà vuoto, Iverrà incrementato).

Si noti che si potrebbe sostituire la costante "quattro" sopra con altri numeri, con valori più grandi che generalmente richiedono meno tag di lavoro, ma valori più piccoli generalmente richiedono meno schiarimenti di lavoro; poiché gli slot di array che sono etichettati devono essere cancellati, un valore di tre o quattro è quasi sicuramente ottimale; poiché il valore quattro è sicuramente vicino all'ottimale, è migliore di due o otto ed è più conveniente di qualsiasi altro numero, sembrerebbe la scelta più ragionevole.


È sufficiente disporre di un contatore di versioni in grado di ospitare un numero sufficiente di ripristini sequenziali prima che tutte le celle vengano aggiornate con valori nuovi. In pratica un byte potrebbe essere sufficiente, o anche meno in loop più stretti.
9000,

@ 9000: il codice che si basa su un simile comportamento può essere fragile, soprattutto perché l'unica ragione per utilizzare un approccio "pseudo-chiaro" (invece di cancellare semplicemente l'array) sarebbe se l'insieme di elementi che avrebbe bisogno essere cancellati era in genere piccolo e variabile - una coppia di condizioni che cospiravano per aumentare la probabilità che un oggetto potesse essere usato, "cancellato", e quindi non toccato per un tempo arbitrariamente lungo. Si potrebbe considerare la scansione dell'array e la cancellazione fisica di tutti i vecchi slot quando il contatore sta per finire, ma ...
Supercat

1
... se il valore di avvolgimento del contatore è costante, la quantità media di lavoro per ciascuna operazione di cancellazione della matrice sarebbe O (N), con N come dimensione della matrice. Non che una cosa del genere potrebbe non essere utile nella pratica, poiché un'implementazione O (N) accelerata di un fattore di 65.536 sarebbe ancora O (N), ma sarebbe anche 65.536 volte più veloce di quella non migliorata . Per inciso, i casi in cui questi approcci sarebbero utili potrebbero anche trarre vantaggio dall'uso di una struttura di dati a matrice sparsa, che potrebbe utilizzare lo spazio O (AlgN) per contenere un array con un array di dimensioni N con elementi A non vuoti.
supercat

1

Lo chiamerei "reinizializzazione delle celle dell'array pigro", ma non sembra avere alcun nome stabilito (vale a dire, il nome è ampiamente utilizzato).

L'algoritmo è intelligente, ma molto specializzato e applicabile in un'area molto ristretta.


1

Credo che sia un caso speciale di memoization , tranne in questo caso, i "memo" implicitamente "invecchiano" con ogni incremento del contatore globale. Immagino una specie di "memoization all'indietro".

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.