【C++&string】大数相乘算法详解:从字符串加法到乘法实现

引言

在计算机科学中,处理超出标准数据类型范围的大数是一个常见问题。我们已经学习了大数相加,现在将探索更复杂的大数相乘算法。本文将详细分析一个完整的字符串乘法实现,探讨其原理、优化方案和实际应用。

目录

引言

问题描述

完整算法实现

算法详解

[1. 整体思路:模拟竖式乘法](#1. 整体思路:模拟竖式乘法)

[2. 函数分解](#2. 函数分解)

[(1) addStrings - 大数相加](#(1) addStrings - 大数相加)

[(2) Single_Multiply - 大数乘以一位数](#(2) Single_Multiply - 大数乘以一位数)

[(3) multiply - 主函数](#(3) multiply - 主函数)

[3. 算法示例](#3. 算法示例)

时间复杂度分析

原始算法复杂度

复杂度优化空间

优化算法

优化1:使用数组存储中间结果

优化2:Karatsuba快速乘法算法

性能对比

实际应用场景

[1. 密码学](#1. 密码学)

[2. 科学计算](#2. 科学计算)

[3. 金融系统](#3. 金融系统)

[4. 分布式系统](#4. 分布式系统)

边界条件与测试

全面测试用例

常见错误与注意事项

[1. 忘记处理乘数为0的情况](#1. 忘记处理乘数为0的情况)

[2. 前导零处理不当](#2. 前导零处理不当)

[3. 进位处理错误](#3. 进位处理错误)

[4. 索引计算错误](#4. 索引计算错误)

扩展应用

[1. 大数阶乘](#1. 大数阶乘)

[2. 大数幂运算](#2. 大数幂运算)

[3. 大数除法](#3. 大数除法)

性能优化技巧

[1. 使用整数数组代替字符串](#1. 使用整数数组代替字符串)

[2. 并行计算优化](#2. 并行计算优化)

总结


问题描述

给定两个以字符串形式表示的非负整数 num1num2,返回它们的乘积,也以字符串形式表示。

注意:

  • 不能使用任何内置的大数库或直接将输入转换为整数

  • 字符串中不包含前导零,除了数字0本身

  • 1 ≤ num1.length, num2.length ≤ 200

示例 1:

text

复制代码
输入:num1 = "2", num2 = "3"
输出:"6"

示例 2:

text

复制代码
输入:num1 = "123", num2 = "456"
输出:"56088"

完整算法实现

cpp

复制代码
class Solution {
public:
    // 大数相加函数
    string addStrings(string num1, string num2) {
        string str;
        int end1 = num1.size() - 1, end2 = num2.size() - 1;
        // 进位
        int next = 0;
        while (end1 >= 0 || end2 >= 0) {
            int val1 = end1 >= 0 ? num1[end1] - '0' : 0;
            int val2 = end2 >= 0 ? num2[end2] - '0' : 0;

            int ret = val1 + val2 + next;
            next = ret / 10;
            ret = ret % 10;

            str += (ret + '0');

            end1--;
            end2--;
        }

        if (next == 1) {
            str += '1';
        }

        // 区间左闭右开
        reverse(str.begin(), str.end());
        return str;
    }

    // 大数乘以一位数函数
    string Single_Multiply(string str, int num) {
        int next = 0;
        string result;
        for (int i = str.size() - 1; i >= 0; i--) {
            int ret = (str[i] - '0') * num + next;
            next = ret / 10;
            ret = ret % 10;
            result += ret + '0';
        }
        if (next != 0) {
            result += next + '0';
        }

        reverse(result.begin(), result.end());
        return result;
    }

    // 大数相乘主函数
    string multiply(string num1, string num2) {
        // 处理乘数为0的情况
        if(num1 == "0" || num2 == "0") {
            return "0";
        }

        string result;
        int n = num2.size() - 1;
        
        // 模拟竖式乘法
        for (int i = n; i >= 0; i--) {
            // 计算num1与num2当前位的乘积
            string ret = Single_Multiply(num1, num2[i] - '0');
            
            // 根据位数补0(相当于乘以10的幂)
            int count = n - i;
            while (count--) {
                ret += '0';
            }
            
            // 累加部分乘积
            result = addStrings(result, ret);
        }
        
        return result;
    }
};

算法详解

1. 整体思路:模拟竖式乘法

该算法模拟了我们手工进行多位数乘法的过程:

  1. 将乘法分解为多个"大数乘以一位数"的问题

  2. 对每个部分乘积进行适当的移位(补0)

  3. 将所有部分乘积相加得到最终结果

2. 函数分解

(1) addStrings - 大数相加
  • 从个位开始逐位相加

  • 处理进位

  • 反转结果字符串

(2) Single_Multiply - 大数乘以一位数
  • 从个位开始逐位相乘

  • 处理乘积累加和进位

  • 反转结果字符串

(3) multiply - 主函数
  • 处理乘数为0的特殊情况

  • 遍历第二个数的每一位

  • 调用Single_Multiply计算部分乘积

  • 根据位数补0

  • 累加所有部分乘积

3. 算法示例

text

复制代码
计算:123 × 456

步骤1:123 × 6 = 738
步骤2:123 × 5 = 615,补1个0 → 6150
步骤3:123 × 4 = 492,补2个0 → 49200

累加:
     738
   6150
+ 49200
--------
  56088

时间复杂度分析

原始算法复杂度

  1. Single_Multiply函数:O(n),其中n是num1的长度

  2. addStrings函数:O(max(m, n)),其中m和n是两个加数的长度

  3. multiply函数:

    • 循环执行m次(m是num2的长度)

    • 每次循环:调用Single_Multiply (O(n)) + 补0 (O(1)) + 调用addStrings (O(k)),k是当前结果的长度

    • 总时间复杂度:O(m × (n + k)) ≈ O(m × n × m) = O(n × m²)(最坏情况)

复杂度优化空间

原始算法的时间复杂度较高,主要是每次循环都要进行字符串加法。我们可以使用数组来优化。

优化算法

优化1:使用数组存储中间结果

cpp

复制代码
class Solution {
public:
    string multiply(string num1, string num2) {
        if (num1 == "0" || num2 == "0") return "0";
        
        int m = num1.size(), n = num2.size();
        vector<int> res(m + n, 0);  // 乘积的最大长度为 m+n
        
        // 逐位相乘
        for (int i = m - 1; i >= 0; i--) {
            int x = num1[i] - '0';
            for (int j = n - 1; j >= 0; j--) {
                int y = num2[j] - '0';
                int sum = res[i + j + 1] + x * y;
                res[i + j + 1] = sum % 10;      // 当前位
                res[i + j] += sum / 10;         // 进位
            }
        }
        
        // 转换为字符串,跳过前导0
        string result;
        for (int i = 0; i < res.size(); i++) {
            if (result.empty() && res[i] == 0) continue;  // 跳过前导0
            result += (res[i] + '0');
        }
        
        return result.empty() ? "0" : result;
    }
};

优化2:Karatsuba快速乘法算法

对于非常大的数,可以使用Karatsuba算法,时间复杂度为O(n^log₂³) ≈ O(n^1.585):

cpp

复制代码
// Karatsuba算法实现大数相乘
class Karatsuba {
private:
    // 辅助函数:字符串相加
    string add(string a, string b) {
        // ... 实现同上
    }
    
    // 辅助函数:字符串相减(保证a>=b)
    string subtract(string a, string b) {
        // ... 实现需要
    }
    
    // 去除前导0
    string trimZeros(string s) {
        int i = 0;
        while (i < s.size() && s[i] == '0') i++;
        return i == s.size() ? "0" : s.substr(i);
    }
    
public:
    string multiply(string x, string y) {
        // 转换为等长
        int n = max(x.size(), y.size());
        while (x.size() < n) x = "0" + x;
        while (y.size() < n) y = "0" + y;
        
        // 基准情况
        if (n == 1) {
            int prod = (x[0] - '0') * (y[0] - '0');
            return to_string(prod);
        }
        
        int m = n / 2;
        
        // 分割x和y
        string x1 = x.substr(0, n - m);
        string x0 = x.substr(n - m);
        string y1 = y.substr(0, n - m);
        string y0 = y.substr(n - m);
        
        // 递归计算
        string z2 = multiply(x1, y1);
        string z0 = multiply(x0, y0);
        string z1 = multiply(add(x1, x0), add(y1, y0));
        z1 = subtract(subtract(z1, z2), z0);
        
        // 组合结果
        for (int i = 0; i < 2 * m; i++) z2 += "0";
        for (int i = 0; i < m; i++) z1 += "0";
        
        string result = add(add(z2, z1), z0);
        return trimZeros(result);
    }
};

性能对比

算法 时间复杂度 空间复杂度 适用场景
模拟竖式乘法 O(m×n) O(m+n) 中小规模大数相乘
原始字符串方法 O(n×m²) O(m+n) 教学理解
Karatsuba算法 O(n^log₂³) ≈ O(n^1.585) O(n) 大规模大数相乘
FFT算法 O(n log n) O(n) 超大规模大数相乘

实际应用场景

1. 密码学

  • RSA加密算法中的大数运算

  • 椭圆曲线密码学

  • 大素数生成和验证

2. 科学计算

  • 高精度数值计算

  • 物理模拟

  • 天文学中的大数计算

3. 金融系统

  • 高精度货币计算

  • 利率计算

  • 风险评估模型

4. 分布式系统

  • 一致性哈希

  • 分布式ID生成

  • 数据分片计算

边界条件与测试

全面测试用例

cpp

复制代码
void testMultiply() {
    Solution solution;
    
    // 基础测试
    assert(solution.multiply("2", "3") == "6");
    assert(solution.multiply("123", "456") == "56088");
    
    // 边界测试
    assert(solution.multiply("0", "123") == "0");
    assert(solution.multiply("123", "0") == "0");
    assert(solution.multiply("0", "0") == "0");
    
    // 大数测试
    assert(solution.multiply("999", "999") == "998001");
    assert(solution.multiply("123456789", "987654321") == "121932631112635269");
    
    // 前导零处理(输入不会包含前导零,但结果可能)
    assert(solution.multiply("10", "10") == "100");
    assert(solution.multiply("99", "99") == "9801");
    
    // 一位数测试
    assert(solution.multiply("5", "7") == "35");
    assert(solution.multiply("9", "9") == "81");
    
    // 长字符串测试
    string num1(100, '9');  // 100个9
    string num2(100, '9');  // 100个9
    string expected = string(199, '9') + "0" + string(199, '0').substr(1);
    // 实际上 999...9 × 999...9 = 999...98000...01
    
    cout << "所有测试通过!" << endl;
}

常见错误与注意事项

1. 忘记处理乘数为0的情况

cpp

复制代码
// 错误示例:没有检查0
string multiply(string num1, string num2) {
    // 如果num1或num2为"0",会进行不必要的计算
    // 最终结果可能正确,但效率低下
}

2. 前导零处理不当

cpp

复制代码
// 错误示例:结果包含前导零
string result = "00123";  // 应该为"123"

3. 进位处理错误

cpp

复制代码
// 错误示例:进位可能超过一位
int sum = x * y + carry;
carry = sum / 10;  // 正确
result += (sum % 10) + '0';

4. 索引计算错误

cpp

复制代码
// 错误示例:数组索引计算错误
// 两个n位数相乘,结果最多为2n位
vector<int> res(m + n, 0);  // 正确
vector<int> res(m + n - 1, 0);  // 错误:可能不够存储

扩展应用

1. 大数阶乘

cpp

复制代码
string factorial(int n) {
    string result = "1";
    for (int i = 2; i <= n; i++) {
        result = multiply(result, to_string(i));
    }
    return result;
}

2. 大数幂运算

cpp

复制代码
string power(string base, int exponent) {
    if (exponent == 0) return "1";
    if (exponent == 1) return base;
    
    string result = "1";
    while (exponent > 0) {
        if (exponent % 2 == 1) {
            result = multiply(result, base);
        }
        base = multiply(base, base);
        exponent /= 2;
    }
    return result;
}

3. 大数除法

cpp

复制代码
pair<string, string> divide(string dividend, string divisor) {
    if (divisor == "0") {
        throw runtime_error("Division by zero");
    }
    
    string quotient = "0";
    string remainder = dividend;
    
    while (compare(remainder, divisor) >= 0) {
        // 使用二分法加速
        string low = "1", high = remainder;
        string mid, product;
        
        while (compare(low, high) <= 0) {
            mid = divideByTwo(add(low, high));
            product = multiply(divisor, mid);
            
            if (compare(product, remainder) <= 0) {
                quotient = add(quotient, mid);
                remainder = subtract(remainder, product);
                break;
            } else {
                high = subtract(mid, "1");
            }
        }
    }
    
    return {quotient, remainder};
}

性能优化技巧

1. 使用整数数组代替字符串

cpp

复制代码
// 使用vector<int>存储大数,每个元素存储多位数字
// 例如:使用基数10000,每个元素存储0-9999
class BigInt {
private:
    vector<int> digits;
    static const int BASE = 10000;
    static const int BASE_DIGITS = 4;
    
public:
    // 乘法实现
    BigInt multiply(const BigInt& other) const {
        vector<long long> res(digits.size() + other.digits.size(), 0);
        
        for (size_t i = 0; i < digits.size(); i++) {
            long long carry = 0;
            for (size_t j = 0; j < other.digits.size() || carry > 0; j++) {
                long long cur = res[i + j] + carry;
                if (j < other.digits.size()) {
                    cur += (long long)digits[i] * other.digits[j];
                }
                res[i + j] = cur % BASE;
                carry = cur / BASE;
            }
        }
        
        // 转换为BigInt
        BigInt result;
        result.digits.resize(res.size());
        for (size_t i = 0; i < res.size(); i++) {
            result.digits[i] = res[i];
        }
        result.normalize();
        return result;
    }
};

2. 并行计算优化

cpp

复制代码
// 使用多线程并行计算部分乘积
string parallelMultiply(const string& num1, const string& num2) {
    int m = num1.size(), n = num2.size();
    vector<vector<int>> partialProducts(n, vector<int>(m + n, 0));
    
    // 并行计算每个部分乘积
    #pragma omp parallel for
    for (int j = 0; j < n; j++) {
        int y = num2[n-1-j] - '0';
        int carry = 0;
        for (int i = 0; i < m; i++) {
            int x = num1[m-1-i] - '0';
            int product = x * y + carry;
            partialProducts[j][i+j] = product % 10;
            carry = product / 10;
        }
        if (carry > 0) {
            partialProducts[j][m+j] = carry;
        }
    }
    
    // 合并所有部分乘积
    vector<int> result(m + n, 0);
    int carry = 0;
    for (int i = 0; i < m + n; i++) {
        int sum = carry;
        for (int j = 0; j < n; j++) {
            sum += partialProducts[j][i];
        }
        result[i] = sum % 10;
        carry = sum / 10;
    }
    
    // 转换为字符串
    string resStr;
    int idx = m + n - 1;
    while (idx > 0 && result[idx] == 0) idx--;
    for (; idx >= 0; idx--) {
        resStr += result[idx] + '0';
    }
    
    return resStr;
}

总结

大数相乘算法是计算机科学中的一个基础但重要的问题。通过本文的分析,我们学习了:

  1. 模拟手工计算:将复杂问题分解为多个简单步骤

  2. 算法优化:从简单实现到高效算法的演进

  3. 边界处理:处理特殊情况如乘数为0、前导零等

  4. 性能分析:理解不同算法的时间复杂度和空间复杂度

关键要点

  • 大数相乘可以通过模拟竖式乘法实现

  • 使用数组存储中间结果比字符串操作更高效

  • 对于非常大的数,Karatsuba或FFT算法是更好的选择

  • 正确处理边界情况和进位是算法的关键

掌握大数相乘算法不仅有助于解决类似问题,还能提高对算法设计、性能优化和数学思维的理解。在实际应用中,根据数据规模和性能要求选择合适的算法是关键。

相关推荐
苏纪云2 小时前
蓝桥杯考前突击
c++·算法·蓝桥杯
粥里有勺糖2 小时前
视野修炼-技术周刊第129期 | 上一次古法编程是什么时候
前端·javascript·github
W23035765732 小时前
经典算法详解:最长公共子序列 (LCS) —— 从暴力递归到动态规划完整实现
算法·动态规划·最长子序列
海兰2 小时前
使用 Spring AI 打造企业级 RAG 知识库第二部分:AI 实战
java·人工智能·spring
pzx_0012 小时前
【优化器】 随机梯度下降 SGD 详解
人工智能·python·算法
‎ദ്ദിᵔ.˛.ᵔ₎2 小时前
模板template
开发语言·c++
大邳草民2 小时前
Python 中 global 与 nonlocal 的语义与机制
开发语言·笔记·python
charlie1145141912 小时前
通用GUI编程技术——图形渲染实战(二十九)——Direct2D架构与资源体系:GPU加速2D渲染入门
开发语言·c++·学习·架构·图形渲染·win32
小肝一下2 小时前
每日两道力扣,day8
c++·算法·leetcode·哈希算法·hot100