Изменения

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

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

1508 байт убрано, 14:20, 2 ноября 2020
Опечатка
{{Задача
|definition = Дана последовательность из <tex>n</tex> матриц, в которой требуется найти самый эффективный способ их перемножения.
}}
У нас есть множество способов перемножить матрицы, потому что операция перемножения ассоциативна. Другими словами, нет разницы в каком порядке расставляются скобки между множителями, результат будет один и тот же.
[[Правильные скобочные последовательности | Расстановок скобок]] достаточно много и их количество очень быстро растет. Точное количество всевозможных вариантов равно <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>\dim{(A}\times\dim{B})\times\dim{C} = </tex> будет <tex>(10\times30\times5) + (10\times5\times60) = 1500 + 3000 = 4500</tex> операций:Для <tex>\dim{A}\times(\dim{B}\times\dim{C}) = </tex> будет <tex>(30\times5\times60) + (10\times30\times60) = 9000 + 18000 = 27000</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>, что является а это быстро возрастающей функциейвозрастающая функция, нам бы хотелось решение, которое работает быстрее.
=== Оптимизация динамическим программированием Псевдокод ===Одна из простых оптимизаций — это запоминание уже посчитанных значений. Каждый раз, когда считаем минимальную стоимость перемножения определенной подпоследовательности, давайте запоминать ответ. Если мы когда либо захотим посчитать это ещё раз, то уже будет иметь ответ и не будем пересчитывать. Поскольку существует всего <tex>O(n^2)</tex> подотрезков, где <tex>n</tex> — это количество матриц, то память занимаемая программой будет не так велика. С помощью запоминания ответа мы уменьшили асимптотику алгоритма (перебор) с <tex>O(n \cdot C_n)</tex> до <tex>O(n^3)</tex>, что является достаточно эффективным для реальных приложений.
=== Восстановление ответа ===
С помощью вышеописанного алгоритма можно восстановить порядок, в котором нам необходимо перемножать матрицы, чтобы достичь минимального количества арифметических операций, затрачиваемых на вычисление ответа. Когда узнаем, как нам нужно разбить отрезок на два подотрезка, то при восстановлении ответа заключаем эти два подотрезка(последовательности матриц) в скобки и передаем получившийся ответ выше по рекурсии.
 
=== Псевдокод ===
<code> '''int''' dp[][] '''int''' v[] <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) <font color="green">//l — включая в отрезок //r — исключая из отрезка</font> '''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] = infinity<tex>\infty</tex>
'''for''' i = l + 1 '''to''' r - 1
dp[l][r] = min(dp[l][r], v[l] * v[i] * v[r - 1] + matrixChainMultiplication(l, i) + matrixChainMultiplication(i, r)) '''return ''' dp[l][r]</code>
== См. также ==
*[[Числа Каталана]]
*[[Динамическое программирование | Принцип оптимальности на подотрезках]]
*[[Задача о наибольшей общей подпоследовательности ]]
*[[Кратчайший путь в ациклическом графе ]]
*[[Задача о расстановке знаков в выражении]]
*[[Задача о выводе в контекстно-свободной грамматике, алгоритм Кока-Янгера-Касами | Aлгоритм Кока-Янгера-Касами ]]
*[[Правильные скобочные последовательности | Правильные скобочные последовательности ]]
== Источники информации ==
Анонимный участник

Навигация