標籤:desc positive gen log 中間 故事 老師 lan --
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++版)