【算法题解】 矩阵乘法计算量估算

427 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

描述

矩阵乘法的运算量与矩阵乘法的顺序强相关。 例如:

A是一个50×10的矩阵,B是10×20的矩阵,C是20×5的矩阵

计算ABC有两种顺序:((AB)C)或者(A(BC)),前者需要计算15000次乘法,后者只需要3500次。

编写程序计算不同的计算顺序需要进行的乘法次数。

数据范围:矩阵个数:1≤n≤15 ,行列数:1≤rowi,coli≤100 ,保证给出的字符串表示的计算顺序唯一。

进阶:时间复杂度:O(n) ,空间复杂度:O(n) 

输入描述:

输入多行,先输入要计算乘法的矩阵个数n,每个矩阵的行数,列数,总共2n的数,最后输入要计算的法则
计算的法则为一个字符串,仅由左右括号和大写字母('A'~'Z')组成,保证括号是匹配的且输入合法!

输出描述:

输出需要进行的乘法次数

示例1

输入:

3
50 10
10 20
20 5
(A(BC))

输出:

3500

题目的主要信息:

  • 编写程序计算不同的计算顺序时矩阵乘法需要进行的乘法次数
  • 计算顺序由字符串给出,A-Z的大写字母表示矩阵,括号决定运算顺序,每次运算都有括号
  • 比如 ( ( A B ) C ) 或者 ( A ( B C ) )
  • 进阶要求:时间复杂度O(n)O(n),空间复杂度O(n)O(n)

方法一:递归

具体做法:

输入的运算规则中,字母与矩阵一一对应,如果没有括号,就是依次从左到右计算,然后结果新矩阵存在靠右的矩阵的位置,继续往右计算。加了括号之后,我们还是可以这样,只需要把括号中的问题当成子问题进入递归即可。

遍历运算法则字符串,如果是左括号我们进入括号以后开始遍历到右边,每次遇到一个左括号累增一个层数,遇到一个右括号递减一个层数,层数为0则最后的右括号刚好匹配了最左端的左括号,则两个括号之间就是子问题,可以送入函数递归,递归的结果我们返回括号计算后的矩阵,方便与其他部分再计算,我们用一个数组记录每一轮递归中计算的矩阵(事实上,数组中只会有一个矩阵),如果此时该数组为空,则递归的结果送入数组,否则与数组中的元素计算,并更新数组为计算后的矩阵。

如果遇到非括号,意味着遇到矩阵,按照上述说法要么加入空数组中,要么与数组中的矩阵计算后更新。

如果遇到右括号,则代表一个子问题结束,返回数组记录的矩阵,最终累加的次数就是需要计算的乘法量。

#include<iostream>
#include<stack>
#include<string>
#include<vector>
using namespace std;

pair<int, int> compute(string s, vector<pair<int, int>>& matrix, int& count){
    vector<pair<int, int> > st; //记录要计算的矩阵
    for(int i = 1; i < s.length();){
        if(s[i] == '('){ //进入左括号
            int layer = 0; //统计左括号层数
            int j = i;
            while(j <= s.length()){ //遍历到右边
                if(s[j] == '(')
                    layer++; //遇到左括号,层数累加
                else if(s[j] == ')'){
                    layer--; //遇到右括号层数递减
                    if(layer == 0) //直到层数为0
                        break;
                }
                j++;
            }
            auto mat = compute(s.substr(i, j - i + 1), matrix, count);//递归计算括号中的部分
            i = j + 1;
            if(st.empty()) //st为空,进入到st中
                st.push_back(mat);
            else{ //否则与st中元素计算,并更新结果矩阵的大小
                count += st[0].first * st[0].second * mat.second;
                st[0] = make_pair(st[0].first, mat.second);
            }
        }else if(s[i] == ')') //该子递归结束
            return st[0];
        else{ //非括号意味着是矩阵
            if(st.empty()) //st为空,进入到st中
                st.push_back(matrix[s[i] - 'A']);
            else{ //否则与st中元素计算,并更新结果矩阵的大小
                auto cur = matrix[s[i] - 'A']; 
                count += st[0].first * st[0].second * cur.second;
                st[0] = make_pair(st[0].first, cur.second);
            }
            i++;
        }
    }
    return matrix[0]; //没有实际作用,函数必要的返回语句
}

int main(){
    int n;
    while(cin >> n){
        vector<pair<int, int> > matrix(n);
        string rule;
        for(int i = 0; i < n; i++) //输入n个矩阵的行列数
            cin >> matrix[i].first >> matrix[i].second;
        cin >> rule;  //输入运算法则
        stack<char> s; //记录代表矩阵的字符
        int count = 0;
        compute(rule, matrix, count);
        cout << count << endl;
    }
    return 0;
}

复杂度分析:

  • 时间复杂度:O(n)O(n),相当于遍历计算n个矩阵的乘法次数
  • 空间复杂度:O(n)O(n),递归栈最坏情况下为n

方法二:栈

具体做法:

上述递归也可以用栈来代替。 我们用一个栈记录代表矩阵的字符,遍历运算法则字符串,遇到右括号则弹出栈中前两个矩阵,计算测试,并更新前面个矩阵的行列为运算后的矩阵的行列,遇到左括号不做任何事,遇到其他字符就加入栈中。

这里是默认了一个左括号匹配一个右括号,运算式不会有问题,题目也是这样安排的。

#include<iostream>
#include<stack>
#include<string>
#include<vector>
using namespace std;

int main(){
    int n;
    while(cin >> n){
        vector<pair<int, int> > matrix(n);
        string rule;
        for(int i = 0; i < n; i++) //输入n个矩阵的行列数
            cin >> matrix[i].first >> matrix[i].second;
        cin >> rule;  //输入运算法则
        stack<char> s; //记录代表矩阵的字符
        int count = 0;
        for(int i = 0; i < rule.length(); i++){ //遍历运算法则
            if(rule[i] == ')'){ //遇到右括号计算一次
                if(s.size() != 1){ 
                    auto temp1 = matrix[s.top() - 'A']; //矩阵2
                    s.pop(); 
                    auto& temp2 = matrix[s.top() - 'A']; //矩阵1
                    count += temp2.first * temp2.second * temp1.second; //计算矩阵1*矩阵2的运算次数
                    temp2 = make_pair(temp2.first, temp1.second); //结果加入到矩阵1的位置
                }
            }else if(rule[i] != '(') //其他字符加入栈中代表要运算的矩阵
                s.push(rule[i]);
        }
        cout << count << endl;
    }
    return 0;
}

复杂度分析:

  • 时间复杂度:O(n)O(n),遍历n个矩阵,字符串长度也是O(n)O(n)级别的
  • 空间复杂度:O(n)O(n),栈的长度不会超过n