蓝桥杯 十六进制转为八进制 C语言版

**

蓝桥杯 十六进制转为八进制

**

题目

给定n个十六进制正整数,输出它们对应的八进制数。
输入格式
  输入的第一行为一个正整数n (1<=n<=10)。
  接下来n行,每行一个由09、大写字母AF组成的字符串,表示要转换的十六进制正整数,每个十六进制数长度不超过100000。
输出格式
  输出n行,每行为输入对应的八进制正整数。
【注意】
  输入的十六进制数不会有前导0,比如012A。
  输出的八进制数也不能有前导0。
样例输入
  2
  39
  123ABC
样例输出
  71
  4435274
【提示】
  先将十六进制数转换成某进制数,再由某进制数转换成八进制。

使用十进制作为中转

笔者一开始看到这个题目想的就是先将十六进制转为十进制,再将十进制转为八进制。可能是脑袋瓜子不灵活,也可能是因为学过C语言做过类型的题目,像什么十进制转八进制,八进制转十进制之类的。
代码如下:

#include<stdio.h>
#include<string.h>
int main()
{
    int n,t,num_ten,num_eight;
    char str[10][100000];
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%s",str[i]);
    for(int i=0;i<n;i++)
    {
        int len = strlen(str[i])-1;         
        t=1;
        num_ten=0;
        while(len>=0)               //将16进制转为10进制
        {
            if(str[i][len]>='0'&&str[i][len]<='9')
                num_ten+=(str[i][len]-'0')*t;
            else
                num_ten+=(str[i][len]-'A'+10)*t;
            t=t*16;
            len--;
        }
        t=1;
        num_eight=0;
        while(num_ten)      //将10进制转为8进制
        {
            num_eight+=num_ten%8*t;
            t=t*10;
            num_ten/=8;
        }
        printf("%d\n",num_eight);
    }
    return 0;
}

笔者很单纯的将测试样例带入程序,结果显示是正确的,但是,将代码提交却是说编译结果不正确。后面仔细分析题目,每个十六进制数长度不超过100000。这肯定是不能用十进制整型类型来做的。因为肯定会超过int,long,long long的范围。C语言int的取值范围在32/64位系统中都是32位,范围为-2147483648到+2147483647,无符号情况下表示为0到4294967295。(PS:之前学习C语言以为这个范围没什么用,但现在深刻体会到该范围的限制性)
接着看了大佬们的作品,思路发生转变,内心不禁感叹。
链接: https://blog.csdn.net/try_fei_ge/article/details/53239501.

使用二进制作为中转

本来是想将大佬的第二种方法细化的分析下的,可是我将他的第一个没改进前的代码提交了下,发现结果显示正确,且提交没有超时,结果如下图,就干脆将这个一起分析下算了。
在这里插入图片描述

先上代码,我将大佬代码的格式规范化了一下其实很简单,只要右击鼠标,然后点击format this file即可 ,还加了一点点注释,代码虽长,但很好理解。
先简单介绍下代码的思路:在进制转换中,可以直接将1位十六进制的数转换为4位二进制的数,1位八进制的数转换位3位二进制的数,因为此题一个十六进制的数最大有100000位,那么就需要400000位二进制作为中转站,为了节约运行时间和内存,采用同时将3位十六进制转为12位二进制,接着将这十二位二进制转为4位八进制的思路解决问题。

在看代码前先介绍一下各个变量的含义:

  1. n:代表输入十六进制数的个数
  2. s:二维数组,用来存放十六进制数
  3. a:用来表示某一个十六进制数的位数距离能被3整除还差几位
  4. ok:用于判断12位二进制转为八进制时,最前面的四位是否为0
  5. z:一维数组,用来暂存某个十六进制的某三位转为的12位二进制数
  6. i,j,k,cur只是作为循环的中间变量

下面看代码应该会很好理解了,请大家一定不要心躁,心平气和的来看代码,一遍不懂就再看多看几遍,坚持下去总归会弄明白的,加油。

