Как выполнять вставку и удаление элемента в массиве?
Массив размещает элементы последовательно, поэтому вставка в середину требует освободить позицию сдвигом хвоста, а удаление — закрыть образовавшийся разрыв. Даже если язык предоставляет готовый метод, понимание направления копирования объясняет стоимость O(n) и предотвращает потерю данных.
Вставка
При наличии свободной ячейки элементы от последнего занятого до позиции k сдвигают вправо. Проход начинается с конца: a[i + 1] получает a[i]. После сдвига новое значение записывается в a[k], а логическая длина увеличивается на единицу.
Почему нельзя идти вперёд
Если копировать справа налево? Для вставки именно справа налево безопасно; прямой проход сначала запишет a[k + 1] значением a[k], затем возьмёт уже изменённую ячейку и размножит один элемент. Маленький массив из трёх разных чисел наглядно показывает повреждение.
Удаление
После удаления позиции k каждый следующий элемент сдвигают влево: a[i] получает a[i + 1]. Здесь проход, наоборот, идёт вперёд, потому что источник расположен правее назначения и ещё не затронут. Логическая длина уменьшается; значение в бывшей последней ячейке больше не относится к массиву.
Границы и ёмкость
Вставка разрешена в позиции от нуля до текущей длины включительно, если есть ёмкость. Удаление требует существующей позиции от нуля до длины минус один. Динамический массив при нехватке места создаёт больший буфер и копирует элементы, поэтому отдельная вставка иногда дорога, хотя добавления в конец имеют хорошую амортизированную оценку.
Доказательство сдвига
Сформулируйте, какая часть хвоста уже перемещена и какие исходные значения ещё целы. Протрассируйте вставку в начало, конец и пустой массив, затем удаление единственного элемента. Такое доказательство объясняет не только результат, но и обязательное направление прохода.
Направление сдвига защищает ещё не перенесённые данные
При вставке элементы [index, size) сдвигают вправо от конца к началу. Если идти вперёд, первое присваивание затрёт значение, которое ещё требуется перенести. При удалении движение идёт слева направо, потому что источник расположен правее назначения.
def insert_at(buffer: list[int], size: int, index: int, value: int) -> int:
if not 0 <= index <= size or size >= len(buffer):
raise IndexError("no position or capacity")
for position in range(size, index, -1):
buffer[position] = buffer[position - 1]
buffer[index] = value
return size + 1
storage = [10, 20, 30, 0, 0]
size = insert_at(storage, 3, 1, 15)
assert storage[:size] == [10, 15, 20, 30]Стоимость равна числу сдвигаемых элементов: O(n) в худшем случае. Свободная ёмкость и логический размер — разные характеристики, смешивать их опасно.
Практика: симметричное удаление
Напишите delete_at, возвращающую удалённое значение и новый логический размер. После сдвига очистите ставшую свободной ячейку условным маркером. Проверьте удаление первого, среднего и последнего элементов, а также неверный индекс. Сформулируйте инвариант обоих циклов через уже перенесённый диапазон. Затем сравните со связным списком: при каких условиях список действительно удаляет за O(1), а когда поиск позиции всё равно стоит O(n)?
Источники
- МФТИ: Программа вступительного испытания по информатике и информационно-коммуникационным технологиям.
- МФТИ: Вступительные испытания в 2026 году.
- Семакин И.Г., Хеннер Е.К., Шеина Т.Ю. Информатика. 11 класс. Базовый уровень.