Alla ricerca di un'implementazione set con ingombro di memoria ridotto


9

Sto cercando l'implementazione del tipo di dati impostato. Cioè, dobbiamo

  • mantenere un sottoinsieme dinamico (di dimensione ) dall'universo U = \ {0, 1, 2, 3, \ dots, u - 1 \} di dimensione u conSnU={0,1,2,3,,u1}u
  • operazioni insert(x)(aggiungi un elemento xa S ) e find(x)(controlla se l'elemento xè un membro di S ).

Non mi importa di altre operazioni. Per l'orientamento, nelle applicazioni con cui sto lavorando abbiamo u1010 .

Conosco implementazioni che forniscono entrambe le operazioni nel tempo O(1) , quindi mi preoccupo principalmente delle dimensioni della struttura dei dati. Mi aspetto miliardi di voci, ma voglio evitare il più possibile lo scambio.

Sono disposto a sacrificare l'autonomia, se necessario. Il runtime ammortizzato di O(logn) è ciò che posso ammettere; i runtime previsti o runtime in ω(logn) non sono ammissibili.

Un'idea che ho è che se S può essere rappresentato come un'unione di intervalli [xmin, xmax], saremo in grado di risparmiare sulle dimensioni di archiviazione con il prezzo di un certo calo delle prestazioni. Inoltre, sono possibili alcuni altri schemi di dati, come [0, 2, 4, 6].

Potresti indicarmi strutture di dati che possono fare qualcosa del genere?



In che modo il numero di elementi entra nell'immagine? Cioè, cosa succede se un elemento è inserito e ci sono già ? nn
vonbrand,

@vonbrand - nè la dimensione dell'insieme S. Può aumentare con ogni insert, oppure può rimanere lo stesso se l'elemento xè già nell'insieme.
HEKTO,

3
Riesci ad accettare una piccola probabilità di falsi positivi? In tal caso, un filtro di fioritura potrebbe essere l'ideale: en.wikipedia.org/wiki/Bloom_filter
Joe

1
@AlekseyYakovlev, il tasso di falsi positivi di un filtro bloom non ha nulla a che fare con la dimensione dell'universo (solo con il numero di funzioni hash , la dimensione della struttura dati e il numero di elementi ), ma se lo è davvero vicino a (dire per un piccolo costante ), si sarà difficile fare meglio di una semplice vettore di bit credo che, con la sola totali bit di spazio. kmnnuu=ncccn
Joe,

Risposte:


8

La risposta di Joe è estremamente buona e ti dà tutte le parole chiave importanti.

Dovresti essere consapevole del fatto che la ricerca sintetica della struttura dei dati è ancora in una fase iniziale e molti dei risultati sono in gran parte teorici. Molte delle strutture di dati proposte sono piuttosto complesse da implementare, ma la maggior parte della complessità è dovuta al fatto che è necessario mantenere la complessità asintotica sia sulla dimensione dell'universo che sul numero di elementi memorizzati. Se uno di questi è relativamente costante, gran parte della complessità scompare.

Se la raccolta è semi-statica (vale a dire, gli inserti sono rari o almeno a basso volume), allora vale sicuramente la pena considerare una struttura di dati statici di facile implementazione (lo sdarray di Sadakane è una buona scelta) insieme a un aggiornamento cache. Fondamentalmente, si registrano gli aggiornamenti in una struttura di dati tradizionale (ad esempio B-tree, trie, tabella hash) e si aggiorna periodicamente in blocco la struttura di dati "principale". Questa è una tecnica molto popolare nel recupero delle informazioni, poiché gli indici invertiti hanno molti vantaggi per la ricerca ma sono difficili da aggiornare sul posto. In questo caso, per favore fatemi sapere in un commento e modificherò questa risposta per darvi alcuni suggerimenti.

Se gli inserti sono più frequenti, allora suggerisco un hashing succinto. L'idea di base è abbastanza semplice da spiegare qui, quindi lo farò.

Quindi il risultato teorico delle informazioni di base è che se stai memorizzando elementi da un universo di u elementi e non ci sono altre informazioni (ad es. Nessuna correlazione tra gli elementi), allora hai bisogno di bit per memorizzarlo. (Tutti i logaritmi sono base-2 se non diversamente specificato.) Sono necessari molti bit. Non c'è modo di aggirarlo.nulog(un)+O(1)

Ora un po 'di terminologia:

  • Se disponi di una struttura di dati che può memorizzare i dati e supportare le tue operazioni in bit di spazio, la chiamiamo struttura di dati implicita .log(un)+O(1)
  • Se disponi di una struttura di dati in grado di archiviare i dati e supportare le tue operazioni in bit di spazio, chiamiamo questo una struttura di dati compatta . Si noti che in pratica ciò significa che il relativo sovraccarico (rispetto al minimo teorico) è all'interno di una costante. Potrebbe essere il 5% di spese generali, o il 10% di spese generali o 10 volte di spese generali.log(un)+O(log(un))=(1+O(1))log(un)
  • Se disponi di una struttura di dati in grado di memorizzare i dati e supportare le tue operazioni in bit di spazio, chiamiamo questo una struttura di dati sintetica .log(un)+o(log(un))=(1+o(1))log(un)

La differenza tra succinto e compatto è la differenza tra little-oh e big-oh. Ignorando la cosa dal valore assoluto per un momento ...

  • g(n)=O(f(n)) significa che esiste una costante e un numero tale che per tutti , .cn0n>n0g(n)<cf(n)
  • g(n)=o(f(n)) significa che per tutte le costanti esiste un numero tale che per tutti , .cn0n>n0g(n)<cf(n)

Informalmente, big-oh e little-oh sono entrambi "all'interno di un fattore costante", ma con big-oh la costante viene scelta per te (dal progettista dell'algoritmo, dal produttore della CPU, dalle leggi della fisica o altro), ma con poco -oh scegli tu stesso la costante e può essere piccola quanto vuoi . Per dirla in altro modo, con strutture di dati sintetiche, il relativo sovraccarico diventa arbitrariamente piccolo all'aumentare della dimensione del problema.

Naturalmente, la dimensione del problema potrebbe dover diventare enorme per realizzare il relativo sovraccarico che si desidera, ma non si può avere tutto.

OK, con quello sotto le nostre cinture, mettiamo alcuni numeri sul problema. Supponiamo che le chiavi siano numeri interi -bit (quindi la dimensione dell'universo è ) e vogliamo memorizzare di questi numeri interi. Supponiamo di poter disporre magicamente una tabella di hash idealizzata con piena occupazione e senza sprechi, quindi abbiamo bisogno di esattamente slot di hash.n2n2m2m

Un'operazione di ricerca esegue l'hashing della chiave -bit, maschera bit per trovare gli slot hash e quindi controlla se il valore nella tabella corrisponde alla chiave. Fin qui tutto bene.nm

Tale tabella hash utilizza bit. Possiamo fare di meglio?n2m

Supponiamo che la funzione hash sia invertibile. Quindi non è necessario memorizzare l'intera chiave in ogni slot hash. La posizione dello slot hash ti dà bit del valore hash, quindi se hai memorizzato solo i bit rimanenti , puoi ricostruire la chiave da quelle due informazioni (la posizione dello slot hash e il valore memorizzato lì). Quindi occorrerebbero solo bit di memoria.hmnm(nm)2m

Se è piccolo rispetto a , l'approssimazione di Stirling e un po 'aritmetica (la prova è un esercizio!) Rivela che:2m2n

(nm)2m=log(2n2m)+o(log(2n2m))

Quindi questa struttura di dati è concisa.

Tuttavia, ci sono due catture.

La prima cattura sta costruendo funzioni hash invertibili "buone". Fortunatamente, questo è molto più semplice di quanto sembri; i crittografi fanno sempre funzioni invertibili, solo loro li chiamano "cyphers". Ad esempio, è possibile basare una funzione hash su una rete Feistel, che è un modo semplice per costruire funzioni hash invertibili da funzioni hash non invertibili.

Il secondo problema è che i veri tavoli hash non sono l'ideale, grazie al paradosso del compleanno. Quindi ti consigliamo di utilizzare un tipo più sofisticato di tabella hash che ti avvicini alla piena occupazione senza versamenti. L'hashing del cuculo è perfetto per questo, in quanto ti consente di avvicinarti arbitrariamente all'ideale in teoria e abbastanza vicino nella pratica.

L'hash del cuculo richiede più funzioni di hash e richiede che i valori negli slot di hash siano etichettati con la funzione di hash utilizzata. Pertanto, se si utilizzano quattro funzioni hash, ad esempio, è necessario memorizzare altri due bit in ogni slot hash. Questo è ancora sintetico man mano che cresce, quindi in pratica non è un problema, e batte ancora la memorizzazione di chiavi intere.m

Oh, potresti anche voler guardare gli alberi di Van Emde Boas.

PIÙ PENSIERI

Se è da qualche parte intorno a , allora è approssimativamente , quindi (ancora una volta) supponendo che non ci sia ulteriore correlazione tra i valori, in pratica non puoi fare nulla meglio di un po 'vettoriale. Noterai che la soluzione di hash sopra degenera efficacemente in quel caso (finisci per memorizzare un bit per slot hash), ma è più economico usare la chiave come indirizzo piuttosto che usare una funzione hash.nu2log(un)u

Se è molto vicino ad , tutte le strutture di dati succinta letteratura vi consiglia di invertire il senso del dizionario. Memorizza i valori che non si verificano nel set. Tuttavia, ora è necessario supportare efficacemente l'operazione di eliminazione e per mantenere un comportamento conciso, è anche necessario essere in grado di ridurre la struttura dei dati man mano che vengono aggiunti "elementi". Espandere una tabella hash è un'operazione ben compresa, ma non lo è contrarla.nu


Ciao, come per il secondo paragrafo della tua risposta, mi aspetto che ogni chiamata a insertsarà accompagnata da una chiamata findcon lo stesso argomento. Quindi, se i findritorni true, saltiamo semplicemente il insert. Quindi, la frequenza delle findchiamate è più della frequenza delle insertchiamate, anche quando nsi avvicina u, quindi le insertchiamate diventano molto rare.
HEKTO,

Ma ci si aspetta per avvicinarsi a n alla fine? un
Pseudonimo del

Nel mondo reale n sta crescendo fino a quando non ti raggiunge, tuttavia non possiamo prevedere se accadrà o no. La struttura dei dati dovrebbe funzionare bene per qualsiasin <= u
HEKTO

Giusto. Quindi è corretto affermare che non conosciamo una singola struttura di dati che sia succinta (nel senso precedente) e che raggiunga questo obiettivo nell'intero intervallo di . Penso che si sta andando a voler una struttura di dati sparsi quandon<u, quindi passare a una fitta uno (ad esempio un vettore di bit), quandonè di circaunun<un , quindi una struttura di dati sparso con un senso invertito senè vicino au. u2nu
Pseudonimo del

5

Sembra che tu voglia una struttura dati concisa per il problema dell'appartenenza dinamica .

Ricordiamo che una struttura di dati concisa è una struttura per la quale il requisito di spazio è "vicino" al limite inferiore teorico delle informazioni, ma a differenza di una struttura di dati compressi, consente comunque query efficienti.

Il problema dell'iscrizione è esattamente quello che descrivi nella tua domanda:

SnU={0,1,2,3,...,u-1}u

  • find(x)xS
  • insert(x)xS
  • delete(x)xS

Se findè supportata solo l' operazione, questo è il problema dell'appartenenza statica . Se uno inserto deletesono supportati, ma non entrambi, si chiama semi-dinamico e se tutte e tre le operazioni sono supportate, si chiama problema di appartenenza dinamica .

Tecnicamente, penso che tu abbia richiesto solo una struttura di dati per il problema dell'appartenenza semi-dinamica, ma non conosco alcuna struttura di dati che sfrutti questo vincolo e soddisfi anche i tuoi altri requisiti. Tuttavia, ho il seguente riferimento:

Nel Teorema 5.1 dell'articolo Appartenenza a tempo costante e spazio quasi minimo , Brodnik e Munro danno il seguente risultato:

O(B)

B=log(un)

L'idea di base è che hanno diviso ricorsivamente l'universo in gamme di dimensioni scelte con cura, quindi sembra anche che le tecniche potrebbero essere sulla falsa riga a cui stai pensando.

un


1
L'estratto di carta di Brodnik e Munro non dice nulla sugli inserti. Ma il loro risultato è quello che possiamo aspettarci, giusto? Se n = u/2, quindi lo spazio necessario è massimo.
HEKTO,

@AlekseyYakovlev In realtà non menzionano il caso dinamico in astratto, ma il teorema che si occupa del caso dinamico è citato nella mia risposta (dalla sezione 5).
Joe,
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.