大数乘法(分治思想)

大数乘法(分治思想)

代码如下:

#include <stdlib.h>
#include <cstring>
#include <iostream>
using namespace std;
#define M 100
char sa[1000];
char sb[1000];
typedef struct _Node {  // 面向过程的语言 面向对象 其实封装性 C语言可以完成这个思想   函数指针赋值 
   int s[M];
   int l;  // 长度 
   int c;  // 补位 
} Node, *pNode;
void cp(pNode src, pNode des, int st, int l) {
	int i, j;
 	for (i = st, j = 0; i < st + l; i++, j++) {
 		des->s[j] = src->s[i];
 	}
 	des->l = l;
 	des->c = st + src->c;
}
void add(pNode pa, pNode pb, pNode ans) {
 	int i, cc, k, palen, pblen, len;
 	int ta, tb;
 	pNode temp;
 	//保证pa是高次幂
 	if ((pa->c < pb->c)) {
     	temp = pa;
     	pa = pb;
     	pb = temp;
 	}
 	ans->c = pb->c; //结果的幂取最少的幂
	 cc = 0;
	 palen = pa->l + pa->c; //pa的长度
	 pblen = pb->l + pb->c; //pb的长度
	 if (palen > pblen)   //选取最长的长度
      	len = palen;
 	else
 	     len = pblen;
 	k = pa->c - pb->c; //k是幂差,len是最长的位数
 	for (i = 0; i < len - ans->c; i++) {
 	    if (i < k)
 	         ta = 0;
 	    else
 	         ta = pa->s[i - k];
 	    if (i < pb->l)
 	         tb = pb->s[i];
 	    else
	          tb = 0;
	     if (i >= pa->l + k)
	          ta = 0;
	     ans->s[i] = (ta + tb + cc) % 10;
	     cc = (ta + tb + cc) / 10;
	 }
	 if (cc)
	      ans->s[i++] = cc;
	 ans->l = i;
}
void mul(pNode pa, pNode pb, pNode ans) {
 int i, cc, w;
 int ma = pa->l >> 1, mb = pb->l >> 1;  // 位运算  右移1位 ,相当于是除以2 右移两位是除以4 右移n位  除以 2^n  
 Node ah, al, bh, bl;
 Node t1, t2, t3, t4, z;
 pNode temp;
 if (!ma || !mb) { // 除以2为0 则为证明前面至少有一个字符串为1 
     if (!ma) {     //如果pa是一位数,则和pb交换
         temp = pa;
         pa = pb;
         pb = temp;
    }
    ans->c = pa->c + pb->c;
    w = pb->s[0]; //pb可能为一位数
    cc = 0;
    for (i = 0; i < pa->l; i++) {
    //pa为2位数以上
         ans->s[i] = (w * pa->s[i] + cc) % 10;  // 取余运算: 取出个位数   23 % 10 =3 
         cc = (w * pa->s[i] + cc) / 10;     // 取余运算: 取出十位上的数   23 / 10 = 2
   }
	 if (cc)
    	ans->s[i++] = cc;  // 升幂 
 		ans->l = i;
 		return;
 	}
 	cp(pa, &ah, ma, pa->l - ma);     //高位升幂   12 升幂 
 	cp(pa, &al, 0, ma);                    //低位幂不变 3   
 	cp(pb, &bh, mb, pb->l - mb); 
     cp(pb, &bl, 0, mb);
 	mul(&ah, &bh, &t1); 
 	mul(&ah, &bl, &t2);
 	mul(&al, &bh, &t3);
 	mul(&al, &bl, &t4);
 	add(&t3, &t4, ans);
 	add(&t2, ans, &z);
 	add(&t1, &z, ans);
}

int main() {
 	Node ans, a, b;
 	// C++ 输出函数 
 	cout << "输入大整数 a:" << endl;
	 // 输入函数 
 	cin >> sa;
	 cout << "输入大整数 b:" << endl;
	 cin >> sb;
	 # 求字符串的长度 
	 a.l = strlen(sa);
	 b.l = strlen(sb);
	 int z = 0, i;
	 # 字符串反转的过程 
	 for (i = a.l - 1; i >= 0; i--)
       	a.s[z++] = sa[i] - '0';
 	a.c = 0;
 	z = 0;
	 for (i = b.l - 1; i >= 0; i--)
	       b.s[z++] = sb[i] - '0';
	 b.c = 0;
	 mul(&a, &b, &ans);
	 cout << "最终结果为:";
	 for (i = ans.l - 1; i >= 0; i--)
	       cout << ans.s[i];
	 cout << endl;
 return 0;
}

