Черновик:Перемножение матриц — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Динамическое решение)
(A Dynamic Programming Algorithm)
Строка 14: Строка 14:
 
Как это сделать? Мы можем перебрать все расстановки скобок (brute force), но время выполнение этого алгоритма будет эксапаненциально рости от ''n'' количества матриц. Решение данной проблемы, как мы увидим — это разбить нашу задачу на подзадачи. С помощю решения подзадач по одному разу и повторного использования решения, мы сможем заметно сократить асимптотику. Эта задача входит в классические задачи  
 
Как это сделать? Мы можем перебрать все расстановки скобок (brute force), но время выполнение этого алгоритма будет эксапаненциально рости от ''n'' количества матриц. Решение данной проблемы, как мы увидим — это разбить нашу задачу на подзадачи. С помощю решения подзадач по одному разу и повторного использования решения, мы сможем заметно сократить асимптотику. Эта задача входит в классические задачи  
  
== A Dynamic Programming Algorithm ==
+
== Решение динамическим программированием ==
  
To begin, let's assume that all we really want to know is the minimum cost, or minimum number of arithmetic operations, needed to multiply out the matrices. If we're only multiplying two matrices, there's only one way to multiply them, so the minimum cost is the cost of doing this. In general, we can find the minimum cost using the following [[recursion|recursive algorithm]]:
+
Сначала, давайте считать то, что мы реально хотим знать минимальную стоимость или минимальное количесвто операций, необходимых для перемножения матриц. Если мы перемножаем только две матрицы, то существует едиственный способ сделать это, следовательно минимальная стоимость это стоимость этого перемножения. В общем, мы моежм найти минимальную стоимость испозльуя следующий рекурсивный алгоритм:
  
* Take the sequence of matrices and separate it into two subsequences.
+
* Взять последовательность матриц и разделить её на две части.
* Find the minimum cost of multiplying out each subsequence.
+
* Найти минимальную стоимость перемножения на каждой подпоследовательности.
* Add these costs together, and add in the cost of multiplying the two result matrices.
+
* Сложить эти две стоимости и прибавить к этому стоимость перемножения двух получившихся матриц.
* Do this for each possible position at which the sequence of matrices can be split, and take the minimum over all of them.
+
* Сделать это для каждой возможной позиции в последовательности, в которой она может быть разделена и взять минимум среди всех результатов.
  
For example, if we have four matrices ''ABCD'', we compute the cost required to find each of (''A'')(''BCD''), (''AB'')(''CD''), and (''ABC'')(''D''), making recursive calls to find the minimum cost to compute ''ABC'', ''AB'', ''CD'', and ''BCD''. We then choose the best one. Better still, this yields not only the minimum cost, but also demonstrates the best way of doing the multiplication: just group it the way that yields the lowest total cost, and do the same for each factor.
+
Для пример, если у нас есть четыре матрицы ''ABCD'', мы посчитаем для (''A'')(''BCD''), (''AB'')(''CD''), и (''ABC'')(''D''), делая рекурсивные вызовы, чтобы найти минимальную стоимость на ''ABC'', ''AB'', ''CD'', и ''BCD''. Потом мы выбираем лучший вариант. Так же, этот алгоритм дает не только минимальную стоимость, но и показывает наилучший способ перемножить матрицы: нужно только сгрупировать тем же образом, каким дается нам минимальная стоимость и сделать тоже самое для каждого множителя.
  
Unfortunately, if we implement this algorithm we discover that it's just as slow as the naive way of trying all permutations! What went wrong? The answer is that we're doing a lot of redundant work. For example, above we made a recursive call to find the best cost for computing both ''ABC'' and ''AB''. But finding the best cost for computing ABC also requires finding the best cost for ''AB''. As the recursion grows deeper, more and more of this type of unnecessary repetition occurs.
+
Внезапно, если мы применим этот алгоритм, то мы обнаружим, что это так же медленно, как и наивный способ перебирания всех скобочных последовательностей! Что пошло не так? Ответом является то, что мы делаем много ненужной работы. Например, выше мы сделали рекурсивный вызов, чтобы найти наилучшую стоимость для подсчета ''ABC'' и ''AB''. Но нахождение лучшей стоимости для подсчета ''ABC'' так же требует нахождения лучшей стоимости для ''AB''. Так как рекурсия растет вглбь все больше и больше, то и число ненужных повторений увеличивается.
  
One simple solution is called [[memoization]]: each time we compute the minimum cost needed to multiply out a specific subsequence, we save it. If we are ever asked to compute it again, we simply give the saved answer, and do not recompute it. Since there are about ''n''<sup>2</sup>/2 different subsequences, where ''n'' is the number of matrices, the space required to do this is reasonable. It can be shown that this simple trick brings the runtime down to O(''n''<sup>3</sup>) from O(2<sup>''n''</sup>), which is more than efficient enough for real applications. This is [[Top-down and bottom-up design|top-down]] dynamic programming.
 
  
 
Pseudocode (without memoization):
 
Pseudocode (without memoization):

Версия 06:09, 9 ноября 2011

Задача о порядке перемножения матриц — классическая задача, которая может быть решена с помощью динамического программирования. В этой задаче нам последовательность матриц, в которой мы хотим найти самый эффективный способ перемножения их. На самом деле задача заключается не в нахождении результата перемножения, а просто нужно найти порядок, в котором нам надо их перемножить.

У нас есть много способов, потому что перемножение ассоциативно. Другими словами, нет разницы как мы расставим скобки между множителями, результат будет один и тот же. Например, если у нас есть четыре матрицы 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. Так как рекурсия растет вглбь все больше и больше, то и число ненужных повторений увеличивается.


Pseudocode (without memoization):

// Matrix Ai has dimension p[i-1] x p[i] for i = 1..n
Matrix-Chain-Order(int p[])
{
    // length[p] = n + 1
    n = p.length - 1;
    // m[i,j] = Minimum number of scalar multiplications (i.e., cost)
    // needed to compute the matrix A[i]A[i+1]...A[j] = A[i..j]
    // cost is zero when multiplying one matrix
    for (i = 1; i <= n; i++) 
       m[i,i] = 0;

    for (L=2; L<=n; L++) { // L is chain length
        for (i=1; i<=n-L+1; i++) {
            j = i+L-1;
            m[i,j] = MAXINT;
            for (k=i; k<=j-1; k++) {
                // q = cost/scalar multiplications
                q = m[i,k] + m[k+1,j] + p[i-1]*p[k]*p[j];
                if (q < m[i,j]) {
                    m[i,j] = q;
                    // s[i,j] = Second auxiliary table that stores k
                    // k      = Index that achieved optimal cost
                    s[i,j] = k;
                }
            }
        }
    }
}
  • Note : The first index for p is 0 and the first index for m and s is 1

Another solution is to anticipate which costs we will need and precompute them. It works like this:

  • For each k from 2 to n, the number of matrices:
    • Compute the minimum costs of each subsequence of length k, using the costs already computed.

When we're done, we have the minimum cost for the full sequence. Although it also requires O(n3) time, this approach has the practical advantages that it requires no recursion, no testing if a value has already been computed, and we can save space by throwing away some of the subresults that are no longer needed. This is bottom-up dynamic programming: a second way by which this problem can be solved.

Ссылки

использованы материалы ru.wikipedia.org [1]

Литература

  • Томас Х. Кормен и др. Алгоритмы: построение и анализ
  • Sanjoy Dasgupta , Christos H. Papadimitriou, Umesh Vazirani Algorithms