大數算法的總結


目錄:

  1. 前言

  2. 大數加法

  3. 大數減法

  4. 大數乘法

  5. 大數除法

  6. 大數階乘位數

  7. 大數階乘求解

  8. 總結 

 


一.前言.

        眾所周知,計算機數據類型的長度是有限的,因此在處理較大的數據時候會發生數據溢出,此時聰明的我們需要想辦法處理這批數據,那么我們如何處理呢?答案是用數組存儲數據,再做批量處理。

    不知道你們是否觀察過用遞歸求階乘,當n為13時(數據類型為int型),數據明顯不對,有朋友可能就說了如果是longlong型數據呢?longlong型數據肯定可以求出13以上,但是100以上呢?是不是又越界了?這里我參考了其他博主的文章寫了一下大數算法的有關內容。


二.正文


 一.大數加法

       所謂大數加法,就是在我們計算機數據無法存儲的大數據時進行的加法運算。那我們如何實現大數加法呢,我們可以用數組,鏈表等結構來分段存儲大數據,這里我采用 數組進行大數運算的描寫。假如我們輸入123456789123456 + 123這兩個數據時,如果要發生相加的運算,那么第一個數字怎么存儲呢?答案是用數組來存,准確點說是字符數組,我們把這兩個當成一個字符串輸入到一個字符數組中,然后再進行倒序處理,為什么要倒序處理呢?比如說123456789123456 + 123,這里我們把6和3相加,5和2相加,4和3相加,但是在程序中是否表達對位相加不便呢?還有一個優點是我們可以很方便的進行進位處理,所以我們采用倒序處理。那怎么倒序呢,用整形數組從下表0開始存字符數組從后往前推的字符-‘0’(這里解釋一下減字符0是為了從字符轉成相應的數字,如果不了解可以去查找Ascii碼表)。兩個數組依次進行這個操作后,那么用一個sum數組進行相加即可。具體代碼實現如下:

#include "bits/stdc++.h"
#include "cstring"
using namespace std;
char s1[600],s2[600];
int a[600],b[600];
int sum[1200];
int main()
{
    int i,j,res;
    int length1,length2;
    int len;
    cin >> s1;
    cin >> s2;
    length1 = strlen(s1);
    length2 = strlen(s2);
    j = 0;
    for(i = length1 - 1 ;i >= 0;i--)
        a[j++] = s1[i] - '0';//倒轉
    j = 0;
    for(i = length2 - 1;i >= 0;i--)
        b[j++] = s2[i] - '0';//倒轉
    if(length1 > length2)
        len = length1;//遍歷到最長的那個數字
    else
        len = length2;
    res=0;//存進位
    for(i = 0;i < len;i++)
    {
        sum[i] = (a[i] + b[i] + res) % 10;
        res = (a[i] + b[i] + res)/10;
    }
    if(res)//如果此時還存在進位
    {
        sum[i] = res;
        i++;
    }
    for(j = i - 1;j >= 0;j--)
        cout << sum[j];//從后往前打印
    cout << endl;
    return 0;
}

           


二.大數減法

      大數減法采用大數加法的思路存儲數據,處理數據時從低位開始往高位減,例如123456 - 123 逆轉后就是654321 - 321,這時候6 - 3,5- 2,4-1,然后其他的可以直接賦給minus數組了,如果被減數相應位數小於減數相應位數的話,我們采用借位的方式。例如1234-345,4-5<0,往前借位,借位后前一位-1,也就是3-1,(這里我默認是逆轉過后的數組)。如果被減數小於減數呢?我們怎么判斷呢?這時候我們只需要判斷如果被減數的長度小於減數的長度,那么我們可以直接輸出一個負號然后再用減數-被減數。有人又問了,那如果兩個數位數相等,且被減數小於減數呢?此時我們只需要把逆轉后的數組從前往后遍歷,如果發現被減數位數小於減數位數的話,我就可以判斷出來了。直接輸出一個負號,然后用減數減去被減數,總的來說思路就是如果被減數大於減數,那么可以直接減去就行;如果被減數小於減數,就直接輸出一個負號,然后用減數減去被減數。例如123456 - 789 ,123456是被減數,789是減數,別搞混了啊。接下來貼出我的代碼實現(代碼有點長,有能力的小伙伴可以自行實現):

