大整數加法和乘法(C#)
昨天晚上做夢,夢到Java老師講演算法,對我提問,讓我給出大整數的加法和乘法思路。醒來抽空把它們做出來了。
其實這兩個都不難,關鍵在於儲存和讀取,計算的部分仿照小學生的做法就行了...
先看加法吧。leetcode上我做過連結串列模擬的大整數加法,用一個node儲存數字的一位,連結到一起表示整個整數。連結串列有兩個缺點:1,把數字串轉化成連結串列比較麻煩。2,做加法要將個位對齊,連結串列做這個也比較麻煩。leetcode中的題目,是把數字逆著儲存的,個位放在首部,最高位放在末端,這樣就不需要手動對齊了。
我今天是用字串來儲存大整數的。先把要相加的兩個數對齊,從最低位開始,兩個數各取一位相加,再加上上一位加法的進位,記錄本位和進位,本位儲存到結果,進位留著給下一位加法用。如果兩個數位數(長度)不一致,在短的那個數前面補零。一直加到最高位,判斷最高位有沒有向前再進位。有的話,還要記錄這個進位。
加法的C#實現:
static string Add(string str1, string str2) { //記錄最終結果 string rst = ""; //i和j用於位數對齊 int i = str1.Length-1, j = str2.Length-1, carry = 0; int tempI = 0, tempJ = 0, tempR = 0; while (i >= 0 || j >= 0) { //採集數字。長度不夠的,高位用0補 if (i >= 0) tempI = str1[i--] - '0'; else tempI = 0; if (j >= 0) tempJ = str2[j--] - '0'; else tempJ = 0; //加法 tempR = tempI + tempJ + carry; carry = tempR > 9 ? 1 : 0; tempR %= 10; tempR += '0'; //本位留存到結果 rst = (char)tempR + rst; } //最高位的向前進位 if (carry == 1) rst ='1' + rst; return rst; }
乘法要複雜一些,但是跟筆算乘法是一樣的:拿其中一個數的每一位分別去乘另一個數,然後把所有的中間結果相加,得到最終結果。需要注意的是,不同位上的數,含義是不同的:個位上的a,就是數a;千位上的數b,是b*1000。所以相乘的時候,要注意在末尾補零,而記錄補零的數量也比較麻煩。由於(b*1000)*B=b*(B*1000),我把補零的操作放到另一個運算元上。
乘法的C#實現:
static string Mlt(string str1, string str2 ) { string rst = "0"; int i = str1.Length-1; //迴圈用str1的每一位去乘str2 while(i>=0) { //temp儲存單輪的結果 string temp = ""; //curr本位,carry進位 int curr = 0, carry = 0; int j = str2.Length - 1; while(j>=0) { curr = (str1[i] - '0') * (str2[j] - '0')+carry; carry = curr / 10; curr = curr % 10; curr = curr + '0'; temp = (char)curr + temp; j--; } if(carry!=0) { temp = (char)(carry + '0') + temp; } //做完一輪乘法,先把中間結果加到最終結果 rst = Add(rst, temp); //開始下一輪乘法,提前補零。 str2 = str2 + '0'; i--; } return rst; }
假定做運算的兩個數長度分別是m、n,那麼加法的時間複雜度是 O(max(m,n)),空間複雜度是O(1)。乘法的時間複雜度是O(m*n),空間複雜度是O(max(m,n))。
我這裡是一邊拆數字下來,一邊計算的。我想過先把它們全部拆成單個數字再來計算,不過時間複雜度沒有優化,反而空間複雜度會增加。
還可以把大整數拆成一段一段地計算,估計會快一點,不過那要先把數字串按段拆成在計算範圍內的整數,而中間的思路跟我這個應該差不多,可以認為我的做法是“一段恰好只有1位”這樣的特殊情況。
另外我沒有考慮負數和小數的情況,下次再做吧...