來源:http://blog.csdn.net/chhuach2005/article/details/21168179
1.題目
編寫兩個任意位數的大數相乘的程序,給出計算結果。
2.題目分析
該題相繼被ACM、華為、騰訊等選作筆試、面試題,筆者2014年替師兄去騰訊筆試就遇到此題,當然若無准備要寫出這種程序,還是要花一定的時間的。故,覺得有必要深入研究一下。搜索了網上的大多數該類程序和算法,發現,大數乘法主要有模擬手工計算的普通大數乘法,分治算法和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傳參數,按原思路編程如下:
頭文件和數據結構:
- #include <iostream>
- #include <string>
- #include <vector>
- #include <stdlib.h>
- using namespace std;
- struct bigcheng
- {
- vector<int> a;
- vector<int> b;
- string result_str;
- };
- void chartonum(string a,string b,bigcheng &tempcheng);//字符串轉換為數字並反轉
- void multiply(bigcheng &tempchengh,vector<int> &result_num);//逐位相乘,處理進位消除多余的0
- void numtochar(bigcheng &tempcheng,vector<int> &result_num);//將計算結果轉換為字符串並反轉
(1)轉換並反轉,字符串轉換為數字並將字序反轉;
- void chartonum(string a,string b,bigcheng &tempcheng)
- {
- int size_a=a.size();
- int size_b=b.size();
- for (int i=size_a-1;i>=0;i--)
- {
- tempcheng.a.push_back(a[i]-'0');
- }
- for (int i=size_b-1;i>=0;i--)
- {
- tempcheng.b.push_back(b[i]-'0');
- }
- }
(2)逐位相乘,結果存放在result_num[i+j]中;
(3)處理進位,消除多余的0;代碼為:
- void multiply(bigcheng &tempcheng,vector<int> &result_num)
- {
- for (unsigned int i=0;i<tempcheng.a.size();i++)
- {
- for (unsigned int j=0;j<tempcheng.b.size();j++)
- {
- result_num[i+j]+=(tempcheng.a[i])*(tempcheng.b[j]);
- }
- }
- for (int i=result_num.size()-1;i>=0;i--)
- {
- if (result_num[i]!=0)
- {
- break;
- }
- else
- result_num.pop_back();
- }
- int c=0;
- for (unsigned int i=0;i<result_num.size();i++)//處理進位
- {
- result_num[i]+=c;
- c=result_num[i]/10;
- result_num[i]=result_num[i]%10;
- }
- if (c!=0)
- {
- result_num.push_back(c);
- }
- }
(4)轉換並反轉,將計算結果轉換為字符串並反轉。
- void numtochar(bigcheng &tempcheng,vector<int> &result_num)
- { int size=result_num.size();
- for (unsigned int i=0;i<result_num.size();i++)
- {
- tempcheng.result_str.push_back(char(result_num[size-1-i]+'0'));
- }
- }
主函數為:
- int main()
- {
- bigcheng tempcheng;
- string a,b;
- cin>>a>>b;
- chartonum(a,b,tempcheng);
- vector<int> resultnum(a.size()+b.size(),0);
- multiply(tempcheng,resultnum);
- numtochar(tempcheng,resultnum);
- cout<<tempcheng.result_str<<endl;
- system("pause");
- return 0;
- }
上面的思路還是很清晰的,但代碼有些過長,考慮優化如下:
(1)上述思路是先轉換反轉,其實無需先將全部字符串轉換為數字的,可即用即轉,節約空間;
(2)無需等到逐位相乘都結束,才處理進位,可即乘即進;
(3)無需等到所有結果出來后,將結果轉換為字符,可即乘即轉。
優化后時間復雜度不變,但節省了空間,代碼更簡潔。如下:
頭文件和數據結構:
- #include <iostream>
- #include <string>
- #include <vector>
- #include <stdlib.h>
- #include <assert.h>
- using namespace std;
- struct bigcheng2
- {
- string a;
- string b;
- string result_str;
- };
- void reverse_data( string &data);//字符串反轉
- void multiply2(bigcheng2 &tempcheng2);//字符串模擬相乘
字符串反轉:
- void reverse_data( string &data)
- {
- char temp = '0';
- int start=0;
- int end=data.size()-1;
- assert( data.size()&& start <= end );
- while ( start < end )
- {
- temp = data[start];
- data[start++] = data[end];
- data[end--] = temp;
- }
- }
兩數相乘:
- void multiply2(bigcheng2 &tempcheng2)
- {
- reverse_data(tempcheng2.a);//字符串反轉
- reverse_data(tempcheng2.b);
- int c=0;
- string temp(tempcheng2.a.size()+tempcheng2.b.size(),'0');//將temp全部初始化為0字符
- for (unsigned int i=0;i<tempcheng2.a.size();i++)
- {
- unsigned int j;
- for (j=0;j<tempcheng2.b.size();j++)
- {
- c+=temp[i+j]-'0'+(tempcheng2.a[i]-'0')*(tempcheng2.b[j]-'0');//注意temp[i+j]可能保存有上一次計算的結果
- temp[i+j]=(c%10)+'0';//將結果轉換為字符
- c=c/10;
- }
- while(c)
- {
- temp[i+j++]+=c%10;//temp里已存字符
- c=c/10;
- }
- }
- for (int i=temp.size()-1;i>=0;i--)
- {
- if (temp[i]!='0')
- break;
- else
- temp.pop_back();
- }
- reverse_data(temp);//結果?字Á?符¤?串ä?反¤¡ä轉Áa
- tempcheng2.result_str=temp;
- }
主函數:
- int main()
- {
- bigcheng2 tempcheng2;
- string a,b;
- cin>>a>>b;
- tempcheng2.a=a;
- tempcheng2.b=b;
- multiply2(tempcheng2);
- cout<<tempcheng2.result_str<<endl;
- system("pause");
- return 0;
- }
3.2 移位進位法
移位進位法也是普通的大數相乘算法,其時間復雜度也為O(N²)其基本思路參考博客5,簡述如下:
按照乘法的計算過程來模擬計算:
1 2
× 3 6
---------- ---- 其中,上標數字為進位數值。
71 2 --- 在這個計算過程中,2×6=12。本位保留2,進位為1.這里是一個簡單的計算過程,如果在高位也需要進位的情況下,如何處理?
3 6
-----------
413 2
其代碼優化如下:
- #include <iostream>
- #include <string>
- #include <vector>
- #include <stdlib.h>
- #include <assert.h>
- using namespace std;
- void reverse_data( string &data);//字符串反轉
- void compute_value( string lhs,string rhs,string &result );
- void reverse_data( string &data)
- {
- char temp = '0';
- int start=0;
- int end=data.size()-1;
- assert( data.size()&& start <= end );
- while ( start < end )
- {
- temp = data[start];
- data[start++] = data[end];
- data[end--] = temp;
- }
- }
- void compute_value( string lhs,string rhs,string &result )
- {
- reverse_data(lhs);
- reverse_data(rhs);
- int i = 0, j = 0, res_i = 0;
- int tmp_i = 0;
- int carry = 0;
- for ( i = 0; i!=lhs.size(); ++i, ++tmp_i )
- {
- res_i = tmp_i; //在每次計算時,結果存儲的位需要增加
- for ( j = 0; j!= rhs.size(); ++j )
- {
- carry += ( result[res_i] - '0' )+(lhs[i] - '0') * (rhs[j] - '0');//此處注意,每次計算並不能保證以前計算結果的進位都消除, 並且以前的計算結果也需考慮。
- result[res_i++] = ( carry % 10 + '0' );
- carry /= 10;
- }
- while (carry)//乘數的一次計算完成,可能存在有的進位沒有處理
- {
- result[res_i++] = (carry % 10 + '0');
- carry /= 10;
- }
- }
- for (int i=result.size()-1;i>=0;i--)
- {
- if (result[i]!='0')
- break;
- else
- result.pop_back();
- }
- reverse_data(result);
- }
- int main()
- {
- string a,b;
- cin>>a>>b;
- string result(a.size()+b.size(),'0');
- compute_value(a,b,result);
- cout<<result<<endl;
- system("pause");
- return 0;
- }
3.3運行結果
運行結果如圖1、圖2所示
圖1
圖2