C++大数乘法

1. 模拟竖式运算+滑动窗口

2. 分治法 Karatsuba 乘法原理(大数乘法)


1. 模拟竖式运算+滑动窗口

如 134 * 25
__134
x__25
按竖式运算应该是这样分组
4 — 5      --------> 20 ,进位2,此位0
34 — 25     --------> 3x5+2x4 = 23 进位2,此位3,+上一个进位2 ,最终为5
13 — 25     -------->1x5+2x3 = 11 进位1,此位1+2=3
1 — 2      --------> 2+1=3

所以134*25 = 3350

其中34和25 应该 前者从左到右,后者从右到左,依次相乘,最终求和。
即3x5, 2x4,最后求和结果为15+8=23,那么这一位的结果就是3,进位为2.

此外,可以发现发现一共分了 n1+n2-1 组,因此一个运行n1+n2-1 次乘法。n1,n2分别表示两个运算数的位数

#include <cstring>
#include <cmath>
using namespace std;

int ch2int(char ch){
    return ch-'0';
}

int main(){

    string num1;
    string num2;
    
    while(cin>>num1>>num2){

        if(num1=="0" || num2=="0"){
            cout<<"0"<<endl;
            continue;
        }

        string res;

        int val=0;
        int next_val=0;#下一位进位
        int curr_val=0;#当前进位

        int n1 = num1.length();
        int n2 = num2.length();

        int raiseFlag = true;//表示滑动窗口长度是否继续上升或者维持不变或者减少的标志

        // 默认n2更短,也就是小数字放在下面区做乘法
        if(n1<n2){//否则就交换
            string temp = num1;
            num1=num2;
            num2=temp;

            n1 = num1.length();
            n2 = num2.length();
        }
        
        int maxWinSize = n2;//最大滑动窗口长度要取最短的那个
        int index1=n1-1,index2=n2-1;//分别表示数字1和2的下标
        int winSize = 1;//滑动窗口初始为1

        for(int i=0;i<n1+n2-1;i++){//模拟竖式运算,所以一共会运行n1+n2-1次

            val = 0;
            curr_val = next_val;//curr_val表示就是上次的进位放到这次的运算来用了,在运算完成后加上上一次的进位
            next_val = 0;

            for(int j=0;j<winSize;j++){
                //每次都是取滑动窗口,num1的最左边开始,num2的最右边开始,依次相乘,结果相加进val里
                //示例:4567*132     7--2  67--32 567--132  456--132  45-- 13  4--1  
                val += ch2int(num1[index1+j])*ch2int(num2[index2-j]);
                // cout<<"part:"<<j+1<<":"<<num1[index1+j]<<"x"<<num2[index2-j]<<'\t';              
            }
            // cout<<endl;
            val += curr_val;//加上上一次进位的结果
            next_val = val/10;//取这次的进位给下一次
            val%=10;//取个位数,作为这个位置的最终结果
            res+=val+'0';//加入最终结果中
            
            if(winSize==maxWinSize){//查看标志
                raiseFlag=false;//修改标志,不再上升,而是维持不变或者下降
            }

            if(raiseFlag){//窗口上升期
                winSize++;
                index1--;//index1一直往左走,直到为0
            }
            else{//维持不变或者下降
                if(index1==0){//下降期
                    winSize--;
                    index2--;//index2终于开始下降
                }
                else{//维持不变,仅仅是index1继续往左走
                    index1--;  
                }                            
            }
        }
        if(next_val!=0){//最后如果还有进位也要记得加上去
            res+=next_val+'0';
        }

        for(int i=res.length()-1;i>=0;i--){//反向输出整个结果string
            cout<<res[i];
        }
        cout<<endl;
    }

    return 0;
}





2. 分治法 Karatsuba 乘法原理(大数乘法)

h(x) = f(x) * g(x)

其中
f(x) = a * x + b,
g(x) = c * x + d.

