一、问题描述
- 大数的加法即两个非常大的数字进行相加,数字的大小超过了long long的表示范围(溢出)。由于数字可能非常大,不能直接转换为内置整型计算,必须逐位模拟竖式相加。
- 例如,要求实现两个非负整数的加法(负数的话直接算完前面加个负号即可),输入的数值为a = 42312313432351231276453587647867123561273189378912673678和b = 239123128973127663451234523478634675781267814562345612837324,请输出它们的和。
二、算法思想(竖式相加)
- 从两数的最低位(字符串最后一个字符)开始逐位相加。
- 每一位的计算包括两个对应位的值以及来自低位的进位(carry)。
- 若相加结果 ≥ 10,则该位结果为
sum - 10,并向高位产生进位 1。 - 当两个字符串长度不同,较长数剩余的位要继续加上可能存在的进位。
- 最终若最高位仍有进位,则在结果最前面补上
1。 - 将每位结果拼接回字符串并返回(注意去掉可能的前导零)。
三、示例代码解析(核心思想与实现)

c
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
typedef long long ll;
/*
将按位存放的数组转为字符串,并忽略前导零
参数 arr: 每个元素是 0~9 的位值(高位在前,低位在后),可能有前导 0
返回值: 去除前导零后的数字字符串;如果全为 0 则返回空字符串(使用者可判定)
*/
std::string num_arr_to_string(std::vector<int> arr){
std::string a("");
bool begin = false; // 标记是否遇到第一个非零位
for (int c: arr){
if (c != 0){
begin = true;
}
if (begin){
a += std::to_string(c);
}
}
return a;
}
// 调试用:打印数组中的每个元素(空格分隔)
void show_arr(std::vector<int> arr){
for (int c: arr){
std::cout << c << " ";
}
std::cout << std::endl;
}
// 检查一个字符串是否全为 0
bool is_zero_str(std::string a){
for (char c: a){
if (c != '0')
return false;
}
return true;
}
/*
大数相加(把结果保存在字符串中)
输入:a, b ------ 表示两个非负整数的字符串(例如 "123", "9")
输出:a + b 的结果(字符串形式)
说明:
- 作者用一个整型数组 arr 保存计算过程的每一位,最后再转成字符串返回
- 注意:代码里把 arr 的长度设为 lens = max_len * max_len,这里长度远大于实际需要。
这样做虽然"安全"但浪费空间。后面博客会指出更合理的做法:长度为 max_len + 1 即可。
*/
std::string big_num_add(std::string a, std::string b){
if (is_zero_str(a) && is_zero_str(b)){
return "0";
}
int max_len = std::max(a.length(), b.length());
ll lens = max_len * max_len; // 原代码使用 max_len*max_len(过度分配)
std::vector<int> arr(lens + 1, 0); // 存放每一位的结果(高位在前,低位在后),初始化为 0
bool jin = false; // 进位标记(carry),true 表示上一位有进位 1
int i = a.length() - 1; // 指向字符串 a 的最低位(从后向前遍历)
int j = b.length() - 1; // 指向字符串 b 的最低位
int idx = lens; // arr 的填充索引(从后向前填充)
// 同时处理 a 和 b 都还有位的情况
while (i >= 0 and j >= 0){
int xi = a[i] - '0'; // 当前 a 的位(0-9)
int xj = b[j] - '0'; // 当前 b 的位(0-9)
int num = xi + xj + jin; // 当前位相加(含进位)
if (num >= 10){
jin = true;
num -= 10; // 只保留当前位
}else{
jin = false;
}
arr[idx] = num; // 存入当前位
i -= 1;
j -= 1;
idx -= 1;
}
// 如果 a 还有剩余位,继续处理(把进位加到剩余位上)
if (i >= 0){
while(i >= 0){
int xi = a[i] - '0';
int num = xi + jin;
if (num >= 10){
jin = true;
num -= 10;
arr[idx] = num;
}else{
jin = false;
arr[idx] = num;
}
i -= 1;
idx -= 1;
}
// 若遍历完仍然有进位,把 1 放到当前 idx 处(高位)
if (jin) { arr[idx] = 1; }
}
// 如果 b 还有剩余位,继续处理(逻辑与上面对称)
if (j >= 0){
while(j >= 0){
int xj = b[j] - '0';
int num = xj + jin;
if (num >= 10){
jin = true;
num -= 10;
arr[idx] = num;
}else{
jin = false;
arr[idx] = num;
}
j -= 1;
idx -= 1;
}
if (jin) { arr[idx] = 1; }
}
if (jin) {arr[idx] = 1;} // 没有分支时的进位
// show_arr(arr); // 可启用来查看数组计算状态(调试用)
return num_arr_to_string(arr); // 把结果数组转换为字符串返回(去除前导零)
}
int main(){
std::string a, b;
std::cin >> a >> b; // 从标准输入读取两个字符串(假设都是非负整数且无额外空格)
std::string res = big_num_add(a, b);
std::cout << res;
return 0;
}
- 存储结构 :代码使用
std::vector<int> arr来保存每一位的结果(高位在前,低位在后),最后通过num_arr_to_string去掉前导零并转换为字符串返回。 - 进位处理 :用布尔变量
jin表示当前是否有进位(true表示有 1 的进位)。在每一步相加后更新jin。 - 数组大小 :原代码中将
arr的长度设为max_len * max_len,这是一种"超保守"的做法------保证数组足够大,但会严重浪费空间。更合理的做法是把数组长度设为max_len + 1,因为两个长度为max_len的数相加最多产生一个额外的最高位进位。 - 返回值 :
num_arr_to_string会去掉前导零;若结果为零,当前实现会返回空字符串(这点可以改进为返回"0",更符合直觉)。
四、代码逐行要点说明(关键处总结)
int i = a.length() - 1, j = b.length() - 1;:从两数的最低位开始处理。int num = xi + xj + jin;:加上三个部分:a 的位、b 的位、进位。- 若
num >= 10,设置jin=true并num-=10,否则jin=false。 - 遍历结束后,若仍有
jin,需在更高位写入1。 - 最后调用
num_arr_to_string去掉高位多余的 0 并得到字符串结果。
五、时间与空间复杂度
- 时间复杂度:
O(n),其中n = max(len(a), len(b)),每个位最多被访问常数次。 - 空间复杂度:原代码
O(L^2)(因为用了max_len * max_len),但最合理的实现只需O(n)(长度n + 1的数组或直接构造字符串存放结果)。
八、总结
大数运算是很多竞赛与工程场景常见的基础问题,掌握逐位相加、进位处理,以及如何在字符串/数组与结果字符串之间高效转换,是写出可靠代码的关键。