大数乘法

8 篇文章 0 订阅

大数乘法是一个经常会遇到的问题,在Java中这个问题很容易解决,直接用BigInteger就okl了。但是在C++、C中就不那么容易了,在做LeetCode过程中就遇到了这道题,第43题“Multiply Strings”:https://leetcode.com/problems/multiply-strings/#/description。 我在网上查了一下,也看了LeetCode上的discussion,大致总结三种方法:普通进位法,分治法,傅里叶变换法(FFT)。

一 :普通进位法
很多人先把string反转过来,再做模拟乘法的操作,因为string中第一位对应数值的最高位,当然不倒转也可以。 这个方法很好理解,代码量也很少。时间复杂度是O(n^2),我在discussion上看到大神写的代码十分漂亮,就记下来了,以后求职笔试过程中遇到以便能默写出来。

string Mutiply(string num1, string num2){

    string sum(num1.length() + num2.length() + 1, '0');
    int carry; //进位 
    int tmp;
    for(int i=num1.length()-1; i>=0; i--){
       for(int j=num2.length()-1; j>=0; j--){
           tmp = sum[i+j+1]-'0' + (num1[i]-'0') * (num2[j]-'0') + carry;
           sum[i+j+1] = tmp % 10;
           carry = tmp / 10;

       }//for
        sum[i] = carry;
     }//for

     size_t pos = sum.find_first_not_of("0");
     if(string::npos != pos) 
       return sum.substr(pos);
     else return "0";
}

二: 分治法
http://cnn237111.blog.51cto.com/2359144/1201901


#include <iostream> 
#include <sstream> 
#include <string> 

using namespace std; 
//string类型转换成int类型

int string_to_num(string k)//string字符串变整数型例如str="1234",转换为整数的1234. 
{ 
    int back; 
    stringstream instr(k); 
    instr>>back; 
    return back; 
} 

 //整形数转换为string类型
string num_to_string(int intValue)
{
    string result;
    stringstream stream;
    stream << intValue;//将int输入流
    stream >> result;//从stream中抽取前面放入的int值
    return result;
}

//在字符串str前添加s个零
string stringBeforeZero(string str,int s)
{
    for(int i=0;i<s;i++)
        str.insert(0,"0");

    return str;
}

//两个大整数字符串相加,超出计算机表示范围的数也能实现相加(本函数可以实现大整数加法运算)
string stringAddstring(string str1,string str2)
{
    //假定str1和str2是相等的长度,不相等时在前面自动补零,使两个字符串长度相等
    if (str1.size() > str2.size()) 
        str2 = stringBeforeZero(str2,str1.size() - str2.size());
    else if (str1.size() < str2.size()) 
        str1 = stringBeforeZero(str1,str2.size() - str1.size());


    string result;
    int flag=0;//前一进位是否有标志,0代表无进位,1代表有进位
    for(int i=str1.size()-1;i>=0;i--)
    {
        int c = (str1[i] - '0') + (str2[i] - '0') + flag;//利用ASCII码对字符进行运算,这里加上flag代表的是:当前一位有进位时加1,无进位时加0
        flag = c/10;//c大于10时,flag置为1,否则为0
        c %= 10;//c大于10时取模,否则为其本身
        result.insert(0,num_to_string(c));//在result字符串最前端插入新生成的单个字符
    }
    if (0 != flag) //最后一为(最高位)判断,如果有进位则再添一位
        result.insert(0,num_to_string(flag));

    return result;
}

/*两个大整数字符串相减,超出计算机表示范围的数也能实现相减(在这里比较特殊,第一个参数一定大于第二个参数,
因为:a1*b0+a0*b1=(a1+a0)*(b1+b0)-(a1*b1+a0*b0) > 0 ,所以(a1+a0)*(b1+b0) > (a1*b1+a0*b0)
这个函数的两个参数,第一个代表的其实就是(a1+a0)*(b1+b0),第二个代表的其实就是(a1*b1+a0*b0)
所以本函数里不用考虑最终得到结果为负数的情况,至于计算有关大整数负数相乘的问题可以通过其他途径判断
*/
string stringSubtractstring(string str1,string str2)
{
    //对传进来的两个数进行修剪,如果前面几位有0则先去掉,便于统一处理
    while ('0' == str1[0]&&str1.size()>1)
        str1=str1.substr(1,str1.size()-1);

    while ('0' == str2[0]&&str2.size()>1)
        str2=str2.substr(1,str2.size()-1);


    //使两个字符串长度相等
    if (str1.size() > str2.size()) 
        str2 = stringBeforeZero(str2,str1.size() - str2.size());


    string result;
    for(int i=str1.size()-1;i>=0;i--)
    {
        int c = (str1[i] - '0') - (str2[i] - '0');//利用ASCII码进行各位减法运算
        if (c < 0) //当不够减时向前一位借位,前一位也不够位时再向前一位借位,依次如下
        {
            c +=10;
            int prePos = i-1; 
            char preChar = str1[prePos];
            while ('0' == preChar) 
            {
                str1[prePos]='9';
                prePos -= 1;
                preChar = str1[prePos];
            }

            str1[prePos]-=1;
        }

        result.insert(0,num_to_string(c));//在result字符串最前端插入新生成的单个字符
    }
    return result;
}