#include
#include
int main()
{
    int n,i,j,k,a=0,cur,ok;
    char s[10][100000],z[12];
    scanf("%d",&n);
    for(j=0; j<n; j++)
        scanf("%s",s[j]);
    for(j=0; j<n; j++)
    {
        ok=1;
        if(strlen(s[j])%3!=0&&a==0&&(a=(3-(strlen(s[j])%3)))!=0)    //是判断当前的这个十六进制的位数离3的整数还差几位
        {
            for(k=0; k<a*4; k++)    //将三位16进制的转二进制,不足12位的,在前面补全0
                z[k]='0';
        }
        for(i=0; i<strlen(s[j]); i++)   //将每位16进制位数转为四位二进制
        {

            switch(s[j][i])
            {
            case '0':
                z[k]='0';
                z[k+1]='0';
                z[k+2]='0';
                z[k+3]='0';
                break;
            case '1':
                z[k]='0';
                z[k+1]='0';
                z[k+2]='0';
                z[k+3]='1';
                break;
            case '2':
                z[k]='0';
                z[k+1]='0';
                z[k+2]='1';
                z[k+3]='0';
                break;
            case '3':
                z[k]='0';
                z[k+1]='0';
                z[k+2]='1';
                z[k+3]='1';
                break;
            case '4':
                z[k]='0';
                z[k+1]='1';
                z[k+2]='0';
                z[k+3]='0';
                break;
            case '5':
                z[k]='0';
                z[k+1]='1';
                z[k+2]='0';
                z[k+3]='1';
                break;
            case '6':
                z[k]='0';
                z[k+1]='1';
                z[k+2]='1';
                z[k+3]='0';
                break;
            case '7':
                z[k]='0';
                z[k+1]='1';
                z[k+2]='1';
                z[k+3]='1';
                break;
            case '8':
                z[k]='1';
                z[k+1]='0';
                z[k+2]='0';
                z[k+3]='0';
                break;
            case '9':
                z[k]='1';
                z[k+1]='0';
                z[k+2]='0';
                z[k+3]='1';
                break;
            case 'A':
                z[k]='1';
                z[k+1]='0';
                z[k+2]='1';
                z[k+3]='0';
                break;
            case 'B':
                z[k]='1';
                z[k+1]='0';
                z[k+2]='1';
                z[k+3]='1';
                break;
            case 'C':
                z[k]='1';
                z[k+1]='1';
                z[k+2]='0';
                z[k+3]='0';
                break;
            case 'D':
                z[k]='1';
                z[k+1]='1';
                z[k+2]='0';
                z[k+3]='1';
                break;
            case 'E':
                z[k]='1';
                z[k+1]='1';
                z[k+2]='1';
                z[k+3]='0';
                break;
            case 'F':
                z[k]='1';
                z[k+1]='1';
                z[k+2]='1';
                z[k+3]='1';
                break;
            }
            k+=4;
            if(k==12)   //当三位十六进制都转为4为二进制后,开始将12位二进制变为四位八进制
            {
                for(cur=0; cur<12; cur+=3)  //四位体现在cur+=3
                    if(z[cur]=='0'&&z[cur+1]=='0'&&z[cur+2]=='0')if(ok);
                        else putchar('0');
                    else if(z[cur]=='0'&&z[cur+1]=='0'&&z[cur+2]=='1')
                    {
                        putchar('1');
                        ok=0;
                    }
                    else if(z[cur]=='0'&&z[cur+1]=='1'&&z[cur+2]=='0')
                    {
                        putchar('2');
                        ok=0;
                    }
                    else if(z[cur]=='0'&&z[cur+1]=='1'&&z[cur+2]=='1')
                    {
                        putchar('3');
                        ok=0;
                    }
                    else if(z[cur]=='1'&&z[cur+1]=='0'&&z[cur+2]=='0')
                    {
                        putchar('4');
                        ok=0;
                    }
                    else if(z[cur]=='1'&&z[cur+1]=='0'&&z[cur+2]=='1')
                    {
                        putchar('5');
                        ok=0;
                    }
                    else if(z[cur]=='1'&&z[cur+1]=='1'&&z[cur+2]=='0')
                    {
                        putchar('6');
                        ok=0;
                    }
                    else if(z[cur]=='1'&&z[cur+1]=='1'&&z[cur+2]=='1')
                    {
                        putchar('7');
                        ok=0;
                    }
                k=0;//将k置0,开始将下面三位16进制进行转换
            }
        }
        a=0;//假设第j个十六进制数的位数是三的倍数
        putchar('\n');
    }
    return 0;
}

下面解释一下比较难理解代码的含义:

if(strlen(s[j])%3!=0&&a==0&&(a=(3-(strlen(s[j])%3)))!=0)    //是判断当前的这个十六进制的位数离3的整数还差几位
        {
            for(k=0; k<a*4; k++)    //将三位16进制的转二进制,不足12位的,在前面补全0
                z[k]='0';
        }

正如注释中写到的,这段代码的含义就是将长度未满3的倍数的十六进制补全,例如所给的案例输入:39,因为它只有2位十六进制,不能满足上面所说的3位十六进制转为12位二进制,故用此段代码来进行补全,将十六进制39转为二进制0000 0011 1001,这样,所有情况都可以用下面的代码来转为8进制了。
经过我测试,发现这段代码放在ok=1的后面也是可以正确运行的,因为无论怎样,这段代码都只会运行一次,即if里面的条件只会满足一次,因为补全为三只需要一次即可。读者可以自行尝试。

