Какова асимптотическая сложность добавления элемента в конец `slice` в Go (операция `append`)?

Ответ

Добавление элемента в конец slice с помощью append имеет амортизированную сложность O(1).

Это означает, что в среднем операция выполняется за константное время. Однако, в худшем случае, когда внутренний массив (capacity) слайса переполнен, сложность становится O(n). В этот момент происходит:

  1. Выделение нового, большего по размеру массива.
  2. Копирование всех n элементов из старого массива в новый.

Go runtime использует стратегию роста, которая обеспечивает амортизированную сложность O(1). Для небольших слайсов capacity обычно удваивается, а для больших (более 1024 элементов) — увеличивается примерно в 1.25 раза. Из-за того, что дорогостоящие операции O(n) происходят редко, их стоимость "размазывается" по множеству быстрых O(1) вставок.

Пример:

// Создаем слайс с длиной 0 и емкостью 2
s := make([]int, 0, 2)

// len=1, cap=2. Сложность O(1)
s = append(s, 10)

// len=2, cap=2. Сложность O(1)
s = append(s, 20)

// len=3, cap=4. Происходит реаллокация.
// Создается новый массив (емкостью 4), копируются 2 элемента.
// Сложность O(n), где n=2.
s = append(s, 30)