除法是四则运算里唯一一个要从最高位开始算的——模拟竖式除法,带着余数从左往右一路除下去。
前面三节(加、减、乘)都是从最低位(个位)算起,这也是为什么之前都要把数字倒序存进数组。但除法完全反过来——回忆一下小学做除法竖式的过程:永远是从数字的最高位开始,一位一位地"落下来"继续除,这一点和加减乘正好相反。
正因为方向不同,高精度除法(这里特指"高精度 ÷ 低精度",也就是大数除以一个 long long 范围内的整数)不需要把数字倒序存储,直接按字符串的正常顺序(从左到右,最高位在前)处理反而更方便。
具体做法:维护一个"当前余数",从字符串最左边的字符开始,每次把"当前余数 × 10 + 新落下来的这一位数字"作为新的被除数,除以除数得到商的这一位,再更新余数,一路做到字符串末尾。
| 1 | string s; |
| 2 | long long divisor; |
| 3 | cin >> s >> divisor; // s 是被除数(字符串,不需要倒序),divisor 是除数 |
| 4 | |
| 5 | string quotient = ""; // 商,边算边往后追加字符 |
| 6 | long long remainder = 0; // 当前余数 |
| 7 | |
| 8 | for (int i = 0; i < (int)s.size(); i++) // 从最高位(下标0)开始往后遍历 |
| 9 | { |
| 10 | remainder = remainder * 10 + (s[i] - '0'); // 落下一位,拼成新的被除数 |
| 11 | quotient += (char)(remainder / divisor + '0'); // 这一位的商 |
| 12 | remainder %= divisor; // 更新余数,带到下一位继续用 |
| 13 | } |
| 14 | |
| 15 | // 去掉商前面多余的 0(比如 0045 应该只输出 45) |
| 16 | int start = 0; |
| 17 | while (start < (int)quotient.size() - 1 && quotient[start] == '0') |
| 18 | { |
| 19 | start++; |
| 20 | } |
| 21 | |
| 22 | cout << quotient.substr(start) << endl; // 商 |
| 23 | cout << remainder << endl; // 最后剩下的余数 |
string 按下标从 0 开始正向遍历即可,商也直接拼接成一个新的字符串,反而比前三节更简洁。如果除数本身也是一个大数(不能存进一个 long long),问题会复杂得多——每一位商是多少,不能像上面那样直接用 / 算出来,往往需要"试商":猜一个商的值,用竖式乘法算出"商 × 除数",再和当前余数比较大小(用到 1.10 节的比较方法),反复调整,或者用二分查找加速试商的过程。
把 1.9~1.12 四节的内容汇总成一张表,方便复习和查阅:
| 运算 | 处理方向 | 核心操作 | 时间复杂度 |
|---|---|---|---|
| 加法 | 从低位到高位 | 逐位相加,满 10 进位 | O(len) |
| 减法 | 从低位到高位 | 先比大小,逐位相减,不够借位 | O(len) |
| 乘法 | 先按位置累加,再统一处理 | a[i]*b[j] 累加到第 i+j 位,最后统一进位 | O(len1 × len2) |
| 除法 | 从高位到低位 | 余数×10+新一位,除以除数得到商 | O(len)(仅限除以低精度) |
把本节内容汇总成几条最容易踩坑的规则:
0045"这种带多余 0 的结果,必须先跳过前导 0 再输出。remainder * 10 + 下一位 时数值会先变大再取模,如果除数本身接近 int 的上限,中间结果可能超出 int 范围,建议统一用 long long 存储余数和除数,避免溢出。