1. 程式人生 > >大數(加、減、乘、除、低精度*大數)模板詳解(C++)

大數(加、減、乘、除、低精度*大數)模板詳解(C++)

一、大數四個基本操作

首先,我們來了解什麼是大數,大數就是指用我們平常常見的高階語言(如:c 、c++)的基本資料型別的最大長度都裝不下的資料,例如(1234567899876543211234567896542132165465),這些只能用字元陣列(char[])或者字串(string)來處理,大數操作最基礎的四個操作就是:大數加法、大數減法、大數乘法、大數除法。下面我來詳細介紹下四個基礎操作的演算法。

在介紹演算法之前,先了解一下幾個C++中 string資料型別的用法。因為string類字元元素的訪問比C字串有所增強,優點十分多,用起來很方便,所以下面我大數模板全部是基於string來作為基礎資料型別。

String 優點如下(相對C

1string可以像C的字串陣列一樣用下標來直接訪問索引值對於的字元。

string str=”123456789123123”  //初始化一個字串

str[i]                 //返回str中索引i處字元的引用,不查是否出界

str.at(i)              //返回str中索引i處字元的引用,查是否出界

2string過載了一些運算子,特別注意當目標串較小,無法容納新的字串,系統會自動分配更多的空間給目標串,不必顧慮出界:

3string字串之間的賦值不再需要像C那樣移動陣列或者用strcpy()等函式輔助,string類可以直接賦值:

str1=str2;                     //str1成為str2的程式碼

str1+=str2;                    //str2的字元資料連線到str1的尾部

str1+str2;                 //返回一個字串,它將str2和str1連線起來

4string字串之間的比較也不需要用strcmp()等函式來比較,可以直接用<>==

str1==str2; str1!=str2;       //比較串是否相等,返回布林值

str1<str2;  str1>str2;         //基於字典序的比較,返回布林值

str1<=str2; 

str1>=str2;

5) string字串還有一個標準且強大的STL庫,裡面提供了許多非常便利有用的輔助函式,如 str. erase ()函式(詳細介紹這個,因為下面演算法經常利用到)

erase函式的原型如下:
(1)string& erase ( size_tpos = 0, size_t n = npos );(常用)

作用:從下標size_t pos開始,去掉size_t n個字元

例子:str=”023543”

用法:str.erase(0,1);

結果:str=“23543”
(2)iterator erase ( iteratorposition );

作用:去掉一個下標為:position的字元

例子:str=”023543”

用法:str.erase(0);

結果:str=“23543”
(3)iterator erase ( iteratorfirst, iterator last );

作用:刪除從first到last之間的字元(first和last都是迭代器)

好了,瞭解string到這裡也差不多了。接下來就是演算法分析

一、大數加法

原理:首先我們要對字串進行初始化處理,就是給它們前面補上一個‘0’,防止有進位,如果沒有進位我們最後可以用erase()函式將其刪掉,然後直接對字串進行操作,從字串尾開始操作,往回對應相加,如果相加的結果>10,那麼前一個字元就要加上當前字元的進位,當前字元就只保留結果個位數

下面是圖解:


程式碼如下

二、大數減法

原理:減法操作和加法操作類似,首先先初始化,保持輸入都是大數減小數,然後從低位開始對應相減,不夠減就從借位減一。

圖解:


程式碼如下


三、大數乘法

原理:乘法的主要思想是把乘法轉化為加法進行運算。

例子(12345*24)首先用相對小的大數(24)作為迴圈分割,4和 20,個位數4代表著4個123456相加。

12345*4=12345+12345+12345+12345

20代表著20個12345相加,也就是2個123450相加

12345*20=123450+123450

最終結果就是4個12345加上2個123450

12345*24=12345+12345+12345+12345+123450+123450

程式碼如下:


                                                                                        不明白?

                                                                                     


我帶大家走一遍Debug,看一下資料的變化

首先初始化:


迴圈加上4次加上12345後結果res=49380


重複,迴圈2次加上123450,res=296280


最終結果


四、大數除法

原理:首先想到的是運用大數減法,例如144 / 12,就是迴圈利用大數減法144-12,減一次就用大數加法加1,最終結束迴圈的條件是被減數小於減數。這個方法實現起來簡單,邏輯直觀,但是效率明顯不夠高,不信可以試一試(978973548932486564654654654654654654654564564564654532165454654 /2)

這個運用上面的方法會編譯器會直接卡死。其實呢,上面的只要進行優化一下就可以AC了。怎麼優化呢?