该算法与Karatsuba算法的算法思想类似,接下来先介绍一下Karatsuba算法

Karatsuba算法

Karatsuba算法是一种快速乘法算法,由Anatolii Alexeevitch Karatsuba在1960年发现。这个算法基于分治策略,可以比传统的长乘法(也就是小学时我们学到的按位乘法)更高效地执行大数乘法运算。它主要通过减少乘法操作的次数来提升计算速度,尤其是在处理非常大的数字时。

基本思想

考虑两个大整数XY,我们可以将它们表示为:

  • X = A * B^m + B
  • Y = C * B^m + D

其中,ACXY的高位部分,BD是低位部分,B^m表示基数Bm次幂,通常B取10,并且根据实际情况选择m值,使得ABCD大致位于原数的中间位置。

在传统乘法中,X * Y会被展开成四个乘积:A*C, A*D, B*C, B*D。然而,Karatsuba观察到只需进行三次乘法就可以得到最终结果:

  1. 计算A * C
  2. 计算B * D
  3. 计算(A + B) * (C + D),得到的结果中减去第1步和第2步的结果,即(A+B)*(C+D) - A*C - B*D = A*D + B*C

Karatsuba算法的步骤

给定两个大整数XY,执行以下步骤:

  1. 分割数字:将XY分别分割成两部分,X = A*10^n + BY = C*10^n + D

  2. 递归地计算乘积:

    • 计算A*C
    • 计算B*D
    • 计算(A+B)*(C+D)
  3. 组合结果:利用这些中间结果计算: X * Y

    • X*Y = A*C*10^(2*n) + ((A+B)*(C+D) - A*C - B*D)*10^n + B*D
效率分析

标准的长乘法的时间复杂度是O(n^2),其中n是数字的位数。而Karatsuba算法将这个复杂度降低到了O(n^log2(3)) ≈ O(n^1.585),显著提高了大数乘法的效率。

应用场景

Karatsuba算法特别适合于大整数的乘法运算,在计算机科学中广泛应用,尤其是在密码学、大数运算库等领域。随着需要处理的数据量不断增加,这种快速乘法算法变得尤为重要。

总结

Karatsuba算法是分治策略的一个经典应用,它通过减少必须执行的乘法次数,实现了比传统方法更快的乘法运算。这种算法不仅是计算机科学的一个重要成就,也是对如何优化问题解决方案的深刻洞察。

回到我们的代码中来,我们分析一下改代码的算法的实现:

这段代码是一个实现大整数乘法的程序,采用分治策略以及类似于Karatsuba算法的思路进行优化,提高了大数乘法的效率。通过将大数拆分成较小的部分,递归地计算这些小部分的乘积,然后合并结果来得到最终的乘积。下面我将逐步解释关键函数和过程中的数学概念和细节。

数据结构 _Node

首先定义了一个_Node结构体用于表示大整数。其中,s[M]是用来存储数字的数组,每个位置存储一位数字;l表示当前数字的长度(即位数);c是补位信息,用于在进行乘法操作时处理幂次问题。

函数 cp

cp(pNode src, pNode des, int st, int l)函数的作用是将源节点src中从st开始的l长度的数字复制到目标节点des中。这里主要用于分割大整数的高低位。同时更新目标节点的长度l和补位c

函数 add

add(pNode pa, pNode pb, pNode ans)函数实现了两个大整数的加法操作,考虑了进位和位数不等的情况。结果保存在ans中。这在合并乘法的中间结果时非常重要。

函数 mul

