16进制转8进制的位运算 Java算法

来源:互联网 发布:db2 sql获取当前时间 编辑:程序博客网 时间:2024/05/16 17:47

又是一道算法题,这道题目其实很简单,看下题目:
这里写图片描述
题目要求是:
输入十六进制数的字符串形式, 然后转换成8进制数输出.

要求很简单, 因为Java内置的api就可以搞定, 下面是我最开始的思路和代码:

  • 最开始的思路:
    • 用字符串数组接收输入的16进制字符串
    • 然后用大数BigInteger(为什么用BigInteger呢?因为测试数据一共有10个,最后一个16进制数有10万位,你没看错,就是10万个数字组成的16进制数)的方法将16进制的字符串转成16进制,然后再接一个转成8进制的方法,最后按照格式输出即可, 代码如下.
import java.math.BigInteger;import java.util.Scanner;public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int n = sc.nextInt();        String[] arr = new String[n];        //存储输入的十六进制数        for (int i = 0; i < arr.length; i++) {            arr[i] = sc.next();        }        //将十六进制字符串转换成8进制输出        long start = System.currentTimeMillis();        for (int i = 0; i < arr.length; i++) {            System.out.println(new BigInteger(arr[i], 16).toString(8));        }        long end = System.currentTimeMillis();        System.out.println(end-start);    }}

代码只需要20行左右, 思路也很简单, 是不是感觉很舒服? 这也符合程序员都喜欢偷懒的习惯, 原本我以为提交就行了, 结果测试要求测试完10个数据的时间不能超过 1秒! 告诉我运行超时, 我返回去测试了一下, 光是转换最后一个10万位的16进制数就要 3秒多(就算配置好一点的电脑也在秒级), 于是不得不想别的算法重做.

思考了很多种方案, 发现还是位运算的速度是最快的, 然后又在网上找了找资料, 借用了一个网友的思路, 如下:

  • 首先, 算法使用位运算, 计算机使用位运算都是采用2进制, 那么就考虑将16进制转换成2进制, 然后再由2进制转换成8进制输出.
  • 又由于一个16进制数对应 4位 2进制数, 一个8进制数对应 3位 2进制数, 它们的最小公倍数是 12位 2进制数, 于是在转换的时候就 每3个 16进制数 转换一次, 每次可以得到 4个 8进制数.
  • 考虑到补0的情况(不清楚这块儿的可以查一下计算机移位运算的规则), 如果人工补0会让代码变得复杂, 所以采用 右移运算 ,每次取最后面的 3个16进制数, 这样计算机会自动在高位补0(而且因为是取最低的3位,所以不用考虑高位补0后的情况),将会大大简化我们的操作.

    可能上述思路不是太清晰,结合图示应该比较好理解:
    这里写图片描述

有了思路,那么就可以开始用代码实现了, 下面是代码实现的步骤分析:

  1. 首先是要存储输入的16进制数, 采用字符串数组将其保存起来.
  2. 每次截取3个16进制数, 将其运算后转换成4个8进制数.
  3. 将得到的8进制数添加到一个集合中.
  4. 因为是逆序添加, 所以最后将集合中的8进制数逆序输出即可.

技术和问题分析:

  1. 首先是截取字符串的问题, 可以采用api中的截取方法, 但是这里要注意两个问题.
    第一:
    最开始我是采用每次从原来的字符串截取了3个数字后,就将原来的字符串赋值成截取后的新字符串, 因为这样做我们只需要每次截取字符串尾巴的后三位数字即可, 但是事实证明偷懒的代价就是效率不达标, 没办法还是超时, 因为对字符串重新赋值还有截取的操作可是很耗费时间的; 所以我的办法是自己写算法判断每次需要截取的部分, 保持原来的字符串不变.

    第二:
    我们每次截取的是字符串的后三位, 如果字符串的位数刚好能被3整除那还好,如果字符串不能被3整除呢, 这里就需要写两个算法, 分别对两种情况进行不同判断, 虽然麻烦了一点, 但是为了效率没办法.

  2. 获取到3个字符串后, 首先将3个字符串转换成10进制(因为输入的是16进制数, 而且计算机运算10进制数时采用的就是位运算), 这里转换成8进制数的方法是采用 和 7进行 &(与)运算 , 进行与运算会自动转成2进制数, 然后右移 3位, 如此循环 4次, 将 12位二进制数 转换完成即可.
    这里写图片描述
    这里写图片描述

  3. 将所有16进制数转换完成后, 得到的8进制数就保存到了集合中, 不过要注意的是: 集合中现在保存的是逆序的结果, 我们在输出的时候要逆序输出.
    问题:
    同样,这里也有一个问题, 转换的8进制数里面可能会有0, 而且这个0很可能会在高位(也就是8进制数的最左边)输出, 但是测试中明确要求输出的8进制数首位不能是0! 所以我们在输出前需要判断首位是否是0, 如果是0就跳过判断下一个数, 直到判断的下一个数不为0的时候才开始输出(中间的0不需要管).