#include "bits/stdc++.h"
using namespace std;
char s1[100],s2[100];
int length1,length2;
int a1[100],b2[100];
int i,j;
int len;
int minus1[200];
int main()
{
    cin >> s1 >>s2;
    length1 = strlen(s1);
    length2 = strlen(s2);
    j=0;//從頭開始添加a1數組
    for(i = length1 - 1;i >= 0;i--)
    {
        a1[j++] = s1[i] - '0';//倒置
    }
    j=0;//從頭開始添加b數組
    for(i = length2 - 1;i >= 0;i--)
    {
        b2[j++] = s2[i] - '0';
    }
    len = (length1 > length2)?length1 : length2;//len為兩個字符串最長的那個
    if(length1 - length2 > 0)//第一個數組減第二個
    {
        for(i = 0;i < len;i++)
        {
            if(a1[i] >= b2[i])//如果大於的話直接減
                minus1[i] = a1[i] - b2[i];
            else
            {
                minus1[i] = a1[i] + 10 -b2[i];
                a1[i+1] = a1[i+1] -1;
            }
        }
        for(j = len -1;j >= 0;j--)//因為兩個數長度不一樣,所以相減一定不為0,否則一定要保留最后一個0
            if(minus1[j] == 0)
                len--;//進行刪0操作
            else
                break;
    }
    else if(length1 - length2 < 0)//第二個數組減第一個
    {
        cout << "-";//輸出負號
        for(i = 0;i < len;i++)
        {
            if(b2[i] >= a1[i])//如果大於的話直接減
                minus1[i] = b2[i] - a1[i];
            else
            {
                minus1[i] = b2[i] + 10 -a1[i];
                b2[i+1] = b2[i+1] -1;
            }
        }
        for(j = len -1;j >= 0;j--)//因為兩個數長度不一樣,所以相減一定不為0,否則一定要保留最后一個0列如99-100
            if(minus1[j] == 0)
                len--;//進行刪0操作
            else
                break;
    }
    else//如果相等則判斷誰大誰小
    {
        for(i = len - 1;i >= 0;i--)
        {
            if(a1[i] == b2[i])
                continue;
            else if(a1[i] > b2[i])
                break;
            else
                break;
        }
        if(a1[i] > b2[i])
        {

            for(i = 0;i < len;i++)
            {
                if(a1[i] >= b2[i])//如果大於的話直接減
                    minus1[i] = a1[i] - b2[i];
                else
                {
                    minus1[i] = a1[i] + 10 -b2[i];
                    a1[i+1] = a1[i+1] -1;
                }
            }
            for(j = len -1;j >= 0;j--)//因為兩個數長度不一樣,所以相減一定不為0,否則一定要保留最后一個0
                if(minus1[j] == 0)
                    len--;//進行刪0操作
                else
                    break;
        }
        else if(a1[i] < b2[i])
        {
            cout << "-";//輸出負號
            for(i = 0;i < len;i++)
            {
                if(b2[i] >= a1[i])//如果大於的話直接減
                    minus1[i] = b2[i] - a1[i];
                else
                {
                    minus1[i] = b2[i] + 10 -a1[i];
                    b2[i+1] = b2[i+1] -1;
                }
            }
            for(j = len -1;j >= 0;j--)//因為兩個數長度不一樣,所以相減一定不為0,否則一定要保留最后一個0列如99-100
                if(minus1[j] == 0)
                    len--;//進行刪0操作
                else
                    break;
        }
        else
        {
            printf("0");
            return 0;
        }
    }
    for(j = len-1;j >= 0;j--)
        cout << minus1[j];
    cout << endl;
    return 0;
}

 


三.大數乘法

      不知道小伙伴們是否了解過計算器的原理,我曾有幸在圖書館了解過一本有關計算器原理的書,里面就介紹了一下計算器的四則運算,所謂乘法就是多次加法,小學時候我們還不懂乘法的時候計算5*3,就會把5加上三次,其實這個就是乘法的原理——對同一個數累加。那我們如何用程序實現它呢,比如說123 * 45 我們逆轉后為321 *54 ,3*5就是15個1,3*4就是12個十, 2*5就是10個十,2*4就是8個一百,然后1*5就是五個一百,1*4就是4個1千,然后將結果進位即可。具體代碼實現如下:

