C/C++中__builtin_popcount()的使用及原理
__builtin_popcount()用于计算一个 32 位无符号整数有多少个位为1
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
#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] =
{
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
};
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。
C/C++中__builtin_popcount()的使用及原理的更多相关文章
- 学习重点:1、金典的设计模式在实际中应用2、JVM原理3、jui源代码
学习重点:1.金典的设计模式在实际中应用 2.JVM原理 3.jui源代码
- Spring中EmptyResultDataAccessException异常产生的原理及处理方法
Spring中EmptyResultDataAccessException异常产生的原理及处理方法 Spring中使用JdbcTemplate的queryForObject方法,当查不到数据时会抛出如 ...
- 基于接口回调详解JUC中Callable和FutureTask实现原理
Callable接口和FutureTask实现类,是JUC(Java Util Concurrent)包中很重要的两个技术实现,它们使获取多线程运行结果成为可能.它们底层的实现,就是基于接口回调技术. ...
- Spring框架中IoC(控制反转)的原理(转)
原文链接:Spring框架中IoC(控制反转)的原理 一.IoC的基础知识以及原理: 1.IoC理论的背景:在采用面向对象方法设计的软件系统中,底层实现都是由N个对象组成的,所有的对象通过彼此的合作, ...
- [置顶] Hadoop2.2.0中HDFS的高可用性实现原理
在Hadoop2.0.0之前,NameNode(NN)在HDFS集群中存在单点故障(single point of failure),每一个集群中存在一个NameNode,如果NN所在的机器出现了故障 ...
- Oracle中的SQL分页查询原理和方法详解
Oracle中的SQL分页查询原理和方法详解 分析得不错! http://blog.csdn.net/anxpp/article/details/51534006
- Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理
Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...
- Java中的HashMap的工作原理是什么?
问答题23 /120 Java中的HashMap的工作原理是什么? 参考答案 Java中的HashMap是以键值对(key-value)的形式存储元素的.HashMap需要一个hash函数,它使用ha ...
- Spring.Net是怎么在MVC中实现注入的(原理)
本文将介绍Spring.Net(不仅仅是Spring.Net,其实所有的IoC容器要向控制器中进行注入,原理都是差不多的)在MVC控制器中依赖注入的实现原理,本文并没有关于在MVC使用Spring怎么 ...
随机推荐
- http中的get和post(二)
博客园精华区有篇文章< GET 和 POST 有什么区别?及为什么网上的多数答案都是错的 >,文中和回复多是对以下两个问题进行了深究: 长度限制 Url 是否隐藏数据 在我看来这两者都不是 ...
- 【Zookeeper】源码分析之Leader选举(一)
一.前言 分析完了Zookeeper中的网络机制后,接着来分析Zookeeper中一个更为核心的模块,Leader选举. 二.总结框架图 对于Leader选举,其总体框架图如下图所示 说明: 选举的父 ...
- ArcGIS API for JavaScript 4.2学习笔记[0] AJS4.2概述、新特性、未来产品线计划与AJS笔记目录
放着好好的成熟的AJS 3.19不学,为什么要去碰乳臭未干的AJS 4.2? 4.2全线基础学习请点击[直达] 4.3及更高版本的补充学习请关注我的博客. ArcGIS API for JavaScr ...
- 【WebGL】《WebGL编程指南》读书笔记——第6章
一.前言 最近重感冒发烧,妈蛋好难受,请假了3天,驾校也没去,简直僵硬!今天继续WebGL的学习. 二.正文 A. GLSL支持两种数据值类型: 整数型(int)与浮点型( ...
- crm踩坑记(三)
React 如何同步更新state 由于setState方法是异步的,而通常很多时候在一个生命周期里更新state后需要在另一个生命周期里使用这个state. 下面介绍几个方法 // 1 this.s ...
- Mysql5.7.20 On Windows安装指导
安装环境 Windows版本:Windows10 64bit MySQL版本: MySQL5.7.20 配置过程 1.下载MySQL Community Server (下载链接) 根据自己操作系统需 ...
- 【简单理解】gulp和webpack的区别
Gulp和Webpack的基本区别: gulp可以进行js,html,css,img的压缩打包,是自动化构建工具,可以将多个js文件或是css压缩成一个文件,并且可以压缩为一行,以此来减少文件体积,加 ...
- vue2.0父子组件以及非父子组件如何通信
1.父组件传递数据给子组件 父组件数据如何传递给子组件呢?可以通过props属性来实现 父组件: <parent> <child :child-msg="msg" ...
- 安装两个JDK后配置环境变量没用?
在实际开发中,由于项目的需要,可能JDK的版本是不同的.比如我们前一个项目所需JDK版本是1.6的,项目完成后,下一个项目JDK版本又是需要1.7的,为了防止由于切换项目我们需要频繁的安装卸载JDK, ...
- Webpack 2 视频教程 015 - Webpack 2 中的文件压缩
原文发表于我的技术博客 这是我免费发布的高质量超清「Webpack 2 视频教程」. Webpack 作为目前前端开发必备的框架,Webpack 发布了 2.0 版本,此视频就是基于 2.0 的版本讲 ...