x是一个基准值,一般是最大的操作数十进制位数的一半,最后对十进制数直接左移操作(也就是后面补0)即可
A,C分别为原计算值的高位和低位,分割直接就是对半切
B,D同理

然后小的乘法也继续递归调用,由此叫分治。

比如
1234 x 567

X位数取最大位数的一半,4/2=2
(12x100 + 34 ) x (56x100 + 7)
其中,12*X,也就是12*100可以直接用字符串12后面加两个0来实现,所以我们可以先不管X

原式展开=acX^2 + (ad+bc)X + bd

而根据一个巧合:引入一个新的式子, (a + b) * (c + d)
将其展开为 ac+ad+bc+bd
所以原式中间项的 (ad+bc) ------> (a+b) * (c+d) - (ac+bc)

而对于计算机来说,ac和bd的值刚好也要算,因此一次函数调用的乘运算为3次,勉强小于O(n^2)的时间复杂度.

acX^2 + [ (a+b) * (c+d) - (ac+bc) ]X + bd
所以一次运算要求的东西为:
ac,bd,(a+b)*(c+d)
然后就是加法和减法了.

精简版本:

#include <iostream>
#include <string>

#define max(a,b) ((a) > (b) ? (a) : (b))
using namespace std;

//大数相加
string add(string lhs, string rhs) {
    int length = max(lhs.size(), rhs.size());
    int carry = 0;//进位
    int sum_col;  // sum of two digits in the same column
    string result;

    //让短的补0对齐
    // pad the shorter string with zeros
    while (lhs.size() < length) 
      lhs.insert(0,"0");
            
    while (rhs.size() < length) 
      rhs.insert(0,"0");

    //逆序遍历
    // build result string from right to left
    for (int i = length-1; i >= 0; i--) {
      sum_col = (lhs[i]-'0') + (rhs[i]-'0') + carry;
      carry = sum_col/10;
      result.insert(0,to_string(sum_col % 10));
    }
    
    if (carry)
      result.insert(0,to_string(carry));
    
    // remove leading zeros
    return result.erase(0, min(result.find_first_not_of('0'), result.size()-1));
}

//大数相除
string subtract(string lhs, string rhs) {
    int length = max(lhs.size(), rhs.size());
    int diff;
    string result;

    while (lhs.size() < length) 
      lhs.insert(0,"0");
            
    while (rhs.size() < length) 
      rhs.insert(0,"0");

    for (int i = length-1; i >= 0; i--) {
        diff = (lhs[i]-'0') - (rhs[i]-'0');
        if (diff >= 0)
            result.insert(0, to_string(diff));
        else {

            // borrow from the previous column
            int j = i - 1;
            while (j >= 0) {
                lhs[j] = ((lhs[j]-'0') - 1) % 10 + '0';
                if (lhs[j] != '9') 
                    break;
                else 
                    j--;
            }
            result.insert(0, to_string(diff+10));
        }
      
    }

    return result.erase(0, min(result.find_first_not_of('0'), result.size()-1));
}

string multiply(string lhs, string rhs) {
    int length = max(lhs.size(), rhs.size());
    
    while (lhs.size() < length) 
      lhs.insert(0,"0");

    while (rhs.size() < length) 
      rhs.insert(0,"0");

    if (length == 1)
        return to_string((lhs[0]-'0')*(rhs[0]-'0'));

    string lhs0 = lhs.substr(0,length/2);
    string lhs1 = lhs.substr(length/2,length-length/2);
    string rhs0 = rhs.substr(0,length/2);
    string rhs1 = rhs.substr(length/2,length-length/2);
    
    string p0 = multiply(lhs0,rhs0);
    string p1 = multiply(lhs1,rhs1);
    string p2 = multiply(add(lhs0,lhs1),add(rhs0,rhs1));
    string p3 = subtract(p2,add(p0,p1));
    
    //右移B个单位,B是基值
    for (int i = 0; i < 2*(length-length/2); i++)//po是AC.AC*X^2,所以右移两倍的B单位
        p0.append("0");
    for (int i = 0; i < length-length/2; i++)//p3是中间项,只有一个x,所以右移一个B单位
        p3.append("0");
        
    string result = add(add(p0,p1),p3);

    return result.erase(0, min(result.find_first_not_of('0'), result.size()-1));
}