//在字符串str后跟随s个零
string stringFollowZero(string str,int s)
{
    for(int i=0;i<s;i++)
        str.insert(str.size(),"0");

    return str;
}

//分治法大整数乘法实现函数
string IntMult(string x,string y)//递归函数
{ 
    //对传进来的第一个数进行修剪,如果前面几位有0则先去掉,便于统一处理
    while ('0' == x[0]&&x.size()>1)
        x=x.substr(1,x.size()-1);

    //对传进来的第二个数进行修剪,如果前面几位有0则先去掉,便于统一处理
    while ('0' == y[0]&&y.size()>1)
        y=y.substr(1,y.size()-1);

    /*这里的f变量代表在两个数据字符串长度不想等或者不是2的指数倍的情况下,所要统一的长度,这样做是为了让数据长度为2的n次方的情况下便于利用分治法处理*/

    int f=4;
    /*当两字符串中有任意一个字符串长度大于2时都要通过与上面定义的f值进行比较,使其达到数据长度为2的n次方,实现方式是在前面补0,这样可以保证数据值大小不变*/
    if (x.size()>2 || y.size()>2) 
    {
        if (x.size() >= y.size()) //第一个数长度大于等于第二个数长度的情况
        {
            while (x.size()>f) //判断f值
                f*=2;
            if (x.size() != f) 
            {
                x = stringBeforeZero(x,f-x.size());
                y = stringBeforeZero(y,f-y.size());
            }
        }else//第二个数长度大于第一个数长度的情况
        {
            while (y.size()>f) //判断f值
                f*=2;

            if (y.size() != f) 
            {
                x = stringBeforeZero(x,f-x.size());
                y = stringBeforeZero(y,f-y.size());
            }
        }
    }

    if (1 == x.size()) //数据长度为1时,在前面补一个0(这里之所以会出现长度为1的数据是因为前面对数据修剪过)
        x=stringBeforeZero(x,1);

    if (1 == y.size()) //数据长度为1时,在前面补一个0(这里之所以会出现长度为1的数据是因为前面对数据修剪过)
        y=stringBeforeZero(y,1);

    if (x.size() > y.size()) //最后一次对数据校正,确保两个数据长度统一
        y = stringBeforeZero(y,x.size()-y.size());

    if (x.size() < y.size()) //最后一次对数据校正,确保两个数据长度统一
        x = stringBeforeZero(x,y.size()-x.size());

    int s = x.size(); 
    string a1,a0,b1,b0; 
    if( s > 1) 
    { 
        a1 = x.substr(0,s/2); 
        a0 = x.substr(s/2,s-1); 
        b1 = y.substr(0,s/2);    
        b0 = y.substr(s/2,s-1); 
    } 
    string result;
    if( s == 2) //长度为2时代表着递归的结束条件
    { 
        int na = string_to_num(a1); 
        int nb = string_to_num(a0);  
        int nc = string_to_num(b1); 
        int nd = string_to_num(b0);
        result = num_to_string((na*10+nb) * (nc*10+nd)); 
    } 
    else{ //长度不为2时利用分治法进行递归运算
        /***************************************************
        小提示:
        c = a*b = c2*(10^n) + c1*(10^(n/2)) + c0;
        a = a1a0 = a1*(10^(n/2)) + a0;
        b = b1b0 = b1*(10^(n/2)) + b0;
        c2 = a1*b1;  c0 = a0*b0;
        c1 = (a1 + a0)*(b1 + b0)-(c2 + c0);
        ****************************************************/
        string c2 = IntMult(a1,b1);// (a1 * b1)
        string c0 = IntMult(a0,b0);// (a0 * b0)
        string c1_1 = stringAddstring(a1,a0);// (a1 + a0)
        string c1_2 = stringAddstring(b1,b0);// (b1 + b0)
        string c1_3 = IntMult(c1_1,c1_2);// (a1 + a0)*(b1 + b0)
        string c1_4 = stringAddstring(c2,c0);// (c2 + c0)
        string c1=stringSubtractstring(c1_3,c1_4);// (a1 + a0)*(b1 + b0)-(c2 + c0)
        string s1=stringFollowZero(c1,s/2);// c1*(10^(n/2))
        string s2=stringFollowZero(c2,s);// c2*(10^n)
        result = stringAddstring(stringAddstring(s2,s1),c0);// c2*(10^n) + c1*(10^(n/2)) + c0
    } 

    return result; 
} 

