Изменения

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

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

1088 байт добавлено, 13:27, 7 февраля 2018
м
Псевдокод
'''{{Задача о порядке перемножения матриц''' — классическая задача, которая может быть решена с помощью динамического программирования. В этой задаче нам дана |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 </tex> операций.
ОчевидноКак мы видим, что первый способ гораздо эффективней. Теперь мы поняли, что нам надо найти оптимальную расстановку скобок в нашем выражении из ''n'' матриц. Как это сделать? Мы можем перебрать все расстановки скобок (brute force), но время выполнение этого алгоритма будет эксапаненциально рости от ''n'' количества матриц. Решение данной проблемы, как мы увидим — это разбить нашу задачу на подзадачи. Так же мы увидим, что с помощю решения однократного решения подзадачи и повторного использования ответа, мы сможем заметно сократить асимптотику.
== Решение динамическим программированием задачи ==
Сначала, давайте считать то, что мы хотим знать минимальное количесвто операций (или минимальную стоимость), необходимых для перемножения матриц. Если мы перемножаем только две матрицы, то мы можем осуществить это только едиственным способом, следовательно минимальная стоимость — это стоимость этого перемножения. В общем, мы можем найти минимальную стоимость используя следующий рекурсивный алгоритм:=== Перебор всех вариантов ===
* Взять последовательность матриц и разделить её на две части.* Найти В данной задаче нужно узнать минимальное количество операций (или минимальную стоимость ), необходимых для перемножения на каждой подпоследовательностиматриц.* Сложить эти Если перемножить только две стоимости и прибавить к этому матрицы, то можно осуществить это едиственным способом, следовательно минимальная стоимость — это стоимость перемножения этих двух получившихся матриц.* Сделать это для каждой возможной позиции в последовательностиВ общем, в которой она может быть разделена и взять минимум среди всех результатов.можно найти минимальную стоимость используя следующий [[Динамическое программирование | рекурсивный алгоритм]]:
Например, если у нас есть четыре матрицы ''ABCD'', мы посчитаем для (''A'')(''BCD''), (''AB'')(''CD''), * взять последовательность матриц и (''ABC'')(''D'')разделить её на две части, делая рекурсивные вызовы, чтобы * найти минимальную стоимость перемножения на ''ABC'', ''AB'', ''CD''каждой подпоследовательности, * сложить эти две стоимости и ''BCD''. Потом среди них мы выбираем лучший вариант. Так же, этот алгоритм дает не только минимальную прибавить к этому стоимость, но и показывает наилучший способ перемножения двух получившихся матриц: нужно только сгрупировать тем же образом, каким дается нам минимальная стоимость * сделать это для каждой возможной позиции в последовательности, в которой она может быть разделена и перемножить их между собойвзять минимум среди всех результатов.
ВнезапноИли другими словами, если мы применим этот алгоритмдавайте обозначим через <tex>f(i, то мы обнаружимj)</tex> минимальное количество скалярных умножений для вычисления матрицы <tex>M_{i..j}</tex>, что это так же медленно, как и наивный способ перебирания всех скобочных последовательностей! Что пошло не так? Ответом на этот вопрос является то фактполучаем следующее рекуррентное соотношение:<tex> f(i, что мы делаем много ненужной работы. Напримерj) = \left \{ \begin{array}{ll} 0, в выше описанном алгоритме& i=j \\ \min\limits_{i \leqslant k < j}{(f(i, мы сделали рекурсивный вызовk) + f(k+1, чтобы найти наилучшую стоимость для подсчета ''ABC'' и ''AB''. Но нахождение наилучшей стоимости для подсчета ''ABC'' так же требует нахождения лучшей стоимости для ''AB''. Так как рекурсия растет вглбь все больше и больше, то и число ненужных повторений увеличиваетсяj) + p_{i-1}p_kp_j)} & 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>.(Оно будет равно кол-ву операций, потраченное на решение подзадач + стоимость умножения матриц <tex>M_{i..k}M_{k+1..j}</tex>). Когда у нас просят посчитать это ещё разСчитаем, то мы сразу же выдадим ответ что размеры матриц заданы в массиве <tex>p</tex> и не будем пересчитывать. Хоть у нас размер матрицы <mathtex> n^2M_i</2 tex> равен <tex>p_{i-1} \times p_i</mathtex>.
Одно из простых решений — это меморизация. Каждый раз, когда мы считаем минимальную стоимость перемножения определенной подпоследовательности, давайте мы будем запоминать ответ. Если мы когда либо ещё раз захотим посчитать это ещё раз, то мы уже будет иметь ответ и не будем пересчитывать. Поскольку существует всего около <math>n^2/2</math>, где ''n'' — это количество матриц, то память занимаемая программой будет не так велика. Можно сказать, что с помощью этого простого трюка мы уменьшили асимптотику алгоритма с O(<math>2n</math>) до O(<math>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<pre/tex>, чтобы найти минимальную стоимость. Потом среди них выбираем лучший вариант. Так же, этот алгоритм дает не только минимальную стоимость, но и показывает наилучший способ перемножения матриц: нужно только сгрупировать тем же образом матрицы, каким дается нам минимальная стоимость.
int dpОднако, если применить этот алгоритм, то обнаружим, что он работает также медленно, как и наивный способ перебирания всех [[1000Правильные скобочные последовательности | скобочных последовательностей][1000];vector. Делается значительное количество ненужной работы. Например, в выше описанном алгоритме, осуществляется рекурсивный вызов, чтобы найти наилучшую стоимость для подсчета <pairtex>ABC<int, int/tex> и <tex> v;AB<// v[i]tex>.first — размер i-той матрицы по горизонтали Но нахождение наилучшей стоимости для подсчета <tex>ABC</tex> так же требует нахождения лучшей стоимости для <tex>AB</ v[i]tex>. Так как рекурсия растет вглубь все больше и больше, то и число ненужных повторений увеличивается.second — размер i-той матрицы по вертикалиИтоговая асимптотика, как было сказано выше, равняется <tex>n<// dptex>–ому [[iЧисла Каталана | числу Каталана]], да плюс вычисление для каждой [[jПравильные скобочные последовательности | правильной скобочной последовательности]] — меморизация ''затрат'' на отрезке [i, j)int matrixChainMultiplicationперемножение (то есть <tex>O(int l, int rn \cdot C_n){ </tex>). Так как <tex>N</l — включая в отрезок //r — исключая из отрезка if dp[l][r] == -1 if l == r tex>­- 1 dp[l]ое [r] = 0; else dp[lЧисла Каталана | число Каталана][r] равняется <tex dpi= 1000 * 1000 * 1000; for (int i = l "163"> \frac{1}{n+ 1; i }{2 n \choose n} < r; i++) dp[l][r] /tex> или асимптотически <tex dpi= min(dp[l][r], v[l].first * v[i].first * v[r - 1].second + matrixChainMultiplication(l, i) + matrixChainMultiplication(i, r)); return dp[l][r];"163"> \frac{4^n}{n^{3/2}\sqrt{\pi}}</pretex>, а это быстро возрастающая функциия, нам бы хотелось решение, которое работает быстрее.
== Литература =Псевдокод ===   '''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
Также были использованы материалы 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]
[[Категория: Дискретная математика и алгоритмы]]
[[Категория:Динамическое_программирование]]

Навигация