Изменения

Перейти к: навигация, поиск

Задача о порядке перемножения матриц

598 байт добавлено, 14:20, 2 ноября 2020
Опечатка
'''{{Задача о порядке перемножения матриц''' (англ. ''chain matrix multiplication'') — классическая задача, которая может быть решена с помощью динамического программирования. Нам дается |definition = Дана последовательность из <tex>n</tex> матриц, в которой мы хотим требуется найти самый эффективный способ их перемножения.}}
У нас есть множество способов перемножитьматрицы, потому что операция перемножения ассоциативна. Другими словами, нет разницы в каком порядке мы расставим расставляются скобки между множителями, результат будет один и тот же. Например, если у нас есть четыре матрицы ''A'', ''B'', ''C'' и ''D'', то существуют следующие варианты::(''ABC'')''D'' = (''AB'')(''CD'') = ''A''(''BCD'') = ''A''(''BC'')''D'' = ....
[[Правильные скобочные последовательности | Расстановок скобок]] достаточно много и их количество очень быстро растет. Точное количество всевозможных вариантов равно <tex>n</tex>–ому [[Числа Каталана | числу Каталана]]. Однако, порядок в котором мы расставим расставляются скобки между матрицами повлияет на количество арифметических операций, которые потребуются на вычисление ответа, или, другими словами, на ''эффективность''.
Например, предположим, что А <tex>\dim{A}= (10 &\times; 30)</tex>, <tex>\dim{B } = (30 &\times; 5)</tex>, <tex>\dim{C } = (5 &\times; 60)</tex>. Тогда:
:Для <tex> (''AB''A \times B)''\times C'' = </tex> будет <tex>(10&times;30&times;5\times30\times5) + (10&times;5&times;60\times5\times60) = 1500 + 3000 = 4500 </tex> операций:''Для <tex> A''\times(''BC''B \times C) = </tex> будет <tex>(30&times;5&times;60\times5\times60) + (10&times;30&times;60\times30\times60) = 9000 + 18000 = 27000 операций. Как мы видим, первый способ гораздо эффективней. Теперь стало понятно, что нам надо найти оптимальную расстановку скобок в нашем выражении из <math>n</mathtex> матриц. Как бы это сделать? Мы можем перебрать все расстановки скобок (brute force), но время выполнения этого алгоритма будет расти экспоненциально от <math>n</math> количества матриц, так как навскидку в каждую позицию мы можем поставить открывающуюся и закрывающуюся скобку, то асимптотика будет равна <math>O(2^n)</math>. Решение данной задачи, как мы увидим — это разбить нашу задачу на подзадачи. Так же мы увидим, что с помощю решения однократного решения подзадачи и повторного использования ответа, мы сможем заметно сократить асимптотикуопераций.
Как мы видим, первый способ гораздо эффективней.
== Решение задачи ==
=== Перебор всех вариантов ===
Сначала, давайте определимся, что мы хотим В данной задаче нужно узнать минимальное количество операций (или минимальную стоимость), необходимых для перемножения матриц. Если мы перемножаем перемножить только две матрицы, то мы можем можно осуществить это едиственным способом, следовательно минимальная стоимость — это стоимость перемножения этих двух матриц. В общем, мы можем можно найти минимальную стоимость используя следующий [[Динамическое программирование | рекурсивный алгоритм]]:
* Взять взять последовательность матриц и разделить её на две части.,* Найти найти минимальную стоимость перемножения на каждой подпоследовательности.,* Сложить сложить эти две стоимости и прибавить к этому стоимость перемножения двух получившихся матриц.,* Сделать сделать это для каждой возможной позиции в последовательности, в которой она может быть разделена и взять минимум среди всех результатов.
НапримерИли другими словами, если у нас есть четыре давайте обозначим через <tex>f(i, j)</tex> минимальное количество скалярных умножений для вычисления матрицы ''ABCD''<tex>M_{i..j}</tex>, то мы посчитаем для получаем следующее рекуррентное соотношение:<tex> f(''A''i,j)= \left \{ \begin{array}{ll} 0, & i=j \\ \min\limits_{i \leqslant k < j}{(''BCD'')f(i, (''AB''k)+ f(''CD'')k+1, и (''ABC''j)(''D''+ p_{i-1}p_kp_j), делая рекурсивные вызовы на отрезках ''ABC'', ''AB'', ''CD'', и ''BCD'', чтобы найти минимальную стоимость. Потом среди них мы выбираем лучший вариант. Так же, этот алгоритм дает не только минимальную стоимость, но и показывает наилучший способ перемножения матриц: нужно только сгрупировать тем же образом матрицы, каким дается нам минимальная стоимость} & i < j \end{array} \right.</tex>
ОднакоОбъясняется оно просто: для того, если чтобы найти произведение матриц <tex>M_{i..j}</tex> при <tex>i=j</tex> не нужно ничего делать — это и есть сама матрица <tex>M_i</tex>. При нетривиальном случае мы применим этот алгоритмперебираем все точки разбиения матрицы <tex>M_{i..j}</tex> на матрицы <tex>M_{i..k}</tex> и <tex>M_{k+1..j}</tex>, то мы обнаружимищем количество операций, что он работает также медленно, как необходимое чтобы их получить и наивный способ перебирания всех скобочных последовательностей! Что пошло не так? Ответом на этот вопрос является то факт, что мы делаем значительное количество ненужной работызатем перемножаем для получения матрицы <tex>M_{i..j}</tex>. Например(Оно будет равно кол-ву операций, в выше описанном алгоритме, мы делали рекурсивный вызов, чтобы найти наилучшую потраченное на решение подзадач + стоимость для подсчета ''ABC'' и ''AB''умножения матриц <tex>M_{i..k}M_{k+1.. Но нахождение наилучшей стоимости для подсчета ''ABC'' так же требует нахождения лучшей стоимости для ''AB''j}</tex>). Так как рекурсия растет вглубь все больше и большеСчитаем, то что размеры матриц заданы в массиве <tex>p</tex> и число ненужных повторений увеличиваетсяразмер матрицы <tex>M_i</tex> равен <tex>p_{i-1} \times p_i</tex>.
=== Оптимизации динамическим программированием ===
Одно из простых решений — это ''мемоизация''. Каждый раз, когда мы считаем минимальную стоимость перемножения определенной подпоследовательности, давайте мы будем запоминать ответ. Если мы когда либо ещё раз захотим посчитать это ещё раз, то мы уже будет иметь ответ и не будем пересчитывать. Поскольку существует всего около <math>O(n^2)</math> подотрезков, где <math>n</math> — это количество матриц, то память занимаемая программой будет не так велика. Можно сказать, что с помощью этого простого трюка мы уменьшили асимптотику алгоритма (перебор) с <math>O(2^n)</math> до <math>O(n^3)</math>, что является достаточно эффективным для реальных приложений.
=== Восстановление ответа ===С помощью вышеописанного алгоритма Чтобы привести пример, давайте вернемся к нашим матрицам. Если у нас есть четыре матрицы <tex>ABCD</tex>, то мы можем восстановить порядокпосчитаем для <tex>(A)(BCD)</tex>, в котором нам необходимо перемножать матрицы<tex>(AB)(CD)</tex>, чтобы достичь минимального количества арифметических операцийи <tex>(ABC)(D)</tex>, затрачиваемых делая рекурсивные вызовы на вычисление ответаотрезках <tex>ABC</tex>, <tex>AB</tex>,<tex>CD</tex>, и <tex>BCD</tex>, чтобы найти минимальную стоимость. Потом среди них выбираем лучший вариант. Когда мы узнаемТак же, этот алгоритм дает не только минимальную стоимость, но и показывает наилучший способ перемножения матриц: нужно только сгрупировать тем же образом матрицы, как каким дается нам нужно разбить отрезок на два подотрезкаминимальная стоимость. Однако, если применить этот алгоритм, то при восстановлении ответа мы заключаем эти два подотрезка(обнаружим, что он работает также медленно, как и наивный способ перебирания всех [[Правильные скобочные последовательности матриц) | скобочных последовательностей]]. Делается значительное количество ненужной работы. Например, в скобки выше описанном алгоритме, осуществляется рекурсивный вызов, чтобы найти наилучшую стоимость для подсчета <tex>ABC</tex> и <tex>AB</tex>. Но нахождение наилучшей стоимости для подсчета <tex>ABC</tex> так же требует нахождения лучшей стоимости для <tex>AB</tex>. Так как рекурсия растет вглубь все больше и передаем получившийся ответ больше, то и число ненужных повторений увеличивается. Итоговая асимптотика, как было сказано выше по рекурсии, равняется <tex>n</tex>–ому [[Числа Каталана | числу Каталана]], да плюс вычисление для каждой [[Правильные скобочные последовательности | правильной скобочной последовательности]] ''затрат'' на перемножение (то есть <tex>O(n \cdot C_n)</tex>). Так как <tex>N</tex>­-ое [[Числа Каталана | число Каталана]] равняется <tex dpi="163"> \frac{1}{n+1}{2 n \choose n} </tex> или асимптотически <tex dpi="163"> \frac{4^n}{n^{3/2}\sqrt{\pi}} </tex>, а это быстро возрастающая функция, нам бы хотелось решение, которое работает быстрее.
=== Псевдокод ===
<pre>
int dp[1000][1000];
int v[];
// dp[i][j] — меморизация на отрезке [i, j)
int matrixChainMultiplication(int l, int r)
{
//l — включая в отрезок
//r — исключая из отрезка
if dp[l][r] == -1 //Если значение динамики не посчитано
if l == r - 1
dp[l][r] = 0; //Если у нас подотрезок длины 1, то количество операций для перемножения равно нулю
else
dp[l][r] = infinity;
for (int i = l + 1; i < r; i++)
dp[l][r] = min(dp[l][r], v[l] * v[i] * v[r - 1] + matrixChainMultiplication(l, i) + matrixChainMultiplication(i, r));
return dp[l][r];
}
</pre>
'''int''' dp[][] <font color="green">// dp[i][j] — ответ на отрезке [i, j)</font> '''int''' v[] <font color= Литература "green">// Массив v[] — хранит все размеры матриц по порядку // Так как у нас размеры соседних матриц по вертикали и горизонтали совпадают, то они занесены в этот массив однократно // l — включая в отрезок, r — исключая из отрезка. Изначально l =0, r =n, где n {{---}} длина последовательности</font> '''int''' matrixChainMultiplication('''int''' l, '''int''' r) '''if''' dp[l][r] == -1 <font color="green">// Если значение динамики не посчитано</font> '''if''' l == r - 1 dp[l][r] = 0 <font color="green"> // Если у нас подотрезок длины 1, то количество операций для перемножения равно нулю</font> '''else''' dp[l][r] = <tex>\infty</tex> '''for''' i = l + 1 '''to''' r - 1 dp[l][r] = min(dp[l][r], v[l] * v[i] * v[r] + matrixChainMultiplication(l, i) + matrixChainMultiplication(i, r)) '''return''' dp[l][r] == См. также == *[[Задача о наибольшей общей подпоследовательности ]]*[[Кратчайший путь в ациклическом графе ]]*[[Задача о расстановке знаков в выражении]]*[[Задача о выводе в контекстно-свободной грамматике, алгоритм Кока-Янгера-Касами | Aлгоритм Кока-Янгера-Касами ]]*[[Правильные скобочные последовательности | Правильные скобочные последовательности ]]== Источники информации == *[http://en.wikipedia.org/wiki/Matrix_chain_multiplication Wikipedia {{---}} Matrix chain multiplication]
* Томас Х. Кормен и др. Алгоритмы: построение и анализ
* Sanjoy Dasgupta , Christos H. Papadimitriou, Umesh Vazirani Algorithms
* Английская википедиа [http://en.wikipedia.org/wiki/Matrix_chain_multiplication Matrix chain multiplication]
[[Категория: Дискретная математика и алгоритмы]]
[[Категория:Динамическое_программирование]]
Анонимный участник

Навигация