int main() {
    string s1, s2;
    cin >> s1 >> s2;
    cout << multiply(s1,s2) << endl;
    return 0;
}



下面是自己复现的版本,很多冗余操作,仅作个人笔记

#include <bits\stdc++.h>
using namespace std;


string strAdd(string lhs,string rhs){
    if(lhs=="0"){
        return rhs;
    }
    if(rhs=="0"){
        return lhs;
    }

    int l1 = lhs.length();
    int l2 = rhs.length();

    int MimLen = min(lhs.length(),rhs.length());
    int MaxLen = max(lhs.length(),rhs.length());

    int tmp=0;
    int carry=0;
    //开数组是因为我们要逆序存储,而string一直insert(0,ch)非常耗时
    char* res = new char[MaxLen+2];//预留了最左进位和\0两个的位置
    int index = MaxLen+1;
    res[index--] = '\0';

    int i;
    for(i=0;i<MimLen;i++){//按最小长度算
        tmp = carry + (lhs[l1-1-i] - '0') + (rhs[l2-1-i] - '0');
        carry = tmp/10;
        res[index--] = tmp%10+'0';
    }

    //处理剩下的,两个循环只可能进去一个
    while(i<l1){
        res[index--] = (lhs[l1-1-i] - '0') + carry +'0';
        carry = 0;//只有第一次array才可能不为0
        i++;
    }
    while(i<l2){
        res[index--] = (rhs[l2-1-i] - '0') + carry +'0';
        carry = 0;//只有第一次array才可能不为0
        i++;
    }

    //如果是两数的位数相等,则只需要处理最左进位
    if(carry){//如果不是位数相等的情况,则会在上面的while中array置为0,不会进入.
        res[index--] = carry + '0';
    }
    string result(res+1-carry);//如果位数相等时有进位要处理,则起始值为res,否则为res+1(因为开数组时预留了最左进位和\0两个的位置)
    delete[] res;
    return result;

}

string strSubtract(string lhs_,string rhs_){
    if(lhs_=="0" && rhs_!="0"){       
        return "-"+rhs_;
    }
    if(rhs_=="0"){
        return lhs_;
    }

    int l1 = lhs_.length();
    int l2 = rhs_.length();

    //如果最终差为正数则直接求,如果差为负数,则反过来求,最后再加负号
    int minusFlag = 0;
    string lhs = lhs_;
    string rhs = rhs_;
    if(l1<l2 || (l1==l2 && lhs_[0]-'0' < rhs_[0] -'0')){
        minusFlag = 1;
        lhs = rhs_;
        rhs = lhs_;

        l1 = lhs.length();
        l2 = rhs.length();
    }
    // while(rhs.length()<l1){
    //     rhs.insert(0,"0");
    // }

    char* res = new char[l1+1];
    int index = l1;
    res[index--] = '\0';

    int carry=0;
    int borrow=0;
    int i;
    //从后往前,将被减数每一位都变成二位数,也就是默认都借位了.
    for(i=0;i<l2;i++){
        int tmp = carry + 10 + (lhs[l1-1-i] - '0') - borrow - (rhs[l2-1-i] - '0');
        carry = tmp/10;
        res[index--] = tmp%10 + '0';

        borrow = 1;//第一次借位为0,后续借位都是1.
    }

    if(i<l1){
        res[index--] = ( carry + 10 + (lhs[l1-1-i] - '0') - borrow ) % 10 + '0';
        i++;
    }
    while(i<l1){
        res[index--] = lhs[l1-1-i];
        i++;
    }

    int resStart = 0;
    while(res[resStart]=='0')
        resStart++;

    string result(res+resStart);
    delete[] res;

    if(result.length()==0)
        result = "0";
    
    if(minusFlag)
        return "-"+result;
    return result;


}

