Listy jednokierunkowe

1. Czym jest lista jednokierunkowa?

Lista jednokierunkowa (singly linked list) to dynamiczna struktura danych składająca się z węzłów (nodes), gdzie każdy węzeł zawiera: - Dane (wartość) - Wskaźnik do następnego węzła

Wizualizacja:

HEAD → [3|→] → [7|→] → [1|→] → [9|→] → None
       dane next  dane next  dane next  dane next

Różnice vs lista pythonowa (list):

Cecha Lista pythonowa Lista jednokierunkowa
Dostęp po indeksie O(1) O(n)
Wstawianie na początku O(n) O(1)
Wstawianie na końcu O(1)* O(n) lub O(1) z tail
Usuwanie z początku O(n) O(1)
Zużycie pamięci Ciągły blok Rozproszona

2. Implementacja węzła

Każdy węzeł zawiera dane i referencję do następnego węzła.

Tworzenie węzłów:


3. Implementacja listy jednokierunkowej

Użycie:


4. Dodawanie elementów

4.1. Dodawanie na początku – O(1)

Krok po kroku:

1. Przed:  HEAD → [7|→] → [1|→] → None

2. Tworzenie nowego węzła:
   new_node = [3|→]

3. Połączenie:
   [3|→] → [7|→] → [1|→] → None

4. Po:     HEAD → [3|→] → [7|→] → [1|→] → None

Przykład:

4.2. Dodawanie na końcu – O(n)

Przykład:

4.3. Wstawianie na określonej pozycji – O(n)

Przykład:


5. Usuwanie elementów

5.1. Usuwanie z początku – O(1)

Przykład:

5.2. Usuwanie z końca – O(n)

5.3. Usuwanie przez wartość – O(n)

Przykład:


6. Wyszukiwanie

6.1. Wyszukiwanie przez wartość – O(n)

6.2. Pobieranie przez indeks – O(n)

Przykład:


7. Odwracanie listy

7.1. Iteracyjne odwracanie – O(n)

Wizualizacja:

1. Przed:  HEAD → [1|→] → [2|→] → [3|→] → None

2. Iteracja 1:
   prev = None, current = [1|→]
   [1|→] None ← [1|None]
   prev = [1|None], current = [2|→]

3. Iteracja 2:
   [1|None] ← [2|→] [1|None]
   prev = [2|→] [1|None], current = [3|→]

4. Po:     HEAD → [3|→] → [2|→] → [1|→] → None

Przykład:

7.2. Rekurencyjne odwracanie – O(n)


8. Zaawansowane operacje

8.1. Znajdowanie środkowego elementu – O(n)

Jak to działa: - slow porusza się o 1 krok - fast porusza się o 2 kroki - Gdy fast dojdzie do końca, slow będzie w środku

8.2. Wykrywanie cyklu (pętli) – O(n)

8.3. Usuwanie duplikatów – O(n²) lub O(n) ze zbiorem

Przykład:


9. Pełna implementacja z iter

Użycie z iteracją:


10. Złożoność czasowa i pamięciowa

Złożoność czasowa:

Operacja Złożoność
prepend(data) O(1)
append(data) O(n)
insert(data, index) O(n)
pop_first() O(1)
pop_last() O(n)
remove(data) O(n)
search(data) O(n)
get(index) O(n)
reverse() O(n)

Złożoność pamięciowa:

  • O(n) – każdy węzeł zajmuje pamięć na dane + wskaźnik

11. Optymalizacja – lista z tail pointer

Zalety: - append() teraz O(1) zamiast O(n)

Wady: - Więcej pamięci (dodatkowy wskaźnik) - Bardziej skomplikowana implementacja


12. Praktyczne zastosowania

12.1. Implementacja stosu

12.2. Historia przeglądarki (uproszczona)


13. Lista vs Lista jednokierunkowa

Kiedy używać listy jednokierunkowej?

✅ Częste dodawanie/usuwanie z początku ✅ Nie potrzebujesz dostępu po indeksie ✅ Implementacja innych struktur (stos, kolejka) ✅ Pamięć dynamiczna (rozmiar nieznany z góry)

Kiedy używać listy pythonowej?

✅ Potrzebujesz szybkiego dostępu po indeksie ✅ Często sortujemy lub przeszukujemy ✅ Prostota implementacji ✅ Większość przypadków użycia


14. Typowe błędy i pułapki

Błąd 1: Zapomnienie o None

Błąd 2: Zgubienie referencji


15. Podsumowanie

Lista jednokierunkowa to podstawowa dynamiczna struktura danych:

  • Każdy węzeł wskazuje tylko na następny
  • Doskonała do stosu (LIFO)
  • O(1) dla operacji na początku
  • O(n) dla operacji na końcu (bez tail pointer)
  • Zużywa więcej pamięci niż tablica (wskaźniki)

Zalety:

  • Dynamiczny rozmiar
  • Szybkie wstawianie/usuwanie z początku
  • Brak przepełnienia (jak w tablicach statycznych)

Wady:

  • Brak dostępu po indeksie O(1)
  • Dodatkowa pamięć na wskaźniki
  • Nie cache-friendly (rozproszona w pamięci)

Co dalej warto poznać:

  • Lista dwukierunkowa (prev + next)
  • Lista cykliczna
  • Skip list
  • XOR linked list