Коллекция алгоритмов

Умножение матриц
    Матрица - математический объект, эквивалентный двумерному массиву. Числа располагаются в матрице по строкам и столбцам. Числа располагаются в матрице по строкам и столбцам. Две матрицы одинакового размера можно поэлементно сложить или вычесть друг их друга. Если число столбцов в первой матрице совпадает с числом строк во второй, то эти две матрицы можно перемножить. У произведения будет столько же строк, сколько в первой матрице, и столько же столбцов, сколько во второй. При умножении матрицы размером 3 х 4 на матрицу размером 4 х 7 мы получаем матрицу размером 3 х 7. Умножение матриц некоммутативно: оба произведения AB и BA двух квадратных матриц одинакового размера можно вычислить, однако результаты, вообще говоря, будут отличаться друг от друга. (Отметим, что умножение чисел коммутативно, и произведения AB и BA двух чисел A и B равны.)
    Для вычисления произведения двух матриц каждая строка первой почленно умножается на каждый столбец второй. Затем подсчитывается сумма таких произведений и записывается в соответствующую клетку результата. На рисунке 1 приведен пример умножения двух матриц.
    Умножение матриц на рисунке 1 требует 24 умножений и 16 сложений. Вообще, стандартный алгоритм умножения матрицы размером a х b на матрицу размером b х c выполняет abc умножений и a(b - 1)c сложений.
    Вот как выглядит стандартный алгоритм умножения матрицы G размером a x b на матрицу H размером b x c. Результат записывается в матрицу R размером a x c.
for i = 1 to a do
	for j = 1 to c do
		R[i, j] =0 
		for k = 1 to b do
			R[i, j] = R[i, j] + G[i, k]*H[k, j]
		end for k
	end for j
end for i	
    На первый взгляд это минимальный объем работы, необходимый для перемножения двух матриц. Однако исследователям не удалось доказать минимальность, и в результате они обнаружили другие алгоритмы, умножающие матрицы более эффективно.

Умножение матриц по Винограду
    Если посмотреть на результат умножения двух матриц, то видно, что каждый элемент в нем представляет собой скалярное произведение соответствующих строки и столбца исходных матриц. Можно заметить также, что такое умножение допускает предварительную обработку, позволяющую часть работы выполнить заранее.
    Рассмотрим два вектора V = (v1, v2, v3, v4) и W = (w1, w2, w3, w4). Их скалярное произведение равно:
V • W = v1w1 + v2w2 + v3w3 + v4w4.
    Это равенство можно переписать в виде:
V • W = (v1 + w2)(v2 + w1) + (v3 + w4)(v4 + w3) - v1v2 - v3v4 - w1w2 - w3w4.
    Вы сами можете без труда проверить эквивалентность двух последних выражений. Кажется, что второе выражение задает больше работы, чем первое: вместо четырех умножений мы насчитываем их шесть, а вместо трех сложений - десять. Менее очевидно, что выражение в правой части последнего равенства допускает предварительную обработку: его части можно вычислить заранее и запомнить для каждой строки первой матрицы и для каждого столбца второй. На практике это означает, что над предварительно обработанными элементами нам придется выполнять лишь первые два умножения и последующие пять сложений, а также дополнительно два сложения.
    Вот как выглядит полный алгоритм Винограда для умножения матрицы G размером a x b на матрицу H размером b x c. Результат записывается в матрицу R размером a x c.

  d = b/2
  // вычисление rowFactors для G
  for i = 1 to a do
  	rowFactor[i] = G[i, 1] * G[i, 2]
	for j = 2 to d do
		rowFactor[i] = rowFactor[i] + G[i, 2j - 1] * G[i, 2j]
	end for j
end for i

// вычисление columnFactors для H for i = 1 to c do columnFactor[i] = H[1, i] * H[2, i] for j = 2 to d do columnFactor[i] = columnFactor[i] + H[2j - 1, i] * H[2j, i] end for j end for i
// вычисление матрицы R for i = 1 to a do for j = 1 to c do R[i, j] = -rowFactor[i] - columnFactor[j] for k = 1 to d do R[i, j]=R[i, j]+(G[i, 2k-1]+H[2k, j])*(G[i, 2k] + H[2k-1, j]) end for k end for j end for i
// прибавление членов в случае нечетной общей размерности if (2 * (b/2) /= b) then for i = 1 to a do for j = 1 to c do R[i, j] = R[i, j] + G[i, b] * H[b, j] end for j end for i end if
Анализ алгоритма Винограда
    Рассмотрим случай четной общей размерности b. Результаты подсчета сложений и умножений сведены в следующую таблицу.

 
Умножения
Сложения
Предварительная обработка матрицы G
ad
a(d - 1)
Предварительная обработка матрицы H
cd
c(d - 1)
Вычисление матрицы R
acd
ac(2d + d + 1)
Всего
(abc + ab + bc)/2
(a(b - 2) + c(b - 2) + ac(3b + 2))/2


Умножение матриц по Штрассену
    Алгоритм Штрассена работает с квадратными матрицами. На самом деле он настолько эффективен, что иногда разумно расширить матрицы до квадратных, и при этом он все равно дает выигрыш, превышающий расходы на введение дополнительных элементов.
    Для умножения матриц алгоритм Штрассена использует семь формул. Эти формулы чрезвычайно неестественны и, к сожалению, в оригинальной статье Штрассена не объясняется, как он пришел к ним. Замечательно, что как сами формулы, так и их использование не требуют, чтобы умножение элементов матриц было коммутативным. Это означает, в частности, что сами эти элементы могут быть матрицами, а значит, алгоритм Штрассена можно применять рекурсивно. Вот формулы Штрассена:


x1 = (G1,1 + G2,2)(H1,1 + H2,2); x5 = (G1,1 + G2,2)H2,2;
x2 = (G2,1 + G2,2)H1,1; x6 = (G2,1 - G1,1)(H1,1 + H1,2);
x3 = G1,1(H1,2 - H2,2); x7 = (G2,1 - G2,2)(H2,1 + H2,2)
x4 = G2,2(H2,1 - H1,1);  

    Теперь элементы матрицы R могут вычисляться по формулам:

R1,1 = x1 + x4 - x5 + x7; R1,2 = x3 + x5;
R2,1 = x2 + x4; R2,2 = x1 + x3 - x2 + x6.

    При умножении двух 2 х 2-матриц алгоритм выполняет 7 умножений и 18 сложений. Экономия не видна: мы получили 14 сложений в обмен на одно умножение в стандартных алгоритмах. Анализ общего случая показывает, что число умножений при перемножении двух N x N-матриц приблизительно равно N2.81, а число сложений 6N2.81 - 6N2. При умножении двух 16 х 16-матриц алгоритм Штрассена экономит 1677 умножений за счет дополнительных 9138 сложений.
    Сводя три результата воедино, мы получаем следующую картину.


 
Умножения
Сложения
Стандартный алгоритм
N3
N3 - N2
Алгоритм Винограда
(N3 + 2N2)/2
(3N3 + 4N2 - 4N)/2
Алгоритм Штрассена
N2.81
6N2.81 - 6N2

    На практике алгоритм Штрассена применяется редко: его использование требует аккуратного отслеживания рекурсии. Важность его состоит в том, что это первый алгоритм, умножение матриц с помощью которого требует менее, чем N3 операций. Работа над повышением эффективности матричного умножения и поиском возможных нижних оценок его сложности продолжается.

Рейтинг@Mail.ru Rambler's Top100
Сайт управляется системой uCoz