Задача о порядке перемножения матриц — различия между версиями
GosuGDR (обсуждение | вклад) |
GosuGDR (обсуждение | вклад) |
||
Строка 1: | Строка 1: | ||
//Не окончательный вариант --[[Участник:GosuGDR|GosuGDR]] 07:02, 10 декабря 2011 (MSK) | //Не окончательный вариант --[[Участник:GosuGDR|GosuGDR]] 07:02, 10 декабря 2011 (MSK) | ||
− | '''Задача о порядке перемножения матриц''' — классическая задача, которая может быть решена с помощью динамического программирования. | + | '''Задача о порядке перемножения матриц''' — классическая задача, которая может быть решена с помощью динамического программирования. Нам дается последовательность матриц, в которой мы хотим найти самый эффективный способ их перемножения. На самом деле задача заключается не в нахождении результата перемножения, а в нахождении нужного порядка этого перемножения. |
− | У нас есть | + | |
+ | |||
+ | == Решение динамическим программированием == | ||
+ | |||
+ | === Постановка задачи === | ||
+ | У нас есть множество способов перемножить, потому что операция перемножения ассоциативна. Другими словами, нет разницы в каком порядке мы расставим скобки между множителями, результат будет один и тот же. Например, если у нас есть четыре матрицы ''A'', ''B'', ''C'' и ''D'', то существуют следующие варианты: | ||
:(''ABC'')''D'' = (''AB'')(''CD'') = ''A''(''BCD'') = ''A''(''BC'')''D'' = .... | :(''ABC'')''D'' = (''AB'')(''CD'') = ''A''(''BCD'') = ''A''(''BC'')''D'' = .... | ||
− | Однако, порядок в котором мы расставим скобки | + | Однако, порядок в котором мы расставим скобки между матрицами повлияет на количество арифметических операций, которые потребуются на вычисление ответа, или, другими словами, на ''эффективность''. |
Например, предположим, что А = (10 × 30), B = (30 × 5), C = (5 × 60). Тогда: | Например, предположим, что А = (10 × 30), B = (30 × 5), C = (5 × 60). Тогда: | ||
Строка 13: | Строка 18: | ||
:''A''(''BC'') = (30×5×60) + (10×30×60) = 9000 + 18000 = 27000 операций. | :''A''(''BC'') = (30×5×60) + (10×30×60) = 9000 + 18000 = 27000 операций. | ||
− | + | Как мы видим, первый способ гораздо эффективней. Теперь стало понятно, что нам надо найти оптимальную расстановку скобок в нашем выражении из ''n'' матриц. | |
− | Как это сделать? Мы можем перебрать все расстановки скобок (brute force), но время | + | Как бы это сделать? Мы можем перебрать все расстановки скобок (brute force), но время выполнения этого алгоритма будет расти экспоненциально от ''n'' количества матриц. Решение данной задачи, как мы увидим — это разбить нашу задачу на подзадачи. Так же мы увидим, что с помощю решения однократного решения подзадачи и повторного использования ответа, мы сможем заметно сократить асимптотику. |
− | == | + | === Перебор всех вариантов === |
Сначала, давайте считать то, что мы хотим знать минимальное количесвто операций (или минимальную стоимость), необходимых для перемножения матриц. Если мы перемножаем только две матрицы, то мы можем осуществить это только едиственным способом, следовательно минимальная стоимость — это стоимость этого перемножения. В общем, мы можем найти минимальную стоимость используя следующий рекурсивный алгоритм: | Сначала, давайте считать то, что мы хотим знать минимальное количесвто операций (или минимальную стоимость), необходимых для перемножения матриц. Если мы перемножаем только две матрицы, то мы можем осуществить это только едиственным способом, следовательно минимальная стоимость — это стоимость этого перемножения. В общем, мы можем найти минимальную стоимость используя следующий рекурсивный алгоритм: | ||
Строка 28: | Строка 33: | ||
Внезапно, если мы применим этот алгоритм, то мы обнаружим, что это так же медленно, как и наивный способ перебирания всех скобочных последовательностей! Что пошло не так? Ответом на этот вопрос является то факт, что мы делаем много ненужной работы. Например, в выше описанном алгоритме, мы сделали рекурсивный вызов, чтобы найти наилучшую стоимость для подсчета ''ABC'' и ''AB''. Но нахождение наилучшей стоимости для подсчета ''ABC'' так же требует нахождения лучшей стоимости для ''AB''. Так как рекурсия растет вглбь все больше и больше, то и число ненужных повторений увеличивается. | Внезапно, если мы применим этот алгоритм, то мы обнаружим, что это так же медленно, как и наивный способ перебирания всех скобочных последовательностей! Что пошло не так? Ответом на этот вопрос является то факт, что мы делаем много ненужной работы. Например, в выше описанном алгоритме, мы сделали рекурсивный вызов, чтобы найти наилучшую стоимость для подсчета ''ABC'' и ''AB''. Но нахождение наилучшей стоимости для подсчета ''ABC'' так же требует нахождения лучшей стоимости для ''AB''. Так как рекурсия растет вглбь все больше и больше, то и число ненужных повторений увеличивается. | ||
+ | |||
+ | === Динамическое программирование === | ||
Одно из простых решений: ''меморизация''. Каждый раз, когда мы считаем минимальную стоимость на отрезке, мы сохраняем ответ. Когда у нас просят посчитать это ещё раз, то мы сразу же выдадим ответ и не будем пересчитывать. Хоть у нас <math> n^2/2 </math> | Одно из простых решений: ''меморизация''. Каждый раз, когда мы считаем минимальную стоимость на отрезке, мы сохраняем ответ. Когда у нас просят посчитать это ещё раз, то мы сразу же выдадим ответ и не будем пересчитывать. Хоть у нас <math> n^2/2 </math> |
Версия 07:42, 10 декабря 2011
//Не окончательный вариант --GosuGDR 07:02, 10 декабря 2011 (MSK)
Задача о порядке перемножения матриц — классическая задача, которая может быть решена с помощью динамического программирования. Нам дается последовательность матриц, в которой мы хотим найти самый эффективный способ их перемножения. На самом деле задача заключается не в нахождении результата перемножения, а в нахождении нужного порядка этого перемножения.
Содержание
Решение динамическим программированием
Постановка задачи
У нас есть множество способов перемножить, потому что операция перемножения ассоциативна. Другими словами, нет разницы в каком порядке мы расставим скобки между множителями, результат будет один и тот же. Например, если у нас есть четыре матрицы 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 матриц. Как бы это сделать? Мы можем перебрать все расстановки скобок (brute force), но время выполнения этого алгоритма будет расти экспоненциально от n количества матриц. Решение данной задачи, как мы увидим — это разбить нашу задачу на подзадачи. Так же мы увидим, что с помощю решения однократного решения подзадачи и повторного использования ответа, мы сможем заметно сократить асимптотику.
Перебор всех вариантов
Сначала, давайте считать то, что мы хотим знать минимальное количесвто операций (или минимальную стоимость), необходимых для перемножения матриц. Если мы перемножаем только две матрицы, то мы можем осуществить это только едиственным способом, следовательно минимальная стоимость — это стоимость этого перемножения. В общем, мы можем найти минимальную стоимость используя следующий рекурсивный алгоритм:
- Взять последовательность матриц и разделить её на две части.
- Найти минимальную стоимость перемножения на каждой подпоследовательности.
- Сложить эти две стоимости и прибавить к этому стоимость перемножения двух получившихся матриц.
- Сделать это для каждой возможной позиции в последовательности, в которой она может быть разделена и взять минимум среди всех результатов.
Например, если у нас есть четыре матрицы ABCD, мы посчитаем для (A)(BCD), (AB)(CD), и (ABC)(D), делая рекурсивные вызовы, чтобы найти минимальную стоимость на ABC, AB, CD, и BCD. Потом среди них мы выбираем лучший вариант. Так же, этот алгоритм дает не только минимальную стоимость, но и показывает наилучший способ перемножения матриц: нужно только сгрупировать тем же образом, каким дается нам минимальная стоимость и перемножить их между собой.
Внезапно, если мы применим этот алгоритм, то мы обнаружим, что это так же медленно, как и наивный способ перебирания всех скобочных последовательностей! Что пошло не так? Ответом на этот вопрос является то факт, что мы делаем много ненужной работы. Например, в выше описанном алгоритме, мы сделали рекурсивный вызов, чтобы найти наилучшую стоимость для подсчета ABC и AB. Но нахождение наилучшей стоимости для подсчета ABC так же требует нахождения лучшей стоимости для AB. Так как рекурсия растет вглбь все больше и больше, то и число ненужных повторений увеличивается.
Динамическое программирование
Одно из простых решений: меморизация. Каждый раз, когда мы считаем минимальную стоимость на отрезке, мы сохраняем ответ. Когда у нас просят посчитать это ещё раз, то мы сразу же выдадим ответ и не будем пересчитывать. Хоть у нас
Одно из простых решений — это меморизация. Каждый раз, когда мы считаем минимальную стоимость перемножения определенной подпоследовательности, давайте мы будем запоминать ответ. Если мы когда либо ещё раз захотим посчитать это ещё раз, то мы уже будет иметь ответ и не будем пересчитывать. Поскольку существует всего около
, где n — это количество матриц, то память занимаемая программой будет не так велика. Можно сказать, что с помощью этого простого трюка мы уменьшили асимптотику алгоритма с O( ) до O( ), что является достаточно эффективным для реальных приложений.Псевдокод:
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].first * v[i].first * v[r - 1].second + matrixChainMultiplication(l, i) + matrixChainMultiplication(i, r)); return dp[l][r]; }
Литература
- Томас Х. Кормен и др. Алгоритмы: построение и анализ
- Sanjoy Dasgupta , Christos H. Papadimitriou, Umesh Vazirani Algorithms
Также были использованы материалы ru.wikipedia.org [1]