void main() 
{ 
    int f=1;
    while (1 == f)
    {
    string A,B,C,D; 
    string num1,num2; 
    string r;
    cout<<"大整数乘法运算(分治法实现)"<<endl;
    cout<<"请输入第一个大整数(任意长度):";
    cin>>num1;
    int i=0;
    //判断第一个输入的数据是否合法,当字符串中包含非数字字符时提示用户重新输入
    for(i=0 ;i < num1.size();i++)
    {
        if (num1[i]<'0' || num1[i]>'9') 
        {
            cout<<"您输入的数据不合法,请输入有效的整数!"<<endl;
            cin>>num1;
            i=-1;
        }
    }



    cout<<"请输入第二个大整数(任意长度):";
    cin>>num2;
    //判断第二个输入的数据是否合法,当字符串中包含非数字字符时提示用户重新输入
    for(i=0 ;i < num2.size();i++)
    {
        if (num2[i]<'0' || num2[i]>'9') 
            {
            cout<<"您输入的数据不合法,请输入有效的整数!"<<endl;
            cin>>num2
            i=-1;
        }
    }

    r=IntMult(num1,num2); 

    //对求得的结果进行修剪,去掉最前面的几个0
    while ('0' == r[0]&&r.size()>1)
    {
        r=r.substr(1,r.size()-1);
    }
    cout<<"两数相乘结果为:"<<endl;
    cout<<num1<<" "<<"*"<<" "<<num2<<" "<<"="<<" "<<r<<endl<<endl;     
    }
}


三: FFT算法

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

const double PI = acos(-1);
typedef complex<double> cp;  
typedef long long int64;  

const int N = 1 << 16;  
int64 a[N], b[N], c[N << 1];  

void bit_reverse_copy(cp a[], int n, cp b[])  
{  
    int i, j, k, u, m;  
    for (u = 1, m = 0; u < n; u <<= 1, ++m);  
    for (i = 0; i < n; ++i)  
    {  
        j = i; k = 0;  
        for (u = 0; u < m; ++u, j >>= 1)  
            k = (k << 1) | (j & 1);  
        b[k] = a[i];  
    }  
}  

void FFT(cp _x[], int n, bool flag)  
{  
    static cp x[N << 1];  
    bit_reverse_copy(_x, n, x);  
    int i, j, k, kk, p, m;  
    for (i = 1, m = 0; i < n; i <<= 1, ++m);  
    double alpha = 2 * PI;  
    if (flag) alpha = -alpha;  
    for (i = 0, k = 2; i < m; ++i, k <<= 1)  
    {  
        cp wn = cp(cos(alpha / k), sin(alpha / k));  
        for (j = 0; j < n; j += k)  
        {  
            cp w = 1, u, t;  
            kk = k >> 1;  
            for (p = 0; p < kk; ++p)  
            {  
                t = w * x[j + p + kk];  
                u = x[j + p];  
                x[j + p] = u + t;  
                x[j + p + kk] = u - t;  
                w *= wn;  
            }  
        }  
    }  
    memcpy(_x, x, sizeof(cp) * n);  
}  

void polynomial_multiply(int64 a[], int na, int64 b[], int nb, int64 c[], int &nc)  
{  
    int i, n;  
    i = max(na, nb) << 1;  
    for (n = 1; n < i; n <<= 1);  
    static cp x[N << 1], y[N << 1];  
    for (i = 0; i < na; ++i) x[i] = a[i];  
    for (; i < n; ++i) x[i] = 0;  
    FFT(x, n, 0);  
    for (i = 0; i < nb; ++i) y[i] = b[i];  
    for (; i < n; ++i) y[i] = 0;  
    FFT(y, n, 0);  
    for (i = 0; i < n; ++i) x[i] *= y[i];  
    FFT(x, n, 1);  
    for (i = 0; i < n; ++i)   
    {  
        c[i] =(int64)(x[i].real() / n + 0.5);
    }  
    for (nc = na + nb - 1; nc > 1 && !c[nc - 1]; --nc);  
}  

const int LEN = 5, MOD = 100000;  
void convert(char *s, int64 a[], int &n)  
{  
    int len = strlen(s), i, j, k;  
    for (n = 0, i = len - LEN; i >= 0; i -= LEN)  
    {  
        for (j = k = 0; j < LEN; ++j)  
            k = k * 10 + (s[i + j] - '0');  
        a[n++] = k;  
    }  
    i += LEN;  
    if (i)  
    {  
        for (j = k = 0; j < i; ++j)  
            k = k * 10 + (s[j] - '0');  
        a[n++] = k;  
    }  
}  

void print(int64 a[], int n)  
{  
    printf("%I64d", a[--n]);  
    while (n) printf("%05I64d", a[--n]);  
    puts("");  
}  

char buf[N + 10];  

int main()  
{  
    int na, nb, nc;  

    while (scanf("%s", buf) != EOF)  
    {  
        bool sign = false;  
        if (buf[0] == '-')  
        {  
            sign = !sign;   
            convert(buf + 1, a, na);  
        }  
        else convert(buf, a, na);  
        scanf("%s", buf);  
        if (buf[0] == '-')  
        {  
            sign = !sign;  
            convert(buf + 1, b, nb);  
        }  
        else convert(buf, b, nb);  
        polynomial_multiply(a, na, b, nb, c, nc);  
        int64 t1, t2;  
        t1 = 0;  
        for (int i = 0; i < nc; ++i)  
        {  
            t2 = t1 + c[i];  
            c[i] = t2 % MOD;  
            t1 = t2 / MOD;  
        }  
        for (; t1; t1 /= MOD) c[nc++] = t1 % MOD;  
        if (sign) putchar('-');  
        print(c, nc);  
    }  
    return 0;  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值