Изменения

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

Черновик:Перемножение матриц

1295 байт добавлено, 19:41, 4 сентября 2022
м
rollbackEdits.php mass rollback
'''Задача о порядке перемножения матриц''' — классическая задача, которая может быть решена с помощью динамического программирования. В этой задаче нам дана последовательность матриц, в которой мы хотим найти самый эффективный способ перемножения их. На самом деле задача заключается не в нахождении результата перемножения, а просто нужно найти порядокв нахождении нужного порядока, в котором нам надо их перемножитьмы будем перемножать.
У нас есть много способов, потому что перемножение ассоциативнооперация перемножения ассоциативна. Другими словами, нет разницы как мы расставим скобки между множителями, результат будет один и тот же. Например, если у нас есть четыре матрицы ''A'', ''B'', ''C'' и ''D'', то у нас есть следующие варианты:
:(''ABC'')''D'' = (''AB'')(''CD'') = ''A''(''BCD'') = ''A''(''BC'')''D'' = ....
Однако, порядок в котором мы расставим скобки в нашем выражении повлияет на количество простых арифметических операций, которые мы потратим на вычисление ответа, или, другими словами, на ''эффективность''.
Например, предположим, что А = (10 × 30), B = (30 × 5), C = (5 × 60). Тогда:
Очевидно, что первый способ гораздо эффективней. Теперь мы поняли, что нам надо найти оптимальную расстановку скобок в нашем выражении из ''n'' матриц.
Как это сделать? Мы можем перебрать все расстановки скобок (brute force), но время выполнение этого алгоритма будет эксапаненциально рости от ''n'' количества матриц. Решение данной проблемы, как мы увидим — это разбить нашу задачу на подзадачи. С Так же мы увидим, что с помощю решения подзадач по одному разу однократного решения подзадачи и повторного использования решенияответа, мы сможем заметно сократить асимптотику. Эта задача входит в классические задачи
==Динамическое решениеРешение динамическим программированием =====Сведение задачи к подзадачам ===Обозначим результат перемножения матриц <tex>A_iA_{i+1} \ldots A_j</tex> через <tex>A_{i..j}</tex>, где <tex>i \le j</tex>. Если <tex> i<j</tex>, то при любом способе расстановки скобок, последнее выполненное умножение для вычисления <tex>A_{i..j}</tex> между матрицами <tex>A_k</tex> и <tex>A_{k+1}, i \le k<j</tex>, то есть чтобы вычислить <tex>A_{i..j}</tex> надо сначала вычислить <tex>A_{i..k}</tex>, потом <tex>A_{k+1..j}</tex> и затем их перемножить.Заметим, что если способ расстановки скобок оптимален, то расстановка скобок в этих матрицах должна быть оптимальной, иначе если бы существовал более эффективный способ расстановки скобок в матрицах <tex>A_{i..k}</tex> и <tex>A_{k+1..j}</tex>, то мы могли бы получить <tex>A_{i..j}</tex> за меньшее число умножений, получаем противоречие, что расстановка скобок в <tex>A_{i..j}</tex> оптимальна. Таким образом мы свели задачу к подзадачам. Это означает, что возможно решить задачу, используя динамическое программирование на подотрезке.===Рекурсивное решение ===Обозначим через <tex>m[i, j]</tex> минимальное количество скалярных умножений для вычисления матрицы <tex>A_{i..j}</tex>. Получаем следующее рекуррентное соотношение:<tex> m[i,j] = \left \{ \begin{array}{ll} 0, & i=j \\ min(m[i,k] + m[k+1,j] + p_{i-1}p_kp_j | i \le k < j) & i < j \end{array} \right.</tex>
Объясняется оно простоСначала, давайте считать то, что мы хотим знать минимальное количесвто операций (или минимальную стоимость), необходимых для перемножения матриц. Если мы перемножаем только две матрицы, то мы можем осуществить это только едиственным способом, следовательно минимальная стоимость — это стоимость этого перемножения. В общем, мы можем найти минимальную стоимость используя следующий рекурсивный алгоритм:  * Взять последовательность матриц и разделить её на две части.* Найти минимальную стоимость перемножения на каждой подпоследовательности.* Сложить эти две стоимости и прибавить к этому стоимость перемножения двух получившихся матриц.* Сделать это для тогокаждой возможной позиции в последовательности, в которой она может быть разделена и взять минимум среди всех результатов. Например, если у нас есть четыре матрицы ''ABCD'', мы посчитаем для (''A'')(''BCD''), (''AB'')(''CD''), и (''ABC'')(''D''), делая рекурсивные вызовы, чтобы найти произведение матриц <tex>A_{iминимальную стоимость на ''ABC'', ''AB'', ''CD'', и ''BCD''.Потом среди них мы выбираем лучший вариант.j}</tex> при i=j Так же, этот алгоритм дает не только минимальную стоимость, но и показывает наилучший способ перемножения матриц: нужно ничего делать — только сгрупировать тем же образом, каким дается нам минимальная стоимость и перемножить их между собой. Внезапно, если мы применим этот алгоритм, то мы обнаружим, что это так же медленно, как и есть сама матрица <tex>A_i</tex>наивный способ перебирания всех скобочных последовательностей! Что пошло не так? Ответом на этот вопрос является то факт, что мы делаем много ненужной работы. При нетривиальном случае Например, в выше описанном алгоритме, мы перебираем сделали рекурсивный вызов, чтобы найти наилучшую стоимость для подсчета ''ABC'' и ''AB''. Но нахождение наилучшей стоимости для подсчета ''ABC'' так же требует нахождения лучшей стоимости для ''AB''. Так как рекурсия растет вглбь все точки разбиения матрицы <tex>A_{iбольше и больше, то и число ненужных повторений увеличивается. Одно из простых решений: ''меморизация''. Каждый раз, когда мы считаем минимальную стоимость на отрезке, мы сохраняем ответ.Когда у нас просят посчитать это ещё раз, то мы сразу же выдадим ответ и не будем пересчитывать.j}Хоть у нас <math> n^2/tex> на матрицы 2 <tex/math>A_{i Одно из простых решений — это меморизация.Каждый раз, когда мы считаем минимальную стоимость перемножения определенной подпоследовательности, давайте мы будем запоминать ответ.k}</tex> Если мы когда либо ещё раз захотим посчитать это ещё раз, то мы уже будет иметь ответ и не будем пересчитывать. Поскольку существует всего около <texmath>A_{k+1..j}n^2/2</texmath>, ищем кол-во операцийгде ''n'' — это количество матриц, то память занимаемая программой будет не так велика. Можно сказать, необходимое чтобы их получить и затем перемножаем для получения матрицы что с помощью этого простого трюка мы уменьшили асимптотику алгоритма с O(<texmath>A_{i..j}2n</texmath>.) до O(Оно будет равно кол-ву операций, потраченное на решение подзадач + стоимость умножения матриц <texmath>A_{i..k}A_{k+1..j}n^3</texmath>). Считаем, что размеры матриц заданы в массиве является достаточно эффективным для реальных приложений. Псевдокод:<texpre>p int dp[1000][1000];vector<pair<int, int> > v;//tex> и v[i].first — размер i-той матрицы <tex>A_i<по горизонтали //tex> равен <tex>p_{v[i].second — размер i-1} \times p_i<той матрицы по вертикали// dp[i][j] — меморизация на отрезке [i, j)int matrixChainMultiplication(int l, int r){ //l — включая в отрезок //tex>. В данном случае рекурсивный метод нельзя использовать напрямую — он будет экспоненциальным r — исключая из-за большого кол-ва перекрывающихся подзадач.отрезка if dp[l][r] ==-1 if l = Динамическое программирование =r - 1 dp[l][r] =0; else dp[l][r] =1000 * 1000 * 1000;Будем запоминать в двумерном массиве for (int i = l + 1; i <tex>m</tex> результаты вычислений для подзадачr; i++) dp[l][r] = min(dp[l][r], чтобы избежать пересчета для уже вычислявшихся подзадачv[l].first * v[i]. После вычислений ответ будет в <tex>mfirst * v[r - 1,n]</tex>.second + matrixChainMultiplication(Сколько перемножений требуется для последовательности матриц от <tex>1</tex> до <tex>n</tex> — то есть ответ на поставленную задачуl, i).Сложность алгоритма будет <tex>O+ matrixChainMultiplication(n^3i, r)); return dp[l][r];}</texpre*Замечания : Индексирование массива ''p'' начинается с нуля, так как а у нас <tex>{n \choose 2}</tex> вариантов выбора <tex>i, j : 1 \le i \le j \le n</tex> массивов ''m'' и <tex>O(N)</tex> точек разделения для каждого варианта''s'' с единицы.
==Ссылки==
* Томас Х. Кормен и др. Алгоритмы: построение и анализ
* Sanjoy Dasgupta , Christos H. Papadimitriou, Umesh Vazirani Algorithms
 
[[Категория: Дискретная математика и алгоритмы]]
[[Категория:Динамическое_программирование]]
1632
правки

Навигация