窺探算法之美妙——統計整數二進制中1的個數


原文發表在我的博客主頁,轉載請注明出處

前言

我一直是一個比較喜歡算法的人,覺得算法真的是相當美妙和神奇!!!趁春節有時間看看算法書,體會思想和技術沉淀下來的美妙,今天看到了統計二進制中1的個數這個原本很簡單的題目,之前也看過,不過這次看書加深入思考之后發現里面的水還是很深的,特別是用python的程序猿更應該明白,閑話少說,開始正題。

題目

實現一個函數,輸入一個整數,輸出該數二進制表示中1的個數

題目分析

拿到這個題目,所有人肯定立馬有了思路,幾個關鍵詞立馬在腦海中顯現:“循環”,“右移”,“與”。然后很快寫出了函數,隨便輸入幾個數驗證,沒問題。。。但是這個題目需要注意的首先是整數,包括正整數和負整數,其次在python中,數據位數是一個比較模糊的概念,在程序中基本不存在,因為越位之后他會自動將int轉為為long類型,所以對python程序員來說,需要提前搞明白整數的位數,或者在python語言中調用C語言,下面來列舉其中的集中解法。

技巧:python中的左移和右移與其他C/C++等的定義和結果都是不一樣的,大家可以自行做實驗,python中的定義:右移n位定義為除以pow(2,n),左移n位定義為乘以pow(2,n),而且沒有溢出(移除的int會升級為long類型)

解法

上面題目分析的時候說過每個人看見題目心中都會涌現出最naive但是不對的解法(不論用什么語言):

def count(num):
    cnt = 0
    while num:
        if num & 1 == 1:
            cnt += 1
        num = num >> 1
    return cnt

為什么說他不對呢,大家可以輸入一個負整數看看,程序會陷入死循環,因為在其他語言中,負數在計算機中是用補碼表示的,最高位是1,在右移的過程中,高位都是用1來填補的,所以while num這個條件一直為真;在python中,根據右移的定義就可以自行推斷出來。既然現在右移num不行,那我們可以左移1,在32的整數中,最多左移32位,1就會變為零,所以這可以作為判斷條件,這在C語言中可以寫出和上面類似的代碼,但是在python中,我們一起可以左移下去(到虛擬內存大小的位數),所以這里我用到了python中的庫ctypes,在python中使用C語言,代碼如下:

from ctypes import *
def count(num):
    cnt = 0
    flag = 1
    while c_int(flag).value:
        if c_int(num & flag).value:
            cnt += 1
        flag = flag << 1
    return cnt

上面的代碼不論輸入正數負數還是零,都可以得到正確的答案,但是對於所有的整數都需要循環32次才能得到結果,繼續改進。看一個簡單的例子,整數12的二進制表示為1100,將其減一變為1011,將得到的結果和原樹進行按位與,得到1000,所以發現規律沒有?把一個整數減去1之后再和原來的整數做按位與,得到的結果相當於是把整數的二進制表示中最右邊的一個1變成0,按照這個規律進行遍歷,則函數的循環次數為二進制中一的個數次。代碼如下:

from ctypes import *
def count(num):
    cnt = 0
    while c_int(num).value:
        cnt += 1
        num = (num -1) & num
    return cnt

技巧:把一個整數減去1之后再和原來的整數做按位與,得到的結果相當於是把整數的二進制表示中最右邊的一個1變成0

以上都是各種語言通用的方法,只不過是以python作為例子而已,那么在python中語言中可以利用他的庫函數很容易的解決這個問題,代碼如下:

def num_of_one(num):
    '''
    count the num of "one" in num n
    bin():convert the num to binary string
    :param num: num num
    :return: the num of "one" in num
    '''
    if num >= 0:
        nbin = bin(num)
        return nbin.count('1')
    else:
        num = abs(num)
        nbin = bin(num-1)
        return 32 - nbin.count('1')

上面的代碼是為了更加詳細的區分正數和負數,當然利用python的一些特性,簡化代碼如下:

def num_of_one(num):
    nbin = bin(n & 0xffffffff)
    return nbin.count('1')

技巧:對於二進制來說,先減一后取反和先去反后加一結果一樣

博主John Rambo在我博客下面提供了另外一種方法,這個方法也是十分巧妙,先用一個列表存儲下來0到15的二進制中1的個數,在計算的時候每四位進行查表,代碼如下:

#Add up the number of 1 bits in every 4 bits.
#number of 1 bits in 0x0 to 0xF.
counts = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4]

def num_of_one(num):
    result = 0
    for i in range(0,32,4):
        result += counts[num >> i & 0xf]
    return result

技巧:在python中,負數和0xffffffff按位與之后變成一個無符號數,二進制表示為編碼形式

網友PowerShell免費軟件提供了一種方法,也比較巧妙:

$num=Read-Host -Prompt "請輸入一個整數(可以是負數)"
([System.Convert]::ToString($num,2)).replace("0","").length
#一句話的事兒

總結

本篇blog中的題目對於很多程序員來說一點也不陌生,但是要完美的解決這些問題還是需要一些思考的,同時技巧也是必不可少的,算法如此美妙,計算機的世界令人神往~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM