Stos, kolejka i lista jednokierunkowa w Javie
Zaktualizowano 22 października 2026
Stos, kolejka i lista jednokierunkowa to najważniejsze struktury danych, które możesz zbudować sam w Javie. Zwykle przechowujesz dane w tablicach albo w gotowych kolekcjach, takich jak ArrayList. W tym artykule sam zbudujesz stos, kolejkę i listę jednokierunkową. Różnią się one przede wszystkim tym, gdzie elementy są dodawane i skąd są wyjmowane, a od tego zależy, jak szybka jest każda operacja.
LIFO i FIFO
Stos (stack) działa według zasady LIFO, Last In, First Out: co położysz na wierzch jako ostatnie, zdejmujesz jako pierwsze. Operacje nazywają się push (połóż na wierzch), pop (zdejmij) i peek (tylko podejrzyj). Stosy są wszędzie tam, gdzie coś się cofa albo zagnieżdża: przycisk Wstecz w przeglądarce, Ctrl+Z, sprawdzanie nawiasów, wywołania metod w rekurencji.
Kolejka (queue) działa według zasady FIFO, First In, First Out: kto pierwszy przyszedł, ten pierwszy obsłużony. Operacje nazywają się enqueue (ustaw się na końcu) i dequeue (wyjmij z początku). Przykłady to zadania drukowania albo zapytania do serwera.
ArrayDeque z java.util potrafi jedno i drugie. W przykładzie te same trzy litery wychodzą raz w odwrotnej kolejności, a raz w kolejności dodania.
import java.util.ArrayDeque;import java.util.Deque;
public class Main { public static void main(String[] args) { Deque<String> stack = new ArrayDeque<>(); stack.push("A"); stack.push("B"); stack.push("C"); System.out.print("Stack: "); while (!stack.isEmpty()) { System.out.print(stack.pop() + " "); } System.out.println();
Deque<String> queue = new ArrayDeque<>(); queue.offer("A"); queue.offer("B"); queue.offer("C"); System.out.print("Queue: "); while (!queue.isEmpty()) { System.out.print(queue.poll() + " "); } System.out.println(); }}Stos na tablicy
Do własnego stosu wystarczą tablica i licznik size. size jest jednocześnie indeksem następnego wolnego miejsca, a element na wierzchu leży pod data[size - 1].
push: zapisz dodata[size], potemsize++.pop:size--, potem zwróćdata[size]. Nic nie jest usuwane, miejsce po prostu uznaje się za wolne.
Żadna z tych operacji nie potrzebuje pętli, bez względu na to, ile elementów leży na stosie. Ich złożoność to O(1), czyli stała. Co dzieje się przy pustym albo pełnym stosie, musisz ustalić sam: tutaj rzucany jest wyjątek IllegalStateException.
public class Main { static int[] data = new int[10]; static int size = 0;
static void push(int value) { if (size == data.length) { throw new IllegalStateException("Stack is full"); } data[size] = value; size++; }
static int pop() { if (size == 0) { throw new IllegalStateException("Stack is empty"); } size--; return data[size]; }
public static void main(String[] args) { push(1); push(2); push(3); System.out.println(pop()); System.out.println(pop()); push(9); System.out.println(pop()); System.out.println("Size: " + size); }}Kolejka jako pierścień
W kolejce na tablicy elementy dochodzą na końcu i odchodzą z początku. Gdybyś po każdym dequeue przesuwał wszystkie elementy o jedno miejsce do przodu, kosztowałoby to O(n). Zamiast tego przesuwa się początek: head wskazuje na pierwszy element, a count liczy elementy.
Gdy indeks dojdzie do końca tablicy, wraca do 0. Załatwia to operator modulo:
int tail = (head + count) % data.length; // next free slothead = (head + 1) % data.length; // after dequeue
W ten sposób tablica staje się buforem cyklicznym (ring buffer), a zarówno enqueue, jak i dequeue pozostają O(1). W przykładzie 40 i 50 trafiają z powrotem na indeksy 0 i 1, bo tam zwolniło się już miejsce.
public class Main { static int[] data = new int[3]; static int head = 0; static int count = 0;
static void enqueue(int value) { if (count == data.length) { throw new IllegalStateException("Queue is full"); } int tail = (head + count) % data.length; data[tail] = value; count++; System.out.println("enqueue " + value + " at index " + tail); }
static int dequeue() { if (count == 0) { throw new IllegalStateException("Queue is empty"); } int value = data[head]; head = (head + 1) % data.length; count--; return value; }
public static void main(String[] args) { enqueue(10); enqueue(20); enqueue(30); System.out.println("dequeue " + dequeue()); System.out.println("dequeue " + dequeue()); enqueue(40); enqueue(50); while (count > 0) { System.out.println("dequeue " + dequeue()); } }}Listy wiązane
Lista jednokierunkowa składa się z węzłów. Każdy węzeł zna swoją wartość i następny węzeł (next), a ostatni wskazuje na null. Sama lista pamięta tylko head, czyli pierwszy węzeł.
Przechodzi się ją zawsze tak samo:
Node current = head;while (current != null) { // use current.value current = current.next;}
Tablica i lista wiązana w porównaniu:
- Odczyt elementu o indeksie
i: tablica O(1), lista O(n), bo musisz iść węzeł po węźle. - Wstawianie na początek: tablica O(n), bo wszystko trzeba przesunąć, lista O(1) z nowym
head. - Wstawianie na koniec: tablica O(1), dopóki jest miejsce, lista O(n), a z referencją
tailO(1). - Rozmiar: tablica ma stały rozmiar, lista rośnie dowolnie.
Stos da się zbudować na jednym i na drugim. W kolejce na węzłach pamięta się dodatkowo tail, żeby enqueue nie musiało iść aż do końca.
public class Main { static class Node { int value; Node next;
Node(int value) { this.value = value; } }
public static void main(String[] args) { Node head = new Node(3); head.next = new Node(5); head.next.next = new Node(8);
Node node = new Node(1); node.next = head; head = node;
int sum = 0; Node current = head; while (current != null) { System.out.print(current.value + " -> "); sum += current.value; current = current.next; } System.out.println("null"); System.out.println("Sum: " + sum); }}Typowe błędy
Wyjście poza null
Ostatni węzeł ma
next == null. Jeśli w pętli odczytujeszcurrent.next.valuealbo przy pustej liście sięgasz dohead.next, dostajeszNullPointerException. Sprawdź najpierw, czyhead == null, i dobierz odpowiedni warunek:current != null, żeby przejść przez wszystkie węzły,current.next != null, żeby zatrzymać się na ostatnim.Zgubiona referencja
Węzeł, na który nic już nie wskazuje, jest stracony na zawsze. Dlatego przy przepinaniu ważna jest kolejność:
current.next = previous; // the rest of the list is lostNode next = current.next; // too lateZapamiętaj
current.next, zanim go nadpiszesz. A gdy kolejka na węzłach staje się pusta, ustaw teżtailnanull, bo inaczej następneenqueuedoczepi element do starego węzła.Pomyłka o jeden przy zawijaniu
W buforze cyklicznym ostatni poprawny indeks to
data.length - 1. Własne zawijanie zifłatwo może pójść nie tak:if (tail == data.length - 1) { tail = 0;}tail++; // after the wrap-around, tail becomes 1Indeks 0 zostaje pominięty, a kolejka nadpisuje zajęte miejsca. Bezpieczna jest forma z modulo:
tail = (tail + 1) % data.length.
Zadania z tego tematu
Przećwicz to, czego się nauczyłeś, od razu w przeglądarce. Testy natychmiast sprawdzają twoje rozwiązanie.
Powiązane artykuły
- Listy i mapyListy i mapy w Javie w prosty sposób: ArrayList, HashSet i HashMap z przykładami. Do tego typowe błędy, np. List<int> i remove na liczbach.Czytaj artykuł
- Drzewa i kopceDrzewo binarne, drzewo BST, kopiec i sortowanie przez kopcowanie w Javie: przechodzenie drzewa, przykłady i typowe błędy na kolokwium.Czytaj artykuł
- Haszowanie i grafyTablica mieszająca, przeszukiwanie wszerz i w głąb oraz algorytm Dijkstry w Javie: przykłady kodu, złożoność i typowe błędy prosto wyjaśnione.Czytaj artykuł