string strMultiply(string lhs,string rhs){

    // cout<<endl<<"New"<<lhs<<" * "<<rhs<<endl<<endl;

    
    
    int len = max(lhs.length(),rhs.length());

    while(lhs.length()<len){
        lhs.insert(0,"0");
    }
    while(rhs.length()<len){
        rhs.insert(0,"0");
    }

    if(len==1){
        return to_string((lhs[0]-'0') * (rhs[0] - '0'));
    }

    

    string A = lhs.substr(0,len/2);
    string B = lhs.substr(len/2,len-len/2);
    string C = rhs.substr(0,len/2);
    string D = rhs.substr(len/2,len-len/2);

    // printf("A:%s B:%s C:%s D:%s\n",A.c_str(),B.c_str(),C.c_str(),D.c_str());

    string p1 = strMultiply(A,C);
    string p2 = strMultiply(B,D);

    string p3 = strMultiply(strAdd(A,B),strAdd(C,D));
    string p4 = strSubtract(p3,strAdd(p1,p2));
    
    // printf("AC:%s BD:%s p3:%s p4:%s\n",p1.c_str(),p2.c_str(),p3.c_str(),p4.c_str());

    for(int i=0;i<2*(len - len/2);i++){
        p1.append("0");
    }
    for(int i=0;i<(len - len/2);i++){
        p4.append("0");
    }

    // printf("%s %s\n",p1.c_str(),p2.c_str());

    string res =  strAdd(p1,strAdd(p2,p4));
    return res.erase(0,min(res.find_first_not_of('0'),res.length()-1));
    

}


int main(){

    string lhs;
    string rhs;
    
    while(cin>>lhs>>rhs){

        if(lhs=="0" || rhs=="0"){
            cout<<"0"<<endl;
            continue;
        }

        
        string res = strMultiply(lhs,rhs);
        cout<<res<<endl;

        
    }
    return 0;
}


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的实现大数乘法C++ 代码: ```c++ #include <iostream> #include <vector> #include <string> #include <algorithm> using namespace std; vector<int> multiply(vector<int>& num1, vector<int>& num2) { int n1 = num1.size(), n2 = num2.size(); vector<int> res(n1 + n2, 0); for (int i = n1 - 1; i >= 0; i--) { for (int j = n2 - 1; j >= 0; j--) { int mul = num1[i] * num2[j]; int p1 = i + j, p2 = i + j + 1; int sum = mul + res[p2]; res[p2] = sum % 10; res[p1] += sum / 10; } } while (res.size() > 1 && res.back() == 0) { res.pop_back(); } return res; } int main() { string s1, s2; cin >> s1 >> s2; vector<int> num1(s1.size()), num2(s2.size()); for (int i = 0; i < s1.size(); i++) { num1[i] = s1[i] - '0'; } for (int i = 0; i < s2.size(); i++) { num2[i] = s2[i] - '0'; } reverse(num1.begin(), num1.end()); reverse(num2.begin(), num2.end()); vector<int> res = multiply(num1, num2); reverse(res.begin(), res.end()); for (int i = 0; i < res.size(); i++) { cout << res[i]; } cout << endl; return 0; } ``` 这段代码中,我们使用了 vector 来存储大数,并且从低位到高位进行计算。其中,multiply 函接受两个大数的 vector,返回它们的乘积。在函中,我们使用两个指针 i 和 j 分别指向两个的末尾,然后从后往前遍历,依次计算每一位的乘积,并将结果加到 res 组中。最后,我们再去掉 res 组中的前导零,然后将结果翻转后输出即可。 需要注意的是,这里的大数应该以字符串的形式输入,然后转换为 vector<int>。因为在输入过程中,如果使用 int 或者 long long 类型来存储,已经超过这些类型的范围时,就无法正确地读入这些

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值