• 关于几个位运算的算法分析


    问题一:

    给定一个正整数N,求其二进制形式的第一个比特位1(从低位到高位的顺序)。

    例如,给定正整数12,其低8位二进制表示为:00001100

    从低位到高位的顺序,第一个1出现在第三位。

    版本一:

    最低位开始,针对每一位进行与(&)操作判断是否为1,直到遇到第一个1为止。

    算法实现(版本一):

        public static int getFirstBit1(int N){
         int mask = 1; int pos = 1; while(mask <= N){ if((N&mask) == mask){ break; }else { mask <<= 1; pos++; } } return pos; }

    版本二:

    假定第一个1出现在第pos位,注意到tmp=(N^(N-1))+1实际上代表pos位及其右边全部置1并且pos的左边全部置0的那个数,求pos的值问题转化为求tmp有多少个1的问题。

        public static int getFirstBit1(int N){
            int tmp = (N^(N-1))+1;//代表pos位及其右边全部置1并且pos的左边全部置0的那个数
            int pos = 0;
            while(tmp > 1){
                tmp >>= 1;
                pos++;
            }
            return pos;
        }

    问题二:

    给定一正整数N,求其二进制形式中比特位为1的个数。

    版本一:

    依次从低位到高位校验每一位是否为1并计数。

        /*常规的算法*/
        public static int getNumberOfBit1(int N){
            int count = 0;
            int mask = 1;
            while(mask <= N){
                System.out.println("ok");
                if((N&mask) == mask){
                    count++;
                }
                mask <<= 1;
            }
            return count;
        }

    版本二:

    注意到(N-1)&N的结果必然会将N的最右边的1消掉,以N=12的低8位举例:

    12:00001100

    12-1:00001011

    12&11:00001000

    其结果就是将12的最右边的1消掉。

    按照如此规律依次循环直到N为0为至,经历了多少次循环,代表原数N中有多少个1。

        /*较为高效的算法*/
        public static int getNumberOfBit1(int N){
            int count = 0;
            while(N != 0){
                count++;
                N = (N-1)&N;
                System.out.println("ok");
            }
            return count;
        }

    问题三:

    一个无序数组里有若干个正整数,其中有一个整数出现了奇数次,其余整数都出现了偶数次,找出这个出现奇数次的整数。

    例如,给定无序数组:

    int[] ary = {2,3,6,3,4,9,4,2,6};

    出现奇数次的整数为:9

    分析:

    异或运算满足交换性质,比如2^3^2^3的结果和2^2^3^3的结果是一样的。

    注意到偶数次的现象,因此,可以考虑异或运算:当两位相同时返回0,不同是返回1。

    这一规律表现在:对于一个整数,重复偶数次,对其进行异或运算的结果必然为0;

    更进一步,对以上现象的叠加,表现在:对多个整数,每个整数都出现偶数次,对其进行异或运算的结果也必然为0,而与其出现的顺序性无关;

    算法实现:

        public static int find(int[] ary){
            int tmp = 0;
            for (int i = 0; i < ary.length; i++) {
                tmp = tmp^ary[i];//依次进行异或运算
            }
            return tmp;
        }

    问题四(问题三的扩展):

    一个无序数组里有若干个正整数,若其中有两个整数出现了奇数次,其余整数都出现了偶数次,找出这个出现奇数次的两个整数并返回其和。

    例如,给定无序数组:

    int[] ary = {2,3,6,3,4,9,4,2,1,6};

    出现了奇数次的两个整数为:9和1,返回其和10。

    分析:

    假设出现了奇数次的两个整数为A和B;

    一个中心思想就是将A和B分开到不同的两个组,此时的情况回归到问题三的情形。分别对这两个组中的元素依次做异或运算,将会分别得到A和B。

    现在的问题是怎么来分组?

    按照问题三的思路:队原数组ary依次进行异或操作后得到的结果必然等价于A^B的结果,同时注意到A与B不相等,因此A^B的结果必不为0。

    既然A^B不为零,则其二进制表示形式中必然存在1,就是由于这个1的存在,可以区分A和B,因为只有A和B在这个位的值不相同时,其异或结果的对应位才能为1。

    选取A^B结果的二进制表示形式中任何一个1(比如选择最右边的1)作为分组条件,在遍历ary数组时,首先进行分组判断,分组后在各自做异或运算即可。

    算法实现:

        public static int find(int[] ary){
            int tmp = 0,mask;
            int targetOne = 0,targetTwo = 0;
            for(int ele:ary){
                tmp = tmp^ele;
            }
            int pos = getFirstBit1(tmp);//以tmp的最右边变的1作为分组条件
            mask = 1<<(pos-1);//这里的mask实际上就是一个比特掩码,只有pos位为1 ,其余位均为0
            for (int i = 0; i < ary.length; i++) {
                if ((ary[i]&mask) ==  mask) {//说明ary[i]的pos位为1
                    targetOne = targetOne^ary[i];
                }else {//说明ary[i]的pos位为0
                    targetTwo = targetTwo^ary[i];
                }
            }
            return targetOne+targetTwo;
        }

    转载注明原文地址:http://www.cnblogs.com/qcblog/p/7689800.html 

  • 相关阅读:
    1295: [SCOI2009]最长距离
    [vijos p1028] 魔族密码
    HJ浇花
    1060: [ZJOI2007]时态同步
    1816: [Cqoi2010]扑克牌
    1800: [Ahoi2009]fly 飞行棋
    4300: 绝世好题
    1237: [SCOI2008]配对
    1801: [Ahoi2009]chess 中国象棋
    1189: [HNOI2007]紧急疏散evacuate
  • 原文地址:https://www.cnblogs.com/qcblog/p/7689800.html
Copyright © 2020-2023  润新知