← Назад к вопросам

Какая сложность у основных операций в LinkedList?

1.0 Junior🔥 201 комментариев
#Коллекции

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Временная сложность операций LinkedList

LinkedList в Java — это двусвязный список, где каждый узел содержит данные и ссылки на следующий и предыдущий узлы. Это влияет на производительность разных операций.

Таблица сложностей

ОперацияСложностьКомментарий
get(int index)O(n)Требуется обход с начала или конца
add(E element)O(1)Добавление в конец
add(int index, E element)O(n)Требуется найти позицию
remove()O(1)Удаление с конца
remove(int index)O(n)Требуется найти позицию
remove(Object o)O(n)Нужно найти элемент
contains(Object o)O(n)Нужно пройти весь список
indexOf(Object o)O(n)Линейный поиск
addFirst(E element)O(1)Добавление в начало
addLast(E element)O(1)Добавление в конец
removeFirst()O(1)Удаление с начала
removeLast()O(1)Удаление с конца
getFirst()O(1)Получение первого элемента
getLast()O(1)Получение последнего элемента

Детальный анализ

O(1) операции

LinkedList<Integer> list = new LinkedList<>();
list.add(1);      // O(1) - добавляет в конец, только обновляет last
list.add(2);
list.add(3);

list.addFirst(0); // O(1) - просто обновляет head и first указатель
list.addLast(4);  // O(1) - просто обновляет last указатель

list.removeFirst(); // O(1) - удаляет первый элемент
list.removeLast();  // O(1) - удаляет последний элемент

int first = list.getFirst(); // O(1) - прямой доступ к head
int last = list.getLast();   // O(1) - прямой доступ к tail

O(n) операции

// get(int index) - ВАЖНО: O(n)
// LinkedList должен обойти список с начала или конца
int element = list.get(500); // Если список из 1000 элементов
// Если index < size/2, начинается с головы
// Если index >= size/2, начинается с хвоста
// В худшем случае 500 переходов

// add(int index, E element) - O(n)
list.add(500, 999); // Нужно найти позицию 500, затем вставить
// Это требует 500 переходов + вставка O(1)

// remove(int index) - O(n)
list.remove(500); // Найти позицию 500, затем удалить

// contains(Object o) - O(n)
boolean has = list.contains(500); // Проверяет каждый элемент

// indexOf(Object o) - O(n)
int pos = list.indexOf(500); // Линейный поиск

Внутренняя структура

private class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
}

private transient Node<E> first;  // Быстрый доступ к началу
private transient Node<E> last;   // Быстрый доступ к концу
private transient int size;

// Оптимизация: get() выбирает начало или конец
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

Node<E> node(int index) {
    if (index < (size >> 1)) {      // index < size/2?
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {                         // Начинаем с конца
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

Сравнение с ArrayList

Операция              | LinkedList | ArrayList
----------------------|------------|----------
add() в конец         | O(1)       | O(1) amortized
add(index, element)   | O(n)       | O(n)
get(index)            | O(n)       | O(1)
remove(index)         | O(n)       | O(n)
remove(object)        | O(n)       | O(n)
contains()            | O(n)       | O(n)
iterator traversal    | O(n)       | O(n)
Memory per element    | 24 bytes   | 8 bytes (2 ref + item)

Когда использовать LinkedList

// ХОРОШО: Частые операции в начале/конце
LinkedList<String> queue = new LinkedList<>();
queue.addLast("first");      // O(1)
queue.addLast("second");     // O(1)
String removed = queue.removeFirst(); // O(1)

// Реализация очереди или стека
LinkedList<Integer> stack = new LinkedList<>();
stack.push(1);    // addFirst()
stack.push(2);
int top = stack.pop(); // removeFirst()

// ПЛОХО: Частые операции по индексу
LinkedList<Integer> list = new LinkedList<>();
for (int i = 0; i < 10000; i++) {
    list.add(list.get(i % list.size()), i); // O(n) для каждого get!
}
// Лучше использовать ArrayList

Практический пример: очередь сообщений

public class MessageQueue {
    private LinkedList<String> messages = new LinkedList<>();
    
    // O(1)
    public void enqueue(String message) {
        messages.addLast(message);
    }
    
    // O(1)
    public String dequeue() {
        return messages.removeFirst();
    }
    
    // O(1)
    public String peek() {
        return messages.getFirst();
    }
    
    // O(1)
    public int size() {
        return messages.size();
    }
}

// Использование
MessageQueue queue = new MessageQueue();
queue.enqueue("msg1"); // O(1)
queue.enqueue("msg2"); // O(1)
queue.dequeue();       // O(1) - очень эффективно!

Вывод

LinkedList эффективен для:

  • Операций в начале/конце (O(1))
  • Очередей и стеков
  • Итерации по всем элементам

LinkedList неэффективен для:

  • Доступа по индексу (O(n))
  • Больших наборов данных (память = 3x больше)
  • Частых поисков (O(n))

Правило: Если нужны операции по индексу → ArrayList. Если нужны операции на концах → LinkedList.

Какая сложность у основных операций в LinkedList? | PrepBro