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

问题一:

给定一个正整数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 

posted @ 2017-10-18 23:51  Qcer  阅读(371)  评论(0编辑  收藏  举报