优化二进制中转

感受到代码的奇妙之后,咱们再来看看优化之后的算法,怎么来优化呢,首先细细品味咱们上面的那个代码,将1位十六进制转为4位二进制时,每位都要进行判断,然后转换,这实在是太费事了。这可以怎么解决呢?咱们来看,十六进制0对应二进制0000,1对应0001,2对应0010…F对应1111,看出来了吧,我们可以之间建立一个大小为16的字符串数组,也就是二维字符数组,直接将十六进制作为四位二进制的下标,那不就简单了,比如说d16存放0-F的二进制字符串,那么d16[0]就是0000,d16[15]就是1111。
还有就是在进行二进制转八进制的时候,我们可以将十二位二进制之间按位权展开, 然后一起输出,就不必在进行一个一个判断,这又能节约很多时间,不过节约了时间就得多好费点空间,下面会说到空间是为什么会耗费的。
同样,在看代码前来解释下各变量的含义,以上一个大同小异,这里相同的就不再介绍了。
1. ok:是十六进制数的位数否能被3整除。
2. d16:二维数组,存放0-F的四位二进制代码
3. out:一维数组,用来保存某个十六进制数转为八进制数后的结果

#include
#include
int main()
{
    int n,i,j,k,a,cur,ok,m,l;
    char s[10][100001],d16[16][5]= {"0000","0001","0010","0011","0100","0101","0110","0111","1000","1001","1010","1011","1100","1101","1110","1111"},out[140000]= {},z[13]= {};
    scanf("%d",&n);
    for(j=0; j<n; j++)
        scanf("%s",s[j]);
    for(j=0; j<n; j++)
    {
        k=0;
        ok=1;
        m=0;                            /*初始化标记数据*/
        l=strlen(s[j]);                           /* 初始化标记数据*/
        a=3-l%3;                                   /*初始化标记数据*/
        if(a==3)	ok=0;                    /*十六进制数长度刚好为3的倍数时转二进制不需补0,ok标记其是否为3的倍数0是1不是*/
        for(i=0; i<l; i++)                     /*逐位读取十六进制数进行转换*/
        {
            if(65<=s[j][i])
                s[j][i]-=7;
            if(ok)                                     /*十六进制数位数不足转二进制时补0占位*/
                if(a==1)
                {
                    strcat(z,"0000");
                    k=k+4;
                    ok=0;
                }
                else if(a==2)
                {
                    strcat(z,"00000000");
                    k=k+8;
                    ok=0;
                }
            z[k++]=d16[s[j][i]-48][0];                  /*一位十六进制转四位二进制*/
            z[k++]=d16[s[j][i]-48][1];
            z[k++]=d16[s[j][i]-48][2];
            z[k++]=d16[s[j][i]-48][3];
            if(k==12)                                    /*每转三位十六进制数将其转为四位八进制数*/
            {
                for(cur=0; cur<12; m++)
                    out[m]=((z[cur++]-48)*4+(z[cur++]-48)*2+(z[cur++]-48)*1)+48;
                //z[0]='\0';
                k=0;                               /*z[0]='\0' 初始化字符串结束符位置避免溢出*/
            }
        }
        for(; k<3; k++)                                /*输出时忽略前导0*/
            if(out[k]!=48)	break;
        for(; k<m; k++)
            printf("%c",out[k]);
        putchar('\n');
    }
    return 0;
}

该代码和前一个代码的意思是相同的,让我们来详细的一段一段代码的分析下。

        l=strlen(s[j]);                           /* 初始化标记数据*/
        a=3-l%3;                                   /*初始化标记数据*/
        if(a==3)	ok=0;                    /*十六进制数长度刚好为3的倍数时转二进制不需补0,ok标记其是否为3的倍数0是1不是*/

这段代码的意思也是判断十六进制的位数是否是3的倍数,不是的话得补几位

           if(65<=s[j][i])
                s[j][i]-=7;
            if(ok)                                     /*十六进制数位数不足转二进制时补0占位*/
                if(a==1)
                {
                    strcat(z,"0000");
                    k=k+4;
                    ok=0;
                }
                else if(a==2)
                {
                    strcat(z,"00000000");
                    k=k+8;
                    ok=0;
                }
            z[k++]=d16[s[j][i]-48][0];                  /*一位十六进制转四位二进制*/
            z[k++]=d16[s[j][i]-48][1];
            z[k++]=d16[s[j][i]-48][2];
            z[k++]=d16[s[j][i]-48][3];

这段代码就是实现将十六进制数对应为四位二进制数的下标,0-9很好对应,直接减去48即可。但是A-F呢?为了将0-F一同对待,同样减去48,我们知道A对应的ASCAII是65,A的十六进制数是10,所以先将A-?=10,?=55,所以A先减去7,在减去48就是对应的十六进制数10。

            if(k==12)                                    /*每转三位十六进制数将其转为四位八进制数*/
            {
                for(cur=0; cur<12; m++)
                    out[m]=((z[cur++]-48)*4+(z[cur++]-48)*2+(z[cur++]-48)*1)+48;
                //z[0]='\0';
                k=0;                               /*z[0]='\0' 初始化字符串结束符位置避免溢出*/
            }

这里和前一个程序的思路不太一样,前一个程序是把每次三位的二进制数转八进制数后直接输出来,但这里是将某个十六进制数的转为八进制后,直接数出来。

z[0]=‘\0’ 初始化字符串结束符位置避免溢出*/。这句笔者也不太能理解是做什么用的,不过我将这句去掉判分系统也是判的是正确的。望知道的读者能够解答解答。

最后咱们来看看这两个程序的对比。
蓝桥杯 十六进制转为八进制 C语言版_第1张图片
可以明显看出,cpu的时间是大大减小,不过内存占用稍微大了一点点,是因为加了out[140000]这个一位数组的原因。

在我看来,可以将以上两个程序结合一下,这样内存的使用会不会也大大减小了,先看看结果:
请添加图片描述
果然,时间和空间都达到了理想效果,下面贴上代码以及解析。

#include
#include
int main()
{
    int n,i,j,k,a,cur,ok,m,l,flag;
    char s[10][100001],d16[16][5]= {"0000","0001","0010","0011","0100","0101","0110","0111","1000","1001","1010","1011","1100","1101","1110","1111"},out[4]= {},z[13]= {};
    scanf("%d",&n);
    for(j=0; j<n; j++)
        scanf("%s",s[j]);
    for(j=0; j<n; j++)
    {
        k=0;
        ok=1;
        flag = 0;
        m=0;                            /*初始化标记数据*/
        l=strlen(s[j]);                           /* 初始化标记数据*/
        a=3-l%3;                                   /*初始化标记数据*/
        if(a==3)	ok=0;                    /*十六进制数长度刚好为3的倍数时转二进制不需补0,ok标记其是否为3的倍数0是1不是*/
        for(i=0; i<l; i++)                     /*逐位读取十六进制数进行转换*/
        {
            if(65<=s[j][i])
                s[j][i]-=7;
            if(ok)                                     /*十六进制数位数不足转二进制时补0占位*/
                if(a==1)
                {
                    strcpy(z,"0000");
                    k=k+4;
                    ok=0;
                    a=0;
                }
                else if(a==2)
                {
                    strcpy(z,"00000000");
                    k=k+8;
                    ok=0;
                }
            z[k++]=d16[s[j][i]-48][0];                  /*一位十六进制转四位二进制*/
            z[k++]=d16[s[j][i]-48][1];
            z[k++]=d16[s[j][i]-48][2];
            z[k++]=d16[s[j][i]-48][3];
            if(k==12)                                    /*每转三位十六进制数将其转为四位八进制数*/
            {
                m=0;
                for(cur=0; cur<12; m++)
                    out[m]=((z[cur++]-48)*4+(z[cur++]-48)*2+(z[cur++]-48)*1)+48;
                cur = 0;
                k=0;
                if(!flag)
                    while(out[cur]=='0'&&cur<m)
                        cur++;
                if(cur==m)
                    flag = 0;
                else
                    flag = 1;
                for(; cur<m; cur++)
                    printf("%c",out[cur]);
            }

        }
        putchar('\n');
    }
    return 0;
}

首先,此代码和第一个未优化的代码相似,是每次三位的二进制数转八进制数后直接输出来,并且加上了判断开头是否为0的情况。其次,在改写这个代码的时候,笔者发现上面的那个疑问,即z[0]=‘\0’ 初始化字符串结束符位置避免溢出*/。一开始编写改代码发现一直报错,但一直找不到原因,后来仔细分析原来是strcat的原因。这句话去掉后,把strcat改成strcpy即可。

最后,接下来会不断更新蓝桥杯有关题目,希望大家共同学习,共同进步,并不是为了奖状而去比赛,是为了自己能够学到真本领,这样才会越学越快乐,越学越有动力哦,加油!!!

你可能感兴趣的:(几个蓝桥杯题目,蓝桥杯,c语言,算法)