mul(pNode pa, pNode pb, pNode ans)是核心函数,实现了大整数的乘法。如果其中一个数字长度为1,直接计算乘法并处理进位即可。如果长度大于1,则使用分治策略:

  1. 将两个大整数papb分别从中点分割为高位(ah, bh)和低位(al, bl)。
  2. 然后分别计算:
    • 高位与高位的乘积(mul(&ah, &bh, &t1)
    • 高位与低位的乘积(mul(&ah, &bl, &t2)mul(&al, &bh, &t3)
    • 低位与低位的乘积(mul(&al, &bl, &t4)
  3. 最后,将这些中间结果正确合并到最终结果中。这个合并操作需要注意幂的处理和加法操作。

这种分治方法的关键思想是将一个大规模问题分解成较小的问题,这些较小的问题更容易解决,最后再将它们的解组合起来得到原问题的解。通过递归应用这个策略,可以有效减少乘法操作的总次数,从而提高算法的效率。

主函数 main

main函数中,首先读入两个表示大整数的字符串,然后将它们转换成_Node类型,确保数字是逆序存储的(因为数学运算是从最低位开始进行的)。之后调用mul函数计算乘积,并将结果打印出来。

有了一个总体的认识,我们来看看一些细节的部分,首先是核心的部分:

 cp(pa, &ah, ma, pa->l - ma);     //高位升幂   12 升幂 
 cp(pa, &al, 0, ma);                    //低位幂不变 3   
 cp(pb, &bh, mb, pb->l - mb); 
 cp(pb, &bl, 0, mb);
 mul(&ah, &bh, &t1); 
 mul(&ah, &bl, &t2);
 mul(&al, &bh, &t3);
 mul(&al, &bl, &t4);
 add(&t3, &t4, ans);
 add(&t2, ans, &z);
 add(&t1, &z, ans);

这部分代码是实现大整数乘法的核心:

分割操作
  1. 高位和低位的分割
    • cp(pa, &ah, ma, pa->l - ma);:将pa的高位部分复制到ah中。这里,mapa的长度除以2后取得的中点位置,所以pa->l - ma表示高位部分的长度。通过这一步,我们得到了pa的高位部分。
    • cp(pa, &al, 0, ma);:将pa的低位部分复制到al中。这里从索引0开始,直到ma结束,表示低位部分。
    • 类似地,pb也被分割为高位部分bh和低位部分bl

通过这种方式,大整数papb都被分割成了两部分,即它们各自的高位和低位。这样做的目的是为了应用分治策略,将原来的大数乘法问题转化为较小数的乘法问题。

乘法操作
  1. 分别计算乘积
    • mul(&ah, &bh, &t1);:计算papb的高位部分的乘积,并把结果存储在t1中。
    • mul(&ah, &bl, &t2);:计算pa的高位部分与pb的低位部分的乘积,并把结果存储在t2中。
    • mul(&al, &bh, &t3);:计算pa的低位部分与pb的高位部分的乘积,并把结果存储在t3中。
    • mul(&al, &bl, &t4);:计算papb的低位部分的乘积,并把结果存储在t4中。

通过上述操作,我们分别得到了四个乘积:两个高位的乘积t1、一个高位和一个低位的乘积t2t3、两个低位的乘积t4

合并操作
  1. 合并乘积
    • add(&t3, &t4, ans);:首先,将t3t4的结果相加,得到的中间结果存储在ans中。
    • add(&t2, ans, &z);:然后,将t2与上一步的结果ans相加,得到的中间结果存储在z中。
    • add(&t1, &z, ans);:最后,将t1与上一步的结果z相加,最终结果存储在ans中。

这个合并过程是基于Karatsuba乘法的关键步骤,正确地将分治策略中得到的部分乘积组合起来得到最终的乘积结果。

细节:在该算法中是怎么实现升幂操作
升幂的含义

首先,我们需要理解“升幂”在大数乘法上下文中的含义。给定两个大数A和B,它们的乘积C可以看作是若干部分乘积的和,这些部分乘积根据其权重(即在结果中代表的位数)不同被“升幂”。例如,如果A和B都是两位数,那么它们的乘积可以表示为(A的高位×B的高位)×10^2 + ((A的高位×B的低位 + A的低位×B的高位)×10^1 + (A的低位×B的低位),其中每个括号内的乘积需要根据它们代表的位数进行相应的升幂操作。

实现升幂

在这个算法中,升幂实际上是通过以下几点隐式实现的:

  1. 结果合并:在add函数中进行结果合并时,由于我们是按照从低位到高位的顺序进行加法运算的,每个乘积部分的位置已经事先通过切割点的选择正确设定。比如mul(&ah, &bh, &t1);得到的乘积t1实际上代表了最高位的部分,因此在最后合并回最终结果时,它自然成为了最终乘积的最高位部分。
  2. 索引与补位:各部分乘积之间的相对位置(即它们的升幂信息)通过它们在数组中的索引和补位c来隐式地处理。每个节点的c属性记录了该节点所代表的数字相对于原始数据的偏移量,即它们的升幂状态。当执行add操作时,通过比较和调整这些偏移量来正确地把几个部分的结果加在一起。
  3. 大小调整:通过递归地对原始数字进行分割,然后再将这些部分的结果逐步合并,每次合并都隐含了升幂操作。这是因为我们在合并较小部分的乘积结果时,会根据这些部分在原始数字中的位置来确定它们在最终结果中的位置(即升幂)。

总的来说,这个算法中的升幂操作是通过控制乘积部分的相对位置(通过递归分割和结果合并),以及使用节点的补位属性c来隐式管理的,而不是通过显式的数学公式或操作来实现的。这种方法有效地支持了大数乘法的分治策略,同时保持了算法的效率和简洁性。

细节:add函数逐步分析
函数原型
void add(pNode pa, pNode pb, pNode ans)
  • papb分别指向参与加法运算的两个大整数。
  • ans用于存储加法运算的结果。
步骤解析
  1. 确保pa是高次幂:如果pa的补位(pa->c)小于pb的补位(pb->c),则交换papb。这样做是为了确保pa代表的数字在加法运算中作为基准,从而简化后续计算。
  2. 初始化结果的幂次ans->c被设置为pb->c,即结果的幂次取两者之间较小的幂次,这有助于后续正确地对齐数字进行加法运算。
  3. 确定运算长度
    • 计算papb的实际长度,包括其补位(即palen = pa->l + pa->cpblen = pb->l + pb->c)。
    • 选取palenpblen中较大值作为加法运算的范围,确保所有有效数字都会被处理。
  4. 进行逐位加法
    • 遍历每一位,根据当前索引i及补位差k = pa->c - pb->c,从papb中取出相应位的数字进行加法。如果某一数组已经没有更多的位可用,则这部分的值视为0。
    • 将当前位的加法结果加上前一位的进位cc,并计算当前位的值以及新的进位值。这里使用%/运算符来分别获取加法结果的个位和进位。
    • 更新结果数组ans->s[i]和进位cc
  5. 处理最后的进位:如果在完成所有位的加法后仍有进位(cc != 0),则需要将该进位添加到结果的下一位。
  6. 确定结果长度:更新结果数字ans的长度ans->l,确保它正确反映了加法运算后数字的真实长度。
add方法中确定升幂和补0

add函数中,升幂和补0(乘以10^n)主要通过对参与加法操作的两个数的补位c进行比较和调整来实现:

  • 补位的意义c表示当前数字相对于某个基准的偏移量,或可以理解为该数字需要向左移动的位数(即低位补0的个数)。
  • 确定运算长度和操作:通过计算两个数的实际长度(包含补位)和它们之间的补位差异,add函数能够正确地对这两个数进行对齐,并逐位相加。更高的补位意味着一个数在加法运算中“看起来”更小(因为它相当于被除以了10^n),从而实现了升幂的效果。
细节:cp函数逐步解释

cp函数的作用是将源节点的一部分复制到目标节点,同时更新目标节点的长度l和补位c

void cp(pNode src, pNode des, int st, int l) {
    int i, j;
    for (i = st, j = 0; i < st + l; i++, j++) {
        des->s[j] = src->s[i];
    }
    des->l = l;
    des->c = st + src->c;
}
  • 参数说明
    • src:源节点指针,表示待复制的大整数。
    • des:目标节点指针,用于存放复制结果。
    • st:开始复制的起始索引,相对于src->s数组。
    • l:需要复制的长度。
  • 执行步骤
    1. 循环复制:从src->s[st]开始,复制l长度的数字到des->s中。这里使用两个变量i(遍历src)和j(遍历des),确保正确地复制了指定范围内的数字。
    2. 更新长度:将des->l设置为复制的长度l
    3. 更新补位des->c被更新为st + src->c。这一步是关键,它考虑了从源节点复制的起始点st,将其加上原节点的补位src->c,得到新节点的补位。这相当于在数字上进行了升幂操作。例如,如果从src的第3位开始复制(假设每一位代表一个十进制位),那么des相对于src实际上就是乘以了10^3。
cp 函数怎么实现升幂

cp函数中,升幂是通过更新目标节点的补位c来实现的。当我们从源节点的非零起始位置复制数据时,这个起始位置(加上源节点原有的补位)直接转化为目标节点的补位,相当于给目标节点的数字值乘以了相应的10的幂次(因为每个增加的补位都是向数字的右端添加一个零)。这样,cp函数不仅复制了数字的一部分,还隐式地实现了升幂操作,为后续的数学运算提供了方便。

如果觉得文章对您有帮助,请帮忙点赞或者收藏,如果在文章中发现什么错误或不准确的地方,欢迎与我交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

5-StarrySky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值