Многомерные массивы
Введение и общая идея
Многомерный массив - структура данных, в которой элементы организованы в несколько измерений и индексируются набором индексов, по одному на каждое измерение.
Понятие многомерного массива обобщает идею одномерного массива: вместо одного индекса используется набор индексов длины . Такой массив часто называют -мерным. Наиболее распространённые частные случаи — это -мерные (матрицы) и трёхмерные массивы, используемые, например, для представления объёмных данных или изображений в нескольких слоях.
Многомерные массивы используются для представления табличных данных, матриц в алгебре, таблиц значений функций нескольких переменных, а также для хранения объектов, имеющих натуральную структуру измерений (строки/столбцы/слои).
Пример общей идеи: представьте таблицу таблицу размеров строк и столбцов — это -мерный массив. Для изображения с каналами цвета мы получаем -мерную структуру с дополнительным измерением для каналов.
Физическое представление в памяти
В памяти многомерный массив обычно хранится непрерывно как последовательность элементов. Два основных способа расположения элементов — построчно (row-major) и по столбцам (column-major). Адрес элемента вычисляется по формуле смещения относительно базового адреса массива; для -мерного массива в построчном порядке смещение часто записывают как .
Row-major - стратегия упаковки элементов многомерного массива в линейную память, при которой элементы одной строки идут подряд в памяти.
Для произвольного -мерного массива общая формула смещения в терминах размеров измерений и индексов может быть записана компактно как . Эта формула показывает, что для вычисления смещения каждого индекса умножают на произведение размеров последующих измерений и суммируют результаты, затем умножают на размер одного элемента.
Column-major - альтернатива row-major, при которой элементы одного столбца располагаются подряд; формула смещения для -мерного массива в column-major даётся как .
Индексация и проверки границ
Индексы в многомерном массиве обычно принимают целые значения в пределах размеров соответствующих измерений. Для -мерного массива с элементами в простом представлении индексы удовлетворяют условиям и , где описывает допустимые значения для индекса строк, а — для индекса столбцов.
Проверка границ (bounds checking) — важная операция безопасности: она предотвращает доступ за пределы выделенной памяти. Без проверки неправильно вычисленное смещение может привести к ошибкам или уязвимостям. Для оценки количества итераций, требуемых для полного перебора всех элементов в -мерном массиве, используют выражение .
Пример: при работе с матрицей формы полное число элементов равно , а если каждая ячейка занимает байт памяти, то общий объём памяти равен .
Операции: срезы, трансформация и пересчёт индексов
Срез (slice) - подмассив, получаемый фиксированием некоторых индексов или диапазонов индексов; срез имеет меньше измерений или уменьшенные размеры измерений по сравнению с исходным массивом.
При получении среза чаще всего меняется только логическая форма (shape) массива: например, при выборе строки из матрицы формы получится одномерный массив длины, равной числу столбцов. При этом физически элементы могут оставаться на тех же позициях в памяти, и срез определяется лишь базовым смещением и набором новых шагов (strides).
Шаги (strides) — это набор величин, показывающих, на сколько байт или элементов нужно сдвинуться в памяти при увеличении соответствующего индекса на единицу. Для простого представления в row-major шаг для первого индекса равен произведению размеров всех последующих измерений, для следующего — произведению оставшихся и т.д. Это выражается в формуле общего вида через произведения и суммы, например, в формуле смещения .
Пример использования среза: если у вас матрица формы и вы берёте вторую строку, то это соответствует фиксированию первого индекса, и полученный срез можно интерпретировать как одномерный массив длины столбцов.
Асимптотика и алгоритмы обхода
Многие алгоритмы, работающие с многомерными массивами, используют вложенные циклы по каждому измерению. Общее число итераций при полном переборе всех элементов -мерного массива равно произведению размеров по всем измерениям, записываемому как .
Выбор порядка обхода (например, сначала по строкам, затем по столбцам) влияет на производительность из-за кеширования: последовательный доступ по размещению в памяти более эффективен. Для матрицы в row-major эффективнее внешним циклом сделать перебор строк, внутренним — столбцов, поскольку тогда адреса смещаются последовательно на единицу элемента.
Пример оценки количества операций: при алгоритме, который просматривает все элементы матрицы формы , число шагов цикла равно . В общем случае количество шагов равно .
Многомерные массивы в языках программирования
В разных языках программирования реализация многомерных массивов может отличаться: где-то это синтаксический сахар над одномерным массивом с вычислением смещений, где-то — массив массивов с несмежным расположением строк в памяти. В языках низкого уровня объявление фиксированных размеров часто выглядит как пример встраивания размеров прямо в объявление, например: и .
Важно различать логическую форму массива (shape) и физические шаги в памяти (strides). Некоторые библиотеки (например, научные библиотеки) позволяют иметь общий буфер памяти и различные представления этого буфера в виде массивов разной формы без копирования данных — это достигается манипуляцией базовым смещением и шагами.
Пример объявления в стиле C (иллюстрация): int a[][]; В таком случае матрица имеет строк и столбцов.
Практические примеры вычислений
Рассмотрим ещё один конкретный пример индексации в row-major. Линейный индекс элемента с индексами i и j вычисляется как . Это выражение затем умножается на размер элемента и прибавляется к базовому адресу, что даёт полный адрес в памяти и описывается ранее приведённой формулой смещения .
Линейный индекс - позиция элемента в одномерном представлении массива, соответствующая набору многомерных индексов.
Конкретный числовой пример: если в матрице число столбцов равно , и мы рассматриваем элемент с индексами , то линейный индекс по формуле будет вычисляться подставлением соответствующих значений; итоговое численное значение можно получить подстановкой и вычислением выражения, например .
Для оценки объёма памяти массива произвольной размерности удобно использовать формулу общего вида: объём в байтах равен произведению размера элемента на произведение размеров всех измерений, что можно записать как .
Заключение: понимание логической структуры многомерных массивов, формул смещения и влияния расположения в памяти позволяет эффективно писать код, правильно оценивать потребности по памяти и оптимизировать доступы для повышения производительности алгоритмов.