计算一个 32 位无符号整数有多少个位为 1

原创 2007年09月18日 02:03:00

引用:

Counting out the bits
    可以很容易的判断一个数是不是2的幂次:清除最低的1位(见上面)并且检查结果是不是0.尽管如此,有的时候需要直到有多少个被设置了,这就相对有点难度了。

    GCC有一个叫做__builtin_popcount的内建函数,它可以精确的计算1的个数。尽管如此,不同于__builtin_ctz,它并没有被翻译成一个硬件指令(至少在x86上不是)。相反的,它使用一张类似上面提到的基于表的方法来进行位搜索。这无疑很高效并且非常方便。

    其他语言的使用者没有这个选项(尽管他们可以重新实现这个算法)。如果一个数只有很少的1的位,另外一个方法是重复的获取最低的1位,并且清除它。

查看引用来源

为了在 VC 上实现 __builtin_popcount  (unsigned u) 的功能,自己写了两个函数,分别是 popcnt (unsigned u), popcount  (unsigned u) 。
前者是通过清除 u 最低的 bit 1 ,直至 u 为 0 ,每次都为计数器加 1 。时间复杂度为 O (m) , m 为 bit 1 的个数。
后者是使用二分法,比较巧妙,跟踪调试一下就知道原理了。时间复杂度为 O (lg N) , N 为位数。
不过最高效的还是使用查表的方式来计算。
但是需要弄一个很大的表,不然随着位数的增长,查表的速度还是比不上二分法的速度。例如 64 位整数,保存 所有 8 位整数的结果 (256 个)。查表需要操作 8 次,而二分法需要 6 次而已。

测试代码如下:

// 计算一个 32 位无符号整数有多少个位为 1
// By Rappizit@yahoo.com.cn

#include 
<iostream>
#include 
<ctime>
using namespace std;

unsigned popcnt (unsigned u)
{
    unsigned ret 
= 0;
    
while (u)
        {
        u 
= (u & (u - 1));    // 将 u 最右边的 1 清除
        ret ++;
        }
    
return ret;
}

unsigned popcount (unsigned u)
{
    u = (u & 0x55555555) + ((u >> 1) & 0x55555555);
    u = (u & 0x33333333) + ((u >> 2) & 0x33333333);
    u = (u & 0x0F0F0F0F) + ((u >> 4) & 0x0F0F0F0F);
    u = (u & 0x00FF00FF) + ((u >> 8) & 0x00FF00FF);
    u = (u & 0x0000FFFF) + ((u >> 16) & 0x0000FFFF);
    return u;
}


int main ()
{
     
// 先测试一下函数是否正常工作
    for (int i = 0; i <= 1000; i ++)
        {
        
int k = popcnt (i);
        cout 
<< k << " ";
        }
    cout 
<< endl;
    
    
for (int i = 0; i <= 1000; i ++)
        {
        
int k = popcount (i);
        cout 
<< k << " ";
        }
    cout 
<< endl;
    
    
// 比较速度     
    int test;
    test 
= (1U << 31- 1;
    
//test = 1000000;
    clock_t start, finish;

    start 
= clock ();
    
for (int i = 0; i < test; i ++)
        {
        popcnt (i);
        }
    finish 
= clock ();
    cout 
<< (double)(finish - start) / (double)(CLOCKS_PER_SEC) << " s" << endl;

    start 
= clock ();
    
for (int i = 0; i < test; i ++)
        {
        popcount (i);
        }
    finish 
= clock ();
    cout 
<< (double)(finish - start) / (double)(CLOCKS_PER_SEC) << " s" << endl;

    start 
= clock ();
    
for (int i = 0; i < test; i ++)
        {
        
//__builtin_popcount (i);    // G++内建函数, Dev-C++ 可支持
        }
    finish 
= clock ();
    cout 
<< (double)(finish - start) / (double)(CLOCKS_PER_SEC) << " s" << endl;
    
    system (
"pause");

    
return 0;
}


/*
使用 VC 2005 ,在 test = (1U << 31) - 1 时, release 状态是 0 s 出结果的,但是 debug 状态就很慢, 换 test = 1000000 试试。
使用其他 IDE, 即使是 release 状态下,速度也不理想。但是可以测得 __builtin_popcount (通过查表来计算)比较快。
*/

说到底,这个函数到底有什么实际用处呢?当然有了,使用一个二进制数字表示一个集合的时候,枚举一个组合(子集),需要判断这个数字里面的 1 的个数是不是和子集的大小相等。这种方法通常是属于暴力法,如果不是使用二进制数字表示集合,很可能就计算超时了。
下面有个例子(sicily 1158),期待更好的解法,但是暴力法的效果还不错^_^而且实现简单。。

====================================帅气的分割线====================================

Pick numbers

Total Submit : 371    Accepted Submit : 135

Problem
Given a matrix of size M*N, the elements of which are integer numbers from -10 to 10. You task is to go from
the top-left corner (1, 1) to the bottom-right corner (M, N). You can only move right or down, and you can
not go out of the matrix. The number in the grid you passed must be picked. The sum of numbers you picked must
be positive and as minimal as possible.
Input

The input contains several test cases.

      The first line of each test case are two integer numbers M, N (2<=M<=10, 2<=N<=10), indicating the number
      of rows and columns of the matrix. The following M lines, each contains N integer numbers.
Output
For each test case, output the sum of numbers you picked on a single line. If you can't get a positive sum,
output -1.

Sample input
2 2
0 2
1 0
3 3
0 0 0
0 0 0
0 0 0

Sample output
1
-1

Problem Source
2006 Algorithm Course Examination

====================================无聊的分割线====================================

分析:
用一个二进制数字 path 表示所走的路径,从低位开始,如果某位为 1 ,表示向下走,否则向右走。
path 中的位 1 必须有 m - 1 个,使用 G++ 内建函数 __builtin_popcount (path) 来计算。
path 的起始值为 first = (1 << (m - 1)) - 1 ,表示先向下走 m - 1 步。
path 的终止值为 last = first << (n - 1) ,表示先向右走 n - 1 步。
从 first 到 last ,枚举所有可行的 path ,然后计算对应的 sum ,目标是找一个最小的 sum > 0 ,
如果找不到,sum = -1 。
注意此题不能用动态规划来求最小值,因为该最小值可能是负数
枚举的次数是 2m + n - 2  - 2n - 1, 当 m,  n <= 10 时为 512 。

#include <cstdio>
using namespace std;

const int M = 10;
const int N = 10;
int d [M] [N];
int m, n;

void f ()
{
    
for (int i = 0; i < m; i ++)
        
{
        
for (int j = 0; j < n; j ++)
            
{
            scanf (
"%d"&d [i] [j]);
            }

        }

    
int first = (1 << (m - 1)) - 1;
    
int last = first << (n - 1);
    
int cnt = m - 1;
    
int MASK = 1 << (m + n - 2);
    
int max = (1U << 31- 1;

    
for (int path = first; path <= last; path ++)
        
{
        
if (__builtin_popcount (path) == cnt)
            
{
            
int sum = d [0] [0];
            
for (int i = 0, j = 0, mask = 1; mask < MASK; mask <<= 1)
                
{
                
if (mask & path)
                    
{
                    i 
++;                    
                    }
 else    {
                        j 
++;
                        }

                sum 
+= d [i] [j];
                }

            
if (sum > 0 && sum < max)
                
{
                max 
= sum;
                }

            }

        }

    printf (
"%d ", (max == ((1U << 31- 1? -1 : max));
}


int main ()
{
    
while (scanf ("%d%d"&m, &n) != EOF)
        
{
        f ();
        }

    
return 0;
}



提交结果:
Run ID      User Name      Problem      Language      Status      Run Time      Run Memory      Submit Time
84848       rappizit     1158     C++     Accepted     0.01 sec     260 KB     2007-09-17 13:38:57

===================================遗忘的分割线=====================================

以下内容为新添加的:
经测试,该函数的速度是 G++ 的一半,所以有理由认为 G++ 保存的表的大小是 65536 的(或者使用汇编)。但是不可能在自己的代码里面加上那么一大段数据吧?权衡一下,表的大小设为 256 是适当的,可以在 VC 里面使用以下代码。

char poptable [256= 
{
0112122312232334,
1223233423343445,
1223233423343445,
2334344534454556,
1223233423343445,
2334344534454556,
2334344534454556,
3445455645565667,
1223233423343445,
2334344534454556,
2334344534454556,
3445455645565667,
2334344534454556,
3445455645565667,
3445455645565667,
4556566756676778
};
unsigned __builtin_popcount (unsigned u)
{
    
return poptable [u & 0xFF+ poptable [(u >> 8& 0xFF+ poptable [(u >> 16& 0xFF+ poptable [(u >> 24& 0xFF];
}
=====================20080725添加========================
unsigned popcount (unsigned u)
{
    u = (u & 0x55555555) + ((u >> 1) & 0x55555555);
    u = (u & 0x33333333) + ((u >> 2) & 0x33333333);
    u = (u & 0x0F0F0F0F) + ((u >> 4) & 0x0F0F0F0F);
    u = (u & 0x00FF00FF) + ((u >> 8) & 0x00FF00FF);
    u = (u & 0x0000FFFF) + ((u >> 16) & 0x0000FFFF);
    return u;
}

这个二分法的原理:
用8位二进制数来做示范好了,例如 u = 10110011。
10110011
00010001    //每两位取1位,即取偶数位, u & 01010101
01010001    //取奇数位并右移一位, (u >> 1) & 01010101
---------------
01100010    //上面两数相加,赋值给u,注意每两列相加的结果不会进位到第三列
00100010    //每四位取低两位, u & 00110011
00010000    //每四位取高两位并右移两位, (u >> 2) & 00110011
---------------
00110010    //上面两数相加,赋值给u
00000010    //每八位取低四位, u & 00001111
00000011    //每八位取高四位并右移四位,(u >> 4) & 00001111
---------------
00000101    //上面两数相加,赋值给u
最终结果 u = 5。

相关文章推荐

统计从1到n中1的个数

问题: 给定一个十进制正整数N,写下从1开始,到N的所有整数,然后数一下其中出现的所有“1”的个数。 例如: N= 2,写下1,2。这样只出现了1个“1”。 N= 12,我们会写下1, 2, 3,...

计算一个数的二进制位中1的个数的方法总结

方法一、通过移位分别判断各个位 int bit_count(unsigned int n) { int count;         for(count=0;n;n>>=1) { count+=n&1...

求一个整数的二进制中1的个数

题目:输入一个整数,求该整数的二进制表达中有多少个1。例如输入10,由于其二进制表示为1010,有两个1,因此输出2。分析:这是一道很基本的考查位运算的面试题。包括微软在内的很多公司都曾采用过这道题。...

_builtin_popcount()计算二进制中多少个1

计算一个 32 位无符号整数有多少个位为1Counting out the bits     可以很容易的判断一个数是不是2的幂次:清除最低的1位(见上面)并且检查结果是不是0.尽管如此,有的...

视频自动生成字幕、ASR技术与在线教育

2009 年 11 月 Google 宣布自家的语音识别技术为 Youtube 提供支持,使得英文的 Youtube 视频可以在上传时直接将其中的语音转化为字幕,2012 年 Google 宣布支持共...

Linux技巧:多核下绑定硬件进程到不同CPU

硬件中断发生频繁,是件很消耗 CPU 资源的事情,在多核 CPU 条件下如果有办法把大量硬件中断分配给不同的 CPU (core) 处理显然能很好的平衡性能。现在的服务器上动不动就是多 CPU 多核、...

将一个无符号整数的bit位逆序

http://www.leetcode.com/2011/08/reverse-bits.html There are several methods of reversing the bi...

求32位无符号整数中比特为1的二进制位数

测试环境:       1、 Intel(R) core(TM) I5-2410M CPU@2.30GHz 2.30GHz 处理器;       2、操作系统:Win7;       3、开发环...

使用移位操作符实现无符号整数位反转

>> 左移 各二进位全部左移若干位,高位丢弃,低位补0 >>  右移 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)。 ...

按位反转无符号整数问题bitoperate函数(转)

方法一:(最最容易想到的办法) unsigned int ReverseBitsInWord00(unsigned int Num) {     unsigned int ret ...
  • hgdwlsh
  • hgdwlsh
  • 2014年03月17日 21:11
  • 459
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:计算一个 32 位无符号整数有多少个位为 1
举报原因:
原因补充:

(最多只允许输入30个字)