Сложность операций со слайсами в Go
Слайс — это обертка над массивом. Операции с ним очень эффективны, если понимать, как они работают внутри.
1. Доступ по индексу (s[i])
- Сложность: $O(1)$ (Константная)
- Почему: Процессор просто берет указатель на начало массива и прибавляет к нему смещение (
индекс * размер_типа). Это мгновенная операция.
2. Append (Добавление в конец)
Здесь есть важный нюанс — наличие свободной емкости (cap).
- Лучший случай: $O(1)$ (константная). Если есть свободная емкость, Go просто записывает значение в следующую ячейку массива и инкрементирует
len. - Худший случай: $O(n)$ (логарифмическая). Если свободного места нет (
len == cap), происходит реаллокация: выделяется новая память и все $n$ элементов копируются в новый массив. - Амортизированная сложность: $O(1)$. Поскольку Go увеличивает емкость не на 1, а экспоненциально (обычно в 2 раза для малых слайсов), "дорогое" копирование происходит редко. В среднем за $n$ операций добавления сложность остается константной.
3. Удаление элемента (s = append(s[:i], s[i+1:]...))
- Сложность: $O(n - i)$ (логарифмическая), в среднем $O(n)$.
- Почему: При удалении из середины или начала все элементы, стоящие после удаленного, должны быть сдвинуты влево на одну позицию, чтобы в массиве не осталось "дырки".
4. Вставка в середину
- Сложность: $O(n)$ (логарифмическая).
- Почему: Аналогично удалению, нужно сдвинуть все элементы вправо, чтобы освободить место для нового элемента. Если при этом еще и превышена емкость, добавится операция копирования всего массива ($O(n)$) (логарифмическая).
5. Копирование слайса (copy(dst, src))
- Сложность: $O(n)$ (логарифмическая).
- Почему: Нужно физически прочитать каждый байт из одного места и записать в другое. Чем больше элементов $n$, тем дольше процесс.
6. Поиск элемента (Unsorted)
- Сложность: $O(n) (логарифмическая)$.
- Почему: В худшем случае (если элемент в самом конце или его нет) нам придется проверить каждый из $n$ элементов.
Итоговая таблица
| Операция | Сложность | Примечание |
|---|---|---|
| Доступ по индексу | $O(1)$ | Самая быстрая операция |
| Append (в конец) | $O(1)$ аморт. | $O(n)$, если происходит реаллокация |
| Вставка (в начало/середину) | $O(n)$ | Требует сдвига элементов |
| Удаление (из начала/середины) | $O(n)$ | Требует сдвига элементов |
Копирование (copy) | $O(n)$ | Зависит от количества переносимых данных |
| Поиск (линейный) | $O(n)$ | Проход циклом до нахождения |
Совет для интервью:
"В среднем (амортизировано) — $O(1)$, но в худшем случае, когда емкость исчерпана и требуется переаллокация, она составляет $O(n)$ из-за необходимости копирования всех существующих элементов в новую область памяти."