Menu
Coddy logo textTech

Lista dwukierunkowa

Ostatnia aktualizacja

Lista dwukierunkowa to lista, w której każdy węzeł przechowuje dwa wskaźniki: next (na następny węzeł) i prev (na poprzedni). To dodatkowe połączenie wstecz pozwala przechodzić listę w obu kierunkach i usuwać węzeł w O(1), gdy masz już do niego referencję, bo do poprzednika docierasz bezpośrednio, zamiast iść od głowy. Kliknij odtwarzanie powyżej i zobacz, jak węzły łączą się i przepinają w obie strony.

Kosztem jest jeden dodatkowy wskaźnik na węzeł i więcej pracy przy aktualizacjach: każde wstawienie i usunięcie musi zaktualizować zarówno next, jak i prev u sąsiadów. To struktura stojąca za wieloma kolejkami dwustronnymi i pamięciami podręcznymi LRU, w których liczy się szybkie usuwanie z obu końców.

Złożoność czasowa

OperacjaZłożonośćUwagi
Wstawianie na początek/koniecO(1)Ze wskaźnikami head i tail
Usuwanie znanego węzłaO(1)Bezpośredni dostęp do prev, bez przechodzenia
WyszukiwanieO(n)Nadal liniowe przeglądanie
Dostęp po indeksieO(n)Brak dostępu swobodnego

Lista jednokierunkowa a dwukierunkowa

AspektJednokierunkowaDwukierunkowa
Wskaźniki na węzeł1 (next)2 (next + prev)
Przechodzenie wsteczNieTak
Usuwanie znanego węzłaO(n) (szukanie prev)O(1)
Narzut pamięciMniejszyWiększy

Przykład krok po kroku

Budowanie [10, 20, 30] przez dodawanie na koniec, a potem usunięcie węzła 20:

KrokStrukturaDziałanie
StartnullPusta lista: head i tail mają wartość null
Dodaj 1010Pierwszy węzeł; head i tail wskazują na niego, prev = next = null
Dodaj 2010 <-> 20Ustaw 10.next = 20 i 20.prev = 10; tail = 20
Dodaj 3010 <-> 20 <-> 30Ustaw 20.next = 30 i 30.prev = 20; tail = 30
Usuń 2010 <-> 30Używając 20.prev i 20.next: ustaw 10.next = 30 i 30.prev = 10 w O(1)

Kiedy używać listy dwukierunkowej

Używaj, gdyUnikaj, gdy
Musisz przechodzić listę lub łączyć jej fragmenty w obu kierunkachZawsze poruszasz się tylko do przodu: lista jednokierunkowa jest lżejsza
Usuwasz dowolne węzły, do których masz już referencję (O(1))Głównie odwołujesz się do elementów po pozycji: użyj tablicy z dostępem O(1)
Implementujesz kolejkę dwustronną, pamięć podręczną LRU lub historię cofaniaPamięci jest mało: dodatkowy wskaźnik prev podwaja narzut połączeń
Wstawienia i usunięcia często zachodzą na obu końcachDanych jest mało i rzadko się zmieniają: kopiowanie tablicy jest tańsze

Doubly Linked List: kod

Przejrzysta, gotowa do uruchomienia implementacja algorytmu Doubly Linked List w językach: Python, JavaScript, Java, C++, C. Wybierz język, skopiuj kod albo otwórz go od razu w edytorze online Coddy.

Doubly Linked List: kod (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())
Uruchom ten kod w edytorze Python online

Lista dwukierunkowa: najczęstsze pytania

Czym różni się lista jednokierunkowa od dwukierunkowej?
Lista jednokierunkowa ma jeden wskaźnik na węzeł (next), więc można poruszać się tylko do przodu. Lista dwukierunkowa dodaje wskaźnik prev, który pozwala przechodzić wstecz i usuwać węzeł, do którego masz referencję, w O(1), kosztem dodatkowego wskaźnika na węzeł i aktualizacji dwóch połączeń przy każdej zmianie.
Kiedy lista dwukierunkowa jest warta dodatkowej pamięci?
Gdy potrzebujesz przechodzenia wstecz albo usuwania w O(1) dowolnych węzłów, do których masz już referencję. Klasyczne przypadki to kolejki dwustronne (deque) i pamięci podręczne LRU, w których wpisy są często przenoszone i usuwane z obu końców.
Dlaczego lista dwukierunkowa może usunąć węzeł w O(1)?
Aby usunąć węzeł, trzeba połączyć jego poprzednika z następnikiem. W liście jednokierunkowej trzeba by przejść od głowy, aby znaleźć poprzednika (O(n)); w liście dwukierunkowej wskaźnik prev węzła daje poprzednika bezpośrednio, więc przepięcie ma złożoność O(1).
Lista dwukierunkowa czy tablica: co wybrać?
Użyj tablicy, gdy potrzebujesz dostępu po indeksie w O(1) i iteracji przyjaznej dla pamięci podręcznej, a wstawienia zachodzą głównie na końcu. Użyj listy dwukierunkowej, gdy często wstawiasz lub usuwasz elementy w środku albo na obu końcach, mając referencję do węzła, bo to O(1) wobec przesuwania O(n) w tablicy. Tablice wygrywają pamięcią i lokalnością; lista wygrywa przy zmianach struktury.
Czym jest węzeł wartownik w liście dwukierunkowej?
Wartownik (węzeł pozorny) to element zastępczy umieszczony przed głową i/lub za ogonem, dzięki któremu lista nigdy nie jest naprawdę pusta. Eliminuje sprawdzanie null dla head, tail i wstawień lub usunięć na granicach, więc każda operacja korzysta z tego samego kodu przepinania. Wiele produkcyjnych implementacji pamięci podręcznej LRU używa dwóch wartowników, aby nie obsługiwać końców osobno.
Jaki jest najczęstszy błąd przy usuwaniu z listy dwukierunkowej?
Zapomnienie o aktualizacji head lub tail przy usuwaniu pierwszego lub ostatniego węzła, co zostawia wiszący wskaźnik na zwolniony węzeł. Zawsze sprawdź, czy node.prev ma wartość null (zaktualizuj head) albo czy node.next ma wartość null (zaktualizuj tail), zanim przepniesz sąsiadów.
Ilustracja języków programowania w Coddy

Opanuj algorytmy z Coddy

ZACZNIJ