Stanford Algorithms(一): 大數相乘(c++版)
Stanford Algorithms(一): 大數相乘(c++版)
剛不就在中國大學Mooc上參加了陳越老師的數據結構
的課程,收獲很大.覺得趁熱打鐵,也把算法的部分也給一塊學了吧,就在Coursera上註冊了一個斯坦福大學的算法課,課程的量很重,估計要學一個學期吧,慢慢的學,穩紮穩打.
課程裏推薦了很多書,我找了一本, 書名就叫Algorithms
,作者是S.Dasgupta教授,簡單翻看了一下,覺得寫的挺不錯,就姑且把這本書當做教材了.
還是那句話,貴精不貴多,一門學深入了,收獲就會很大,總之:
不要做一個浮躁的人.
第一節課就出了一個很有意思的題:兩個數相乘.
也許會有人問,這有什麽難,程序裏直接就能算,但是題目出的是兩個64個數字相乘,這就有意思了.
這個計算兩個數字相乘的算法,視頻裏介紹過了,就是分治的思想,遞歸的調用,把原本的O(n^2)的問題變成了O(logn),效率無以提升很多.
我到網上一查,發現這是個很出名的,叫做大數相乘的算法問題,參考了一些文章,但感覺寫的都不是很詳細.
我是用C++實現的.先看看思路吧:
1.數字太多,int肯定不行,要用string
2.具體的算法已經有了,實現的困難,在於實現兩個string數字之間的加,減,以及乘法.
要用的函數大概是這些:
string multiply(string x, string y); string simplyMultiply(string x, string y); int string2int(string x); string int2string(int x); string add(string x, string y); string Minus(string x, string y); string addZero(string x, int zeroNum); string addPreZero(string x, int zeroNum); string reverseString(string s); int Max(int x, int y);
其中有三個函數比較簡單:
int Max(int x, int y){ /* * Description: find max number * Input: Two integers * Output: Return max between x and y */ return x > y ? x : y; } int string2int(string x){ /* * Description: Change string to int * Input: A string * Output: Return a integer represents origin string */ int n = x.length(); int s = 0; for(int i = 0; i < n; ++i){ s = 10 * s + x[i] - ‘0‘; } return s; } string int2string(int x){ /* * Description: Change int to string * Input: An integers * Output: Return a string represents origin integers */ string result; stringstream stream; stream << x; stream >> result; return result; }
這裏借助了stringstream,可以輕松實現類型之間的轉換,當然是涉及到string的.
兩個string的加和減,考慮平時手算的方式,是尾對齊的,因此用程序實現的話,先把它們倒轉,變成頭對齊,就方便計算了.
string simplyMultiply(string x, string y){
/*
* Description: multiply two string, whose length = 1
* Input: Two string
* Output: Return product
*/
if(x.empty() | y.empty()){
return int2string(0);
}else{
int result = string2int(x) * string2int(y);
return int2string(result);
}
}
string reverseString(string s){
/*
* Description: Reverse the string
* Input: A string
* Output: Return a reversed string
*/
string result;
for(auto temp = s.end() - 1; temp >= s.begin(); --temp){
result.push_back(*temp);
}
return result;
}
還有兩個額外的操作,就是在string前面和後面添加0,在前面添加0是為了讓兩個string的位數相等,因為這個算法處理的是兩個等長string,因此要補位,不然會出問題;後面加0,是要用到與10^n相乘這種情況.
string addZero(string x, int zeroNum){
/*
* Description: Add zero between a string, simulate x * 10^n
* Input: A string, a integer represents zero‘s number after it
* Output: Return a string, which is added n‘s 0
*/
string temp(zeroNum, ‘0‘);
x.append(temp);
return x;
}
string addPreZero(string x, int zeroNum){
/*
* Description: Add zero before a string to fill in empty place
* Input: A string, a integer represents zero‘s number
* Output: Return a string, which is added n‘s 0 before it
*/
string temp(zeroNum, ‘0‘);
temp.append(x);
return temp;
}
比較精彩的是模擬兩個string加減的操作.有了前面幾個方法做鋪墊,實現起來就不困難了.其中,
Add操作模仿的是到10進1
Minus操作模仿的是減時不夠高位來補
細節一定要註意,否則bug很難看出來.
string add(string x, string y){
/*
* Description: Add two string
* Input: Two strings
* Output: Return their sum
*/
int i, more = 0, tempSum = 0;
x = reverseString(x);
y = reverseString(y);
int maxSize = Max(x.size(), y.size());
string s(maxSize + 1, ‘0‘);
for(i = 0; i < x.size() && i < y.size(); ++i){
tempSum = x[i] - ‘0‘ + y[i] - ‘0‘ + more;
s[i] = tempSum % 10 + ‘0‘;
more = tempSum / 10;
}
if(i != y.size()){
for(; i < y.size(); ++i){
tempSum = y[i] - ‘0‘ + more;
s[i] = tempSum % 10 + ‘0‘;
more = tempSum / 10;
}
}else if(i != x.size()){
for(; i < x.size(); ++i){
tempSum = x[i] - ‘0‘ + more;
s[i] = tempSum % 10 + ‘0‘;
more = tempSum / 10;
}
}
if(more != 0){
s[i] += more;
}else{
s.pop_back();
}
s = reverseString(s);
return s;
}
string Minus(string x, string y){
/*
* Description: Minus between strings
* Input: Two strings
* Output: Return their difference
*/
int i;
x = reverseString(x);
y = reverseString(y);
string s(x.size(), ‘0‘);
for(i = 0; i < y.size(); ++i){
if(x[i] < y[i]){
x[i] += 10;
x[i + 1] -= 1;
}
s[i] = x[i] - y[i] + ‘0‘;
}
for(; i < x.size(); ++i){
s[i] = x[i];
}
for(i = x.size() - 1; i > 0; --i){
if(s[i] == ‘0‘){
s.pop_back();
}else{
break;
}
}
s = reverseString(s);
return s;
}
有了前面的這些,multi()寫起來就很簡單了,這裏要註意的是數字位數為奇數時的處理.
string multiply(string x, string y){
/*Description: Multiply between two strings
*Input: Two strings, represents two positive integers
*Output: Return product of x and y
*/
int xSize = x.length();
int ySize = y.length();
int n = Max(xSize, ySize);
if(n == xSize){
y = addPreZero(y, n - ySize);
}else{
x = addPreZero(x, n - xSize);
}
if(n == 1){
return simplyMultiply(x, y);
}
string xLeft = x.substr(0, n / 2);
string xRight = x.substr(n / 2);
string yLeft = y.substr(0, n / 2);
string yRight = y.substr(n / 2);
string p1 = multiply(xLeft, yLeft);
string p2 = multiply(xRight, yRight);
string p3 = multiply(add(xLeft, xRight), add(yLeft, yRight));
string p4 = Minus(Minus(p3, p1), p2);
string result = add(add(addZero(p1, 2 * (n - n / 2)),
addZero(p4, n - n / 2)), p2);
return result;
}
現在,可以盡情的相乘了,兩個64位數也可以.
代碼在這裏:大數相乘
總結:以前很少考慮過兩個數是怎麽相乘的,寫在程序裏,也許只是一個符號而已,不知道這中間,發生了這麽多的故事.現在我們時常面臨著的,不是缺乏工具,而是工具封裝的太好,程序員都喜歡偷懶,但是要想獲得真正的提高,那個盒子,是遲早要打開看看的.我覺得這是數據結構和算法課,交給我的很重要的東西,這種底層的思維習慣,很重要.
Stanford Algorithms(一): 大數相乘(c++版)