Menu
Coddy logo textTech

Lista doppiamente concatenata

Ultimo aggiornamento

Una lista doppiamente concatenata è una lista concatenata in cui ogni nodo contiene due puntatori: next (verso il nodo successivo) e prev (verso quello precedente). Questo collegamento all'indietro in più ti permette di scorrere la lista in entrambe le direzioni e di eliminare un nodo in O(1) quando hai già un riferimento a esso, perché puoi raggiungere direttamente il predecessore invece di partire dalla testa. Premi play qui sopra per vedere i nodi collegarsi e ricollegarsi in entrambe le direzioni.

Il costo è un puntatore in più per nodo e più lavoro di gestione: ogni inserimento ed eliminazione deve aggiornare sia next sia prev sui vicini. È la struttura dietro molte deque e cache LRU, dove conta la rimozione veloce da entrambe le estremità.

Complessità temporale

OperazioneComplessitàNote
Inserimento in testa/codaO(1)Con i puntatori head e tail
Eliminazione di un nodo notoO(1)Raggiunge prev direttamente, senza scorrere
RicercaO(n)Sempre una scansione lineare
Accesso per indiceO(n)Nessun accesso casuale

Lista semplicemente e doppiamente concatenata

AspettoSempliceDoppia
Puntatori per nodo1 (next)2 (next + prev)
Scorrere all'indietroNoSì
Eliminare un nodo notoO(n) (trovare prev)O(1)
Consumo di memoriaMinoreMaggiore

Esempio svolto

Costruzione di [10, 20, 30] aggiungendo in coda, poi eliminazione del nodo 20:

PassoStrutturaAzione
InizionullLista vuota: head e tail sono entrambi null
Aggiungi 1010Primo nodo; head e tail puntano entrambi a esso, prev = next = null
Aggiungi 2010 <-> 20Imposta 10.next = 20 e 20.prev = 10; tail = 20
Aggiungi 3010 <-> 20 <-> 30Imposta 20.next = 30 e 30.prev = 20; tail = 30
Elimina 2010 <-> 30Usando 20.prev e 20.next: imposta 10.next = 30 e 30.prev = 10 in O(1)

Quando usare una lista doppiamente concatenata

Usala quandoEvitala quando
Ti serve scorrere o unire pezzi di lista in entrambe le direzioniVai sempre e solo avanti: una lista semplicemente concatenata è più leggera
Elimini nodi arbitrari di cui hai già un riferimento (O(1))Accedi soprattutto per posizione: usa un array per l'accesso O(1)
Implementi una deque, una cache LRU o una cronologia di annullamento a schermoLa memoria è poca: il puntatore prev in più raddoppia il costo dei collegamenti
Inserimenti e rimozioni avvengono spesso a entrambe le estremitàI dati sono pochi e cambiano di rado: le copie di array costano meno

Codice Doubly Linked List

Un'implementazione di Doubly Linked List pulita ed eseguibile in Python, JavaScript, Java, C++, C. Scegli un linguaggio, copia il codice o aprilo già caricato nel Playground di Coddy.

Codice Doubly Linked List in Python

Python
1class Node:2    def __init__(self, value):3        self.value = value4        self.prev = None5        self.next = None6
7
8class DoublyLinkedList:9    def __init__(self):10        self.head = None11        self.tail = None12
13    def append(self, value):14        node = Node(value)15        if self.tail is None:16            self.head = self.tail = node17            return18        # Wire both directions: old tail <-> new node19        node.prev = self.tail20        self.tail.next = node21        self.tail = node22
23    def prepend(self, value):24        node = Node(value)25        if self.head is None:26            self.head = self.tail = node27            return28        node.next = self.head29        self.head.prev = node30        self.head = node31
32    def forward(self):33        values, current = [], self.head34        while current:35            values.append(current.value)36            current = current.next37        return values38
39    def backward(self):40        values, current = [], self.tail41        while current:42            values.append(current.value)43            current = current.prev44        return values45
46
47lst = DoublyLinkedList()48for value in [2, 3, 4]:49    lst.append(value)50lst.prepend(1)51
52print("Forward: ", lst.forward())53print("Backward:", lst.backward())
Esegui questo codice nel playground Python

Domande frequenti sulla lista doppiamente concatenata

Qual è la differenza tra una lista semplicemente e una doppiamente concatenata?
Una lista semplicemente concatenata ha un puntatore per nodo (next), quindi puoi solo andare avanti. Una lista doppiamente concatenata aggiunge un puntatore prev, che ti permette di scorrere all'indietro e di eliminare in O(1) un nodo di cui hai un riferimento, al costo di un puntatore in più per nodo e dell'aggiornamento di due collegamenti a ogni modifica.
Quando vale la pena spendere la memoria extra di una lista doppiamente concatenata?
Quando ti serve scorrere all'indietro o eliminare in O(1) nodi arbitrari che hai già come riferimento: i casi classici sono le deque (code a doppia estremità) e le cache LRU, dove le voci vengono spostate ed espulse spesso da entrambe le estremità.
Perché una lista doppiamente concatenata può eliminare un nodo in O(1)?
Per rimuovere un nodo devi collegare il suo predecessore al suo successore. In una lista semplicemente concatenata dovresti partire dalla testa per trovare il predecessore (O(n)); in una lista doppiamente concatenata il puntatore prev del nodo ti dà direttamente il predecessore, quindi il ricollegamento è O(1).
Lista doppiamente concatenata o array: quale usare?
Usa un array quando ti serve accesso O(1) per indice e un'iterazione che sfrutti bene la cache, e quando gli inserimenti avvengono soprattutto in fondo. Usa una lista doppiamente concatenata quando inserisci o rimuovi spesso in mezzo o alle due estremità avendo un riferimento al nodo, perché è O(1) contro lo spostamento O(n) di un array. Gli array vincono su memoria e località; la lista vince sulle modifiche strutturali.
Cos'è un nodo sentinella in una lista doppiamente concatenata?
Un nodo sentinella (o fittizio) è un segnaposto che sta prima della testa e/o dopo la coda, così la lista non è mai davvero vuota. Elimina i controlli su null per head, tail e per inserimenti ed eliminazioni ai bordi, permettendo a ogni operazione di seguire lo stesso codice di ricollegamento. Molte implementazioni reali di cache LRU usano due sentinelle per non dover gestire le estremità come casi speciali.
Qual è il bug più comune quando si elimina da una lista doppiamente concatenata?
Dimenticare di aggiornare head o tail quando elimini il primo o l'ultimo nodo, lasciando un puntatore pendente verso un nodo liberato. Controlla sempre se node.prev è null (aggiorna head) o se node.next è null (aggiorna tail) prima di ricollegare i vicini.
Illustrazione dei linguaggi di programmazione di Coddy

Padroneggia gli algoritmi con Coddy

INIZIA