写在前面:这里是小王成长日志,一名在校大学生,想在学习之余将自己的学习笔记分享出来,记录自己的成长轨迹,帮助可能需要的人。欢迎关注与留言。
在做算法题时,我们时常会遇到正常的类型进行不了的运算,如两个long long int 相乘并对某个数取模,有时我们可以使用快速幂和龟速乘解决。
但有的题目直接给的是n位的整数,比如下面这道题-洛谷传送门,给出的数据最大已经达到了10000多位,直接使用int,long long 之类的数据必爆无疑。
因此是时候祭出我们的大数处理方法了,大数,就是指利用已有的类型无法进行保存和运算的数字。
其实大数处理,就是利用字符串一位一位保存我们的大数,并且模拟正常的加减乘除过程进行大数的运算。
接下来,我们将一一讨论大数的加减乘除,其中加减乘较简单,除法相较而言难于理解,保持耐心,我默认你已经理解字符串和字符数组的概念,我们开始吧。
题目描述
高精度减法。
输入格式
两个整数 a,ba,b(第二个可能比第一个大)。
输出格式
结果(是负数要输出负号)。
输入输出样例
输入
2
1
输出
1
说明/提示
20 20 20% 数据 a , b a,b a,b 在 long long 范围内;
100 100 100% 数据 0 < a , b ≤ 1 0 10086 00<a,b≤1010086
想要对大数进行运算,第一个遇到的如何存储大数,对于这个问题,一般我们采用的都是数组,即利用一个整形数组存储一个数字,如下:
首先我们定义一个整形数组,再将这个大数一位位的存入这个数组中。
注意:
大数运算,核心思想就是模拟运算,加减乘都是直接模拟小学学的数学,除法有些特殊,使用的是减法实现(例如4/2就可以表示成4-2-2)
以下是一段除法与减法的关系,以供参考:
除法与减法之间的关系:
1、能够整除的除法
除法可以看作连续减去相同数的减法;被除数就是被减数,除数就是相同的减数,连减的最多次数就是商。
2、不能整除的除法
除法可以看作连续减去相同数的减法;被除数就是被减数,除数就是相同的减数,连减的最多次数就是商,除法的余数就是减法的最后结果数值。
具体的模拟过程在各种运算中会有讲解,这里先知道是模拟运算即可。
以下的大数运算除了除法我都没有抽象出函数来,但在理解后这是非常简单的,不会影响食用。
对于加法,很明显,从低位开始加,满10 进1 模拟很简单
//这里a[] b[] 是存储要运算的两个大数 max是ab两数中较大的一个的位数 c[] 存储运算结果,并且abc所有未被使用过的位置都被初始化为0
for(i=0;i<max;i++)
{
c[i]=a[i]+b[i];
if(c[i]>=10){//进位处理
c[i+1]+=1;//进10位
c[i]%=10;//留下个位
}
}
//处理进位也可以单独拿出做 - 可以手动模拟一下两种方法
/*for(i=0;i=10){
c[i+1]+=1;//进10位
c[i]%=10;//留下个位
}
}*/
完整代码
#include
//大数加法
using namespace std;
int main(){
//读入两个大数
string num1,num2;//字符串 利用字符数组存也是可以的
// 但是注意数组长度需要足够 很多大数题目数字长度都是上万的
cin>>num1>>num2;
//分别获取位数并将每一位存入整形数组中
int n=num1.length(),m=num2.length();//length()函数用于获取存储字符串长度
int max= m>n?m:n +1;//两个数相加 位数不会超过较大的数的位数+1
int a[max],b[max],c[max];//每一位都初始化为0
//int a[max]={0},b[max]={0},c[max]={0};//每一位都初始化为0
/****按理来说如上的初始化是允许的,但是有的编译器
可能初始化出错,所以最后手动出初始化一下,或者定义全局变量了事**/
int i,j;
for(i=0;i<max;i++)//初始化
a[i]=0;
for(i=0;i<max;i++)//初始化
b[i]=0;
for(i=0;i<max;i++)//初始化
c[i]=0;
//num1 - n位 - 将num1字符串存储的大数翻转存储(a[0]存储个位,a[1]存储十位)到int数组a中
for(i=0,j=n-1;i<n;i++,j--)
a[i]=num1[j]-'0';
//num2 - m位
for(i=0,j=m-1;i<m;i++,j--)
b[i]=num2[j]-'0';
/*********上面都是利用数组储存大数,下面开始模拟大数加法*************/
for(i=0;i<max;i++)
{
c[i]=a[i]+b[i];
if(c[i]>=10){//进位处理
c[i+1]+=1;//进10位
c[i]%=10;//留下个位
}
}
//处理进位也可以单独拿出做 - 可以手动模拟一下两种方法
/*for(i=0;i=10){
c[i+1]+=1;//进10位
c[i]%=10;//留下个位
}
}*/
i=max-1;
while(c[i]==0)//去除答案数组c中高位上的0
i--;
while(i>=0)//c[i]是从高位向前看第一个不为0的数
printf("%d",c[i--]);
}
对于减法,也很简单,从低位开始减,减不了就向前一位借1 当前位置加10 ,模拟如下:
注意:不一定要将较大的数放置在被减数的位置,但是这样方便处理
//这里a[] b[] 是存储要运算的两个大数 max是ab两数中较大的一个的位数 c[] 存储运算结果,并且abc所有未被使用过的位置都被初始化为0
for(i=0;i<max;i++){
//处理借位
if(a[i]<b[i]>){
a[i]+=10;
a[i+1]--;
}
c[i]=a[i]-b[i];
}
//处理借位可以单独拿出来 - 可以手动模拟一下两种方法
/*for(i=0;i
#include
//大数减法
using namespace std;
int main(){
//读入两个大数
string num1,num2;
//cin>>num1>>num2;
num1="9823465789117604259074834";
num2="31468761323431";
//差 9823465789086135497751403
//分别获取位数并将每一位存入整形数组中
int n=num1.length(),m=num2.length();
int max= m>n?m:n ;//两个数相减 位数不会超过较大的数的位数
int a[max],b[max],c[max];//每一位都要初始化为0
//将位数较大的数放在第一个 我们始终用绝对值较大的数减去绝对值较小的数
if(n<m){
string temp = num1;
num1=num2;
num2=temp;
int tmp = n;
n=m;
m=tmp;
}
//int a[max]={0},b[max]={0},c[max]={0};//每一位都初始化为0 不这样做的理由同上
int i,j;
for(i=0;i<max;i++)//初始化
a[i]=0;
for(i=0;i<max;i++)//初始化
b[i]=0;
for(i=0;i<max;i++)//初始化
c[i]=0;
//num1 - n位 - 将num1字符串存储的大数翻转存储(a[0]存储个位,a[1]存储十位)到int数组a中
for(i=0,j=n-1;i<n;i++,j--)
a[i]=num1[j]-'0';
//num2 - m位
for(i=0,j=m-1;i<m;i++,j--)
b[i]=num2[j]-'0';
/********以上都是在存储大数,以下开始模拟减法*********/
for(i=0;i<max;i++){
//处理借位
if(a[i]<b[i]>){
a[i]+=10;
a[i+1]--;
}
c[i]=a[i]-b[i];
}
//处理借位可以单独拿出来 - 可以手动模拟一下两种方法
/*for(i=0;i
i=max-1;
while(c[i]==0)//去除答案数组c中高位上的0
i--;
while(i>=0)
printf("%d",c[i--]);
}
这里只是一个个人的尝试,在一个函数中处理符号和加减法问题 ,但实用价值不大,可跳过。
#include
//辨识负数的大数加法
using namespace std;
int main(){
//读入两个大数
string num1,num2;
//cin>>num1>>num2;
num1="9823465789117604259074834";
num2="-31468761323431";
//和 9823465789149073020398265 差 9823465789086135497751403
//分别获取位数并将每一位存入整形数组中
int n=num1.length(),m=num2.length();
int fg1,fg2,fg;//符号位
fg1=(num1[0]=='-')?-1:1;
n=(fg1==-1)?n-1:n;//负号则总位数减一
fg2=(num2[0]=='-')?-1:1;
m=(fg2==-1)?m-1:m;//负号则总位数减一
fg=(fg1*n+fg2*m>0)?1:-1;
//将位数较大的数放在第一个
if(n<m){
string temp = num1;
num1=num2;
num2=temp;
int tmp = n;
n=m;
m=tmp;
}
int max= m>n?m:n +1;//两个数相加减 位数不会超过较大的数的位数+1
int a[max],b[max],c[max];//每一位都初始化为0
//int a[max]={0},b[max]={0},c[max]={0};//每一位都初始化为0
int i,j;
for(i=0;i<max;i++)//初始化
a[i]=0;
for(i=0;i<max;i++)//初始化
b[i]=0;
for(i=0;i<max;i++)//初始化
c[i]=0;
//num1 - n位
for(i=0,j=n-1+(fg1==-1?1:0);i<n;i++,j--)
a[i]=num1[j]-'0';
//num2 - m位
for(i=0,j=m-1+(fg2==-1?1:0);i<m;i++,j--)
b[i]=num2[j]-'0';
/*******到此为止以上都是大数的保存,下面将模拟大数加减法的运算***********/
for(i=0;i<max;i++)
if(fg1*fg2==1)//同号则当正数加法来做
c[i]=a[i]+b[i];
else//异号则是减法
c[i]=a[i]-b[i];//拿较大数每一位减去小数每一位
//处理进位
for(i=0;i<max;i++){
if(c[i]>=10){
c[i+1]+=1;//进10位
c[i]%=10;//留下个位
}else if(c[i]<0){
c[i+1]-=1;
c[i]+=10;
}
}
i=max-1;
while(c[i]==0)
i--;
if((fg1==-1&&fg2==-1)||fg==-1)
printf("-");
while(i>=0)
printf("%d",c[i--]);
}
对于乘法的模拟需要一定的理解,其中最重要的代码既是c[i+j]+=a[i]*b[j];
,意思是a的第i位乘以b的第j位要存在c的第i+j位上,可以自己画一个乘法的列式,确实如此。
乘法模拟:
//定义第三个数组来存储结果
int c[3000]={0};
for(i=0;i<n;i++)
for(j=0;j<m;j++){
c[i+j]+=a[i]*b[j];
}
//处理进位的情况
for(i=0;i<n+m;i++){
if(a[i]>=10){
c[i+1]+=c[i]/10;
c[i]%=10;
}
}
完整代码
#include
using namespace std;
int main(){
//用字符串读入两个大数
string num1 ,num2;
cin>>num1>>num2;
//获取两个大数的位数
int n=num1.length(),m=num2.length();
int a[n],b[m];//定义两个整形数组用于待会存储这两个大数的每一位
int i,j;
//用整形数组从低位到高位存储这两个大数
for(i=0,j=n-1;i<n;i++,j--)
a[i]=num1[j]-'0';
for(i=0,j=m-1;i<m;i++,j--)
b[i]=num2[j]-'0';
/*******到此为止以上都是大数的保存,下面将模拟大数乘法的运算***********/
//定义第三个数组来存储结果
int c[3000]={0};
for(i=0;i<n;i++)
for(j=0;j<m;j++){
c[i+j]+=a[i]*b[j];
}
//处理进位的情况
for(i=0;i<n+m;i++){
if(a[i]>=10){
c[i+1]+=c[i]/10;
c[i]%=10;
}
}
//打印结果
for(j=2999;j>0;j--){
if(c[j]!=0)
break;
}
for(i=j;i>=0;i--)
printf("%d",c[i]);
printf("\n");
return 0;
}
对于大数除法,我们一开始就讲过,其是用减法来实现的:
例如 9999 / 30 9999 / 30 9999/30 直接除以 30 ∗ 100 30*100 30∗100 连除三次 商 300 300 300 余 999 999 999
再除以 30 ∗ 10 30*10 30∗10 连除 3 3 3次 商330 余 99 99 99
再除以 30 ∗ 1 30*1 30∗1 连除 3 3 3次 商333 余 9 9 9
9 9 9除不尽 30 30 30 所以最终商为 333 333 333
#include
using namespace std;
int division(string num1, string num2, int c[], int n, int m);
int substract(int a[], int b[], int n, int m);
int main()
{
//读入两个大数
string num1, num2;
//cin>>num1>>num2;
num1 = "15697445132154737464164654641";
num2 = "13545554345798743";
//结果:1,158,863,249,994.8900064948937468194
//获取两个大数的长度
int n = num1.length(), m = num2.length();
int sum[10001];
int lenc = division(num1, num2, sum, n, m);
for (int i = lenc - 1; i >= 0; i--)
printf("%d", sum[i]);
return 0;
}
/*参数:
a[] 除数
b[] 被除数
c[] 存储商的数组
n 除数长度
m 被除数长度
返回值:
商的长度
*/
int division(string num1, string num2, int c[], int n, int m)
{
if (n < m)
return 0; //除不尽
int diff = n - m;
//定义两个数组去翻转存储这两个大数
int a[n] = {0}, b[n] = {0}, i, j;
for (i = 0, j = n - 1; j >= 0; i++, j--)
a[i] = num1[j] - '0';
for (i = diff, j = m - 1; j >= 0; i++, j--) //注意这里b的存储方式是将b扩大了10^diff倍
b[i] = num2[j] - '0';
cout << endl;
for (i = 0; i < n; i++) //数组c全部初始化为0
c[i] = 0;
int temp; //接收substact 函数返回参数
m = n;
for (i = 0; i <= diff; i++)
while ((temp = substract(a, b + i, n, m - i)) >= 0) //b+i相当于b/(i^10)
{
n = temp;
c[diff - i]++;
}
cout << endl;
//计算长度
for (i = diff; c[i] == 0 && i >= 0; i--)
;
return i + 1;
}
/*
参数:
a[] 减数
b[] 被减数
n 减数长度
m 被减数长度
返回值:
结果的长度
>
*/
int substract(int a[], int b[], int n, int m)
{
if (n < m)
return -1;
int i;
if (n == m) //判断 a > b
for (i = n - 1; i >= 0; i--) //丛大位开始比较
{
if (a[i] > b[i]) //等则继续比 大则a>b
break;
else if (a[i] < b[i])
return -1; // a < b
}
for (i = 0; i <= n - 1; i++)
{
a[i] -= b[i];
if (a[i] < 0)
{
a[i + 1]--;
a[i] += 10;
}
}
for (i = n - 1; i >= 0; i--) // 查找结果的最高位
{
if (a[i]) //最高位第一个不为0
return (i + 1); //得到位数并返回
}
return 0; //两数相等的时候返回0
}
都看到这里了,各位哥哥姐姐叔叔阿姨给小王点个赞 关个注 留个言吧,和小王一起成长吧,你们的关注是对我最大的支持。
有事没事进来看看吧 : 小王的博客目录索引
专栏看这 : 数据结构与算法专栏
如果以上内容有任何不准确或遗漏之处,或者你有更好的意见,就在下面留个言让我知道吧-我会尽我所能来回答。