【算法·算法随笔】(大整数运算)大整数的进制转换问题万能解法,含codeup练习讲解(1950 problem b进制转换、1952 problem f 10进制vs2进制)

《算法笔记》中,对于大整数的运算,给出了最基础的几个算法模板,包括大整数的结构体、输出、与字符串的转换,以及四则预算(不包含高精度与高精度的乘除)。但是在codeup训练当中,又出现了两种经典问题,第一是对大浮点数的处理,第二是对于大整数的进制转换问题,下面对于大整数的进制转换问题,说一种万能超级解法。

codeup相关题型对应标号为:(看完文章就可以试试)直接在csdn搜也可以搜到原题

1950-Problem-D-进制转换

1952-Problem-F-10进制-VS-2进制

对于进制转换问题(把M进制的a转换为N进制的b),如果手算,一个很简单的方法就是先把M进制的形式转换为10进制,然后再通过短除法得到N进制。

但如果对除法的过程比较熟悉,就会发现M到N实际可以直接转换,首先来看大整数运算当中除法函数的函数实现:

bign divide(bign a,int b,int &r)
{
	bign c;
	c.len=a.len;
	for(int i=a.len-1;i>=0;i--)
	{
		r=r*10+a.d[i];
		c.d[i]=r/b;
		r=r%b;
	}
	while(c.len>=2&&c.d[c.len-1]==0)
	{
		c.len--;
	}
	return c;
}

在这个函数里面,最核心的部分很显然就在于:

r=r*10+a.d[i];  1

c.d[i]=r/b;     2

r=r%b;        3

对于步骤2和步骤3中分别除以和取余的这个整数b,就是整个算式的除数,也是当我们利用短除法实现进制转换时的那个除数,也是我们的目标进制N,再看步骤1的r*10的这个10,为什么在这里面,是10呢?是因为我们的这个函数divide默认被除数是10进制的,所以每一位的权值是下一低位的10倍,之所以权值是10倍,不就是因为原来这个数是10进制吗?如果是M进制,那不就是乘以M吗?所以就可以得到一个M进制短除得到N进制的一个代码:

r=r*M+a.d[i];

c.d[i]=r/N;

r=r%N;

现在来考虑:上述这三行代码怎么去用,用在哪里?

很显然,用到这个代码,需要满足两个条件:

  1. 一个M进制的数需要把每一位保存在数组当中,因为如果是就放在字符数组里面,计算的时候还要不停换算,很麻烦
  2. M进制的数的每一位放在数组之后,每一位都是一个数,就是说当M>10时,这个数就会有字母表示更高的权值,我们只需在转换函数change中去稍作修改:
    bign change(char a[])
    {
        bign t;
        for(int i=strlen(a)-1; i>=0; i--)
        {
            if(a[i]>='0'&&a[i]<='9')
                t.d[t.len++] = a[i]-'0';
            else
                t.d[t.len++] = (a[i]-'A'+10);
        }
        return t;
    }

    这样就能把2--36进制的数的每位以整数的形式保存在数组当中。

    有了上面的基础,就可以直接用一次“特殊的”短除法得到进制转换后的结果,通过循环,把每一位保存在字符数组当中,如果有必要,可以写一个颠倒函数reverse,把生成的数颠倒,才是最终正确的顺序,现在以codeup problem b 1950进制转换为例,说明做法:

    while(scanf("%d%d",&m,&n)!=EOF)
        {
            getchar();//消去换行!!!!! 
            char x[10001];
            scanf("%s",x);
            bign a;
            a = change(x);
            char ans[1001];
            int len = strlen(x),index=0;
            while(a.len>0)//a.len==0即辗转相除到了最后
            {
                int carry = 0;
                for(int i=a.len-1; i>=0; i--)
                {
                    int temp = a.d[i] + carry*m;
                    carry = temp % n;
                    a.d[i] = temp/n;
                }
                while(a.d[a.len-1]==0)
                    a.len--;
                if(carry>=0&&carry<=9)
                    ans[index++] = carry+'0';
                else
                    ans[index++] = carry-10+'a';
            }
            for(int i=index-1; i>=0; i--)
                printf("%c", ans[i]);
            printf("\n");
    }
    

    注意是怎么去判断短除的末尾的,即当被除数长度变为0时。

    那能否把这个循环短除过程封装到函数里面呢?

    我们可以尝试写这么一个函数:

    void scale_trans(bign a,char b[],int now,int to)

    解释参数意思:

    a表示准备被转换的大整数

    b表示a转换后的大整数(以字符数组存放,在函数中也可以直接实现把顺序颠倒的功能)

    now:a的进制

    to:b的进制

    直接给出代码:

    void scale_trans(bign a,char b[],int now,int to)
    {
    	memset(b,0,sizeof(b));
    	int index=0;
    	while(a.len>0)//a.len==0即辗转相除到了最后
            {
                int carry = 0;
                for(int i=a.len-1; i>=0; i--)
                {
                    int temp = a.d[i] + carry*now;
                    carry = temp % to;
                    a.d[i] = temp/to;
                }
                while(a.d[a.len-1]==0)
                    a.len--;
                if(carry>=0&&carry<=9)
                    b[index++] = carry+'0';
                else
                    b[index++] = carry-10+'a';
            }
            char temp[index];
            for(int i=0;i<index;i++)
            temp[i]=b[index-1-i];
            for(int i=0;i<index;i++)
            b[i]=temp[i];
    }

    有了这么多的基础,解决codeup problem 1952 f 10进制vs 2进制,跟喝凉水一样简单:

    先看题:

     10进制直接先转换为2进制,然后把上述scale_trans中最后一点的数组颠倒去掉,为什么要求掉?因为题中就是要的倒序2进制再转换为10进制,所以直接调用两次scale_trans就解决了:

    #include<stdio.h>
    #include<string.h>
    struct bign{
        int d[4000];
        int len;
        bign(){
            len = 0;
            memset(d, 0, sizeof(d));
        }
    };
    bign change(char a[])
    {
        bign t;
        for(int i=strlen(a)-1; i>=0; i--)
        {
            if(a[i]>='0'&&a[i]<='9')
                t.d[t.len++] = a[i]-'0';
            else
                t.d[t.len++] = (a[i]-'A'+10);
        }
        return t;
    }
    void scale_trans(bign a,char b[],int now,int to)
    {
    	memset(b,0,sizeof(b));
    	int index=0;
    	while(a.len>0)//a.len==0即辗转相除到了最后
            {
                int carry = 0;
                for(int i=a.len-1; i>=0; i--)
                {
                    int temp = a.d[i] + carry*now;
                    carry = temp % to;
                    a.d[i] = temp/to;
                }
                while(a.d[a.len-1]==0)
                    a.len--;
                if(carry>=0&&carry<=9)
                    b[index++] = carry+'0';
                else
                    b[index++] = carry-10+'a';
            }
        b[index]='\0';//一定注意加0字符,否则会输出超限!!!!!
    }
    int main()
    {
    	bign a;
        char num[4000];
    	while(scanf("%s",num)!=EOF)
    	{
    		char ans[4000];
    		a=change(num);
    		scale_trans(a,ans,10,2);
    		a=change(ans);
    		scale_trans(a,ans,2,10);
    		for(int i=0;i<strlen(ans);i++)
    		printf("%c",ans[strlen(ans)-i-1]);
    		printf("\n");
    	}
    	return 0;
    }

    完工

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值