← Назад к вопросам
Какая временная сложность вставки элемента в начало ArrayList?
1.0 Junior🔥 181 комментариев
#Коллекции
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Какая временная сложность вставки элемента в начало ArrayList?
Ответ: O(n)
Вставка элемента в начало ArrayList имеет линейную временную сложность O(n), где n — количество элементов в списке.
Почему O(n)?
Структура ArrayList:
public class ArrayList<E> extends AbstractList<E> implements List<E> {
private Object[] elementData; // Внутренний массив
private int size; // Количество элементов
}
Вставка в начало требует:
- Сдвига всех элементов на одну позицию вправо — это O(n)
- Помещения нового элемента на позицию 0 — это O(1)
Визуальная демонстрация
Исходный список: [A, B, C, D, E]
Вставляем X в начало:
1. Сдвигаем элементы: [_, A, B, C, D] (E теряется)
2. Вставляем X: [X, A, B, C, D]
Все 5 элементов нужно было переместить → O(n)
Код операции add(0, element)
public void add(int index, E element) {
rangeCheckForAdd(index);
// Проверка, нужна ли расширение массива
if (size == elementData.length)
elementData = grow(); // O(n) — создание нового массива и копирование
// Сдвиг элементов
System.arraycopy(elementData, index,
elementData, index + 1, // Вся операция O(n)
size - index);
elementData[index] = element; // O(1)
size++;
}
System.arraycopy — встроенная функция, которая:
- Требует скопировать все элементы от index до конца
- Для вставки в начало (index=0) скопирует ВСЕ элементы
- Это занимает O(n) времени
Примеры разных операций
ArrayList<String> list = new ArrayList<>();
list.add("A"); // add("A") → O(1) амортизированный
list.add("B"); // add("B") → O(1) амортизированный
list.add("C"); // add("C") → O(1) амортизированный
list.add("D");
list.add("E");
// Текущее состояние: [A, B, C, D, E]
list.add(0, "X"); // add(0, "X") → O(n) — все сдвигаются!
list.add(2, "Y"); // add(2, "Y") → O(n) — элементы с индекса 2 сдвигаются
list.add(5, "Z"); // add(5, "Z") → O(1) или O(n)?
Анализ add(5, "Z"):
Текущее состояние: [X, A, B, Y, C, D, E] (размер = 7, индекс 5 указывает на D)
Операция: System.arraycopy(data, 5, data, 6, 7-5)
Это скопирует элементы 5 и 6 (то есть D и E) на позиции 6 и 7
Сложность: O(2) = O(1) для небольшого количества элементов
Но в общем случае это O(n - index)
Сравнение операций
ArrayList<Integer> list = new ArrayList<>();
// Добавление в конец
for (int i = 0; i < 10000; i++) {
list.add(i); // O(1) амортизированный
}
// Общее время: ~O(10000)
// Добавление в начало (ПЛОХО!)
ArrayList<Integer> badList = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
badList.add(0, i); // O(n) на каждой итерации!
}
// Общее время: O(1 + 2 + 3 + ... + 10000) = O(n²) ← Очень плохо!
LinkedList — лучше для вставки в начало
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 10000; i++) {
linkedList.addFirst(i); // O(1) — просто создаёт новый узел
}
// Общее время: O(10000)
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
arrayList.add(0, i); // O(n) на каждой итерации
}
// Общее время: O(n²) = O(100_000_000) ← Медленнее!
Измерение производительности
import java.util.*;
public class ListPerformanceTest {
public static void main(String[] args) {
int size = 10000;
// ArrayList
long startTime = System.currentTimeMillis();
List<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < size; i++) {
arrayList.add(0, i); // Вставка в начало
}
long arrayListTime = System.currentTimeMillis() - startTime;
// LinkedList
startTime = System.currentTimeMillis();
List<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < size; i++) {
linkedList.add(0, i); // Вставка в начало
}
long linkedListTime = System.currentTimeMillis() - startTime;
System.out.println("ArrayList добавление в начало: " + arrayListTime + "ms");
System.out.println("LinkedList добавление в начало: " + linkedListTime + "ms");
// Вывод примерно:
// ArrayList добавление в начало: 1500ms ← O(n²)
// LinkedList добавление в начало: 15ms ← O(n)
}
}
Таблица сложности операций
Операция ArrayList LinkedList
─────────────────────────────────────────────────
add(E) O(1)* O(1)
add(0, E) O(n) O(1)
add(n-1, E) O(1)* O(n)
get(int) O(1) O(n)
remove(int) O(n) O(n)*
remove(0) O(n) O(1)
contains(E) O(n) O(n)
* амортизированный или в худшем случае
Правильный способ: используй LinkedList или переверни список
Вариант 1: LinkedList для частых вставок в начало
List<Integer> list = new LinkedList<>();
for (int i = 0; i < 10000; i++) {
list.add(0, i); // O(1) для LinkedList
}
Вариант 2: Добавь в конец, потом переверни
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
list.add(i); // O(1) — быстро добавляем в конец
}
Collections.reverse(list); // O(n) — один раз переворачиваем
Вариант 3: Используй Stack (вместо add(0, x))
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < 10000; i++) {
stack.push(i); // O(1) — эффективнее
}
// Итерировать нужно в обратном порядке
while (!stack.isEmpty()) {
int value = stack.pop();
// ...
}
Ключевые моменты
- Вставка в начало ArrayList: O(n) из-за необходимости сдвига всех элементов
- Вставка в конец ArrayList: O(1) амортизированный — нет сдвига
- LinkedList для вставок в начало: O(1) — просто создание нового узла
- Не делай циклов с add(0, x) на ArrayList — это будет O(n²)!
- Используй LinkedList, если часто вставляешь в начало/конец