Изменения

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

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

1388 байт добавлено, 13:27, 7 февраля 2018
м
Псевдокод
{{Задача|definition = Дана последовательность из <tex>n<//Не окончательный вариант --[[Участник:GosuGDR|GosuGDR]] 07:02tex> матриц, 10 декабря 2011 (MSK)требуется найти самый эффективный способ их перемножения.}}
'''Задача о порядке У нас есть множество способов перемножить матрицы, потому что операция перемножения матриц''' — классическая задача, которая может быть решена с помощью динамического программированияассоциативна. Нам дается последовательность матрицДругими словами, нет разницы в которой мы хотим найти самый эффективный способ их перемножения. На самом деле задача заключается не в нахождении результата перемножениякаком порядке расставляются скобки между множителями, а в нахождении нужного порядка этого перемножениярезультат будет один и тот же.
[[Правильные скобочные последовательности | Расстановок скобок]] достаточно много и их количество очень быстро растет. Точное количество всевозможных вариантов равно <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> (A \times B)\times C</tex> будет <tex>(10\times30\times5) + (10\times5\times60) = 1500 + 3000 = 4500</tex> операций
: Для <tex> A \times(B \times C)</tex> будет <tex>(30\times5\times60) + (10\times30\times60) = 9000 + 18000 = 27000</tex> операций.
 
Как мы видим, первый способ гораздо эффективней.
== Решение задачи ==
== Решение динамическим программированием = Перебор всех вариантов ===
=== Постановка задачи ===У нас есть множество способов перемножитьВ данной задаче нужно узнать минимальное количество операций (или минимальную стоимость), потому что операция необходимых для перемножения ассоциативнаматриц. Другими словамиЕсли перемножить только две матрицы, нет разницы в каком порядке мы расставим скобки между множителямито можно осуществить это едиственным способом, результат будет один и тот жеследовательно минимальная стоимость — это стоимость перемножения этих двух матриц. В общем, можно найти минимальную стоимость используя следующий [[Динамическое программирование | Например, если у нас есть четыре матрицы ''A'', ''B'', ''C'' и ''D'', то существуют следующие вариантырекурсивный алгоритм]]::(''ABC'')''D'' = (''AB'')(''CD'') = ''A''(''BCD'') = ''A''(''BC'')''D'' = ....
Однако, порядок в котором мы расставим скобки между матрицами повлияет * взять последовательность матриц и разделить её на количество арифметических операцийдве части, которые потребуются * найти минимальную стоимость перемножения на вычисление ответакаждой подпоследовательности, или* сложить эти две стоимости и прибавить к этому стоимость перемножения двух получившихся матриц, другими словами, на ''эффективность''. Например* сделать это для каждой возможной позиции в последовательности, предположим, что А = (10 &times; 30), B = (30 &times; 5), C = (5 &times; 60)в которой она может быть разделена и взять минимум среди всех результатов. Тогда:
Или другими словами, давайте обозначим через <tex>f(i, j)</tex> минимальное количество скалярных умножений для вычисления матрицы <tex>M_{i..j}</tex>, то получаем следующее рекуррентное соотношение:<tex> f(''AB''i,j)''C'' = (10\left \{ \begin{array}{ll} 0, &times;30&times;5) + (10&times;5&times;60) = 1500 + 3000 i= 4500 операцийj \\:''A'' \min\limits_{i \leqslant k < j}{(''BC'') = f(30&times;5&times;60i,k) + f(10&times;30&times;60k+1,j) = 9000 + 18000 = 27000 операцийp_{i-1}p_kp_j)} & i < j \end{array} \right.</tex>
Как мы видимОбъясняется оно просто: для того, первый способ гораздо эффективней. Теперь стало понятно, что нам надо чтобы найти оптимальную расстановку скобок в нашем выражении из ''n'' произведение матриц<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>.(brute force)Оно будет равно кол-ву операций, но время выполнения этого алгоритма будет расти экспоненциально от ''n'' количества потраченное на решение подзадач + стоимость умножения матриц<tex>M_{i..k}M_{k+1.. Решение данной задачи, как мы увидим — это разбить нашу задачу на подзадачиj}</tex>). Так же мы увидимСчитаем, что с помощю решения однократного решения подзадачи размеры матриц заданы в массиве <tex>p</tex> и повторного использования ответа, мы сможем заметно сократить асимптотикуразмер матрицы <tex>M_i</tex> равен <tex>p_{i-1} \times p_i</tex>.
=== Перебор всех вариантов ===
СначалаЧтобы привести пример, давайте определимсявернемся к нашим матрицам. Если у нас есть четыре матрицы <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>, в которой она может быть разделена и взять минимум среди всех результатова это быстро возрастающая функциия, нам бы хотелось решение, которое работает быстрее.
Например, если у нас есть четыре матрицы ''ABCD'', то мы посчитаем для (''A'')(''BCD''), (''AB'')(''CD''), и (''ABC'')(''D''), делая рекурсивные вызовы на отрезках ''ABC'', ''AB'', ''CD'', и ''BCD'', чтобы найти минимальную стоимость. Потом среди них мы выбираем лучший вариант. Так же, этот алгоритм дает не только минимальную стоимость, но и показывает наилучший способ перемножения матриц: нужно только сгрупировать тем же образом матрицы, каким дается нам минимальная стоимость.=== Псевдокод ===
Однако, если мы применим этот алгоритм, то мы обнаружим, что он работает также медленно, как и наивный способ перебирания всех скобочных последовательностей! Что пошло не так? Ответом на этот вопрос является то факт, что мы делаем значительное количество ненужной работы. Например, в выше описанном алгоритме, мы делали рекурсивный вызов, чтобы найти наилучшую стоимость для подсчета ''ABC'' и ''AB''. Но нахождение наилучшей стоимости для подсчета ''ABC'' так же требует нахождения лучшей стоимости для ''AB''. Так как рекурсия растет вглубь все больше и больше, то и число ненужных повторений увеличивается.
'''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 <mathfont color="green">n^2/2/ Если значение динамики не посчитано</mathfont>, где '''if'n'' — это количество матрицl == r - 1 dp[l][r] = 0 <font color="green"> // Если у нас подотрезок длины 1, то память занимаемая программой будет не так велика. Можно сказать, что с помощью этого простого трюка мы уменьшили асимптотику алгоритма с O(<math>2nколичество операций для перемножения равно нулю</mathfont>) до O( '''else''' dp[l][r] = <mathtex>n^3\infty</mathtex> '''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]
Псевдокод:<pre>== См. также ==
int dp*[1000][1000Задача о наибольшей общей подпоследовательности ];vector<pair<int, int> > v;// v[i].first — размер i-той матрицы по горизонтали // v*[i].second — размер i-той матрицы по вертикали// 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; else dp[l][r] = 1000 * 1000 * 1000; for (int i = l + 1; i < r; i++) dp[l][r] = min(dp[l][r]Задача о выводе в контекстно-свободной грамматике, v[lалгоритм Кока-Янгера-Касами | Aлгоритм Кока-Янгера-Касами ].first * v[i].first * v[r - 1].second + matrixChainMultiplication(l, i) + matrixChainMultiplication(i, r)); return dp[lПравильные скобочные последовательности | Правильные скобочные последовательности ][r];}</pre>== Источники информации ==
== Литература ==*[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]
[[Категория: Дискретная математика и алгоритмы]]
[[Категория:Динамическое_программирование]]

Навигация