Определение. Функция
Например, линейными являются:
Линейная алгебра занимается изучением линейных функций.
Из одних лишь двух пунктов в определении можно вывести много полезных свойств:
Можно показать, что любую линейную функцию
Матрицы ввели просто как очень компактную запись этих коэффициентов
Каждой линейной функции
Если вектор — это упорядоченный набор скаляров, то матрицу можно рассматривать как вектор векторов. Вектор, в частности, можно представить как матрицу, у которой одна из размерностей равна единице — тогда его называют вектор-столбец либо вектор-строка.
typedef vector<vector<int>> matrix;
Ещё есть тензоры — ими называют все объекты ещё более высокого порядка: векторы матриц (трёхмерный тензор), матрицы матриц (четырёхмерный тензор) и векторы матриц матриц и так далее.
У тензоров есть своя интересная алгебра, но в контекстах, в которых с ними сталкивается обычный программист, никакая алгебра, как правило, не подразумневается, и этот термин используется лишь потому, что в словосочетании «многомерный массив» слишком много букв.
Пусть линейной функции
Читатель может убедиться в этом, расписав, какие коэффициенты получаются, если формулы из
При перемножении матриц руками удобно думать так: элемент на пересечении
Исходное выражение для
Напишем функцию, реализующую матричное умножение:
const int n, k, m;
(matrix a, matrix b) {
matrix matmul(n, vector<int>(m, 0));
matrix cfor (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
for (int t = 0; t < k; t++)
[i][j] += a[i][t] * b[t][j];
creturn c;
}
Такая реализация хоть и наиболее простая, но не оптимальная ввиду особенностей работы кэша (см. раздел «Оптимизации матричного умножения»).
К матрицам не нужно относиться как к табличкам, в которых стоят какие-то числа. Каждой матрице соответствует какая-то линейная функция, как-то преобразующая вектора. Центральными объектами линейной алгебры являются именно линейные функции, а не матрицы.
Благодаря этому взаимно однозначному соотношению все ранее упомянутые свойства линейных функций переносятся и на матрицы:
Есть специальная матрица
Некоторые линейные функции обратимы (например, поворот на угол), некоторые — необратимы (например, проекция). Понятие обратимости можно продолжить и на матрицы.
Определение. Матрица
Из формулы следует, что матрица
Матрица «увеличь всё в два раза»:
Матрица «поменяй
Матрица поворота на угол
Матрица проецирования на плоскость
Пока что все примеры были про геометрию. Так просто проще думать: представлять в уме повороты и растяжения векторов на плоскости легче, чем рассуждать о свойствах абстрактных n-мерных пространств.
Однако мы программисты, и нас в основном интересуют задачи вне геометрии.
Переходы в некоторых динамиках иногда можно выразить в терминах матричного умножения.
Рассмотирм конкретный пример. Пусть у нас есть ориентированный граф
Обычно, мы бы ввели динамику
Выясняется, что вектор
Значит, имеет место следующее равенство:
(Поэтому
Получается,
По этой формуле нам нужно
const int n;
(matrix a, int p) {
matrix binpow// создадим единичную матрицу
(n, vector<int>(n, 0));
matrix bfor (int i = 0; i < n; i++)
[i][i] = 1;
b
while (p > 0) {
if (p & 1)
= matmul(b, a);
b = matmul(a, a);
a >>= 1;
p }
return b;
}
В получившейся матрице в ячейке
Модификации задачи:
Практически не меняя сам алгоритм, можно решить задачу «с какой вероятностью мы попадём из вершины
Если нам не нужно количество способов, а только сам факт, можно ли дойти за ровно
Если нас спрашивают «за не более, чем
Эту технику можно применить и к другим динамикам, где нужно посчитать количество способов что-то сделать — иногда очень неочевидными способами. Например, можно решить такую задачу: найти количество строк длины
В некоторых изощрённых случаях в матричном умножении вместо умножения и сложения нужно использовать другие операции, которые ведут себя как умножение и сложение. Пример задачи: «найти путь от
Расмотрим другой пример — последовательность Фибоначчи. Ну а чем не динамика?
Как мы видим,
Вообще говоря, последовательностей Фибоначчи много — им не обязательно начинаться с
(Также можно «шагать» в обратном направлении, если обратить эту матрицу.)
Обозначим за
В общем случае, линейная рекуррента
Очень часто у матриц есть собственные векторы — те, которые не меняют направление при применении этой матрицы к ним.
Вообще, собственные вектора в линейной алгебре имеют почти такое же значение, как простые числа в арифметике.
Базисом множества называется набор векторов, через линейную комбнацию которых единственным способом можно выразить все вектора этого множества и только их.
Базисы есть не только в линейной алгебре. Например,
Система векторов
Два столбца (или две строки) матрицы называются линейно зависимыми, если два образованных ими вектора являются линейно зависимыми (коллинеарными). Рангом матрицы
Иногда встречаются задачи, требующие решения системы линейных уравнений. Очень большая часть из них на самом деле над полем
Нас по сути просят решить следующую систему:
Здесь
Метод Крамера неоптимален — там
В таком случае можно значительно ускорить и упростить обычный метод Гаусса:
(matrix a) {
t gauss for (int i = 0; i < n; i++) {
int nonzero = i;
for (int j = i+1; j < n; j++)
if (a[j][i])
= j;
nonzero (a[nonzero], a[i]);
swapfor (int j = 0; j < n; j++)
if (j != i && a[j][i])
[j] ^= a[i];
a}
;
t xfor (int i = 0; i < n; i++)
[i] = a[i][n] ^ a[i][i];
xreturn x;
}
Код находит вектор
Часто эту систему нужно решить по модулю 2. Тогда код значительно упрощается и ускоряется (опять же, см. Битсет).
Наивная реализация матричного умножения из начала статьи неоптимальна из-за паттерна обращения к элементам b
: мы каждый раз двигаем указатель в памяти на
Однако, чтобы это исправить, достаточно перед всеми циклами транспонировать b
, то есть поменять каждый её
Дальше трудности возникают лишь при работе большими матрицами, которые не помещаются целиком в кэш — мы ведь для каждой строчки
При рекурсивном вычислении этой формулы кэш будет использован оптимально вне зависимости от его размера, а также размера кэш-линии (такие алгоритмы называют cache-oblivious).
Наука знает и более быстрые способы перемножить матрицы, но они имеют весьма большую константу, и более эффективны только на матрицах от тысяч элементов. На практике достаточно транспонирования одной из матриц и итерации по второй размерности во всех трёх массивах.