关于几个位运算的算法分析
问题一:
给定一个正整数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; }