解决 问题 D: 进制转换 的若干思考

文章讲述了如何处理大整数进制转换问题,涉及到了大整数运算、字符与数字映射、乘法、取模及内存优化等技术。作者分享了从问题描述、算法设计到代码实现和优化的心得体会。
摘要由CSDN通过智能技术生成

问题描述

将M进制的数X转换为N进制的数输出。
输入
输入的第一行包括两个整数:M和N(2<=M,N<=36)。
下面的一行输入一个数X,X是M进制的数,现在要求你将M进制的数X转换成N进制的数输出。
输出
输出X的N进制表示的数。
hint
注意输入时如有字母,则字母为大写,输出时如有字母,则字母为小写。
输入输出样例
在这里插入图片描述

思路分析

本题是属于大整数运算章节的题目,这提示了我们题目的数据可能是long与long long都无法表示的大整数,要用结构体专门存储,并为其设计相应的运算函数。意识到这一点是破题第一步。
进制转换的基本思想都已经十分熟稔了:对于m进制表示的数x,从低到高取其数位上的数,乘以m对应的幂次值,累加,可以得到x的十进制数据。当需要相互转换的进制m和n不存在同为某数幂次值(如8=2^3 4=2^2)时,必须先将x转换为十进制数,再逐次对新的基底n取模,得到新数的每一位。
当数据是大整数时,以上常规步骤中,累乘m求各项的基底幂次值这一步存在难点,极有可能出现累乘结果不能用基本数据类型表示的情况,因而涉及到大整数的乘法运算。此外,数据x本身也需要用大整数结构体存储。稍加思索可以分析出程序有以下重要模块:

  1. m进制数据x转十进制大整数
  2. 十进制大整数转换为n进制数据输出
  3. 对于步骤1需要设计计算基底累乘结果的大整数乘法函数,和计算各项运算结果的大整数加法函数
  4. 对于步骤2,需要设计能够实现大整数对新基底n进行取模和除法运算功能的函数。

本题还有其他值得关注的细节,如对于输入数据含有字符的情况,可以建立字符与整数的映射关系表来实现对应与转化。用到C++标准库中的map库。更多的感想不在这块内容里赘述,参见本文最后的思考总结。

参考代码

代码又磨了一下午,值得注意的是为了节省资源,我自创了一些函数,尽量少开大整数结构体,能自接收就自接收。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#include <vector>
#include <algorithm>
using namespace std;
//全局变量 
const int maxn = 100010;//保证数据足够大
char num[maxn];//接收数据x
vector<char> result;//存储进制转换结果
map<char,int> scale;//字符与数的映射表
//数与字符的映射
char reScale[40] = "abcdefghijklmnopqrstuvwxyz";
char scaleMark[40] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
//定义bign结构体 
struct bign{
	int d[maxn];
	int len;
	int sign;
	bign(){//不含参量的初始化函数
		memset(d,0,sizeof(d));
		len = 0;
		sign = 1;
	}
	bign(int _d){//含参量的初始化函数
		memset(d,0,sizeof(d));
		sign = 1;
		if(_d>0) d[0] = _d;
		else{
			d[0] = _d*(-1);
			sign = -1;
		}
		len = 1;
	} 
};
//关于bign的函数 
void calSum(bign &a,bign b,int d){
//相当于执行a += b*d的常规步骤
//先得到b*d的结果并用b存储
	int carry = 0;
	for(int i=0;i<b.len;i++){
		int temp = carry + b.d[i] * d;
		b.d[i] = temp % 10;
		carry = temp / 10;
	}
	while(carry){
		b.d[b.len++] = carry % 10;
		carry /= 10;
	}
	//再计算与a相加的结果并赋值给a
	//整个过程没有新开bign,节省程序运行资源
	for(int i=0;i<a.len||i<b.len;i++){
		int temp = carry + a.d[i] + b.d[i];
		if(i<a.len) a.d[i] = temp % 10;
		else a.d[a.len++] = temp % 10;
		carry = temp / 10;
	}
	if(carry) a.d[a.len++] = carry;
}
void POW(bign &a,int b){
//相当于执行a *= b的常规操作,没有新开bign
	int carry = 0;
	for(int i=0;i<a.len;i++){
		int temp = carry + a.d[i] * b;
		a.d[i] = temp % 10;
		carry = temp / 10;
	}
	while(carry){
		a.d[a.len++] = carry % 10;
		carry /= 10;
	}
}
void smash(bign &a,int b,int &r){
//相当于执行a/=b,r=a%b ,也没有新开bign
	for(int i=a.len-1;i>=0;i--){
		r = r*10 + a.d[i];
		if(r<b){
			a.d[i] = 0;
		}else{
			a.d[i] = r / b;
			r %= b;
		}
	}
	//要记得去除冗余0
	//减法和除法都有去除冗余0的操作
	while(a.d[a.len-1]==0&&a.len>1) a.len--;
}
//进制转换函数 
bign mToDecimal(int m,char x[]){
//将m进制数转换为十进制大整数并返回
	bign product(1);//基底幂值,初始化为1 
	bign ret;//保存转换为十进制的结果  
	int j = 0;
	bool flag = false;
	if(x[0]=='-') {
		j++;
		flag = true;
	} 
	for(int i=strlen(x)-1;i>=j;i--){
		int d;
		if(x[i]>='A'&&x[i]<='z'){
			d = scale[x[i]];
		}else d = x[i] - '0';
		//乘以基底幂值累加 
		calSum(ret,product,d);
		POW(product,m);//累乘基底 
	}
	if(flag) ret.sign = -1;
	return ret;
}
void decimalToM(int m,bign x){
//将十进制大整数转换为新进制数表示并输出
	if(x.len==1&&x.d[0]==0){
	//0需要单独处理,因为我用vector存储结果
		printf("0\n");
		return;
	}
	int r = 0;
	result.clear();//每次都要初始化
	if(x.sign==-1) putchar('-');
	while(x.len!=1||x.d[0]!=0){
	//当x没被除尽(为0),继续smash
		smash(x,m,r);
		char c;
		if(r>=10){
			c = reScale[r - 10];
		}else c = r + '0';
		result.push_back(c);
		r = 0;//r也要重新赋值为0
	}
	//结果要倒着输出哦~
	for(int i=result.size()-1;i>=0;i--) putchar(result[i]);
	putchar('\n');
}
int main(){
	int m,n;
	for(int i=0;i<26;i++) {
	//先要把映射表初始化一下
		scale[scaleMark[i]] = scale[reScale[i]] = 10+i;
	}
	while(scanf("%d%d",&m,&n)!=EOF){
		scanf("%s",num);
		bign c = mToDecimal(m,num);
		decimalToM(n,c);
	}
}

思考总结

这个题目一开始拿到的时候,我居然完全没有意识到大整数的事情,还按照之前进制转换的常规操作去写(而且常规的也写错了:P)。而当我第一次尝试用大整数运算的各类函数去解决这个问题的时候,各种各样的毛病都出现了,比如:对新基底取模的运算r没有重新赋值为0造成了一些难以形容的bug。随着不断的尝试,这些问题逐个得到解决,但是当我以为程序已经功能完备时,codeup再次对我说:你再想想。
不想了,先吃饭
吃完饭我再次来到电脑前盯了一会屏幕上杂乱无章的程序,我从头开始梳理这个问题,忽然意识到麻烦的不仅是数据本身的长度,还有累乘基底的长度!一个是面子,一个是里子,表层的东西总是显然的,但是深一层的却没那么轻易察觉到,但如果没有意识到里子的事,我们就不能说真正懂这个题,那么失败也不是偶然的结果。
所以,要处理product累乘超限的问题,考虑用到大整数乘法,再次堆砌了一个大整数运算函数。这个时候我在DevC++上运行,却发现出现了之前不曾有过的with return value 3221225725这个错误,上网搜索得知是内存空间发生了数据溢出,通常是因为开了许多大容量数组导致的。
显然,是因为我在大整数运算函数中总是新开bign,每次都会对其超大容量数组成员进行初始化,空间消耗超出了计算机的承受范围。有两种解决方案:1.减少函数;2.函数内减少空间开销。我两个方向上都做出了优化,最后亮起绿灯的那一刻,只能说我的大整数运算PTSD终于被治好了!

  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值