首页 > 编程语言 >【基础算法】高精度算法

【基础算法】高精度算法

时间:2024-07-26 17:25:37浏览次数:18  
标签:10 高精度 int 基础 back 算法 vector size

高精度算法


计算机最初、也是最重要的应用就是数值运算。在编程进行数值运算时,有时会遇到运算的精度要求特别高,远远超过各种数据类型的精度范围;有时数据又特别大,远远超过各种数据类型的极限值。这种情况下,就需要进行“高精度运算”。
高精度运算首先要处理好数据的接受和存储问题,其次要处理好运算过程中的“进位”和“借位”问题。

高精度加法

用字符串输入两个数,再导入数组,然后每位相加,如果某位数字>10,则此位模10,下一位加一,最后用while循环去除前导零再输出即可。
高精度加法说白了就是两个大数之间相加,数字长度不超过10^6。注意这里是长度,而不是数据大小!但是这种数字如果放到变量中肯定是存不下的,所以我们一般用数组来存储,在 C++ 中一般用 vector 容器。如果存入数组中,就需要考虑存储顺序,究竟应该正着存还是倒着存?实际上,倒着存,是很合适的,因为对于数组来说,给一个数的后面一个数加1 很简单,但是在一个数的前面加上1 就很麻烦。就比如这张图:
在这里插入图片描述
如果我们 倒着存 那么 a[0]+ b[0]= 11是需要进位的。如果倒着存就可以 很快的进行进位,直接在下标1处进行自增即可;但是如果正着存,那么进位就需要到 -1 下标了,这样就不麻烦,我们算法就是为了更快解决问题,所以自然选择最合适的方式:倒着存
而高精度加法运算其实就像我们小学列竖式 一样
· 从最低位开始计算,如果两个数相加超过 10 ,就需要进位。

思想:
假如数组 a 和6分别用来存数据,c用来存储答案。
通过循环同时遍历 a、b数组,在遍历的同时,使用t来判断是否进位。将 a[i]+ b[i]的数据累加到t中。数据相加有两种结果:
· 如果 a[i] + b[i]<10 ,直接将 t放入c,让 t /= 10 ,以便下一次计算。
· 如果 a[i]+b[i]=10,将t%10=0放入c,让t/= 10
· 如果 a[i] + b[i] >10 ,将t%10 放入c数组,将 t/= 10 作为 进位,下一次t初始就是1.
就拿这张图理解:
在这里插入图片描述
这里就是对最后一位进行运算时,所做的进位操作。
而 t%10 最终的结果肯定在0~ 9之间,如果t< 10 小,那么 %10 不会对运算结果产生影响;对于t> 10的情况,则会将结果控制到0~9之间。

模板:

vector<int> Add(vector<int> &A, vector<int> &B)
{
    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size() || i < B.size(); i++) {
        if (i < A.size()) 
            t += A[i];
        if (i < B.size()) 
            t += B[i];
        C.push_back(t % 10);
        t /= 10;
    }
    if (t) 
        C.push_back(1);
    return C;
}

简单讲一下模板:
a和b是倒着存的,并同步遍历,由于数据大小不确定,所以只要a和b有一个符合条件,则就可以被t累加,符合条件的就加上该位置的元素,否则就不处理,默认为0。每次将 t%10 尾插到结果数组c中,然后将 t/10,以便下次运算,如果有进位,那么下次t的初值就为1。最后循环结束后,再判断一下是否还有进位没进,如果有进位,则将1尾插到c中。

模板题+详解

题目:
给定两个正整数(不含前导 0),计算它们的和。
输入格式:
共两行,每行包含一个整数。
输出格式:
共一行,包含所求的和。
数据范围:
< 整数长度 < 100000
输入样例:
12
23
输出样例
35

#include <iostream>
#include <vector>
using namespace std;
const int N=1e6+10;//定义常变量方便改范围 
 
vector<int> Add(vector<int> &A,vector<int> &B)//高精度加算法模板 
{
	vector<int> C;//定义C用于返回add的结果 
	int t=0;//存放进位 
	for(int i=0;i<A.size()||i<B.size();i++)//A和B全都运算完才结束 
	{
		if(i<A.size()) 
			t+=A[i];//要是没超过A的长度就加上A当前位的数据 
		if(i<B.size()) 
			t+=B[i];//要是没超过B的长度就再加上B当前位的数据 
		C.push_back(t%10);//刚刚两次相加模拟了两数相加,把个位存放到C 
		t/=10;//把十位存回给t,用作下一位数的进位 
	}
	if(t) 
		C.push_back(1);//要是全都结束了还有进位没用完就再补上一个1 
	return C;//返回Add的结果 
}
 
int main() 
{
	string a,b;//用于输入两个大数 
	vector<int> A,B;//用于存放两个大数 
	cin>>a>>b;//输入两个大数 
	for(int i=a.size()-1;i>=0;i--)
		A.push_back(a[i]-'0');//倒序存放大数 
	for(int i=b.size()-1;i>=0;i--)
		B.push_back(b[i]-'0');//倒序存放大数 
	auto C=Add(A,B);//auto用于自动推导变量类型,使代码更简洁
	for(int i=C.size()-1;i>=0;i--)
		printf("%d",C[i]);//由于之前倒序运算,此时倒序输出结果会变正序 
	return 0;
} 

auto用于自动推导变量类型,使代码更简洁

高精度减法

用字符串输入两个数,再导入数组,判断是否后数比前数,如果是则输出负号再交换数组。然后按位相减不够借1,最后用while循环去除前导零再输出即可。
高精度减法是对大整数的减法,数据长度不超过 10^6
我们讲解的 高精度减法是基于对正整数的算法 ,如果计算的是负数,那么需要微调。
高精度减法使用的存储方式为 倒序存储 。还是和竖式计算十分相似。

模板:

bool cmp(vector<int> &A,vector<int> &B)//比较A和B大小,若A>=B则返回true 
{
	if(A.size()!=B.size())
		return A.size()>B.size();//若长度不同,则谁长谁的值就更大 
	for(int i=A.size()-1;i>=0;i--)
	{
		if(A[i]!=B[i])
			return A[i]>B[i];//若长度相同,则逐位比较 
	}
	return true;//若都相同,也返回true 
}
 
vector<int> sub(vector<int> &A,vector<int> &B)//高精度减算法模板 
{
	vector<int> C;//定义C用于返回sub的结果 
	int t=0;//存放进位 
	for(int i=0;i<A.size();i++)//A运算完了才结束 
	{
		t=A[i]-t;//先让A[i]减去进位的数,结果存回给t 
		if(i<B.size()) 
			t-=B[i];//再在B的长度范围内,减去B[i],存回t 
		C.push_back((t+10)%10);//临时存放当前运算位结果的绝对值 
		if(t<0) 
			t=1;//要是当前运算结果是负数,表示要借1位,所以t更新为1 
		else 
			t=0;//否则没进位,t更新为0 
	}
	while(C.size()>1&&C.back()==0) 
		C.pop_back();//删除多余的0 
	return C;//返回sub的结果 
}

讲一下这块是什么意思:

while(c.size()>1&&c.back()==0)
{
	c.pop_back();
}

由于我们的数据时是倒着存放的,而两个数相减结果为0,就会在该位填上0。比如 666 ~ 665 倒着存储并在经过上方的高精度运算后,c中结果为 100 ,所以这种情况就需要去前导0。上面的操作就是检查长度是否至少为1,且c尾部是否为0。

模板题+详解

题目
给定两个正整数(不含前导0),计算它们的差,计算结果可能为负数。
输入格式:
共两行,每行包含一个整数。
输出格式
共一行,包含所求的差。
数据范围:
<整数长度 < 105
输入样例:
32
11
输出样例
22

#include<iostream>
using namespace std;
 
bool cmp (vector<int> &A, vector<int> &B){	//用来比较A,B的大小 
	if(A.size() != B.size())//若A和B的位数不同 
	{	
		return A.size() > B.size();	//则可以直接比较,位数多的数大 
	}else//若A和B的位数相同 
	{		
		for(int i = A.size() - 1; i >= 0; i--)
		{ //比大小,需将逆序输入的数再次逆序,然后从最高位开始往后,分别对两个数的每一位比较大小 
			if(A[i] != B[i])	//如果某位上的数字A和B的不相等 
				return A[i] > B[i];  //就可以直接比较A和B在那一位上的数的大小
		}
	}
	return true;
}

vector<int> sub(vector<int> &A, vector<int> &B)
{  
	vector<int> C;//定义C用于返回sub的结果 
	int t = 0;	//借位
	for(int i = 0; i < A.size(); i++)
	{	
		t = (A[i] - t);		//如果有借位,则在减法刚开始时先减去借位数t 
		if(i < B.size())
			t = t - B[i]; //如果B还有位数没有减的话 
		C.push_back((t + 10) % 10);//(t+10)%10作为某一位上相减的结果,+10为了防止出现负数 
		if(t < 0)	//某一位上不够减 
			t = 1;	//向后面借1 
		else
			t = 0;	
	}
	while(C.size() > 1 && C.back() == 0){  //while循环去掉前导零 
	//C.size() > 1,说明如果结果就一位数,不用去除前导零 C.back() == 0,容器最后一个的位数是零的话
		C.pop_back();	 //去掉容器的最后一个位数
	}
	return C;
}
 
int main(){
	vector<int> A, B;//A,B用于存放两个大数
	string a, b;//用于输入两个大数 
	cin >> a >> b; //输入两个大数 
	//逆序输入容器 ,因为逆序好处理进制 
	for(int i = a.size() - 1; i >= 0; i--) 
		A.push_back(a[i] - '0');//a[i]-'0'将字符串a中字符形式的数字转换成真实数字值。通过减去字符 '0' 的ASCII码,获得对应的数字值。
	for(int i = b.size() - 1; i >= 0; i--) 
		B.push_back(b[i] - '0');
	if(cmp(A, B))//比大小,若A大于B 
	{	
		auto C=sub(A, B);	//说明是大数减去小数,A直接减B 
		for(int i = C.size() - 1; i >= 0; i--)	//逆序输入,所以逆序输出 
			cout << C[i];
	}
	else//A小于B 
	{		
		cout << "-";	//说明是小数减去大数,要在前面添加负号 
		auto C=sub(B , A);	   //用大数B减小数A后在前面加负号即可 
		for(int i = C.size() - 1; i >= 0; i--) //逆序输出 
			cout << C[i];
	}
	return 0;
}

高精度乘法

导入方法与前面一样,导入后按乘法竖式思路相乘,再按常规思路进位。最后去除前导零就行了。
我们这里讲的高精度乘法为大整数 x 小整数,大整数长度不超过 10^6 ,小整数数据范围不超过10^9 高精度乘法就不只是单单的数学计算了,这里有些不同。首先大数 a 倒序存储到 vector 中,这样也是为了方便进位,首先设定进位t。再看一个例子,了解一下进位规则:
在这里插入图片描述
就比如这个例子,大数α 的单独位数直接和b相乘,将结果累加到t中,将乘得的结果 %10 存放到c数组中,然后 t/= 10,将进位去掉一位 。其实这里的进位也很好理解,无非就是要让t到下一位,而下一位是当前位次 x10 ,所以t要前进一位就要 /10.
这就是高精度乘法的运算规则,也不需要分类讨论,就记住这个规律就好。虽然运算方法和我们从前计算方式有些不同,但是最终计算结果是相同的。

模板

vector<int> mul(vector<int> &A,int b)//高精度乘算法模板 
{
	vector<int> C;//定义C用于返回mul的结果 
	int t=0;//存放进位 
	for(int i=0;i<A.size()||t;i++)//A运算完了并且还要t为0才结束 
	{
		if(i<A.size()) 
			t+=A[i]*b;//每一位上进行乘法,并加上进位数t
		C.push_back(t%10);//即保留乘出结果的个位数到对应位上 
		t/=10;//算出进位数t
	}
	return C;//返回mul的结果 
}

模板题+详解

描述:
给定两个非负整数(不含前导0)A和 B,请你计算 A xB的值。
输入格式:
共两行,第一行包含整数 A,第二行包含整数 B
输出格式:
共一行,包含 A x B 的值。
数据范围:
1 <= A的长度 <= 100000
0<=B<=10000
输入样例:
2
3
输出样例:
6

#include <iostream>
#include <vector>
using namespace std;
const int N=1e6+10;//定义常变量方便改范围 
 
vector<int> mul(vector<int> &A,int b)//高精度乘算法模板 
{
	vector<int> C;//定义C用于返回mul的结果 
	int t=0;//存放进位 
	for(int i=0;i<A.size()||t;i++)//A运算完了并且还要t为0才结束 
	{
		if(i<A.size()) 
			t+=A[i]*b;//每一位上进行乘法,并加上进位数t
		C.push_back(t%10);//取末尾数字作为当前结果压入栈中 即保留乘出结果的个位数到对应位上 
		t/=10;//算出进位数t
	}
	return C;//返回mul的结果 
}
 
int main()//调用算法模板测试 
{
	string a;//用于输入一个大数 
	int b; //用于输入一个整数
	vector<int> A;//用于存放一个大数 和上面不同,这里b是int型,故不用压入容器 
	cin>>a>>b;//输入两个数相乘 
	for(int i=a.size()-1;i>=0;i--)
		A.push_back(a[i]-'0');//倒序存放大数 
	vector<int>C=mul(A,b);//调用高进度乘算法 
	for(int i=C.size()-1;i>=0;i--)
		printf("%d",C[i]);//输出mul的结果 
	return 0; 
}  

在C++中,可以在同一个作用域内多次声明同一个类型的变量,只要每次声明的变量名是唯一的。因此,虽然vector< int >C在前面已经定义过了,但是在 vector< int >C=mul(A,B); 这行代码中又声明了一个同样类型的新变量C,这是完全允许的

高精度除法

我们这里讲的高精度除法为大整数 / 小整数,大整数长度不超过 10^6 ,小整数数据范围不超过10^9我们人在做除法时,会先看第一位,如果第一位大于除数,则在结果相应位置写下除以除数之后的数据,否则看下一位,这样以此类推。所以人算除法第一位都是有效数据位。
但是对于计算机不是这样,计算机则会默认从第一位算起,举个例子,比如 1234/11:如果以人的角度,第一位肯定为1,但是计算机,会从第一位开始看,第一位为 0。而除法可能产生余数 ,所以还需要一个变量来记录余数。有了这个概念,我们先看模板:
我们的模板是倒着存数据的,但是高精度除法是可以正着存的,因为除法需要从第一位开始除,所以正着存完全没有问题,但是之后可能会有高精度的混合运算,所以我们这边保持一致,也是倒着存

模板

vector<int> div(vector<int> &A, int b, int &r)
{
    vector<int> C;
    r = 0;
    for (int i = A.size() - 1; i >= 0; i--) {
        r = r * 10 + A[i];
        C.push_back(r / b);
        r %= b;
    }
    reverse(C.begin(), C.end());
    while (C.size() > 1 && C.back() == 0) 
        C.pop_back();
    return C;
}

看完模板之后,我们对里面的一些代码进行讲解
第1块:

for (int i = A.size() - 1; i >= 0; i--) {
    r = r * 10 + A[i];
    C.push_back(r / b);
    r %= b;
}

首先看这一步,高精度除法比另外三个算法难的原因就是出在这一步上,因为运算规则可能不太好理解。
我们知道,如果要做除法运算,那么就需要一定的 补位,r*18+ A[i]就是在补位,因为下一次的需要被除的数据,就是第一次相除后的余数 x10 ,然后加上当前元素 A[i]。而除之后的结果就是r/6,每次除完都有相应的余数,所以,r%=b。下面我们就用一张图演示一下
在这里插入图片描述
通过这张图,我们就可以完美的解释代码究竟在干什么,实际上这就是一个计算的过程,过程涉及补位,相除,得余数等操作…而最后,在进行完所有的操作之后,其实就是最终的余数。
第2块:

reverse(C.begin(), C.end());
while (C.size() > 1 && C.back() == 0) 
    C.pop_back();

这两步就是在去前导 0上面的图中我们也可以发现,高精度除法也是有前导0的,但是对于顺序表来说,前导 0不太好去,所以就逆置一下再去前导 0最后倒着输出 c即可。

模板题+详解

描述:
给定两个非负整数(不含前导0)A,B 请你计算 A/B 的商和余数。
输入格式:
共两行,第一行包含整数 A,第二行包含整数 B
输出格式:
共两行,第一行输出所求的商,第二行输出所求余数。
数据范围:
1< =A的长度< =100000
1<=B<=10000
B 一定不为 0
输入样例:
7
2
输出样例
3
1

#include <iostream>
#include <vector>
#include <algorithm>//调用reverse函数 
using namespace std;
const int N=1e6+10;//定义常变量方便改范围 
 
vector<int> div(vector<int> &A,int b,int &r)//高精度除算法模板 
{
	vector<int> C;//定义C用于返回div的结果 
	r=0;//存放余数 
	for(int i=A.size()-1;i>=0;i--)//由于倒序,所以从最高位运算 
	{
		r=r*10+A[i];//当前运算时的被除数 
		C.push_back(r/b);//算商 
		r%=b;//算余数 
	}
	reverse(C.begin(),C.end());//此时结果为正序,把他反转成倒序 //逆序,是为了方便去除前导零 
 	//不逆序的话,从前往后去除前导零不方便
	//其一是因为vector没有如pop_back类似的方法去去除第一个位置的元素
	// 其二是因为先去除前面的数,那后面的数也要相应地整体往前移动,过于复杂 
	while(C.size()>1&&C.back()==0)
		C.pop_back();//去除前导零
	return C;//返回div的结果 
}
 
int main()//调用算法模板测试 
{
	string a;//用于输入一个大数 
	int b; //用于输入一个整数 
	int r; //用于存放余数 
	vector<int> A;//用于存放一个大数 
	cin>>a>>b;//输入两个数相除 
	for(int i=a.size()-1;i>=0;i--)
		A.push_back(a[i]-'0');//倒序存放大数 
	auto C=div(A,b,r);
	for(int i=C.size()-1;i>=0;i--)
		printf("%d",C[i]);//前面逆序了,故这里逆序输出div的结果 
	cout<<endl<<r<<endl;//输出余数 这行代码的意思:先输出一个换行,然后输出变量 r 的值,再输出一个换行
	return 0; 
}  

标签:10,高精度,int,基础,back,算法,vector,size
From: https://blog.csdn.net/2402_85428625/article/details/140616040

相关文章

  • 灭火器检测算法:防患未然,精准高效,AI智能守护加油站消防安全
    随着科技的飞速发展和安全意识的不断提升,加油站作为易燃易爆场所,其消防安全管理显得尤为重要。其中,消防灭火器的有效部署和及时维护是保障加油站安全的关键环节。近年来,AI技术在消防安全领域的应用日益广泛,特别是加油站消防灭火器检测AI算法的研发与应用,为加油站的消防安全管理提......
  • 黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
    想要成为黑客,却苦于没有方向,不知道从何学起,下面这篇黑客入门教程可以帮你实现自己的黑客梦想,如果想学,可以继续看下去,文章有点长,希望你可以耐心看到最后 1、Web安全相关概念(2周)·熟悉基本概念(SQL注入、上传、XSS、、CSRF、一句话木马等)。通过关键字(SOL注入、上传、XSSC......
  • 黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
    想要成为黑客,却苦于没有方向,不知道从何学起,下面这篇黑客入门教程可以帮你实现自己的黑客梦想,如果想学,可以继续看下去,文章有点长,希望你可以耐心看到最后 1、Web安全相关概念(2周)·熟悉基本概念(SQL注入、上传、XSS、、CSRF、一句话木马等)。通过关键字(SOL注入、上传、XSSC......
  • 地理位置相关基础数据
    基础数据LONGITUDE_LATITUDE={"110000":{"name":"北京市","latitude":39.904989,"longitude":116.405285},"110101":{"name":"东城区","latitude":39.917544,"......
  • 「代码随想录算法训练营」第二十一天 | 回溯算法 part3
    93.复原IP地址题目链接:https://leetcode.cn/problems/restore-ip-addresses/题目难度:中等文章讲解:https://programmercarl.com/0093.复原IP地址.html视频讲解:https://www.bilibili.com/video/BV1XP4y1U73i/题目状态:好难,看题解通过思路:和分割回文串一样,甚至更难,在单层......
  • 【数据结构与算法】快速排序万字全攻略:hoare版本、挖坑法、前后指针法、优化版、非递
          ......
  • Python基础知识点(1)基本语句
    基本语句1.if语句if表达式:语句块其中,表达式是一个返回True或False的表达式。如果表达式为True,则执行if下面的语句块;如果为False,则跳过语句块执行下面的语句。2.if…else语句if表达式:语句块1else:语句块2其中,表达式是一个返回True或False的表达式。如果......
  • python基础函数
    1.为什么使用函数使用函数的目的是去减少代码的冗余性,简化代码的复杂度2.如何去定义一个函数以def开头去进行相关的定义在def的后面我们就去以见明知意的方式去定义一个函数的名称在函数名称后面的括号中去添加参数值,可以是多个参数,也可以是无餐的3.函数的调用无参多......
  • 软考-软件设计师(1)-计算机基础知识点:进制转换、数据编码、内存编址、串并联可靠性、
    场景软考-软件设计师-计算机基础模块高频考点整理。以下为高频考点、知识点汇总,不代表该模块所有知识点覆盖,请以官方教程提纲为准。注:博客:霸道流氓气质-CSDN博客实现知识点进制转换十进制转二进制除以2,反向取余数,直到商为0终止,转换成其他进制同理二进制转十进制其......
  • 软考-软件设计师(3)-数据结构与算法:树、图、队列、查找算法、排序算法、霍夫曼编码/
    场景软考-软件设计师-数据结构与算法模块高频考点整理。以下为高频考点、知识点汇总,不代表该模块所有知识点覆盖,请以官方教程提纲为准。注:博客:霸道流氓气质-CSDN博客实现知识点树:节点的度、树的度、深度、高度、满二叉树、完全二叉树、平衡二叉树、B树、二叉排序树节点......