#include "bits/stdc++.h"
#include "cstring"
using namespace std;
char s1[100],s2[100];
int a1[100],b2[100];
int multi[200];
int main()
{
    int i,j;
    cin >> s1;
    cin >> s2;
    j = 0;
    for(i = strlen(s1) - 1;i >= 0; i--)
        a1[j++] = s1[i] - '0';
    j = 0;
    for(i = strlen(s2) - 1; i >= 0; i--)
        b2[j++] = s2[i] - '0';//翻轉數字
    for(i = 0;i < strlen(s1); i++)
        for(j = 0;j < strlen(s2); j++)
        {
            multi[i + j] = multi[i + j] + a1[i] * b2[j];//先不計算進位
        }
    for(i = 0;i < strlen(s1) + strlen(s2); i++)//兩數相乘最大位數為兩數位數相加
        if(multi[i] >= 10)
        {
            multi[i + 1] += multi[i] / 10;//注意數組要提前開大一點,否則會越界
            multi[i] = multi[i] % 10;
        }
    for(i = strlen(s1) + strlen(s2) - 1;i >= 1; i--)//刪除多余0的操作
        if(multi[i] != 0)
            break;
    while(i >= 0)
    {
        cout << multi[i--];
    }
    cout << endl;
    return 0;
}

四.大數除法

      大數除法和大數乘法相反是不斷做減操作,比如說1234 / 23,那么除一次商就可以加1,當被除數小於除數時直接輸出就可以了,就可以減去53次,余下15此時就可以輸出了。但是碰到12000000 / 23呢?這樣子減未免效率也太低了吧,此時我們就可以把23補成和12000000一樣的位數23000000,然后用12000000 - 23000000,發現小了就把除數縮小一位,在判斷是否能減,但是如果當這個增位的0全部用完后,就不能再縮小了。舉個列子1230 - 456,首先456增1位0,變成4560,然后發現1230 < 4560,這樣我們就要減一位增位0,1230 - 456,至於怎樣減,前面已經在大數減法介紹過了就不在詳細介紹了。這里給出代碼給讀者思考。

#include "bits/stdc++.h"
#include "cstring"
using namespace std;
char s1[100],s2[100];
int a1[100],b1[100];
int len1,len2,len;//len計算len1和len2之間差的長度
int z[100];
int temp;
int subtraction(int length1,int length2)
{
    int i;
    for(i = 0;i < length2;i++)
    {
        if(a1[i] >= b1[i])
        {
            a1[i] -= b1[i];
        }
        else 
        { 
            a1[i] = a1[i] + 10 -b1[i];//如果這個數小於的話,則要借位進行減
            a1[i + 1] -= 1;//借位之后減1
        }
    }
    for(i = length1;i > 0 && !a1[i];i--);//消除被除數的前綴 必須為length1開始
    return i + 1;//i每次都會進行判斷,所以要加1
}
int judge(int length1,int length2)
{
    int i;
    if(length1 < length2)
        return -1;//小於返回-1
    else
    {
        for(i = len1 - 1;i >= 0;i--)
        {
            if(a1[i] == b1[i])
                continue;
            if(a1[i] < b1[i])//小於則返回-1
                return -1;
            if(a1[i] > b1[i])//大於則返回1
                return 1;
        }
    }
    return 0;//如果相等就返回0
}
int main()
{
    int i,j;
    cin >> s1;
    cin >> s2;
    len1 = strlen(s1);
    len2 = strlen(s2);
    if(len1 < len2)
    {
        cout << "consult: 0";
        cout << "mod : ";
        puts(s2);
    }
    else 
    {
        for(i = len1 - 1,j = 0;i >= 0;i--)
        {
            a1[j++] = s1[i] - '0'; //反轉數組  調試成功    
        }
        for(i = len2 - 1,j = 0;i >= 0;i--)
        {
            b1[j++] = s2[i] - '0'; //反轉數組  調試成功
        }  
        len = len1 - len2;
        for(i = len1 -1 ;i >= 0;i--)
            if(i >= len)
                b1[i] = b1[i-len];
            else 
                b1[i] = 0;
        len2 = len1;//同步字符位數  調試成功
        for(j = 0;j <= len;j++)//這一步主要是處理每一位上的
        {
            z[len - j] = 0;
            while((temp = judge(len1,len2)) >= 0)
            {
                z[len - j]++;//如果滿足則可以加1次
                len1 = subtraction(len1,len2);
                cout << len1 << " " << j << endl;
                cout << a1[0] << " " << a1[1] << endl;
            }
            if(temp < 0 && b1[0] == 0)//除數減1 如果前綴不為0的話可以結束了
            {
                for(i = 0;i < len2 - 1;i++)
                {
                    b1[i] = b1[i + 1];
                }
                b1[len2 - 1] = 0;
                len2--;//減小除數位數
            }
        }
    }
    for(i = len;i > 0;i--)
    {
        if(z[i])
            break;//消除商的前綴0
    }
    cout << "consult:";
    while(i >= 0)
        cout << z[i--];
    cout << endl;
    cout << "mod:";
    for(i = len1 - 1;i > 0;i--)
    {
        if(a1[i])
            break;//消除余數的前綴0
    }
    if(len1 == 0)
        i = len1;
    while(i >= 0)
        cout << a1[i--];
    cout << endl;
    return 0;
}

