1. 程式人生 > >大數相乘問題--演算法思想及Java實現解析(附詳細註釋)

大數相乘問題--演算法思想及Java實現解析(附詳細註釋)

大整數乘法():

兩個乘數比較大,最後結果超過了整型甚至長整型的最大範圍,如果要得到精確結果,常規計算方法已經不適用。

這裡採用分治的思想,將乘數“分割”,將大整數計算轉換為小整數計算。 (附:博文針對正整數計算

思路解析:

   3  4  (大的整數作為被乘數恐怕是很多人的習慣,便於計算,哈哈) 
 *1  2 
--------- 
   6  8 
3 4 
--------- 
4 0  8 

找規律。其實大家多做幾個兩位數的乘法,都會發現這個規律:

AB * CD = AC (AD+BC) BD (數位組合)。沒錯,這裡如果BD相乘超過一位數,需要進位(例題中,二四得八,沒有進位);(AD+BC+低位進位)如果超過一位數,需要進位(就像剛才的3*1,最後+1得4的操作)。 


此時可以看出,任意位數的整數相乘,最終都是可以轉化為兩位數相乘。但是,不同位的兩位數乘的結果,最後應該如何拼接呢?這需要我們來找下更深層次的規律。分析一下四位數的乘法,找找感覺: 
             1  2  3  4 
          *  5  6  7  8 
------------------------ 
              9  8  7  2 
          8  6  3  8 
      7  4  0  4 
 6  1  7  0 
------------------------ 
 7  0  0  6  6  5  2 
結果看起來沒什麼特別的,如果按照我們分治的思想,轉換為兩位數相乘,之間能否有些關係呢? 

1234分為 12(高位)和34(低位);5678分為56(高位)和78(低位) 
高位*高位結果:12*56=672 
高位*低位+低位*高位:12*78+34*56=936+1094=2840 
低位*低位結果:34*78=2652 

最後,拼接。需要說明的是,剛才我們提到兩位數分解成一位數相乘的規則:超過一位數,需要進位。同理(這裡就不證明了),兩位數乘以兩位數,結果超過兩位數,也要進位。 
從低位開始:低兩位:2652,26進位,低位保留52;中間兩位,2840+26(低位進位)=2866,28進位,中位保留66;高位,672+28(進位)=700,7進位,高位保留00。再往上就沒有了,現在可以拼接起來:最高位進位7,高兩位00,中位66,低位52,最後結果:7006652。 


規律找到!任意位數(例如N位整數相乘),都可以用這種思想實現:低位保留 N 位數字串,多餘高位進位;高位要加上低位進位,如果超過 N 位,依然只保留 N 位,高位進位。(如果是M位整數乘以N位整數怎麼辦?高位補0,湊成一樣位數的即可,不贅述。) 
分治的規律找到了,接下來就是具體實現的思想了。 
沒啥新花樣,依然是遞迴思想(這裡為了簡化,就不遞迴到兩位數相乘了,4位數相乘,計算機還是能夠得到精確值的): 
1、如果兩個整數M和N的長度都小於等於4位數,則直接返回M*N的結果的字串形式。 
2、如果如果M、N長度不一致,補齊M高位0(不妨設N>M),使都為N位整數。 
3、N/2 取整,得到整數的分割位數。將M、N拆分成m1、m2,n1,n2。 
4、將 m1、n1;m2、n1;m1、n2;m2、n2 遞迴呼叫第1步,分別得到結果AC(高位)、BC(中位)、AD(中位)、BD(低位)。 
5、判斷BD位是否有進位bd,並擷取bd得到保留位BD‘;判斷BC+AD+bd是否有進位abcd,並擷取進位得到保留位ABCD';判斷AC+abcd是否有進位ac,並擷取進位得到保留位AC'。 

6、返回最終大整數相乘的結果:ac AC' ABCD' BD'。 

Java 實現原始碼:

import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class  multipliers {

	// 規模只要在這個範圍內可以直接計算(整型數值滿足)
	private final static int SIZE = 4;

	// 其中,len為X、Y的長度最大值
	private static String bigIntMultiply(String X, String Y, int len) {
		String str = "";

		if (len <= SIZE) { // 少於4位數,可直接計算
			return "" + (Integer.parseInt(X) * Integer.parseInt(Y));
		}

		if (X.length() != Y.length()) { // 長度不同,呼叫formatNumber方法,補齊X、Y,使之長度相同
			X = formatNumber(X, len);
			Y = formatNumber(Y, len);
		}

		// 將X、Y分別對半分成兩部分
		int len1 = len / 2;
		int len2 = len - len1;
		String A = X.substring(0, len1);
		String B = X.substring(len1);
		String C = Y.substring(0, len1);
		String D = Y.substring(len1);

		// 乘法法則,分塊處理
		int lenM = Math.max(len1, len2);
		String AC = bigIntMultiply(A, C, len1);
		String AD = bigIntMultiply(A, D, lenM);
		String BC = bigIntMultiply(B, C, lenM);
		String BD = bigIntMultiply(B, D, len2);

		// 注意處理進位的方法,巧妙地運用了字串的拼接方面
		// 【1】 處理BD,得到原位及進位
		String[] sBD = dealString(BD, len2);
		// 【2】 處理AD + BC的和
		String ADBC = add(AD, BC);
		// 【3】 加上BD的進位
		if (!"0".equals(sBD[1])) {
			ADBC = add(ADBC, sBD[1]);
		}
		// 【4】 得到ADBC的進位
		String[] sADBC = dealString(ADBC, lenM);

		// 【5】 AC加上ADBC的進位
		AC = add(AC, sADBC[1]);
		// 【6】 最終結果
		str = AC + sADBC[0] + sBD[0];

		return str;
	}

	// 兩個數字串按位加
	private static String add(String ad, String bc) {
		// 返回的結果
		String str = "";

		// 兩字串長度要相同
		int lenM = Math.max(ad.length(), bc.length());
		ad = formatNumber(ad, lenM);
		bc = formatNumber(bc, lenM);

		// 按位加,進位儲存在flag中
		int flag = 0;
		// 按序從後往前按位求和
		for (int i = lenM - 1; i >= 0; i--) {
			int t = flag + Integer.parseInt(ad.substring(i, i + 1))
					+ Integer.parseInt(bc.substring(i, i + 1));
			// 結果超過9,則進位當前位,保留個位數
			if (t > 9) {
				flag = 1;
				t = t - 10;
			} else {
				flag = 0;
			}
			// 拼接結果字串
			str = "" + t + str;
		}
		if (flag != 0) {
			str = "" + flag + str;
		}
		return str;
	}

	// 處理數字串,分離出進位,String陣列第一個為原位數字,第二個為進位
	private static String[] dealString(String ac, int lenn) {
		String[] str = { ac, "0" };

		if (lenn < ac.length()) {
			int t = ac.length() - lenn;
			str[0] = ac.substring(t);
			str[1] = ac.substring(0, t);
			// System.out.println("+++++++++");
			// System.out.println(str[0]);
			// System.out.println(str[1]);
			// System.out.println(t);
		} else {
			// 保證結果length與lenn一致,少於則高位補0
			String result = str[0];
			for (int i = result.length(); i < lenn; i++) {
				result = "0" + result;
			}
			str[0] = result;
		}
		return str;
	}

	// 格式化操作的數字字串,高位補零
	private static String formatNumber(String x, int len) {
		while (len > x.length()) {
			x = "0" + x;
		}
		return x;
	}

	public static void main(String[] args) {
		String pat = "^[1-9]\\d*$"; // 正則表示式:不以0開頭的數字串
		Pattern p = Pattern.compile(pat); // 將給定的正則表示式編譯並賦予給Pattern類

		System.out.println("乘數A(不以0開頭的正整數):");
		Scanner sc = new Scanner(System.in);
		String A = sc.next();
		Matcher m = p.matcher(A);

		if (!m.matches()) {
			System.out.println("數字不合法!");
			return;
		}

		System.out.println("乘數B(不以0開頭的正整數):");
		String B = sc.next();
		m = p.matcher(B);
		if (!m.matches()) {
			System.out.println("數字不合法!");
			return;
		}
		// Math.max(A.length(), B.length())比較讀入的字串的長短
		System.out.println(A + " * " + B + " = "
				+ bigIntMultiply(A, B, Math.max(A.length(), B.length())));
	}
}
執行結果顯示: