zoukankan      html  css  js  c++  java
  • 窥探算法之美妙——统计整数二进制中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中的题目对于很多程序员来说一点也不陌生,但是要完美的解决这些问题还是需要一些思考的,同时技巧也是必不可少的,算法如此美妙,计算机的世界令人神往~

  • 相关阅读:
    POJ3159 Candies —— 差分约束 spfa
    POJ1511 Invitation Cards —— 最短路spfa
    POJ1860 Currency Exchange —— spfa求正环
    POJ3259 Wormholes —— spfa求负环
    POJ3660 Cow Contest —— Floyd 传递闭包
    POJ3268 Silver Cow Party —— 最短路
    POJ1797 Heavy Transportation —— 最短路变形
    POJ2253 Frogger —— 最短路变形
    POJ1759 Garland —— 二分
    POJ3685 Matrix —— 二分
  • 原文地址:https://www.cnblogs.com/cotyb/p/5186461.html
Copyright © 2011-2022 走看看