Rimozione dei duplicati in modo efficiente e con un sovraccarico di memoria insufficiente


9

Voglio filtrare in modo efficiente un elenco di numeri interi per i duplicati in modo che solo il set risultante debba essere memorizzato.

In un modo questo può essere visto:

  • abbiamo un intervallo di numeri interi S={1,,N} conN grande (diciamo240 )
  • abbiamo una funzione f:SS con, presumibilmente, molte collisioni (le immagini sono distribuite uniformemente in S )
  • dobbiamo quindi memorizzare , ovvero { f ( x ) | x S }f[S]{f(x)|xS}

Ho una stima (probabilistica) abbastanza accurata di cosa è e può quindi allocare in anticipo le strutture di dati (diciamo | f [ S ] |2 30 ).|f[S]||f[S]|230

Ho avuto alcune idee, ma non sono sicuro di quale sarebbe l'approccio migliore:

  • un bitset è fuori discussione perché il set di input non si adatta alla memoria.
  • una tabella hash, ma (1) richiede un sovraccarico di memoria, diciamo il 150% di e (2) la tabella deve essere esplorata quando costruita, il che richiede tempo aggiuntivo a causa del sovraccarico di memoria.|f[S]|
  • un ordinamento "al volo", preferibilmente con O(N) (ordinamento non comparativo). A tal proposito, non sono sicuro di quale sia la differenza principale tra il bucket bucket e il flashsort .
  • un array semplice con un albero di ricerca binario, ma richiede tempo .O(Nlog|f[S]|)
  • magari usando filtri Bloom o una struttura dati simile potrebbe essere utile in un rilassamento (con falsi positivi) del problema.

Alcune domande su StackOverflow sembrano affrontare questo tipo di cose ( /programming/12240997/sorting-array-in-on-run-time , /programming/3951547/java -array-trovando-duplicati ), ma nessuno sembra corrispondere ai miei requisiti.


2
Devi enumerare f [S] (qualunque esso sia) o essere in grado di dire rapidamente se c'è qualche x?
Gilles 'SO- smetti di essere malvagio' il

@Gilles: credo che, poiché non è possibile trovare una struttura ovvia in f [S], le due soluzioni siano equivalenti.
doc,

I tuoi numeri non si sommano. L'immagine atteso di una funzione a caso su un dominio di dimensioni è approssimativamente ( 1 - 1 / e ) N . Un altro problema è che passare attraverso 2 56 impiegherà troppo tempo a meno che tu non abbia un supercomputer o un cluster di grandi dimensioni a tua disposizione. N(11/e)N256
Yuval Filmus,

1
Il tempo per l'albero di ricerca binario sarebbe , chein praticapotrebbe essere o meno vicino a O ( N log N ) ma è comunque più preciso. O(Nlog|f[S]|)O(NlogN)
jmad,

1
Con , un algoritmo temporale lineare non sarebbe anche proibitivo? (Dai miei calcoli, anche se consideri un elemento di S in 1 nano-secondo, ti occorrerebbero ben 2 anni!). N256S
Aryabhata,

Risposte:


1

Perché non bin and chain?

L'idea è di memorizzare numeri interi positivi rappresentabili con bit in un array A di 2 k voci che rappresentano intervalli di valori: la voce A [ y ] , y 0 , rappresenta l'intervallo [ 2 m y , 2 m ( y + 1 ) - 1 ] . Per ogni 1 x < 2 n possiamo scrivere x = 2 m yn=k+mA2kA[y]y0[2my,2m(y+1)1]1x<2n dove y ha k bit e z ha m bit. Prova a memorizzare z (non x !) Nella posizione y :x=2my+zykzmzxy

  • Quando già, non fare nulla: x è un duplicato.A[y]=zx

  • Quando non è inizializzato, memorizzare z in A [ y ] .A[y]zA[y]

  • Altrimenti, memorizza un indice in un array separato utilizzato per concatenare le (che si sono scontrate con y ) negli elenchi collegati. Dovrai cercare in modo lineare nell'elenco diretto da A [ y ] e, a seconda di ciò che la ricerca scopre, potenzialmente inserisci z nell'elenco.zyA[y]z

Alla fine, è facile da recuperare ripetendo ciclicamente le voci inizializzate di A e - semplicemente concatenando due stringhe di bit - riassemblando ciascuna z trovata nella posizione y (direttamente o all'interno di una catena a cui si fa riferimento) nell'originale valore x = 2 m y + z .f(S)Azyx=2my+z

Quando la distribuzione è vicina all'uniforme e supera N , non ci sarà molto concatenamento (questo può essere valutato nei modi consueti) e le catene tenderanno ad essere corte. Quando la distribuzione non è uniforme, l'algoritmo funziona ancora, ma può raggiungere una tempistica quadratica. Se questa è una possibilità, usa qualcosa di più efficiente delle catene (e paga un po 'di spese generali per lo stoccaggio).2kN

La memoria necessaria è al massimo bit per A e 2 2 k bit per le catene (assumendo m k2nA22kmk ). Questo è esattamente lo spazio necessario per memorizzare valori di n bit ciascuno. Se sei sicuro dell'uniformità, puoi sottoallocare lo spazio di archiviazione per le catene. Se la non uniformità è una possibilità, è possibile che si desideri aumentare k e sostenere completamente la memorizzazione a catena.2knk

Un modo alternativo di pensare a questa soluzione è che tratta di una tabella hash con una funzione hash particolarmente bella (prendi i bit più significativi) e, per questo, abbiamo solo bisogno di memorizzare i bit m = n - k meno significativi in la tavola.km=nk

Esistono modi per sovrapporre l'archiviazione per le catene con l'archiviazione per A ma non sembra preoccuparsi, perché non risparmierebbe molto spazio (supponendo che sia molto più piccolo di k ) e renderebbe più difficile lo sviluppo del codice, debug e manutenzione.mk


1
Penso che il penultimo paragrafo sia quello centrale qui, e probabilmente dovrebbe essere in cima (come idea). Non conosco il termine "bin and chain" (anche se ha senso dopo aver letto il post). Questa idea può essere estesa ai tentativi .
Raffaello

Quindi, questo è su input scarsamente distribuiti. Non vedo come sia efficiente. Θ(n2)
einpoklum,

@einpoklum Questa risposta descrive esplicitamente le condizioni in cui la soluzione è efficiente.
whuber
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.