dynamic-programming
Умножение матричной цепочки
Поиск…
Рекурсивное решение
Матричное цепное умножение является задачей оптимизации, которая может быть решена с помощью динамического программирования. Учитывая последовательность матриц, цель состоит в том, чтобы найти наиболее эффективный способ умножения этих матриц. Проблема заключается не в том, чтобы выполнять умножения, а просто для определения последовательности задействованных матричных умножений.
Скажем, мы имеем две матрицы A 1 и A 2 размерности m * n и p * q . Из правил умножения матриц мы знаем, что,
- Мы можем умножить A 1 и A 2 тогда и только тогда, когда n = p . Это означает, что количество столбцов A 1 должно быть равно числу строк A 2 .
- Если выполняется первое условие, и мы умножим A 1 и A 2 , получим новую матрицу, назовем ее A 3 размерности m * q .
- Чтобы умножить A 1 и A 2 , нам нужно сделать некоторые умножители масштабирования. Общее число умножителей масштабирования, мы должны выполнить ( m * n * q ) или ( m * p * q ).
- A 1 * A 2 не равно A 2 * A 1 .
Если у нас есть три матрицы A 1 , A 2 и A 3, имеющие размерность m * n , n * p и p * q соответственно, то A 4 = A 1 * A 2 * A 3 будет иметь размерность m * q . Теперь мы можем выполнить это матричное умножение двумя способами:
- Сначала умножим A 1 и A 2 , затем с результатом умножим A 3 . То есть: ( A 1 * A 2 ) * A 3 .
- Сначала умножим A 2 и A 3 , затем с результатом умножим A 1 . То есть: A 1 * ( A 2 * A 3 ).
Вы можете заметить, что порядок умножения остается неизменным, т. Е. Мы не умножаем ( A 1 * A 3 ) * A 2, потому что это может быть неверным. Мы только изменяем круглые скобки, чтобы умножить набор, прежде чем умножать его на оставшиеся. Как мы размещаем эти скобки, важно. Зачем? Скажем, размерность 3 матриц A 1 , A 2 и A 3 составляет 10 * 100 , 100 * 5 , 5 * 50 . Затем,
- Для ( A 1 * A 2 ) * A 3 общее число умножителей масштабирования: ( 10 * 100 * 5 ) + ( 10 * 5 * 50 ) = 7500 раз.
- Для A 1 * ( A 2 * A 3 ) общее число умножителей масштабирования: ( 100 * 5 * 50 ) + ( 10 * 100 * 50 ) = 75000 раз.
Для второго типа число умножителей масштабирования в 10 раз больше числа 1-го типа! Поэтому, если вы можете придумать правильную ориентацию скобок, необходимую для минимизации полного умножения масштабирования, это уменьшит время и память, необходимые для матричного умножения. Именно в этом случае полезно умножение матричной цепочки. Здесь мы не будем беспокоиться о фактическом умножении матриц, мы узнаем только правильный порядок скобок, чтобы число умножения масштабирования было минимизировано. У нас будут матрицы A 1 , A 2 , A 3 ........ A n, и мы выясним минимальное количество умножителей масштабирования, необходимое для их умножения. Будем считать, что данные измерения действительны, т. Е. Удовлетворяет нашему первому требованию для матричного умножения.
Для решения этой проблемы мы будем использовать метод «разделяй и властвуй». Динамическое программирование необходимо из-за общих подзадач. Например: при n = 5 мы имеем 5 матриц A 1 , A 2 , A 3 , A 4 и A 5 . Мы хотим узнать минимальное количество умножений, необходимое для выполнения этого матричного умножения A 1 * A 2 * A 3 * A 4 * A 5 . Из множества способов, давайте сосредоточимся на одном: ( A 1 * A 2 ) * ( A 3 * A 4 * A 5 ).
Для этого мы найдем A left = A 1 * A 2 . Правило = A 3 * A 4 * A 5 . Затем мы узнаем ответ A = A left * A right . Общее количество умножителей масштабирования, необходимых для определения ответа A : = Общее количество умножителей масштабирования, необходимых для определения A left + Общее количество умножителей масштабирования, необходимых для определения A right + Общее число умножений масштабирования, необходимое для определения A left * Правильно .
Последний член. Общее количество умножителей масштабирования, необходимых для определения A left * A right, может быть записано как: Число строк в A left * число столбцов в A left * число столбцов в правой части . (Согласно 2-му правилу умножения матрицы)
Но мы могли бы также скопировать круглые скобки. Например:
- A 1 * ( A 2 * A 3 * A 4 * A 5 )
- ( A 1 * A 2 * A 3 ) * ( A 4 * A 5 )
- ( A 1 * A 2 * A 3 * A 4 ) * A 5 и т. Д.
Для каждого возможного случая мы определим количество умножителей масштабирования, необходимое для нахождения A left и A right , а затем для A left * A right . Если у вас есть общее представление об рекурсии, вы уже поняли, как выполнить эту задачу. Наш алгоритм будет:
- Set parenthesis in all possible ways.
- Recursively solve for the smaller parts.
- Find out the total number of scaler multiplication by merging left and right.
- Of all possible ways, choose the best one.
Почему это проблема динамического программирования? Чтобы определить ( A 1 * A 2 * A 3 ), если вы уже рассчитали ( A 1 * A 2 ), это будет полезно в этом случае.
Чтобы определить состояние этой рекурсии, мы видим, что для решения каждого случая нам нужно знать диапазон матриц, с которыми мы работаем. Поэтому нам нужно начать и кончить . Поскольку мы используем divide и conquer, наш базовый регистр будет иметь менее 2 матриц ( begin > = end ), где нам не нужно вообще размножаться. У нас будет 2 массива: строка и столбец . строка [i] и столбец [i] будут хранить количество строк и столбцов для матрицы A i . Мы будем иметь массив dp [n] [n] для хранения уже вычисленных значений и инициализировать его с помощью -1 , где -1 представляет значение, которое еще не было вычислено. dp [i] [j] представляет собой число умножителей масштабирования, необходимое для умножения A i , A i + 1 , ....., A j включительно. Псевдокод будет выглядеть так:
Procedure matrixChain(begin, end):
if begin >= end
Return 0
else if dp[begin][end] is not equal to -1
Return dp[begin][end]
end if
answer := infinity
for mid from begin to end
operation_for_left := matrixChain(begin, mid)
operation_for_right := matrixChain(mid+1, right)
operation_for_left_and_right := row[begin] * column[mid] * column[end]
total := operation_for_left + operation_for_right + operation_for_left_and_right
answer := min(total, answer)
end for
dp[begin][end] := answer
Return dp[begin][end]
Сложность:
Значение начала и конца может варьироваться от 1 до n . Существует n 2 разных состояния. Для каждого состояния цикл внутри будет выполняться n раз. Общая временная сложность: O(n³)
и сложность памяти: O(n²)
.