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)<c⋅f(n)
- g(n)=o(f(n)) significa che per tutte le costanti esiste un numero tale che per tutti , .cn0n>n0g(n)<c⋅f(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.hmn−m(n−m)2m
Se è piccolo rispetto a , l'approssimazione di Stirling e un po 'aritmetica (la prova è un esercizio!) Rivela che:2m2n
(n−m)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