分治法解大整數乘法

    在某些情況下,需要處理很大的整數,它無法在計算機中精確的表述和處理。若要精確的表示大整數,就必須使用軟件的方法來實現大整數的運算。最常用的解決大整數運算的方法是使用一個二重循環,其算法時間複雜度爲O(m*n)(其中m,n分別爲兩個大整數的長度);而選用分治方法則可以將算法時間複雜度降到O(n^(log3))(兩個大整數的長度同爲n)。但分治方法的算法實現較爲複雜,針對這個問題,本文藉助標準C++實現了分治方法求解大整數乘法的算法。


    下面分別介紹兩種算法原理,及其實現:


    1.使用二重循環控制兩個數的各位數按位交叉相乘的過程:

    (1)兩個乘數存儲在字符串數組中,計算結果存儲在整型數組中;

    (2)k位數字與j位數字相乘的表達式爲:(sl[k]-48)*(s2[j]-48);

    (3)結果數組中的元素只保存一位數字,因此要考慮餘數與進位,表達式爲:

        b = a[i] + s1[i]-48)*(s2[j]-48) + d。

    

    算法實現如下:

#include <iostream>
using namespace std;

void algrithem1(const char s1[], const char s2[], int a[]) {
	
	int n1 = strlen(s1);
	int n2 = strlen(s2);
	int highestpos = 0;
	
	for(int i1=0, k=n1-1; i1<n1; i1++, k--) {
		for(int i2=0, j=n2-1; i2<n2; i2++, j--) {
			int i = i1 + i2;
			int carrier = (s1[k] - 48) * (s2[j]  - 48);
			while(carrier > 0) {
				int temp = a[i] + carrier;
				a[i] = temp % 10;
				carrier = temp /10;
				i++;	
			}
			highestpos = i;
		}		
	}
	a[highestpos] = -1;	
	
}

    2.假設X,Y都是n爲的二進制整數,現在要計算他們的乘積,可如下表述: 

        X = AB, Y = CD

        X*Y = (A*2^(n/2) + B)(C*2^(n/2) + D)

    = A*C*2^n + (A*D + C*B)*2^(n/2) + B*D

算法時間複雜度爲O(n^2)。

    對上述模型進行改進如下:

    X*Y = A*C*2^n + [(A-B)(D-C) + A*C + B*D]*2^(n/2) + B*D 

這樣乘法的次數減少了,算法複雜度爲O(n^log3)。


    算法實現如下:

#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <stdexcept>
#define OUT(x) cout << #x << (x) << endl;
using namespace std;

class ParaErrEx : public logic_error {
public:
	ParaErrEx(const string& msg) : logic_error(msg) {
	}
};

class Mstring {
	
public:
	
	int sign; //the sign of a great number
	string num; //restore the great number
	
	Mstring() : sign(0), num("") {
		
 	}
	
	Mstring(int sign, string num="") : sign(sign), num(num){
			
	}
	
	Mstring(const Mstring& cp) {
		sign = cp.sign;
		num = cp.num;	
	}
	
	Mstring& operator=(const Mstring& cp) {
		if(&cp != this) {
			sign = cp.sign;
			num = cp.num;	
		}	
		return *this;
	}
	
	int length() const{		
		return num.length();			
	}
	
	Mstring insert(int pos, char s) {		
		num.insert(pos, string(1, s));
		return *this;			
	}
	
	Mstring substr(int strt, int len) const{	
		return Mstring(sign, num.substr(strt, len));		
	}
	
	char operator[](int n) const {
		assert(n >= 0 && n < num.length());		
		return num[n];			
	}
	
	Mstring operator-() const{		
		return Mstring(-sign, num);		
	}
	
};


Mstring operator+(Mstring a, Mstring b);
Mstring operator-(Mstring a, Mstring b);
Mstring operator*(const Mstring& a, const Mstring& b);
Mstring operator<<(const Mstring& a, int n);
Mstring abs(const Mstring& a);
bool operator>(const Mstring& a, const Mstring& b); 
bool operator==(const Mstring& a, const Mstring& b);

static void reSizeToTheSame(Mstring& a, Mstring& b);
static bool checkNumLength(int n);
static void reSizeNum(Mstring& a, Mstring& b);
static void adjustRst(const Mstring& a);
static Mstring algrithem2(const Mstring& xx, const Mstring& yy);