這樣,首先我們可以直接將減數擴充套件到剛剛好比被減數少一位(用‘0’擴充套件,就是乘於10,例如這裡剛好是擴大10倍)然後再大數相減,減一次就用大數加法加起來(這裡是加10),迴圈操作,同樣,迴圈結束條件也是被減數小於減數,這樣可以避免迴圈減一個小數的尷尬。

程式碼:


                                                                                        不明白?

                                                                                     

下面走一遍debug,看一下資料變化

測試樣例(9090901 / 9

第一步:擴充套件減數,在這裡減數從9擴充套件為900000,相當於*100000

第二步:接著迴圈相減直至被減數小於擴充套件的減數即(a<tmp)結束,此時也迴圈大數相加加上了對應的擴充套件倍數就是結果(res)

由於被減數(a=90901)還是大於減數(b=9),迴圈繼續

此時被減數(a=90901)小於擴充套件的除數(tmp=900000)了,需要將擴充套件的除數變小(這裡將它縮小10倍,即tmp=90000)圖如下:

然後重複第二步的操作,直至被減數小於減數即(a<b)結束。

最終結果:

二、低精度*大數:

低精度*大數例如階乘(N!),1000!將會是一個非常大的數,用我們的基本資料型別是無法裝下的,1*2*3*4*·······*10000

當然這利用我們前一小節所說的大數乘法完成該功能也可以,但是還有一種專門用來處理這種情況的方法,就是利用一個數組來儲存每一位的字元。

例如:

5的階乘在數組裡面就是:

3

0

2

1

 0

 0

 0

 0

 0

 0

6的階乘在數組裡面就是:

3

0

2

7

 0

 0

 0

 0

 0

 0

7的階乘在數組裡面就是:

4

0

4

0

5

 0

 0

 0

 0

發現嗎?

陣列的第一個元素其實就是表示當前結果的位數,其他元素就是結果的每一位數

5!=120

6!=720

7!=5040

那其中是如何實現的呢

程式碼如下:


首先使用一個數組num[50000]來儲存結果,將其初始化為0num[0]=1num[1]=1

這裡設定num[1]=1是因為階乘的原因,因為0的階乘還是1

文字表達水平有限,原理還是看我debug走一遍更好理解

測試樣例是6的階乘(測試樣例太大不好講解)

做好初始化工作:i=1;[結果]res=1


第一步判斷num[0]此時的結果有多少位,再迴圈分別每位乘以i(第一輪i=1)

迴圈後陣列沒有變化

1

1

0

0

 0

 0

 0

 0

 0

 0

第二輪迴圈開始:i此時為2res*2 [注意,這裡*2是指結果的每一位乘以2] res=2


陣列變化

1

2

0

0

 0

 0

 0

 0

 0

 0


第三輪迴圈開始:i=3,res*3

陣列變化

1

6

0

0

 0

 0

 0

 0

 0

 0


第四輪迴圈開始: i=4res*4


注意這裡出現了24,沒錯,就是6*4=2424>10了,此時就要進位,向後進位,原位置保持個位數,num[0]就要加多一位了,代表這結果的位數

陣列變化

2

4

2

0

 0

 0

 0

 0

 0

 0

第五輪迴圈開始: i=5res*5

每位分別乘以5,4*5=202*5=10

再迴圈分割進位

陣列變化

3

0

2

1

 0

 0

 0

 0

 0

 0

第六輪迴圈開始: i=6res*6

同樣,每一位都分別乘以6

即:0*6=0 2*6 =12 1*6=6

掃描有大於10的進行進位拆分


陣列變化

3

0

2

7

 0

 0

 0

 0

 0

 0

結束迴圈

最終:模板程式碼如下

#include<cstdio>
#include<iostream>
#include<cstring>

using namespace std;
//初始化 
void initial(string &a, string &b){
	while (a.size()<b.size())a = '0' + a;
	while (b.size()<a.size())b = '0' + b;
}
//列印 
void print(string &a, string &b){
	cout << a << endl;
	cout << b << endl;
}
//找出最大的字串 
void findMax(string &a, string &b){
	string tmp;
	if (a<b){
		tmp = b;
		b = a;
		a = tmp;
	}
}
//刪除第一個字元'0' 
bool del(string &a){
	if (a[0] == '0'){
		a.erase(0, 1);
		return true;
	}
	else
		return false;
}
//刪除前面所有的 0 
void delAllZroe(string &a){
	while (del(a)){
		del(a);
	};
}
//大數加法 
string bigItergeAdd(string a, string b){
	initial(a, b);
	a = '0' + a;
	b = '0' + b;
	for (int i = a.size() - 1; i >= 0; i--){
		int num1 = a[i] - '0';
		int num2 = b[i] - '0';
		if (num1 + num2>9){
			a[i - 1] = a[i - 1] - '0' + 1 + '0';
			a[i] = (num1 + num2) - 10 + '0';
		}
		else{
			a[i] = (num1 + num2) + '0';
		}
	}
	del(a);
	//	cout<<a<<endl;
	return a;
}
//大數減法 
string bigItergeSub(string a, string b){
	initial(a, b);
	findMax(a, b);
	for (int i = a.size() - 1; i >= 0; i--){
		int num1 = a[i] - '0';
		int num2 = b[i] - '0';
		if (num1<num2){
			a[i - 1] = a[i - 1] - '0' - 1 + '0';
			a[i] = (num1 + 10 - num2) + '0';
		}
		else{
			a[i] = (num1 - num2) + '0';
		}
	}
	del(a);
	//	cout<<a<<endl;
	return a;
}
//大數乘法(大數加法實現) 
void bigItergeMul(string a, string b){
	delAllZroe(a);
	delAllZroe(b);
	if (a == "" || b == ""){ printf("0\n"); return; }
	initial(a, b);
	findMax(a, b);
	string res = "0";
	int count = 0;
	delAllZroe(b);
	for (int i = b.size() - 1; i >= 0; i--){
		int num1 = b[i] - '0';
		if (i != b.size() - 1)		a = a + '0';
		for (int i = 1; i <= num1; i++){
			res = bigItergeAdd(res, a);
		}
	}
	delAllZroe(res);
	cout << res << endl;
}
//大數除法 
void bigItergeDiv(string a, string b){
	initial(a, b);
	if (a<b){ cout << "0" << endl;	return; }
	delAllZroe(b);
	string res = "0";
	string restmp = "1";
	string tmp = b;
	for (int i = 1; i<(a.size() - b.size()); i++){
		tmp += '0';
		restmp += '0';
	}
	initial(a, b);
	while (a >= b){
		initial(a, tmp);
		if (a >= tmp){
			a = bigItergeSub(a, tmp);
			res = bigItergeAdd(res, restmp);
		}
		else{
			tmp.erase(tmp.size() - 1);
			restmp.erase(restmp.size() - 1);
			initial(a, tmp);
			if (a >= tmp){
				a = bigItergeSub(a, tmp);
				res = bigItergeAdd(res, restmp);
			}
		}
		initial(a, b);
	}
	cout << res << endl;
}
//階乘(0~10000)【實際是低精度 乘於(*) 大數 例如:1000 *32132156465465321】 
void factorial(int n){
	int num[50000];
	memset(num, 0, sizeof(num));
	num[0] = 1;
	num[1] = 1;
	for (int i = 1; i <= n; i++){
		int len = num[0];
		for (int j = 1; j <= len; j++){
			num[j] *= i;
		}
		for (int j = 1; j <= num[0]; j++){
			if (num[j]>9){
				num[j + 1] += num[j] / 10;
				num[j] %= 10;
			}
			if (num[num[0] + 1] != 0)num[0]++;
		}

	}
	for (int i = num[0]; i>0; i--){
		printf("%d", num[i]);
	}
	printf("\n");
}

int main(){
	string a, b;
	while (cin >> a >> b){
//		bigItergeAdd(a,b);
//		bigItergeSub(a,b);
		bigItergeMul(a,b);	
//		bigItergeDiv(a, b);
	}
//	int n;
//	while(scanf("%d",&n)!=EOF){
//		factorial(n);
//	}
	return 0;
}

以上的模板已經在acm.hdu.edu.cnopenjudge.cn上測試AC

大數加法

http://bailian.openjudge.cn/practice/1000/

這題在輸入做了手腳,輸入0,也要輸出0WA了好幾次

大數減法

大數乘法

這道題目特坑,竟然在輸入做手腳,測試樣例會輸入一串00000000000000

或者 000000000001 * 000000000000001等等,在這上面WA了好幾次

大數除法

低精度*大數(N!)階乘

這些都是一些非常基礎的題目,一眼就看出是大數的運用,但實際運用上,大數只是作為解題的一個橋段,作為一個契機,例如前一篇文章

sicily1029Rabbit中大OJ解題報告

http://blog.csdn.net/wjb820728252/article/details/60583288