1. 程式人生 > 其它 >十六進位制轉八進位制

十六進位制轉八進位制

技術標籤:程式設計#C/C++

進位制轉換是一個程式設計師基本的能力,今天就來一道進位制轉換來下下飯。

十六進位制轉八進位制

資源限制

時間限制: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次冪,所以先轉為二進位制再轉換會更容易操作。

二進位制,八進位制,十進位制,十六進位制的對應關係如下表所示。

二進位制八進位制十六進位制十進位制
0000000
0001111
0010222
0011333
0100444
0101555
0110666
0111777
100088
100199
1010A
1011B
1100C
1101D
1110E
1111F

十六進位制與二進位制的互轉只需要將1位十六進位制轉換為4位二進位制,二進位制則相反,不足4的倍數的在左側補0。

八進位制與二進位制互轉同理,只不過4變為3。

這樣就有了整道題的思路,只需要對字串進行操作即可。

程式碼

本題的流程可以寫成以下幾點:

  1. 輸入正整數n(十六進位制數個數)
  2. 輸入十六進位制數
  3. 十六進位制字串轉二進位制字串
  4. 二進位制字串轉八進位制字串
  5. 輸出八進位制字串

只需要迴圈以上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評測,正確。

本題還挺考驗思維能力的,有時候不能使用傳統思維進行處理,比如本題的輸入資料雖然是數,但是我們卻不能按照數值來進行計算。嘗試鍛鍊思維能力對演算法設計非常重要。