算法总结——大整数除法

问题描述

求两个大的正整数相除的商
输入数据
第1行是测试数据的组数n,每组测试数据占2行,第1行是被除数,第2行是除数。每组测试数据之间有一个空行,每行数据不超过100个字符
输出要求
n行,每组测试数据有一行输出是相应的整数商
输入样例
3
2405337312963373359009260457742057439230496493930355595797660791082739646
2987192585318701752584429931160870372907079248971095012509790550883793197894


10000000000000000000000000000000000000000
10000000000


5409656775097850895687056798068970934546546575676768678435435345
1
输出样例
0
1000000000000000000000000000000
5409656775097850895687056798068970934546546575676768678435435345

解题思路

基本的思想是反复做减法,看看从被除数里最多能减去多少个除数,商就是多少。一个一个减显然太慢,如何减得更快一些呢?以7546除以23为例来看一下:开始商为0。先减去23的100倍,就是2300,发现够减3次,余下646。于是商的值就增加300。然后用646减去230,发现够减2次,余下186,于是商的值增加20。最后用186减去23,够减8次,因此最终商就是328。
所以本题的核心是要写一个大整数的减法函数,然后反复调用该函数进行减法操作。

计算除数的10倍、100倍的时候,不用做乘法,直接在除数后面补0即可。

参考程序

#include <stdio.h>
#include <string.h>
#define	MAX_LEN	 200
char szLine1[MAX_LEN + 10];
char szLine2[MAX_LEN + 10];
int an1[MAX_LEN + 10];    //被除数,  an1[0]对应于个位
int an2[MAX_LEN + 10];    //除数,  an2[0]对应于个位
int aResult[MAX_LEN + 10]; //存放商,aResult[0]对应于个位
/* Substract函数:长度为 nLen1的大整数p1减去长度为nLen2的大整数p2
减的结果放在p1里,返回值代表结果的长度
如不够减返回-1,正好减完返回 0
p1[0]、p2[0] 是个位 */
int Substract( int * p1, int * p2, int nLen1, int nLen2)
{
	int i;
	if( nLen1 < nLen2 )
		return -1;
	//下面判断p1是否比p2大,如果不是,返回-1
	bool bLarger = false;
	if( nLen1 == nLen2 ) {
		for( i = nLen1-1; i >= 0; i -- ) {
			if( p1[i] > p2[i] )
				bLarger = true;
			else if( p1[i] < p2[i] ) {
				if ( ! bLarger )
					return -1;
			}
		}
	}
	for( i = 0; i < nLen1; i ++ ) { //做减法
		p1[i] -= p2[i];  //要求调用本函数时给的参数能确保当i>=nLen2时,p2[i] = 0
		if( p1[i] < 0 ) {
			p1[i]+=10;
			p1[i+1] --;
		}
	}
	for( i = nLen1 -1 ; i >= 0 ; i-- )
		if( p1[i] )
			return i + 1;
	return 0;
}
int main()
{	
	int t, n;
	char szBlank[20];
	scanf("%d", &n);
	for( t = 0; t < n; t ++ ) {
		scanf("%s", szLine1); 
		scanf("%s", szLine2); 
		int i, j;
		int nLen1 = strlen( szLine1);
		memset( an1, 0, sizeof(an1));
		memset( an2, 0, sizeof(an2));
		memset( aResult, 0, sizeof(aResult));	
		j = 0;
		for( i = nLen1 - 1;i >= 0 ; i --)
			an1[j++] = szLine1[i] - '0';
		int nLen2 = strlen(szLine2);
		j = 0;
		for( i = nLen2 - 1;i >= 0 ; i --)
			an2[j++] = szLine2[i] - '0';
		if( nLen1 < nLen2 ) {
			printf("0\n");
			continue;
		}
		nLen1 = Substract( an1, an2, nLen1, nLen2) ;
		if( nLen1 < 0 ) {
			printf("0\n");
			continue;
		}
		else if( nLen1 == 0) {
			printf("1\n");
			continue;
		}
		aResult[0] ++;  //减掉一次了,商加1
		//减去一次后的结果长度是 nLen1
		int nTimes = nLen1 - nLen2; 
		if( nTimes < 0)  //减一次后就不能再减了
			goto OutputResult;
		else if( nTimes > 0 ) {
			//将 an2 乘以10的某次幂,使得结果长度和 an1相同
			for( i = nLen1 -1; i >= 0; i -- ) {
				if( i >= nTimes )
					an2[i] = an2[i-nTimes];
				else
					an2[i] = 0;
			}
		}
		nLen2 = nLen1;
		for( j = 0 ; j <= nTimes; j ++ ) {
			int nTmp;
			//一直减到不够减为止
			//先减去若干个 an2×(10 的 nTimes 次方),
			//不够减了,再减去若干个 an2×(10 的 nTimes-1 次方),......
			while( (nTmp = Substract(an1, an2+j, nLen1, nLen2-j)) >= 0) {
				nLen1 = nTmp;
				aResult[nTimes-j]++; //每成功减一次,则将商的相应位加1
			}
		}
	OutputResult:
		//下面的循环统一处理进位问题
		for( i = 0; i < MAX_LEN; i ++ )	{
			if( aResult[i] >= 10 ) {
				aResult[i+1] += aResult[i] / 10;
				aResult[i] %= 10;
			}
		}
		//下面输出结果
		bool bStartOutput = false;
		for( i = MAX_LEN ; i >= 0; i -- )
			if( bStartOutput)
				printf("%d", aResult[i]);
			else if( aResult[i] ) {
				printf("%d", aResult[i]);
				bStartOutput = true;
			}
		if(! bStartOutput )
			printf("0\n");
		printf("\n");
	}
	return 0;
}


常见问题

问题一、忘了针对每一组测试数据,都要先将an1, an2和aResult初始化成全0,而是一共只初始化了一次。这导致从第二组测试数据开始就都不对了。
问题二、减法处理借位的时候,容易忽略连续借位的情况,比如 10000 – 87,借位会一直进行到1。

### 大整数除法算法实现与原理 大整数除法是一种用于处理超出计算机原生数据类型表示范围的数值计算方法。其核心在于利用基本运算(如加法、减法、乘法等)来逐步逼近商值,最终完成整个除法操作。 #### 基本原理 大整数通常以数组形式存储每一位数字,在执行除法时可以采用类似于手工除法的方式逐位计算商和余数。具体来说,该过程分为以下几个方面: 1. **初始化** 需要两个大整数分别作为被除数 `A` 和除数 `B`,并确保 `B ≠ 0`。定义初始变量 `quotient = 0` 表示当前部分商,以及 `remainder = A` 表示剩余未处理的部分[^1]。 2. **逐位相除** 对于每一个位置上的数字组合成子问题 `(current_part / B)` 来求解局部结果,并更新全局状态: - 如果当前部分不足以单独构成完整的除法,则继续向右扩展一位; - 否则进行实际的单步除法得到新的商片段附加至总商之上,同时记录对应的余项以便后续迭代使用。 3. **终止条件** 当所有有效位均已完成分配后停止循环,此时所得即为目标答案中的商;而最后一次留下的残差就是最后剩下的余数。 以下是基于以上描述的大致伪代码框架展示如何构建这样一个函数: ```java public class BigIntegerDivision { public static Pair<String, String> divide(String dividendStr, String divisorStr){ List<Integer> quotientDigits=new ArrayList<>(); StringBuilder remainderBuilder=new StringBuilder(); // Convert strings to lists of digits... List<Integer> divdend=digitsFromString(dividendStr); List<Integer> divisior=digitsFromString(divisorStr); int currentRemainder=0; for(Integer digit :divdend ){ currentRemainder=currentRemainder*10+digit; while(currentRemainder >=valueOf(divisior)){ currentRemainder -=valueOf(divisior); addOneToQuotient(quotientDigits); } appendDigitIfNonZero(quotientDigits,digit); } return new Pair<>(toStringFromDigits(quotientDigits),String.valueOf(currentRemainder)); } private static void addOneToQuotient(List<Integer> q){ /*...*/ }; private static boolean shouldAppendDigit(int d,int pos){/*...*/}; } ``` 此版本仅提供了一个高层次的设计思路而非完全可运行的具体编码方案。它展示了通过不断调整试猜可能的最大倍率直至满足约束关系的过程来进行精确分割的技术路线图。 --- ### 数据结构的选择 对于此类涉及大量位级操作的任务而言,链表并不适合作为主要载体——因为频繁访问随机节点的成本过高(O(n))。相比之下,动态数组更加适合用来保存这些按序排列起来待处理的数据单元们,因为它允许常量时间内定位任意指定偏移处的内容读写权限[^2]。 另外值得注意的是,在某些特殊场景下还可以考虑借助更高级别的抽象工具比如Python内置支持无限精度算术类型的库fractions.Fraction 或者 decimal.Decimal ,它们内部封装好了类似的逻辑细节供开发者调用无需自行重新发明轮子[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值