Quando si tratta di insiemi e mappe, molto dipende dalla struttura specifica dei dati. Gli alberi sono abbastanza comuni per i contenitori associativi, compresi insiemi e mappe, quindi mi occuperò solo di questi.
Per gli alberi binari non bilanciati, un'operazione di inserimento funziona in due fasi: la parte in alto per trovare il punto di inserimento e la parte dal basso verso l'alto per modificare (o creare) il nuovo albero.
Il punto qui è che per ogni nodo, solo uno dei due sottoalberi contiene il punto di inserimento, e solo quella sola sottostruttura deve essere duplicata per un inserto dell'albero puramente funzionale. Quindi gli unici nuovi nodi necessari sono in una catena dal punto di inserimento indietro fino alla radice.
Questo significa che supponendo che l'albero sia (accidentalmente) bilanciato, hai O (log n) nuovi nodi per un singolo inserto - il numero di nuovi nodi dipende dalla profondità dell'albero, non dalla dimensione totale.
Un'ipotesi chiave qui è che i nodi memorizzano solo i puntatori ai propri figli. Nelle lingue imperative, è comune che i nodi memorizzino i puntatori ai loro genitori. Ciò significa che ci sono riferimenti ciclici, quindi la struttura dei dati deve essere copiata nel suo complesso. Ci sono problemi simili con alberi binari a thread . La regola generale è che quando si crea un nuovo stato, ogni componente strongmente connesso deve essere copiato nel suo complesso - in pratica, questi le strutture dati sono raramente utilizzate nella programmazione funzionale.
Ovviamente i link genitore possono essere memorizzati al di fuori della struttura dei dati dell'albero, e il modo funzionale per gestirli è usare un cerniera . Un problema (a volte vantaggioso, a volte lo svantaggio) è che le cerniere, come i collegamenti principali memorizzati esternamente, fanno riferimento allo stato particolare in cui sono state create per fare riferimento, non a uno stato più recente.
Con schemi ad albero binario bilanciati come alberi AVL e alberi rosso-nero, ci saranno cambiamenti più complessi dovuti al ribilanciamento, ma in genere si ottengono generalmente gli stessi nodi O (log n) copiati per singolo inserto - a meno che tu non abbia collegamenti principali ancora.
Alberi digitali o tentativi utilizzano una struttura dati ad albero, ma basano quella sul binario (o digitale, almeno) rappresentazione delle chiavi, non dell'ordine. Ad esempio, utilizzando le cifre di base 10, è possibile trovare l'elemento per la chiave 123 iniziando dal nodo radice, seguendo il collegamento figlio 1, quindi il collegamento figlio 2, quindi il collegamento figlio 3. La profondità di questi alberi è logaritmica nella dimensione massima possibile perché se si dispone di un numero di n cifre e di k cifre distinte, è possibile formare stringhe di cifre in modo che la dimensione massima possibile sia k ^ n. Ad esempio, con tre cifre decimali, puoi avere solo 1000 chiavi diverse, quindi la dimensione massima è 1000 - 3 è il logaritmo di base 10 di 1000.
La trama è simile agli alberi binari, ad eccezione del fatto che tendi ad avere costanti migliori (l'albero si dirama in più modi su ciascun nodo) e puoi spesso rivendicare O (1) perché le chiavi hanno tutti un numero fisso di bit. Questa struttura, con hash per le chiavi, può anche essere usata per dare una sorta di tabella hash - con vantaggi e svantaggi rispetto alle solite tabelle hash basate su array dalla programmazione imperativa.
C'è una struttura intermedia chiamata albero ternario - fondamentalmente un albero di alberi binari, quindi fai una ricerca binaria per il primo carattere della stringa, poi il prossimo e così via. Ancora una volta, è ancora un albero e, a patto che non ci siano indicatori genitore o altri riferimenti ciclici, ogni inserto deve solo copiare i nodi O (log n).