Массив — это упорядоченная структура данных для хранения набора элементов под одним именем. Он позволяет хранить множество значений в виде последовательного списка, к каждому элементу которого можно быстро обратиться по его порядковому номеру (индексу).
Упорядоченная здесь означает, что порядок элементов имеет значение. Это не
то же самое, что отсортированная: значения не обязаны идти по возрастанию или
убыванию.
array = sequence[0..n-1]
Для массива длины n допустимые индексы:
0 <= i < n
Пример:
arr = [7, 3, 9, 1]
index: 0 1 2 3
value: 7 3 9 1
Чтобы восстановить содержимое конкретного массива как последовательность,
достаточно знать:
1. длина
2. значение в каждой позиции
Термины
n -> длина массива
i -> индекс
arr[i] -> значение на индексе i
0 -> первый индекс
n - 1 -> последний индекс
Индекс и значение не совпадают по смыслу.
arr[2] = 9
2 -> индекс
9 -> значение
Виды массивов
Статический массив
Статический массив имеет фиксированную длину.
length = n
После создания количество позиций не меняется. Значения по существующим
индексам можно читать и изменять, если массив мутабельный.
arr[i]
arr[i] = x
Для изменения размера требуется создать новый массив и перенести элементы.
Ключевые свойства:
фиксированная длина
непрерывное хранение элементов
доступ по индексу за O(1)
Динамический массив
Динамический массив хранит элементы в массиве фиксированной ёмкости, но
предоставляет операцию расширения.
append(x)
Если свободная ёмкость есть, добавление в конец стоит O(1).
Если свободной ёмкости нет:
1. создаётся новый внутренний массив большей ёмкости
2. старые элементы копируются в него
3. новый элемент записывается в конец
Стоимость отдельного расширения:
O(n)
Амортизированная стоимость добавления в конец:
amortized O(1)
Примеры динамических массивов:
Python list
Java ArrayList
JavaScript Array
C++ vector
Свойства
Короткая карта свойств массива как структуры:
длина -> количество элементов / позиций: n
индексированность -> у каждой позиции есть индекс
индексная область -> допустимые индексы: 0 ... n - 1
упорядоченность -> порядок элементов имеет значение
позиционность -> каждый элемент находится на конкретной позиции
доступ по индексу -> значение можно получить через arr[i]
границы -> нельзя обращаться за пределы индексной области
повторяемость -> одинаковые значения могут встречаться несколько раз
соседство -> у элемента могут быть левый и правый сосед
диапазоны -> можно выделять часть массива по индексам
Важно отделять свойства массива от характеристик конкретного массива:
свойство: у массива есть длина
характеристика: длина этого массива = 4
свойство: массив допускает повторяющиеся значения
характеристика: в этом массиве повторов нет
Длина
Массив содержит определённое число элементов / позиций.
length(arr) = n
Длина — это массивная форма конечности: через неё задаются индексная область,
последний индекс и выход за границы.
Индексированность
Каждый элемент имеет позицию.
arr[0], arr[1], ..., arr[n - 1]
Индексная область
Для массива длины n допустимые индексы:
0, 1, 2, ..., n - 1
Порядок
Порядок элементов является частью массива.
[7, 3, 9, 1] != [1, 9, 3, 7]
Границы
Обращение допустимо только по индексам из диапазона:
0 <= i < n
Индекс n выходит за границу.
arr[n] -> out of bounds
Повторяемость значений
Массив допускает одинаковые значения.
[5, 5, 5, 2]
Массив хранит значение для каждой позиции, а не множество уникальных значений.
Соседство
Для элемента arr[i] возможны соседи:
left = arr[i - 1], если i > 0
right = arr[i + 1], если i + 1 < n
Соседство используется в задачах на сравнение рядом стоящих элементов.
Диапазон
Подмассив задаётся диапазоном индексов.
arr[l..r]
Для полуоткрытого диапазона:
arr[l..r)
условия корректности:
0 <= l <= r <= n
Базовые операции
Длина
length(arr)
Обычно:
O(1)
Чтение по индексу
arr[i]
Условие:
0 <= i < n
Сложность:
O(1)
Запись по индексу
arr[i] = x
Условие:
0 <= i < n
Сложность:
O(1)
Проход
for i from 0 to n - 1:
use arr[i]
Сложность:
O(n)
Поиск значения
find x in arr
В неотсортированном массиве требуется линейный проход.
O(n)
Проверка наличия
x in arr
В неотсортированном массиве:
O(n)
Подсчёт
count elements where condition is true
Сложность:
O(n)
Минимум и максимум
min(arr)
max(arr)
В неотсортированном массиве:
O(n)
Сумма
sum(arr)
Сложность:
O(n)
Обмен двух элементов
swap(arr[i], arr[j])
Условие:
0 <= i < n
0 <= j < n
Сложность:
O(1)
Вставка
insert(arr, i, x)
При вставке элементы справа от i сдвигаются вправо.
[7, 3, 9, 1]
insert index 2 value 100
[7, 3, 100, 9, 1]
Сложность:
O(n)
Удаление
delete(arr, i)
При удалении элементы справа от i сдвигаются влево.
[7, 3, 9, 1]
delete index 1
[7, 9, 1]
Сложность:
O(n)
Добавление в конец
append(arr, x)
Для динамического массива:
amortized O(1)
Отдельная операция может стоить O(n), если требуется расширение внутреннего
хранилища.
Удаление с конца
pop(arr)
Для динамического массива обычно:
O(1)
Разворот
reverse(arr)
Сложность:
O(n)
Сортировка
sort(arr)
Типичная сложность сравнительной сортировки:
O(n log n)
Конкретная сложность зависит от алгоритма сортировки и реализации.
Таблица сложностей
| Операция | Сложность |
|---|---|
length(arr) |
O(1) |
arr[i] |
O(1) |
arr[i] = x |
O(1) |
| проход по всем элементам | O(n) |
| поиск в неотсортированном массиве | O(n) |
| минимум / максимум | O(n) |
| сумма | O(n) |
swap(arr[i], arr[j]) |
O(1) |
| вставка в середину | O(n) |
| удаление из середины | O(n) |
append в динамический массив |
amortized O(1) |
pop с конца |
O(1) |
| разворот | O(n) |
| сортировка сравнением | O(n log n) |
Формальная схема
Array = {
length: n,
indexes: 0..n-1,
values: arr[0], arr[1], ..., arr[n-1]
}
Операции массива изменяют или используют одну из трёх сущностей:
индекс
значение
порядок
Примеры:
arr[i] -> чтение значения по индексу
arr[i] = x -> изменение значения
swap(i, j) -> изменение порядка
insert(i, x) -> изменение длины и порядка
delete(i) -> изменение длины и порядка
sort(arr) -> изменение порядка
Минимальная формулировка:
массив = конечная индексированная последовательность значений