通过这个思路写好代码后测试发现转换10万位的16进制数需要 300多毫秒, 比起第一次的 3秒多, 速度提高了13倍, 终于满足了题目要求. (这个13倍仅仅是和我自己之前的算法比较, 有个大佬的算法(Java)算完10个数据只需要几十毫秒……)
这次的经验让我明白虽然有时候用自带的api写起来很轻松, 代码又少, 但是效率可能不会太高, 有时候还是需要自己动手想想算法.

以上就是全部的思路以及程序的步骤分析, 最后来看一下代码实现吧(Java):

import java.util.ArrayList;import java.util.Scanner;/* * 16进制转8进制思路: *      一个16进制数是4位二进制,一个8进制数是3为二进制,取最小公倍数12,即一次取3个16进制数进行转换(省去了人工补位的操作) *      1.将3个16进制数转换成10进制数,然后将得到的十进制数与数字7(111)进行&与运算,则可以得到这串二进制数的低三位 *      2.将得到的低三位加上前缀'0'转成8进制数,再追加到新的字符串中 *      3.将原来的12个二进制数右移三位,重复1,2步的操作一共四次,即可完成一次的转换 */public class Test_2 {    // 定义变量num保存十进制数(二进制),str保存获取到的三位16进制数,ArrayList保存运算后的8进制数,    // temp控制循环,字符数组保存最后结果    static int num = 0;    static int temp = 0;    static String str = "";    static ArrayList<Integer> array = new ArrayList<Integer>();    public static void main(String[] args) {        // 接收输入        Scanner sc = new Scanner(System.in);        // 记录输入的数字个数        int n = sc.nextInt();        // 字符串数组保存输入的十六进制数        String[] arr = new String[n];        // 存入十六进制数        for (int i = 0; i < arr.length; i++) {            arr[i] = sc.next();        }        // 计算时间        long start = System.currentTimeMillis();        // for循环遍历数组中的每个数        for (int i = 0; i < arr.length; i++) {            cutStr(arr[i], array);            outputArray(array);        }        long end = System.currentTimeMillis();        System.out.println(end-start);    }    //截取字符串存入str交给num处理    public static void cutStr(String arr, ArrayList<Integer> array){        //判断字符串是否小等于3        if(arr.length() <= 3){            str = arr;            change(str, array);        }else{            temp = arr.length() / 3;            //整除的情况            if(arr.length() % 3 == 0){                for (int i = temp; i >= 1; i--) {                    if(i == temp){                        str = arr.substring(arr.length()-3);                        change(str, array);                    }else if(i == 1){                        str = arr.substring(0, 3);                        change(str, array);                    }else{                        str = arr.substring((3*i-3), 3*i);                        change(str, array);                    }                }            }else{                //非整除情况                for (int i = 0; i <= temp; i++) {                    if(i == 0){                        str = arr.substring(arr.length()-3);                        change(str, array);                    }else if(i == temp){                        str = arr.substring(0, arr.length() - (temp*3));                        change(str, array);                    }else{                        str = arr.substring(arr.length()-(3*i)-3, arr.length()-(3*i));                        change(str, array);                    }                }            }        }    }    // 将str解析成8进制存入集合    public static void change(String str, ArrayList<Integer> array) {        // 转成十进制数        num = Integer.parseInt(str, 16);        // 与运算后转成8进制保存进集合, 循环四次        for (int j = 0; j < 4; j++) {            array.add((num & 7));            // 将num右移运算            num = num >>> 3;        }    }    // 将集合逆序输出并且清空    public static void outputArray(ArrayList<Integer> array) {        for (int j = array.size() - 1; j >= 0; j--) {            // 忽略前置的0            if (array.get(j) == 0) {                continue;            } else {                for (int x = j; x >= 0; x--) {                    System.out.print(array.get(x));                }                break;            }        }        array.clear();        System.out.println();    }}
原创粉丝点击