最近看了這篇文章 的第一道題
題目本意是怎麼判斷這段代碼的輸出
int func(x)
{
int countx =0;
while(x)
{
countx ++;
x = x&(x-1);
}
return countx;
}
但深入一想,這不就是求任意整數的二進制表示裏有多少個1嗎?
要想解決上述問題,我能想到的最快方法是按位右移,累計每個移出的數,直到原數爲0
int func2(int x)
{
int countx =0;
while (x)
{
if (x & 1)
countx ++;
x = x >> 1;
}
return countx;
}
但是很明顯,我的算法在二進制表示含有較多中間0時,效率很低,而別人的算法就不存在該問題,而且沒有if分支判斷,避免CPU指令Cache miss,方便並行化
總之,該算法求解1個數,類似於輾轉相除法求最大公約數,都是基於某種數學原理,性能自然比愚蠢的枚舉不知道高到哪裏去了
該算法的原理,我晚上思考了下,覺得是對【判斷一個整數是否爲2的冪】算法的擴展應用
【判斷一個整數是否爲2的冪】算法
bool power_of_2(int x)
{
if (0 == (x & x-1))
return true;
else
return false;
}
該算法的思路是,如果整數i爲2的冪,則其二進制表示必然只有1位爲1,假設是第N位爲1,則0到N-1位必然爲0,且N+1到31位(假設是32位CPU)全零
令j = i-1,則j的二進制表示就是第N位爲0,0到N-1位全1,N+1到31位還是全零,
因爲減法需要從高位借位,對於2的冪整數,能借位的只有唯一的那個1,所以N位必然變爲0
又因爲減數爲1,所以0到N-1位必然全爲1
這樣i和j的【按位與】運算,必然等於0
將上述思路拓展
對於任意一個整數,都可以看成一系列冪的和,比如
520,就是512+8
1111,就是1024+64+16+4+2+1
當對該整數進行【減一】和【按位與】操作時,最低等級的冪先變爲零
然後再次執行【減一】和【按位與】操作,次低等級的冪也變爲零
以此類推,每次循環都將一個非零冪項清除(爲零的冪項不發生借位,所以自動忽略)
最後變爲全零,循環退出