进制转换是一个程序员基本的能力,今天就来一道进制转换来下下饭。
十六进制转八进制
资源限制
时间限制:1.0s 内存限制:512.0MB
问题描述
给定n
个十六进制正整数,输出它们对应的八进制数。
输入格式
输入的第一行为一个正整数n (1<=n<=10)
。
接下来n行,每行一个由0~9
、大写字母A~F
组成的字符串,表示要转换的十六进制正整数,每个十六进制数长度不超过100000
。
输出格式
输出n行,每行为输入对应的八进制正整数。
【注意】
输入的十六进制数不会有前导0
,比如012A
。
输出的八进制数也不能有前导0
。
样例输入
2
39
123ABC
样例输出
71
4435274
【提示】
先将十六进制数转换成某进制数,再由某进制数转换成八进制。
题目解析
首先要注意的是,题中说“每个十六进制数长度不超过100000
”,这里是长度而不是大小,所以输入有可能是十万位的十六进制数,这个数非常大,我们不能通过简单的%X
输入十六进制到变量中再%O
输出八进制来实现,因为没有一个变量能够直接存放这么大的数,所以整个转换流程都是以字符串操作的方式实现的。
再一个就是输入输出的数中都不应该有前导0,也就是不会出现类似012A
的数,而应该是12A
,输出也不应该是0452
,而应该只有452
,前导0在转换过程中会不可避免得出现,必须进行处理保证不会输出。
还有就是在提示中说了,“先将十六进制数转换成某进制数,再由某进制数转换成八进制。”,在实际计算时,对于2的幂的进制互转,都会先转为二进制再进行转换,比如本次的十六进制和八进制,分别都是2的4次幂和2的3次幂,所以先转为二进制再转换会更容易操作。
二进制,八进制,十进制,十六进制的对应关系如下表所示。
二进制 | 八进制 | 十六进制 | 十进制 |
---|---|---|---|
0000 | 0 | 0 | 0 |
0001 | 1 | 1 | 1 |
0010 | 2 | 2 | 2 |
0011 | 3 | 3 | 3 |
0100 | 4 | 4 | 4 |
0101 | 5 | 5 | 5 |
0110 | 6 | 6 | 6 |
0111 | 7 | 7 | 7 |
1000 | 8 | 8 | |
1001 | 9 | 9 | |
1010 | A | ||
1011 | B | ||
1100 | C | ||
1101 | D | ||
1110 | E | ||
1111 | F |
十六进制与二进制的互转只需要将1位十六进制转换为4位二进制,二进制则相反,不足4的倍数的在左侧补0。
八进制与二进制互转同理,只不过4变为3。
这样就有了整道题的思路,只需要对字符串进行操作即可。
代码
本题的流程可以写成以下几点:
- 输入正整数n(十六进制数个数)
- 输入十六进制数
- 十六进制字符串转二进制字符串
- 二进制字符串转八进制字符串
- 输出八进制字符串
只需要循环以上2~5步即可转换所有的十六进制数。
代码框架如下:
#include <stdio.h>
#include <string.h>
int main(void)
{
//1.输入正整数n
//2.输入十六进制字符串
//3.十六进制字符串转二进制字符串
//4.二进制字符串转八进制字符串
//5.输出八进制字符串
return 0;
}
加入string.h
头文件以帮助处理字符串。
然后加入输入n和循环。
#include <stdio.h>
#include <string.h>
int main(void)
{
int n = 0;
int i = 0;
//1.输入正整数n
scanf("%d", &n);
for (i = 0; i < n; i++)
{
//2.输入十六进制字符串
//3.十六进制字符串转二进制字符串
//4.二进制字符串转八进制字符串
//5.输出八进制字符串
}
return 0;
}
由于要通过字符串进行处理,那么必然要有三个数组用来保存输入的十六进制字符串、中间产生的二进制字符串和最后的八进制字符串。并且因为转换时同一个数不同进制的长度不同,所以三个字符串的大小应为3:12:4
的关系(1位十六进制换4位二进制,1位八进制换3位二进制)。
声明以上三个数组并加入十六进制字符串输出部分。
#include <stdio.h>
#include <string.h>
char hex[100001]; //最长100000位,结尾多加一个放结束符
char bin[400001]; //4倍于十六进制
char oct[140001]; //3/4倍于十六进制取整
int main(void)
{
int n = 0;
int i = 0;
//1.输入正整数n
scanf("%d", &n);
for (i = 0; i < n; i++)
{
//2.输入十六进制字符串
scanf("%s", hex);
//3.十六进制字符串转二进制字符串
//4.二进制字符串转八进制字符串
//5.输出八进制字符串
}
return 0;
}
十六进制转二进制时我们首先要知道每一个十六进制位代表的数是多少,所以写一个函数用来将1个十六进制字符转为1个0-15的整型数。题中说明十六进制字符串中只会出现“0~9
和大写字母A~F
”,所以进行如下处理,最后的返回-1为了补全else。
int hex2int(char ch)
{
if (ch >= '0' && ch <= '9')
{
return ch - '0';
}
else if (ch >= 'A' && ch <= 'F')
{
return ch - 'A' + 10;
}
else
{
return -1;
}
}
随后将这一位转为二进制数表示,在我前面的文章中已经写过二进制的计算方法:
进制,即进位计数制,是人为定义的一种带进位的计数方法,便于使用有限的数字字符表示所有的数。对于任何一种进制都表示某一位置上的数字达到某一值后向上进一位。如常用的十进制计算
9+1
时,当个位数字9增加1时,应该变为十,但是我们采用十进制计数,所以在这一位上并不会出现表示十的数字,而是向上进位变为10
。同理,对于8进制而言,当计算7+1
时,并不出现8
这个数字,而是变为10
,而这个8进制的10
与十进制的8
是相等的。也可以发现,进制的转换并不会改变数值本身的大小,只是表示方法的改变。进制的转换通常可以使用连续做除法取余数的方式,如十进制数
6
转二进制可以如下方式计算:6 ÷ 2 = 3......0 6 ÷2 = 3 ...... 0 6÷2=3......0
3 ÷ 2 = 1......1 3 ÷2 = 1 ...... 1 3÷2=1......1
1 ÷ 2 = 0......1 1 ÷2 = 0 ...... 1 1÷2=0......1将余数从下往上倒过来即是相应进制,即6的二进制表示为
110
。因为在进制转换的过程中数值大小本身并不变化,所以人工计算时对于不方便计算的数值也可以通过转换为其他进制作为媒介来进行。
在转为二进制时,我们可以将0~F的二进制字符串写成数组,然后通过char *strcat(char *des, const char *src)
进行拼接即可,
据此,我们可以写出转二进制代码。返回值为二进制字符串长度(非必须)。
//0~F的二进制分别为以下16个字符串
const char binarr[16][5] = {
"0000", "0001", "0010", "0011",
"0100", "0101", "0110", "0111",
"1000", "1001", "1010", "1011",
"1100", "1101", "1110", "1111"};
/**************************************
* @param hex 要转换的十六进制字符串
* @param bin 转换后的二进制字符串
* @retval 转换后的二进制字符串长度
*************************************/
int hex2bin(const char *hex, char *bin)
{
int i = 0;
size_t size = 0;
//将bin数组第0位置为'\0',即字符串长度为0
bin[0] = '\0';
//获取十六进制数长度
size = strlen(hex);
for (i = 0; i < size; i++)
{
//将十六进制字符串的一位转为整型数然后
//通过strcat()将对应的二进制添加至二进制字符串结尾
strcat(bin, binarr[hex2int(hex[i])]);
}
return 4 * size;
}
在编写程序的时候,我们通常会将复杂的程序逻辑分成模块进行,这也是函数的意义之一。对于复杂的程序,应该在编写完一个模块后优先对该模块进行测试,以保证模块的正确性。对于刚刚完成的部分程序,即十六进制字符串转二进制部分,我们可以先编写程序保证这部分的正确性。
在主函数做如下改动,并将上面编写的转换函数一并放入源代码运行。
int main(void)
{
int n = 0;
int i = 0;
int len = 0;
//1.输入正整数n
scanf("%d", &n);
for (i = 0; i < n; i++)
{
//2.输入十六进制字符串
scanf("%s", hex);
//3.十六进制字符串转二进制字符串
//调用十六进制转二进制字符串的函数,将长度返回len
len = hex2bin(hex, bin);
//打印出转换好的二进制字符串
printf(bin);
//4.二进制字符串转八进制字符串
//5.输出八进制字符串
}
return 0;
}
测试数据输入1行十六进制字符串123ABC
,根据1字符变4字符原则转换后应该为000100100011101010111100
。
测试结果如下
1
123ABC
000100100011101010111100
可以看到已经成功将十六进制数转为了二进制。下面开始二进制字符串到八进制字符串的转换。
在二进制转八进制时,可能二进制字符串的长度不是3的倍数,这样在3位二进制换1位八进制的时候会出现长度不足的情况,所以我们需要对二进制字符串进行判断,并使长度不足3的倍数的二进制字符串补足到3的倍数。
随后3个字符一组和1位八进制对应的3位二进制字符串进行比较,使用int strncmp(const char *, const char *, size_t)
比较指定长度的字符串,返回0时为二者相等。循环将3位二进制字符和数组中的进行比较,相等时将对应的八进制数放入字符串。然后返回八进制字符串的长度(非必须)。
//八进制0~7的二进制分别为以下8个字符串
const char oct2binarr[8][4] = {
"000", "001", "010", "011",
"100", "101", "110", "111"};
/**************************************
* @param bin 要转换的十六进制字符串
* @param oct 转换后的二进制字符串
* @retval 转换后的二进制字符串长度
*************************************/
int bin2oct(char *bin, char *oct)
{
int i = 0;
int j = 0;
size_t size = 0;
//将oct数组第0位置为'\0',即字符串长度为0
oct[0] = '\0';
//获取二进制数长度
size = strlen(bin);
//如果二进制字符串长度不为3的倍数,
//则不能直接转换,需要补齐为3的倍数
if (size % 3 != 0)
{
//将二进制字符串复制到向后3 - size % 3个字符的位置
//3 - size % 3
//3减size对3的余数的差即为需要补足的字符个数
j = 3 - size % 3;
for (i = size - 1; i >= 0; i--)
{
bin[i + j] = bin[i];
}
//然后将前面留出的3 - size % 3个字符置为'0'
for (i = 0; i < j; i++)
{
bin[i] = '0';
}
}
//获取新的二进制字符串长度
size = strlen(bin);
for (i = 0; i < size / 3; i++)
{
for (j = 0; j < 8; j++)
{
//比较字符串是否相等
if (strncmp(bin + i * 3, oct2binarr[j], 3) == 0)
{
oct[i] = j + '0';
}
}
}
return size / 3;
}
输入4位十六进制数5678
,二进制应为0101011001111000
,16位不是3的倍数,八进制应补齐3的倍数个并转换为53170
,看我们的代码能否正确转换。
使用如下主函数进行测试。
int main(void)
{
int n = 0;
int i = 0, j = 0;
//1.输入正整数n
scanf("%d", &n);
for (i = 0; i < n; i++)
{
//2.输入十六进制字符串
scanf("%s", hex);
//3.十六进制字符串转二进制字符串
//调用十六进制转二进制字符串的函数,将长度返回len
hex2bin(hex, bin);
//4.二进制字符串转八进制字符串
bin2oct(bin, oct);
//5.输出八进制字符串
printf(oct);
}
return 0;
}
测试结果如下
1
5678
053170
可以看出我们的程序已经正常工作了,但是现在存在前导0,所以还需要在输出时去除,可以在输出时先通过循环找到第一个非'0'
的字符,然后将该字符和后面的字符串输出。
int main(void)
{
int n = 0;
int i = 0, j = 0;
int len = 0;
//1.输入正整数n
scanf("%d", &n);
for (i = 0; i < n; i++)
{
//2.输入十六进制字符串
scanf("%s", hex);
//3.十六进制字符串转二进制字符串
//调用十六进制转二进制字符串的函数,将长度返回len
hex2bin(hex, bin);
//4.二进制字符串转八进制字符串
//len为八进制字符串长度
len = bin2oct(bin, oct);
//5.输出八进制字符串
//从头循环每一个字符
for (j = 0; j < len; j++)
{
//如果不是'0'则从该位开始输出
if (oct[j] != '0')
{
printf("%s\n", oct + j);
//随后直接退出循环
break;
}
}
}
return 0;
}
按照要求使用更长的数据进行测试。结果符合预期。(以下结果除第一行外,输入和输出交替)
7
76
166
931FA
2230772
C9DAB2B36C
14473254531554
248B87D6AE33F9A
11105607655270637632
62D7183A5D5789E4B2D6
142656140722725361171131326
B441E2411DC709E111C7E1E7ACB6F8CAC0BB2FC4C8BC2AE3BAAAB9165CC458E199CB89F51B135F7091A5ABB0874DF3E8CB45
13210170440435616047410434374171726266761453005662770462136052707352525621313461054341463456117521542327670221513256604164676372145505
43A5EB93B0441E9CA4C2B0FB3D30875CBF29ABD5B1ACF38984B35AE882809DD4CFE7ABC5C61BAA52E053B4C3643F204EF259D2E98042A948AAC5E884CB3EC7DB925643FD34FDD467E2CCA406035CB2744CB90A63E51C9737903343947E02086541E4C48A99630AA9AECE153843A4B190274EBC955F8592E30A2205A485846248987550AAF2094EC59E7931DC650C7451CC61C0CB2C46A1B3F2C349FAFF763C7F8D14DDFF946351744378D62C59285A8D7915614F5A2AC9E0D68ACA6248A9227AB8F1930EE38AC7A9D239C9B026A481E49D53161F9A9513FE5271C32E9C21D156EB9F1BEA57F6AE4F1B1DE3B7FD9CEE2D9CCA7B4C242D26C31D000B7F90B7FE48A131C7DEBFBE58165266DE56E1EDF26939AF07EC69AB1B17D8DB62143F2228B51551C3D2C7DE3F5072BD4D18C3AEB64CB9E8CBA838667B6ED2B2FCAB04ABAE8676E318B402A7D15B30D2D7DDB78650CC6AF82BC3D7AA805B02DD9AA523B7374A1323EE6B516D1B81E5F709C2C790EDAF1C3FA9B0A1DBC6DABC2B5ED267244C458752002B106D6381FAD58A7E193657BDE0FE029120F8379316891F828B8D24A049E5B86D855BCFED56765F9DA1AC54CAEAF9257ABC67B451BC70B0E52817DD1B704A6B418A83FD4A9CA4C89E1A6E779F8D9E9DF18747591E5B314C05763EDCD59632423CA83F14D6F073D784DB2B7001643A6760
4164572711660210172345114126076636460416562771232572554326363423022632656420240116724637636536134303352451340247323033103744023571131645646002052451052542750411454766175562225441775151767243176131451003003271311642313441230762434456336201464162437401010312407446110523130605251535470251604164454310047235362252770262270605042013222054106111046072520525710112354263636230734312061642434614160145454215206637454151176577566170776150515677745061521350415706542613112055215362125412365505262360326425451422212444236534361446073434254365164434711540232444036223524613037465124237745116160627234103505267271743372453766534474330736166777316356133163123664604413223303072000133771026777444241143437365773713005451146674533417337115116327407730646530661373066661024176210505521252160751307674375203453651506141656554462717214565016063173335512627712540452727206355614305500124764255460645537355570312063065370127417275250013300556632512216671564502310767153242664334036276702341307441666570703765154120733615552741265732231622114213035220002542033261601765526123741446625736740774012211017406744613211077012134322224022362670333025336376652635457635503261246256537111275274317321215707026071224027672155602246550142501775225162446211703233473637433172357430350726217133142460053543733465313062204362501761232674071727411554533400054416463540
提交练习系统OJ进行评测,发现运行超时。
这说明算法效率还不够,数据处理用时太长。主要原因是以上代码中使用了C库中的函数
char *strcat(char *des, const char *src)
和int strncmp(const char *, const char *, size_t)
。这两个库函数的运行效率非常低,所以我们需要手动实现我们通过这两个库函数实现的功能。
将int hex2bin(const char *hex, char *bin)
函数中的strcat()
使用逐个赋值的方式进行替换,并在最后添加结束符。
int hex2bin(const char *hex, char *bin)
{
int i = 0;
size_t size = 0;
//将bin数组第0位置为'\0',即字符串长度为0
bin[0] = '\0';
//获取十六进制数长度
size = strlen(hex);
for (i = 0; i < size; i++)
{
//将十六进制字符串的一位转为整型数然后
//通过strcat()将对应的二进制添加至二进制字符串结尾
//strcat(bin, hex2binarr[hex2int(hex[i])]);
bin[i * 4 + 0] = hex2binarr[hex2int(hex[i])][0];
bin[i * 4 + 1] = hex2binarr[hex2int(hex[i])][1];
bin[i * 4 + 2] = hex2binarr[hex2int(hex[i])][2];
bin[i * 4 + 3] = hex2binarr[hex2int(hex[i])][3];
}
//结尾添加结束符
bin[size * 4] = '\0';
return 4 * size;
}
将int bin2oct(char *bin, char *oct)
函数中的strcmp()
使用按二进制位加权求和的方式进行替换。并在结尾添加结束符。
int bin2oct(char *bin, char *oct)
{
int i = 0;
int j = 0;
size_t size = 0;
//将oct数组第0位置为'\0',即字符串长度为0
oct[0] = '\0';
//获取二进制数长度
size = strlen(bin);
//如果二进制字符串长度不为3的倍数,
//则不能直接转换,需要补齐为3的倍数
if (size % 3 != 0)
{
//将二进制字符串复制到向后3 - size % 3个字符的位置
//3 - size % 3
//3减size对3的余数的差即为需要补足的字符个数
j = 3 - size % 3;
for (i = size - 1; i >= 0; i--)
{
bin[i + j] = bin[i];
}
//然后将前面留出的3 - size % 3个字符置为'0'
for (i = 0; i < j; i++)
{
bin[i] = '0';
}
}
//获取新的二进制字符串长度
size = strlen(bin);
for (i = 0; i < size / 3; i++)
{
// for (j = 0; j < 8; j++)
// {
// if (strncmp(bin + i * 3, oct2binarr[j], 3) == 0)
// {
// oct[i] = j + '0';
// }
// }
oct[i] = (bin[i * 3 + 0] - '0') * 4 +
(bin[i * 3 + 1] - '0') * 2 +
(bin[i * 3 + 2] - '0') * 1 + '0';
}
//结尾添加结束符
bin[size / 3] = '\0';
return size / 3;
}
最终代码如下所示,篇幅限制,删除注释。
#include <stdio.h>
#include <string.h>
char hex[100001];
char bin[400001];
char oct[140001];
int hex2int(const char ch)
{
if (ch >= '0' && ch <= '9')
{
return ch - '0';
}
else if (ch >= 'A' && ch <= 'F')
{
return ch - 'A' + 10;
}
else
{
return -1;
}
}
const char hex2binarr[16][5] = {
"0000", "0001", "0010", "0011",
"0100", "0101", "0110", "0111",
"1000", "1001", "1010", "1011",
"1100", "1101", "1110", "1111"};
int hex2bin(const char *hex, char *bin)
{
int i = 0;
size_t size = 0;
bin[0] = '\0';
size = strlen(hex);
for (i = 0; i < size; i++)
{
bin[i * 4 + 0] = hex2binarr[hex2int(hex[i])][0];
bin[i * 4 + 1] = hex2binarr[hex2int(hex[i])][1];
bin[i * 4 + 2] = hex2binarr[hex2int(hex[i])][2];
bin[i * 4 + 3] = hex2binarr[hex2int(hex[i])][3];
}
bin[size * 4] = '\0';
return 4 * size;
}
const char oct2binarr[8][4] = {
"000", "001", "010", "011",
"100", "101", "110", "111"};
int bin2oct(char *bin, char *oct)
{
int i = 0;
int j = 0;
size_t size = 0;
oct[0] = '\0';
size = strlen(bin);
if (size % 3 != 0)
{
j = 3 - size % 3;
for (i = size - 1; i >= 0; i--)
{
bin[i + j] = bin[i];
}
for (i = 0; i < j; i++)
{
bin[i] = '0';
}
}
size = strlen(bin);
for (i = 0; i < size / 3; i++)
{
oct[i] = (bin[i * 3 + 0] - '0') * 4 +
(bin[i * 3 + 1] - '0') * 2 +
(bin[i * 3 + 2] - '0') * 1 + '0';
}
return size / 3;
}
int main(void)
{
int n = 0;
int i = 0, j = 0;
int len = 0;
//1.输入正整数n
scanf("%d", &n);
for (i = 0; i < n; i++)
{
//2.输入十六进制字符串
scanf("%s", hex);
//3.十六进制字符串转二进制字符串
hex2bin(hex, bin);
//4.二进制字符串转八进制字符串
len = bin2oct(bin, oct);
//5.输出八进制字符串
for (j = 0; j < len; j++)
{
if (oct[j] != '0')
{
printf("%s\n", oct + j);
break;
}
}
}
return 0;
}
提交OJ评测,正确。
本题还挺考验思维能力的,有时候不能使用传统思维进行处理,比如本题的输入数据虽然是数,但是我们却不能按照数值来进行计算。尝试锻炼思维能力对算法设计非常重要。