Mstring twoGreatMul(const Mstring& x, const Mstring& y);



Mstring operator+(Mstring a, Mstring b) {
	
	if(a.length() != b.length()) {
		reSizeToTheSame(a, b);	
	}
	assert(a.length() == b.length());//check if previous operations succedssful
	
	/*
	*Finish the add operation according to different situations.
	*/	
	Mstring s;  //for return use
	if(a.sign == b.sign) {//situ1: a, b have the same sign, directly add them
	
		s.sign = a.sign;
		int carrier = 0; 
		
		for(int i=0; i<a.length(); i++) {
			int sum = (a[i] - 48) + (b[i]  - 48) + carrier;
			if(sum > 9) {
				s.insert(s.length(), (sum)%10 + 48);
				carrier = sum/10;
			}else {
				s.insert(s.length(), (sum + 48));
				carrier = 0;
			}
		}
		
		if(carrier > 0) {
			s.insert(s.length(), carrier + 48);
		}
		
	}else if(a.sign > b.sign){//situ2: a positive or zero, and b negative
	
		/*Compare the abs value of a and b, then decide the return value's
		*sign and num according to the compare result.*/
		if(abs(a) > abs(b)) {
			s.sign = a.sign;
			int borrow = 0;
			for(int i=0; i<a.length(); i++) {
				int minus = (a[i]-48+borrow) - (b[i]-48);
				if (minus < 0) {
					minus = (a[i]-48+borrow+10) - (b[i]-48);
					borrow = -1;	
				}else {
					borrow = 0;	
				}
				assert(minus >= 0);
				s.insert(s.length(), ('0'+minus));
			}	
		}else if(abs(a) == abs(b)){
			s.sign = 1;
			s.num = "0";	
		}else {
			s.sign = -1;
			int borrow = 0;
			for(int i=0; i<b.length(); i++) {
				int minus = (b[i]-48+borrow) - (a[i]-48);
				if (minus < 0) {
					minus = (b[i]-48+borrow+10) - (a[i]-48);
					borrow = -1;	
				}else {
					borrow = 0;	
				}
				assert(minus >= 0);
				s.insert(s.length(), '0'+minus);
			}	
		}
			
	}else { //situ3: a negtive, and b positive or zero
	
		s = operator+(b, a);//turn to situ2
		
	}
		
	return s;
		
}

Mstring operator-(Mstring a, Mstring b) {
	
	return operator+(a, -b);
		
}

Mstring operator*(const Mstring& a, const Mstring& b) {
	
	assert(a.sign==1 && b.sign==1);//make sure a and b both positive 
	assert(a.length()==1 && b.length()==1);//make sure one bit muliple one bit
	
	Mstring s;
	int carrier = (a[0] - 48) * (b[0]  - 48);
	int i=0;
	if(carrier > 9) {
		s.insert(0, '0' + carrier % 10);	
		s.insert(1, '0' + carrier / 10);
	}else {
		s.insert(0, '0' + carrier);
	}
	
	return s;
		
}

Mstring operator<<(const Mstring& a, int n) {
	
	assert(a.length() != 0 && n>=0);
	
	Mstring s;
	s.sign = a.sign;
	s.num = a.num;
	for(int i=0; i<n; i++) {
		s.insert(0, '0');	
	}
	
	return s;
		
}


Mstring abs(const Mstring& a) {
	
	int sign; 
	if(a.sign == -1) {
		sign = 1;	
	}else {
		sign = a.sign;	
	}
	
	return Mstring(sign, a.num);
	
}

bool operator>(const Mstring& a, const Mstring& b) {
	
	assert(a.length()==b.length());
	assert(a.sign == 1 && b.sign == 1);
	
	for(int i=a.length()-1; i>=0; i--) {
		if(a[i] > b[i]) {
			return true;	
		}else if(a[i] < b[i]) {
			return false;	
		}
	}
	
	return false;
		
}

bool operator==(const Mstring& a, const Mstring& b) {
	
	assert(a.sign==1 && b.sign==1 && a.length()==b.length());
	
	for(int i=0; i<a.length(); i++) {
		if(a[i] != b[i]) {
			return false;	
		}	
	}
		
	return true;
	
}

static void reSizeToTheSame(Mstring& a, Mstring& b) {
	
	int maxlen = a.length() > b.length() ? a.length() : b.length();
	int i = a.length();
	while(i < maxlen) {
		a.insert(a.length(), '0');
		i++;
	}
	i = b.length();
	while(i < maxlen) {
		b.insert(b.length(), '0');
		i++;
	}
		
}

static bool checkNumLength(int n) {
	
	assert(n > 0);
	
	if(n == 1 || n == 2) {
		return true;	
	}else {
		while(n != 2) {
			if(n%2!=0) {
				return false;	
			}	
			n = n/2;
		}	
	}
	
	return true;
}

static void reSizeNum(Mstring& a, Mstring& b) {
	
	assert(a.length() > 0 && b.length() > 0);
	
	int maxlen = a.length() > b.length() ? a.length() : b.length();
	int newlen = 1;
	if( !checkNumLength(maxlen) ) {
		while(newlen < maxlen) {
			newlen *= 2;	
		}	
	}else {
		newlen = maxlen;	
	}

	int i = a.length();
	while(i < newlen) {
		a.insert(0, '0');
		i++;
	}
	i = b.length();
	while(i < newlen) {
		b.insert(0, '0');
		i++;
	}
		
}

static Mstring adjustRst(Mstring& a) {
	
	int pos = a.num.find_last_not_of("0");
	
	if(pos != string::npos) {
		a.num = a.num.substr(0, pos+1);
		int left = 0;
		int right = a.length()-1;
		while(right > left) {
			a.num[left] = a.num[left] ^ a.num[right];
			a.num[right] = a.num[left] ^ a.num[right];
			a.num[left] = a.num[left++] ^ a.num[right--];
		}
	}else {
		a.num = "0";
	}
	
	return a;
	
}

static Mstring algrithem2(const Mstring& xx, const Mstring& yy) {
	
	if(!( (xx.length() >= 0 && yy.length() >= 0) &&
	      (xx.sign == 1 || xx.sign == -1) && 
	      (yy.sign == 1 || yy.sign == -1) ) ) {
				
		string str = "Parameter error happened! Check" 
				   " Mstring object's sign(-1 or 1)"
				   " and num(length at least 1).";
		throw ParaErrEx(str);
			
	}
	
	/*
	*Seperately comupte sign multiple and
	* num multiple of x and y.
	*/	
	Mstring s; //for return use
	s.sign = xx.sign * yy.sign;//compute sign
	Mstring x = abs(xx);//make sure x is positive now
	Mstring y = abs(yy);//make sure y is positive now
		
	if( !( (x.length() == y.length()) && 
	     checkNumLength(x.length()) &&
	     checkNumLength(x.length()) ) ) {
				
		reSizeNum(x, y);
		assert(x.length() == y.length());
		assert(checkNumLength(x.length()));
		assert(checkNumLength(x.length()));
	   		   
	}
	
	
	int n = x.length();
	if(n == 1) {
		
		if(x.num=="0" || y.num=="0") {
			s.sign = 1;
			s.num = "0";
			return s;	
		}else {
			Mstring temp = x*y;
			s.num = temp.num;
			return s;	
		}
		
	}else {
		
		Mstring a = x.substr(0, n/2); 
		Mstring b = x.substr(n/2, n/2);
		Mstring c = y.substr(0, n/2);
		Mstring d = y.substr(n/2, n/2);
			
		Mstring m1 = algrithem2(a, c);
		Mstring m2 = algrithem2(a-b, d-c);
		Mstring m3 = algrithem2(b, d);
	
		Mstring temp = (m1<<n) + ( (m1+m2+m3)<<(n/2) ) + m3;
		s.num = temp.num;
		return s;
		
	}
	
}

Mstring twoGreatMul(const Mstring& x, const Mstring& y) {
	
	Mstring rst = algrithem2(x, y);
	adjustRst(rst);
	return(rst);
			
}

    3.一個測試代碼:

int main(void) {
	
	char s1[255] = "1234567812345678";
	char s2[255] = "12345678";
	int a[255] = {0};
	
	algrithem1(s1, s2, a);
	
	int i = 0;
	while(a[i] != -1) {
		cout << a[i];
		i++;	
	}
	cout << endl;

	Mstring a1(1, "1234567812345678");
	Mstring b1(1, "12345678");
	Mstring s;
	try { 
		s = twoGreatMul(a1, b1);
	}catch(ParaErrEx& e) {
		cout << e.what() << endl;	
	} 
	cout << s.sign << endl;
	cout << s.num << endl;
	
	system("pause");
	
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章