zoukankan      html  css  js  c++  java
  • 大数乘法的几种算法分析及比较(2014腾讯南京笔试题)

    转自:http://blog.csdn.net/chhuach2005/article/details/21168179

    1.题目

           编写两个任意位数的大数相乘的程序,给出计算结果。

    2.题目分析

           该题相继被ACM、华为、腾讯等选作笔试、面试题,若无准备要写出这种程序,还是要花一定的时间的。故,觉得有必要深入研究一下。搜索了网上的大多数该类程序和算法,发现,大数乘法主要有模拟手工计算的普通大数乘法,分治算法和FFT算法。其中普通大数乘法占据了90%以上,其优点是空间复杂度低,实现简单,时间复杂度为O(N²),分治算法虽然时间复杂度降低为,  

           但其实现需要配 合字符串模拟加减法操作,实现较为复杂,

        参考博客1http://cnn237111.blog.51cto.com/2359144/1201901 

        FFT算法则更为复杂,较少适用,有兴趣

        参考博客2 http://blog.csdn.net/hondely/article/details/6938497

            和博客3http://blog.csdn.net/jackyguo1992/article/details/12613287

            普通大数乘法算法,主要有逐位相乘处理进位法、移位进位法,下面对其进行介绍并优化。

    3.题目解答

    3.1 逐位相乘处理进位法

            参考博客4的思路

            乘积是逐位相乘,也就是aibj,结果加入到积C的第i+j位,最后处理进位即可,例如:A =17 = 1*10 + 7 = (7,1)最后是十进制的幂表示法,幂次是从低位到高位,以下同。B=25 = 2*10 + 5 = (5, 2);C = A * B = (7 * 5, 1 * 5 + 2 * 7, 1 * 2) = (35, 19, 2) = (5, 22, 2) = (5, 2. 4)=425。

    原博客的思路为:
    (1)转换并反转,字符串转换为数字并将字序反转;

    (2)逐位相乘,结果存放在result_num[i+j]中;

    (3)处理进位,消除多余的0;

    (4)转换并反转,将计算结果转换为字符串并反转。

         原博客中采用指针参数传递,字符串长度有限制,改为通过string传参数,按原思路编程如下:

    头文件和数据结构:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include <iostream>  
    2. #include <string>  
    3. #include <vector>  
    4. #include <stdlib.h>  
    5. using namespace std;  
    6. struct bigcheng  
    7. {  
    8.     vector<int> a;  
    9.     vector<int> b;  
    10.     string result_str;  
    11. };  
    12. void chartonum(string a,string b,bigcheng &tempcheng);//字符串转换为数字并反转  
    13. void multiply(bigcheng &tempchengh,vector<int> &result_num);//逐位相乘,处理进位消除多余的0  
    14. void numtochar(bigcheng &tempcheng,vector<int> &result_num);//将计算结果转换为字符串并反转  


    (1)转换并反转,字符串转换为数字并将字序反转;

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. void chartonum(string a,string b,bigcheng &tempcheng)  
    2. {  
    3.     int size_a=a.size();  
    4.     int size_b=b.size();  
    5.     for (int i=size_a-1;i>=0;i--)  
    6.     {  
    7.         tempcheng.a.push_back(a[i]-'0');  
    8.     }  
    9.     for (int i=size_b-1;i>=0;i--)  
    10.     {  
    11.         tempcheng.b.push_back(b[i]-'0');  
    12.     }  
    13. }  

    (2)逐位相乘,结果存放在result_num[i+j]中;

    (3)处理进位,消除多余的0;代码为:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. void multiply(bigcheng &tempcheng,vector<int> &result_num)  
    2. {  
    3.     for (unsigned int i=0;i<tempcheng.a.size();i++)  
    4.     {  
    5.         for (unsigned int j=0;j<tempcheng.b.size();j++)  
    6.         {  
    7.             result_num[i+j]+=(tempcheng.a[i])*(tempcheng.b[j]);  
    8.         }  
    9.     }  
    10.     for (int i=result_num.size()-1;i>=0;i--)  
    11.     {  
    12.         if (result_num[i]!=0)  
    13.         {  
    14.             break;  
    15.         }  
    16.         else  
    17.             result_num.pop_back();  
    18.     }  
    19.     int c=0;  
    20.     for (unsigned int i=0;i<result_num.size();i++)//处理进位  
    21.     {  
    22.         result_num[i]+=c;  
    23.         c=result_num[i]/10;  
    24.         result_num[i]=result_num[i]%10;  
    25.     }  
    26.     if (c!=0)  
    27.     {  
    28.         result_num.push_back(c);  
    29.     }  
    30. }  

    (4)转换并反转,将计算结果转换为字符串并反转。

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. void numtochar(bigcheng &tempcheng,vector<int> &result_num)  
    2. {   int size=result_num.size();  
    3.     for (unsigned int i=0;i<result_num.size();i++)  
    4.     {  
    5.         tempcheng.result_str.push_back(char(result_num[size-1-i]+'0'));  
    6.     }  
    7. }  


    主函数为:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. int main()  
    2. {  
    3.        bigcheng tempcheng;  
    4.     string a,b;  
    5.     cin>>a>>b;  
    6.     chartonum(a,b,tempcheng);  
    7.     vector<int> resultnum(a.size()+b.size(),0);  
    8.     multiply(tempcheng,resultnum);  
    9.     numtochar(tempcheng,resultnum);  
    10.     cout<<tempcheng.result_str<<endl;  
    11.     system("pause");  
    12.     return 0;  
    13. }  


         上面的思路还是很清晰的,但代码有些过长,考虑优化如下:

    (1)上述思路是先转换反转,其实无需先将全部字符串转换为数字的,可即用即转,节约空间;

    (2)无需等到逐位相乘都结束,才处理进位,可即乘即进;

    (3)无需等到所有结果出来后,将结果转换为字符,可即乘即转。

         优化后时间复杂度不变,但节省了空间,代码更简洁。如下:

    头文件和数据结构:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include <iostream>  
    2. #include <string>  
    3. #include <vector>  
    4. #include <stdlib.h>  
    5. #include <assert.h>  
    6. using namespace std;  
    7. struct bigcheng2  
    8. {  
    9.     string a;  
    10.     string b;  
    11.     string result_str;  
    12. };  
    13. void reverse_data( string &data);//字符串反转  
    14. void multiply2(bigcheng2 &tempcheng2);//字符串模拟相乘  

    字符串反转:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. void reverse_data( string &data)  
    2. {  
    3.     char temp = '0';  
    4.     int start=0;  
    5.     int end=data.size()-1;  
    6.     assert( data.size()&& start <= end );  
    7.     while ( start < end )  
    8.     {  
    9.         temp = data[start];  
    10.         data[start++] = data[end];  
    11.         data[end--] = temp;  
    12.     }  
    13. }  


    两数相乘:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. void multiply2(bigcheng2 &tempcheng2)  
    2. {  
    3.     reverse_data(tempcheng2.a);//字符串反转  
    4.     reverse_data(tempcheng2.b);  
    5.     int c=0;  
    6.     string temp(tempcheng2.a.size()+tempcheng2.b.size(),'0');//将temp全部初始化为0字符  
    7.     for (unsigned int i=0;i<tempcheng2.a.size();i++)  
    8.     {  
    9.         unsigned int j;  
    10.         for (j=0;j<tempcheng2.b.size();j++)  
    11.         {  
    12.             c+=temp[i+j]-'0'+(tempcheng2.a[i]-'0')*(tempcheng2.b[j]-'0');//注意temp[i+j]可能保存有上一次计算的结果  
    13.             temp[i+j]=(c%10)+'0';//将结果转换为字符  
    14.             c=c/10;  
    15.         }  
    16.         while(c)  
    17.         {  
    18.             temp[i+j++]+=c%10;//temp里已存字符  
    19.             c=c/10;  
    20.         }  
    21.     }  
    22.     for (int i=temp.size()-1;i>=0;i--)  
    23.     {  
    24.         if (temp[i]!='0')  
    25.             break;  
    26.         else  
    27.             temp.pop_back();  
    28.     }  
    29.     reverse_data(temp);//结果?字Á?符¤?串ä?反¤¡ä转Áa  
    30.     tempcheng2.result_str=temp;  
    31. }  

    主函数:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. int main()  
    2. {  
    3.        bigcheng2 tempcheng2;  
    4.        string a,b;  
    5.        cin>>a>>b;  
    6.        tempcheng2.a=a;  
    7.        tempcheng2.b=b;  
    8.        multiply2(tempcheng2);  
    9.        cout<<tempcheng2.result_str<<endl;  
    10.        system("pause");  
    11.        return 0;  
    12. }  

    3.2 移位进位法

           移位进位法也是普通的大数相乘算法,其时间复杂度也为O(N²)其基本思路参考博客5,简述如下:

      按照乘法的计算过程来模拟计算:

           1 2

        × 3 6

       ---------- ---- 其中,上标数字为进位数值。

         71 2  --- 在这个计算过程中,2×6=12。本位保留2,进位为1.这里是一个简单的计算过程,如果在高位也需要进位的情况下,如何处理?

        3 6

        -----------

        413  2

            其代码优化如下:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include <iostream>  
    2. #include <string>  
    3. #include <vector>  
    4. #include <stdlib.h>  
    5. #include <assert.h>  
    6. using namespace std;  
    7. void reverse_data( string &data);//字符串反转  
    8. void compute_value( string lhs,string rhs,string &result );  
    9. void reverse_data( string &data)  
    10. {  
    11.     char temp = '0';  
    12.     int start=0;  
    13.     int end=data.size()-1;  
    14.     assert( data.size()&& start <= end );  
    15.     while ( start < end )  
    16.     {  
    17.         temp = data[start];  
    18.         data[start++] = data[end];  
    19.         data[end--] = temp;  
    20.     }  
    21. }  
    22. void compute_value( string lhs,string rhs,string &result )  
    23. {  
    24.     reverse_data(lhs);  
    25.     reverse_data(rhs);  
    26.     int i = 0, j = 0, res_i = 0;  
    27.     int tmp_i = 0;  
    28.     int carry = 0;  
    29.   
    30.     for ( i = 0; i!=lhs.size(); ++i, ++tmp_i )  
    31.     {  
    32.         res_i = tmp_i;  //在每次计算时,结果存储的位需要增加  
    33.         for ( j = 0; j!= rhs.size(); ++j )  
    34.         {  
    35.             carry += ( result[res_i] - '0' )+(lhs[i] - '0') * (rhs[j] - '0');//此处注意,每次计算并不能保证以前计算结果的进位都消除, 并且以前的计算结果也需考虑。  
    36.             result[res_i++] = ( carry % 10 + '0' );  
    37.             carry /= 10;  
    38.         }  
    39.         while (carry)//乘数的一次计算完成,可能存在有的进位没有处理  
    40.         {  
    41.             result[res_i++] = (carry % 10 + '0');  
    42.             carry /= 10;  
    43.         }  
    44.     }  
    45.     for (int i=result.size()-1;i>=0;i--)  
    46.     {  
    47.         if (result[i]!='0')  
    48.             break;  
    49.         else  
    50.             result.pop_back();  
    51.     }  
    52.         reverse_data(result);  
    53. }  
    54. int main()  
    55. {  
    56.     string a,b;  
    57.     cin>>a>>b;  
    58.     string result(a.size()+b.size(),'0');  
    59.     compute_value(a,b,result);  
    60.     cout<<result<<endl;  
    61.     system("pause");  
    62.     return 0;  
    63. }  

    3.3大数相乘优化

    3.2 移位进位法中的反转字符串其实不必要的,只需从数组的后面开始计算存储即可,下面实现代码:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. char* bigcheng1(char *p1,char *p2)  
    2. {  
    3.     if (check(p1)||check(p2))  
    4.         throw exception("Invalid input!");  
    5.     int index1=strlen(p1)-1,index2=strlen(p2)-1,index3,carry=0;  
    6.     char *p3=new char[index1+index2+3];  
    7.     memset(p3,'0',index1+index2+2);  
    8.     p3[index1+index2+2]='';  
    9.     for (;index2>=0;--index2)  
    10.     {  
    11.         for (index1=strlen(p1)-1;index1>=0;--index1)  
    12.         {  
    13.             int num=p3[index1+index2+1]-'0'+(p1[index1]-'0')*(p2[index2]-'0')+carry;//p3[index1+index2+1]-'0',注意都是数字参与运算  
    14.             p3[index1+index2+1]=num%10+'0';  
    15.             carry=num/10;  
    16.         }  
    17.         int i=0;  
    18.         while(carry)  
    19.         {  
    20.             p3[index1+index2+1+(i--)]+=(carry%10);//注意字符和字符串的不同  
    21.             carry/=10;  
    22.         }  
    23.         index3=index1+index2+1+i;  
    24.     }  
    25.     while(index3>=0)  
    26.         p3[index3--]='0';  
    27.     return p3;  
    28. }  



    或者下面这样,其实是一样的,下面的相加、相减、相除一样的思路啦

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. char* bigcheng(char *p1,char *p2)  
    2. {  
    3.     if (check(p1)||check(p2))  
    4.         throw exception("Invalid input!");  
    5.     int index1=strlen(p1)-1,index2=strlen(p2)-1,index3,carry=0;  
    6.     char *p3=new char[index1+index2+3];  
    7.     memset(p3,'0',index1+index2+2);  
    8.     p3[index1+index2+2]='';  
    9.     for (;index2>=0;--index2)  
    10.     {  
    11.         for (index1=strlen(p1)-1;index1>=0;--index1)  
    12.         {  
    13.             int num=p3[index1+index2+1]-'0'+(p1[index1]-'0')*(p2[index2]-'0')+carry;//p3[index1+index2+1]-'0',注意都是数字参与运算  
    14.             if (num>=10)  
    15.             {  
    16.                 carry=num/10;  
    17.                 num%=10;  
    18.             }  
    19.             else carry=0;  
    20.             p3[index1+index2+1]=num+'0';  
    21.         }  
    22.         int i=0;  
    23.         while(carry)  
    24.         {  
    25.             p3[index1+index2+1+(i--)]+=(carry%10);//注意字符和字符串的不同  
    26.             carry/=10;  
    27.         }  
    28.         index3=index1+index2+1+i;  
    29.     }  
    30.     while(index3>=0)  
    31.         p3[index3--]='0';  
    32.     return p3;  
    33. }  



    3.4运行结果

            运行结果如图1、图2所示

       

                        图1    

     

                                                                        图2

    3.5 大数相加

    check合法性校验,校验字符串中是否有非数字。
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. bool check(char *p)  
    2. {  
    3.     if (!p)  
    4.     {  
    5.         return 1;  
    6.     }  
    7.     int i=0;  
    8.     while(p[i]!='')   
    9.     {  
    10.         if (p[i]<'0'||p[i]>'9')  
    11.         {  
    12.             return 1;  
    13.         }  
    14.         else ++i;  
    15.     }  
    16.     return 0;//合法  
    17. }  

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. char* bigadd(char *p1,char *p2)  
    2. {  
    3.     if (check(p1)||check(p2))  
    4.     {  
    5.         throw exception("Invalid input!");  
    6.     }  
    7.     int len1=strlen(p1);  
    8.     int len2=strlen(p2);  
    9.     int len3=max(len1,len2)+1;  
    10.     char *p3=new char[len3+1];  
    11.     memset(p3,'0',len3);  
    12.     p3[len3]='';  
    13.     int index1=len1-1,index2=len2-1,index3=len3-1;  
    14.     int carry=0;  
    15.     while(index1>=0&&index2>=0)  
    16.     {  
    17.         int num=p1[index1--]-'0'+p2[index2--]-'0'+carry;  
    18.         if (num>=10)  
    19.         {  
    20.             carry=1;  
    21.             num-=10;  
    22.         }  
    23.         else  
    24.             carry=0;  
    25.         p3[index3--]=num+'0';  
    26.     }  
    27.     while(index1>=0)  
    28.     {  
    29.         int num=p1[index1--]-'0'+carry;  
    30.         if (num>=10)  
    31.         {  
    32.             carry=1;  
    33.             num-=10;  
    34.         }  
    35.         else  
    36.             carry=0;  
    37.         p3[index3--]=num+'0';  
    38.     }  
    39.     while(index2>=0)  
    40.     {  
    41.         int num=p1[index2--]-'0'+carry;  
    42.         if (num>=10)  
    43.         {  
    44.             carry=1;  
    45.             num-=10;  
    46.         }  
    47.         else  
    48.             carry=0;  
    49.         p3[index3--]=num+'0';  
    50.     }  
    51.     p3[index3]=carry?'1':'0';  
    52.     return p3;  
    53. }  


    3.6大数相减

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. char* bigminus(char *p1,char *p2,bool &flag)  
    2. {  
    3.     if (check(p1)||check(p2))  
    4.     {  
    5.         throw exception("Invalid input!");  
    6.     }  
    7.     flag=0;//正数默认  
    8.     if (strlen(p1)<strlen(p2))  
    9.     {  
    10.         flag=1;  
    11.         char *tmp=p1;  
    12.               p1=p2;  
    13.               p2=tmp;  
    14.     }  
    15.     else if (strlen(p1)==strlen(p2))  
    16.     {  
    17.         if (strcmp(p1,p2)<0)  
    18.         {  
    19.             flag=1;  
    20.             char *tmp=p1;  
    21.             p1=p2;  
    22.             p2=tmp;  
    23.         }  
    24.     }  
    25.     int index1=strlen(p1)-1,index2=strlen(p2)-1,index3=strlen(p1);  
    26.     char *p3=new char[strlen(p1)+2];  
    27.     memset(p3,'0',index3+1);  
    28.     p3[index3+1]='';  
    29.     int carry=0;  
    30.     while(index1>=0&&index2>=0)  
    31.     {  
    32.         int num=p1[index1--]-p2[index2--]-carry;  
    33.         if (num<0)  
    34.         {  
    35.             carry=1;  
    36.             num+=10;  
    37.         }  
    38.         else carry=0;  
    39.         p3[index3--]=num+'0';  
    40.     }  
    41.     while(index1>=0)  
    42.     {  
    43.         int num=p1[index1--]-'0'-carry;  
    44.         if (num<0)  
    45.         {  
    46.             carry=1;  
    47.             num+=10;  
    48.         }  
    49.         else carry=0;  
    50.         p3[index3--]=num+'0' ;  
    51.     }  
    52.         int i=0;  
    53.         while(p3[i]=='0') ++i;  
    54.         if (flag)  
    55.         {  
    56.           p3[i-1]='-';  
    57.         }  
    58.     return p3;  
    59. }  


    3.7大数相除

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. char* bigchu(char *p1,char *p2)//大数除,有问题,但思想是对的,关键怎么处理前面多样的0  
    2. {  
    3.     bool flag=0;  
    4.     char *tmp1=new char[strlen(p2)-strlen(p2)+1];  
    5.     char *tmp0=tmp1,*p3,*p4;  
    6.     memset(tmp1,'0',strlen(p2)-strlen(p2));  
    7.     tmp1[strlen(p2)-strlen(p2)]='';  
    8.     char *tmp2=bigminus(p1,p2,flag);  
    9.           p1=tmp2;   
    10.     while(!flag)  
    11.     {   
    12.   
    13.         p3=bigadd(tmp0,"1");  
    14.         tmp1=tmp0;  
    15.         tmp0=p3;          
    16.         delete []tmp1;  
    17.   
    18.         tmp2=bigminus(p1,p2,flag);  
    19.         p4=p1;  
    20.         p1=tmp2;   
    21.         delete []p4;  
    22.     }  
    23.     return tmp0;  
    24.       
    25. }  

    3.8主函数测试

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. int _tmain(int argc, _TCHAR* argv[])  
    2. {  
    3.     string a,b;  
    4.     while(1)  
    5.     {  
    6.         cin>>a>>b;  
    7.         char *p1=const_cast<char *>(a.c_str());  
    8.         char *p2=const_cast<char *>(b.c_str());  
    9.         bool flag=0;  
    10.         char *p3=bigadd(p1,p2);  
    11.         char *p4=bigminus(p1,p2,flag);  
    12.         char *p5=bigcheng1(p1,p2);  
    13.         //char *p6=bigchu(p1,p2);  
    14.   
    15.         //cout<<p3<<endl<<endl<<p4<<endl<<endl<<p5<<endl<<endl;  
    16.         cout<<p1<<endl<<"bigadd"<<endl<<p2<<endl<<"equal: ";  
    17.         printnum(p3);  
    18.         cout<<endl;  
    19.         cout<<p1<<endl<<"bigminus"<<endl<<p2<<endl<<"equal: ";  
    20.         printnum(p4);  
    21.         cout<<endl;  
    22.         cout<<p1<<endl<<"bigcheng1"<<endl<<p2<<endl<<"equal: ";  
    23.         printnum(p5);  
    24.         cout<<endl;  
    25.     }  
    26.     system("pause");  
    27.     return 0;  
    28. }  

    测试结果如下:
  • 相关阅读:
    Spring自动代理机制
    JUnit4 详解
    struts2 OGNL
    loj4j的配置跟使用
    junit浅学笔记二
    shell变量设置
    zookeeper使用
    [zz]Linux kernel map
    glog 使用中存在的问题
    shell中特殊字符(串)
  • 原文地址:https://www.cnblogs.com/vincently/p/4338458.html
Copyright © 2011-2022 走看看