'''Задача о порядке перемножения матриц''' — классическая задача , которая может быть решена с помощью динамического программирования, в которой дана последовательность матриц <tex> A_1, A_2, ..., A_n </tex> и требуется минимизировать количество скалярных операций для вычисления их произведения. Матрицы предполагаются совместимыми по отношению к матричному умножению (то есть количество столбцов <tex> A_{i - 1}</tex> совпадает с количеством строк <tex> A_i </tex> матрицы).== Подробное описание задачи ==Произведение матриц — ассоциативная операция. Когда матрицы велики по одному измерению и малы по другому, количество скалярных операций может серьёзно зависеть от порядка перемножений матриц. Допустим, нам даны 3 матрицы <tex> A_1, A_2, A_3 </tex> размерами соответственно <tex> 10 \times 100, 100 \times 5</tex> и <tex>5 \times 50</tex>. Существует 2 способа их перемножения (расстановки скобок): <tex>((A_1A_2)A_3)</tex> и <tex>(A_1(A_2A_3))</tex>. В первом случае этой задаче нам потребуется <tex>10\cdot100\cdot5 + 10\cdot5\cdot50 = 7500</tex> скалярных умножений, а во втором случае <tex>100\cdot5\cdot50 + 10\cdot100\cdot50 = 75000</tex> умножений — разница налицо. Поэтому может оказаться выгоднее потратить некоторое время на предобработку, решив, в каком порядке лучше всего умножать, чем умножать сразу в лоб.Таким образом, даны <tex>n</tex> дана последовательность матриц: <tex>A_1: \, p_0 \times p_1</tex>, <tex>A_2: \, p_1 \times p_2</tex>, …, <tex>A_n: \, p_{n-1} \times p_{n}</tex>. Требуется определить, в каком порядке перемножать их, чтобы количество операций умножения было минимальным.==Динамическое решение=====Сведение задачи к подзадачам ===Обозначим результат которой мы хотим найти самый эффективный способ перемножения матриц <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>
Объясняется оно простоУ нас есть много способов, потому что операция перемножения ассоциативна. Другими словами, нет разницы как мы расставим скобки между множителями, результат будет один и тот же. Например, если у нас есть четыре матрицы ''A'', ''B'', ''C'' и ''D'', то у нас есть следующие варианты: для того:(''ABC'')''D'' = (''AB'')(''CD'') = ''A''(''BCD'') = ''A''(''BC'')''D'' = .... Однако, порядок в котором мы расставим скобки в нашем выражении повлияет на количество простых арифметических операций, которые мы потратим на вычисление ответа, или, другими словами, на ''эффективность''. Например, предположим, что А = (10 × 30), B = (30 × 5), C = (5 × 60). Тогда: :(''AB'')''C'' = (10×30×5) + (10×5×60) = 1500 + 3000 = 4500 операций:''A''(''BC'') = (30×5×60) + (10×30×60) = 9000 + 18000 = 27000 операций. Очевидно, что первый способ гораздо эффективней. Теперь мы поняли, чтобы что нам надо найти произведение оптимальную расстановку скобок в нашем выражении из ''n'' матриц <tex>A_{i. Как это сделать? Мы можем перебрать все расстановки скобок (brute force), но время выполнение этого алгоритма будет эксапаненциально рости от ''n'' количества матриц.j}</tex> при i=j не нужно ничего делать — Решение данной проблемы, как мы увидим — это разбить нашу задачу на подзадачи. Так же мы увидим, что с помощю решения однократного решения подзадачи и есть сама матрица <tex>A_i</tex>повторного использования ответа, мы сможем заметно сократить асимптотику. == Решение динамическим программированием == Сначала, давайте считать то, что мы хотим знать минимальное количесвто операций (или минимальную стоимость), необходимых для перемножения матриц. При нетривиальном случае Если мы перебираем все точки разбиения перемножаем только две матрицы <tex>A_{i, то мы можем осуществить это только едиственным способом, следовательно минимальная стоимость — это стоимость этого перемножения.В общем, мы можем найти минимальную стоимость используя следующий рекурсивный алгоритм: * Взять последовательность матриц и разделить её на две части.j}</tex> * Найти минимальную стоимость перемножения на матрицы <tex>A_{iкаждой подпоследовательности.* Сложить эти две стоимости и прибавить к этому стоимость перемножения двух получившихся матриц.k}</tex> * Сделать это для каждой возможной позиции в последовательности, в которой она может быть разделена и <tex>A_{k+1взять минимум среди всех результатов..j}</tex> Например, если у нас есть четыре матрицы ''ABCD'', мы посчитаем для (''A'')(''BCD''), (''AB'')(''CD''), и (''ABC'')(''D''), ищем кол-во операцийделая рекурсивные вызовы, необходимое чтобы их получить найти минимальную стоимость на ''ABC'', ''AB'', ''CD'', и затем перемножаем для получения матрицы <tex>A_{i''BCD''.Потом среди них мы выбираем лучший вариант.j}</tex>Так же, этот алгоритм дает не только минимальную стоимость, но и показывает наилучший способ перемножения матриц: нужно только сгрупировать тем же образом, каким дается нам минимальная стоимость и перемножить их между собой.(Оно будет равно кол-ву операций Внезапно, если мы применим этот алгоритм, то мы обнаружим, что это так же медленно, потраченное как и наивный способ перебирания всех скобочных последовательностей! Что пошло не так? Ответом на решение подзадач + этот вопрос является то факт, что мы делаем много ненужной работы. Например, в выше описанном алгоритме, мы сделали рекурсивный вызов, чтобы найти наилучшую стоимость умножения матриц <tex>A_{iдля подсчета ''ABC'' и ''AB''.Но нахождение наилучшей стоимости для подсчета ''ABC'' так же требует нахождения лучшей стоимости для ''AB''.k}A_{k+1Так как рекурсия растет вглбь все больше и больше, то и число ненужных повторений увеличивается. Одно из простых решений: ''меморизация''.j}</tex>)Каждый раз, когда мы считаем минимальную стоимость на отрезке, мы сохраняем ответ. СчитаемКогда у нас просят посчитать это ещё раз, что размеры матриц заданы в массиве <tex>p</tex> то мы сразу же выдадим ответ и размер матрицы не будем пересчитывать. Хоть у нас <texmath>A_i<n^2/tex> равен <tex>p_{i-1} \times p_i2 </texmath>. В данном случае рекурсивный метод нельзя использовать напрямую — он будет экспоненциальным Одно из-за большого кол-ва перекрывающихся подзадачпростых решений — это меморизация.=== Динамическое программирование ===Будем Каждый раз, когда мы считаем минимальную стоимость перемножения определенной подпоследовательности, давайте мы будем запоминать в двумерном массиве <tex>m</tex> результаты вычислений для подзадачответ. Если мы когда либо ещё раз захотим посчитать это ещё раз, чтобы избежать пересчета для то мы уже вычислявшихся подзадачбудет иметь ответ и не будем пересчитывать. После вычислений ответ будет в Поскольку существует всего около <texmath>m[1,n]^2/2</texmath>, где ''n'' — это количество матриц, то память занимаемая программой будет не так велика. Можно сказать, что с помощью этого простого трюка мы уменьшили асимптотику алгоритма с O(Сколько перемножений требуется для последовательности матриц от <texmath>12n</texmath> ) до O(<texmath>n^3</texmath> — то есть ответ на поставленную задачу), что является достаточно эффективным для реальных приложений.Сложность алгоритма будет Псевдокод:<texpre>O(n^3) int dp[1000][1000];vector<pair<int, int> > v;/tex>/ v[i].first — размер i-той матрицы по горизонтали // v[i].second — размер i-той матрицы по вертикали// dp[i][j] — меморизация на отрезке [i, j)int matrixChainMultiplication(int l, так как у нас <tex>int r){n \choose 2}< //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>r; i++) dp[l][r] = min(dp[l][r], j : v[l].first * v[i].first * v[r - 1 \le ].second + matrixChainMultiplication(l, i \le j \le n</tex> и <tex>O) + matrixChainMultiplication(Ni, r)); return dp[l][r];}</texpre> точек разделения для каждого варианта.
==Ссылки==
использованы материалы ru.wikipedia.org [http://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B5_%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86]
== Литература ==
* Томас Х. Кормен и др. Алгоритмы: построение и анализ
* Sanjoy Dasgupta , Christos H. Papadimitriou, Umesh Vazirani Algorithms
Также были использованы материалы ru.wikipedia.org [http://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B5_%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86]
[[Категория: Дискретная математика и алгоритмы]]
[[Категория:Динамическое_программирование]]