题目:对于一个字节的无符号整形变量,求其二进制表示中“1”的个数,要求算法的执行效率尽可能高。
刚看到这个题,首先想到的就是对二进制数进行向移位操作,看末位是不是1。这个即为书中给出的第二种方案:
/* 方案2,移位操作*/
int Count(BYTE v){
int num = 0;
while(v){
num += v & 0x01; //与000000001进行与操作,结果为1说明末位为1,num+1
v >>= 1; //v向右移1位,直到变为全0
}
}
该方案与方案1的核心思想差不多,用BYTE v去除以2,就是向右移1位,余数即为移出的位是0还是1,如果移出是1,则num+1。这种移位思想,算法复杂度为O(log2v),即v的二进制表示的位数,如果v为10000000,就比较糟糕了,要算8次才能算完。如果复杂度只与v中的1的数量相关,就会好很多。
想到之前做的题目中http://blog.csdn.net/kimili1987/article/details/8034466,有一个是怎么判断一个数是2^n,事实上,这个数就是只有一个1。方法是,对于数v,判断v & (v-1)是否为0,为0的话则证明有一个1。据此进行扩展,如果有多个1的话,可以逐次使用该方法进行判断,每次会削减掉v中最高位的1,直至最后v为0,判断的次数则为总的1的数量。
/*方案3,判断1的数量*/
int Count(BYTE v){
int num=0;
while(v){
v &= (v-1); //每次与v-1做与操作,削减掉最高位的1
num ++;
}
}
方案5的思路很有意思,因为只是一个BYTE的整数,范围为0~255,索性预存一个256个数的数组countTable[256],保存每个数对应的1的个数会是多少,例如countTable[8]=3,然后直接返回countTable[v]就可以得到1的个数了。时间复杂度O(1),空间复杂度是最高的O(256)。这是一个典型的空间换时间的算法,实现虽简单,但是其给出的思想很重要:可以通过牺牲一定的空间,来换取高的时间效率!
扩展问题:
扩展问题1:如果变量v变为32位DWORD,那么使用哪种方案?
对于扩展问题1,当v变为DWORD的时候,方法5可能不太合适了,需要空间过于庞大。而方案3依旧非常适合。这一点也说明了一个算法对于输入数据的适应性问题。根据算法复杂度对哪些变量敏感,不同的算法对输入数据的适应性有所不同。
扩展问题2:给定两个正整数A,B。判断A和B中有多少位是不同的?
1)最直接的方式,A和B每一位都做异或操作,如果为1,num+1,复杂度O(较大位数{A,B})。
2)对A和B做异或,之后再数其中1的数量,Count(A ^ B),复杂度O(1+A和B位数不同的数量)。
这是编程之美书中指出的一个童鞋给出的更好的一些解法