五.大數階乘位數

   不知道小伙伴是否碰到過用int型求階乘時,會發現數據明顯不對,比如說求13的階乘,我們發現打印13的階乘為

然而我們可以百度搜索階乘計算器后可以得出

那么時哪里出錯了呢,答案是數據類型長度不夠。所以我們應該改變策略,用數組存儲數據,再批量處理。接下來我們先介紹兩個計算階乘位數的算法。

1.用對數函數求解階乘位數

2.用Stirling公式求解位數。再介紹如何進行階乘的計算。


     1.對數函數求位數

        lg1+lg2+lg3+lgn,由對數函數特性可知,lga+lgb等於lga*b,這時候我們發現真數a*b就可以替換成我們的1和2-n。但此時為什么不會越界呢,因為lg函數返回的是一個double型,所以我們用一個double型的數去存這個值就行,這個值是較小的,所以不會發生數據溢出的情況。需要注意的是我們的位數初始化為1,輸出的時候應該把位數取整后輸出。具體代碼如下:

#include "bits/stdc++.h"
#include "cmath"
using namespace std;
int main()
{
    double sum = 0;
    int i;
    for(i = 1;i <= 200;i++)
        sum += log10(i);//計算階乘的位數 利用log10的特性進行判斷
    cout << (int)(sum + 1) << endl;//sum剛開始為1位數
    return 0;
}

     2.Stirling公式求位數

Stirling公式證明過程復雜,我只是個記公式的小菜雞,請移步大佬的blog進行思考,這里給出鏈接如下https://www.cnblogs.com/jokerjason/p/9525590.html,這里我貼出代碼實現

#include "bits/stdc++.h"
#include "cmath"
using namespace std;
const double e = 2.7182818284;//小數越多精度越高
const double Pi = 3.1415926535;
int main()
{
    int res = 0;
    int n;
    cin >> n;
    res = 0.5 * log10(2 * Pi * n) + n * log10(n/e);
    cout << res + 1;
    return 0;
}

六.大數階乘求解

      階乘求解得思路就是用一個數組存儲,然后每次進來一個乘數,把每個數組元素都乘以這個乘數,如果發現了高位要溢出了,那么我們可以增加一位存高位,然后就是處理進位問題了,我們依然是從低位開始向高位遍歷,如果大於10的話我們就往前進位,這里我采用的是一個數組元素存6位的方式,讀者可以采用1位存法實現大數階乘。具體代碼如下:

#include "stdio.h"
int main()
{
	int i = 1,high = 0,tag = 0;//tag為低位,high為高位,i為階乘值
	int a[1000] = {0};//計算精確度為7000位
	a[high] = 1;//把第一次運算賦初值為1
	while(i <= 100)
	{
		for(tag = 0;tag <= high; tag++)
			a[tag] *= i;
		if(a[high] >= 1000000)
			high += 1;
		for(tag = 0;tag <= high; tag++)
		{
			if(a[tag] >= 1000000)
				{
					a[tag + 1] += a[tag] / 1000000;
					a[tag] = a[tag] % 1000000;
				}
		}
		i++;
	}
	tag = high;
	while(high >= 0)
	{
		if(high == tag)
			printf("%d",a[high]);
		else
			printf("%06d",a[high]);//這是一個輸出小技巧,左邊補0,如果是-06d的話是右邊補零
		high--;
	}
	return 0;
}

七.總結

第一次發表博客,寫的不好的地方請及時指出,我會修改的!另外學習是一件很充實又很快樂的事情,一起加油吧!


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM