大数除法
食用指南:
对该算法程序编写以及踩坑点很熟悉的同学可以直接跳转到代码模板查看完整代码
只有基础算法的题目会有关于该算法的原理,实现步骤,代码注意点,代码模板,代码误区的讲解
非基础算法的题目只有题目分析,代码实现,代码误区
题目描述:
-
给定两个非负整数(不含前导 0) A,B,请你计算 A/B 的商和余数。
输入格式
共两行,第一行包含整数 A,第二行包含整数 B。输出格式
共两行,第一行输出所求的商,第二行输出所求余数。数据范围
1≤A的长度≤100000,
1≤B≤10000,
B 一定不为 0
输入样例:
7
2
输出样例:
3
1 -
题目来源:https://www.acwing.com/problem/content/796/
题目分析:
- 被除数A是一个大数,位数最大为10万
- 除数B是一个正常数
- 经典算法:大数除法
- 返回值需要有两部分:商和余数,可以用全局变量或者函数传参实现两个结果的返回
算法原理:
- 关于大数定义和输入输出问题详见我在大数加法和大数减法中的内容:传送门
存储形式:
-
除法过程图示
-
除法和加减乘法的运算顺序不同,是从高位到低位的
将被除数A的A[0]作为最高位,A[size()-1]作为个位,符合除法运算的顺序
此处为了博客连贯,输入还是倒序,计算时从被除数的vector.size()-1开始运算 -
当题目不涉及大数加减乘时,完全可以将string从vector<int>[0]读入
商和余数:
- 商:
某一位上的商 = (前一位数的余数*10 + 这一位的数) / 除数
商的位数和被除数的位数一定是相同的,但是可能存在前导0
如上图的251 / 10,百位的2小于除数,以前导0占位,同时百位余数为2 - 余数:
和每一位都有商一样,每一位都会出现余数,当余数不为0时,参与计算下一位数的商
某一位的余数 = (前一位的余数*10 + 这一位的数) % 除数
写作步骤:
输入输出:
- 除法的被除数从vector<int>.size()-1的最高位开始运算
- 商向量最高位占据vector[0]
商余公式:
- 本位商 = (上位余数*10 + 本位数) / 除数
- 本位余数 = (上位余数*10 + 本位数) % 除数
消除前导0:
- 逆转商vector<int>,将前导0都pop()
核心算法:
-
商:第i位上的商 = (i-1位上的余数*10 + A[i]) / B
考虑前导0的意义,被除数的每一位其实都有商 -
余数:第i位上的余数 =(i-1位的余数*10 + A[i]) % B
同商,被除数的每一位都有余数,非0的余数参与下一位商的计算 -
🌟余数:从高位开始运算,由于模拟地板除,需要返回余数
-
🌟最关键的点:高位余数*10 + 低位 == 低位被除数
-
🌟最容易遗忘的点:
- 消除前导
- 为了pop()前导0,需要高位在vector/后,除法模拟时高位在前,所以需要reverse()函数
代码注意:
- 除法从高位开始,被除数从vector<int>.size()-1开始,但商向量最高位占据vector[0]
- 商余公式
- 前导0的消除:需要reverse()
- 余数也需要输出/返回
代码模板:
#include <iostream>
#include <algorithm>
using namespace std;
//注意点1:参数携带余数
vector<int> div(vector<int> A, int b, int &r){
vector<int> C;
r = 0;
//注意点2:从高位开始模拟
for(int i=A.size()-1; i>=0; i++){
//注意点3:高位余数*10+低位数 == 被除数
r = r*10 + A[i];
C.push_back(r / b);
r = r % b;
}
//注意点4:将高低位逆转,高位在back,又方便了消除前导0
reverse(C.begin() ,C.end());
while(C.size()>1 && C.back()==0)
C.pop_back();
return C;
}
int main(){
string a;
int B = 0;
cin >> a >> B;
vector<int> A;
for(int i=a.size()-1; i>=0; i--){
A.push_back(a[i] - '0');
}
int r = 0;
vector<int> C = (A,B,r);
for(int i=C.size()-1; i>=0; i--){
cout << C[i];
}
cout<<endl <<r;
return 0;
}
代码误区:
1. reverse()的头文件?
- reverse() 和 消除前导0 顺序不可以逆转
reverse()之后back()存储的是高位
pop_back()才能将高位前导0消除 - reverse()中两个参数,开始地址和结束地址:C.begin()和C,end()
2. 为什么被除数从size()-1开始运算?
- 考虑博客系列,被除数输入时是个位占据vector[0]
- 但是除法从被除数的最高位开始计算商和余数
- 所以我们从size()-1开始遍历被除数向量,而所得的商向量则确实是高位在[0],低位在back(),所以需要reverse()
高精度四则运算公式总结:
- 截止目前,高精度运算已经全部完成,四则运算其实是八个公式:
大数加法:
- 当前位 = (上一位的进位t + A[i] + B[i]) % 10;
- 进位 t = (上一位的进位t + A[i] + B[i]) / 10;
大数减法:
- 当前位 = (A[i] - 上次进位t - B[i] + 10) % 10;
- 借位 t = A[i] - t -B[i] <0 ? 1 : 0;
大数乘法:
- 当前位 = (进位t + A[i]*B) % 10; 或者 进位t %10;
- 进位 t = (进位t + A[i]*B) /10; 或者 进位t / 10;
大数除法:
- 当前商 = (上一位余数*10 + 当前位) / 除数
- 当前余数 = (上一位余数*10 + 当前位) % 除数
本篇感想:
-
前导0问题在大数除法中尤其严重,而且由于除法运算顺序,需要reverse()商向量后消除前导0
-
大数除法其实是地板除,需要输出商和余数,商余公式简单应对
-
本篇是第十篇,到现在感觉博客写的虽然很细,但是不够精炼,不够精干,每次自己写的时候都会参照原本刷题的笔记,写着写着就发现比笔记多写了很多内容,最终成品量是笔记的2~3倍
-
现在感觉不行,日后的博客要以精干为主,应该是笔记的提纯而不是丰富;
-
思维导图是一个很好的自察 & 学习的方式,每期的思维导图自我感觉做的很好。
-
第十期了,放个风景图庆祝一下
-
看完本篇博客,恭喜已登《练气境-初期》
距离登仙境不远了,加油