Perché Python usa la tabella hash per implementare dict, ma non Red-Black Tree?
Qual è la chiave? Prestazione?
Perché Python usa la tabella hash per implementare dict, ma non Red-Black Tree?
Qual è la chiave? Prestazione?
Risposte:
Questa è una risposta generale, non specifica per Python.
| Hash Table | Red-Black Tree |
-------+-------------+---------------------+
Space | O(n) : O(n) | O(n) : O(n) |
Insert | O(1) : O(n) | O(log n) : O(log n) |
Fetch | O(1) : O(n) | O(log n) : O(log n) |
Delete | O(1) : O(n) | O(log n) : O(log n) |
| avg :worst | average : worst |
Il problema con le tabelle hash è che gli hash possono scontrarsi. Esistono vari meccanismi per risolvere le collisioni, ad esempio indirizzamento aperto o concatenamento separato. Il caso peggiore è che tutte le chiavi hanno lo stesso codice hash, nel qual caso una tabella hash si degrada in un elenco collegato.
In tutti gli altri casi, una tabella hash è un'ottima struttura di dati che è facile da implementare e offre buone prestazioni. Un aspetto negativo è che le implementazioni che possono far crescere rapidamente la tabella e ridistribuire le loro voci probabilmente sprecheranno quasi tutta la memoria effettivamente utilizzata.
Gli alberi RB sono auto-bilanciati e non cambiano la loro complessità algoritmica nel peggiore dei casi. Tuttavia, sono più difficili da implementare. Le loro complessità medie sono anche peggiori di quelle di una tabella hash.
Tutte le chiavi in una tabella hash devono essere hash e comparabili per l'uguaglianza tra loro. Questo è particolarmente facile per stringhe o numeri interi, ma è anche abbastanza semplice estenderlo a tipi definiti dall'utente. In alcune lingue come Java queste proprietà sono garantite per definizione.
Le chiavi in un RB-Tree devono avere un ordine totale: ogni chiave deve essere comparabile con qualsiasi altra chiave e le due chiavi devono confrontare più piccolo, maggiore o uguale. Questa uguaglianza di ordinamento deve essere equivalente all'uguaglianza semantica. Questo è semplice per numeri interi e altri numeri, anche abbastanza facile per le stringhe (l'ordine deve essere solo coerente e non osservabile esternamente, quindi l'ordine non deve considerare le localizzazioni [1] ), ma difficile per altri tipi che non hanno un ordine intrinseco . È assolutamente impossibile avere chiavi di tipi diversi a meno che non sia possibile un confronto tra loro.
[1]: In realtà, mi sbaglio qui. Due stringhe potrebbero non essere uguali a byte ma essere comunque equivalenti secondo le regole di alcune lingue. Vedere ad esempio le normalizzazioni Unicode per un esempio in cui due stringhe uguali sono codificate in modo diverso. Se la composizione dei caratteri Unicode è importante per la tua chiave hash è qualcosa che un'implementazione della tabella hash non può sapere.
Si potrebbe pensare che una soluzione economica per le chiavi RB-Tree sarebbe prima testare l'uguaglianza, quindi confrontare l'identità (cioè confrontare i puntatori). Tuttavia, questo ordinamento non sarebbe transitivo: se a == b
e id(a) > id(c)
, allora dovrebbe seguire anche quello id(b) > id(c)
, che non è garantito qui. Quindi, invece, potremmo usare il codice hash delle chiavi come chiavi di ricerca. Qui, l'ordinamento funziona correttamente, ma potremmo finire con più chiavi distinte con lo stesso codice hash, che verrà assegnato allo stesso nodo nella struttura RB. Per risolvere queste collisioni di hash possiamo usare il concatenamento separato proprio come con le tabelle di hash, ma questo eredita anche il comportamento peggiore per le tabelle di hash - il peggio di entrambi i mondi.
Mi aspetto che una tabella hash abbia una migliore posizione di memoria rispetto a un albero, perché una tabella hash è essenzialmente solo un array.
Le voci in entrambe le strutture dati hanno un sovraccarico abbastanza elevato:
Inserzioni ed eliminazioni in un albero RB comportano rotazioni dell'albero. Questi non sono molto costosi, ma comportano un sovraccarico. In un hash, l'inserimento e la cancellazione non sono più costosi di un semplice accesso (sebbene ridimensionare una tabella hash dopo l'inserimento sia uno O(n)
sforzo).
Le tabelle hash sono intrinsecamente mutabili, mentre un albero RB potrebbe anche essere implementato in modo immutabile. Tuttavia, questo è raramente utile.
Ci sono tutta una serie di ragioni che potrebbero essere vere, ma è probabile che quelle chiave siano:
Più facile da scrivere / mantenere e un vincitore delle prestazioni in casi d'uso tipici? Registrami, per favore!