Chinaunix首页 | 论坛 | 博客
  • 博客访问: 397177
  • 博文数量: 41
  • 博客积分: 696
  • 博客等级: 上士
  • 技术积分: 727
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-04 20:41
文章分类

全部博文(41)

文章存档

2012年(41)

分类: LINUX

2012-03-04 21:57:56

这是一个在面试中经常被问到的问题。这道题有很多的方法来解答,下面是我掌握的一些比较简单的方法。

         为什么叫普通法呢?我觉得当大家接触到这个问题的时候,第一时间想到的应该就是这种方法,应用位运算。具体的实现代码如下:

int Bit_Count(unsigned char data)
{
    unsigned
int count = 0 ; // 计数器
    while (data > 0)
    {
       
if(data & 1) // 当前位是1
            ++count ; // 计数器加1
        data >>= 1 ; // 移位
    }
   
return count ;
}

         当然在这里面,可以用不同的循环结构,像do…whilefor等结构都可以实现的,其效果相差并不是很大的。

   

         这种方法是在网上找到的。之所以叫快速法,因为其运算次数与输入data的大小无关,只与data1的个数有关。如果data中有k1,那个这个方法只需要循环k次就可以了。其原理是不断的清楚data的二进制表示中最右边的1,同时累加器累加,直至data减为0。具体的代码如下:

int Bit_Count2(unsigned char data)

{

         unsigned int count=0;

         while(data!=0)

         {

                        data &=(data-1)

                        count++;

         }

         return count;

}

         其中的循环结构也可以使用其他的循环结构,达到的效果相差很小。

         为什么data &=(data-1)能够清楚最右边的1呢?从二进制的角度来讲,data相当于在data-1的最低位加1。如:8(1000)= 7(0111)+ (0001),所以8 & 7 = (1000)&(0111)= 0(0000),清除了8最右边的1(其实就是最高位的1,因为8的二进制中只有一个1)

   

    位段法实现很简单,定义一个位段结构体。在“位运算”文件中已经对位段做了详细的介绍,这里不做详细的介绍了。直接上代码:

struct byte_data

{

     unsigned int a:1;

     unsigned int b:1;

     unsigned int c:1;

     unsigned int d:1;

     unsigned int e:1;

     unsigned int f:1;

     unsigned int g:1;

     unsigned int h:1;

};

 

unsigned int Bit_Count3(unsigned char data)

{

     struct byte_data *bd = (struct byte_data *)&data;

     return (bd->a+bd->b+bd->c+bd->d+bd->e+bd->f+bd->g+bd->h);

}

   

         这是一个网友推荐的算法,我也不知道为什么叫平行算法。上代码吧!

         int Bit_Count4(unsigned char data)
    {
        data = (data &
0x55) + ((data >> 1) & 0x55) ;
        data = (data &
0x33) + ((data >> 2) & 0x33) ;
        data = (data &
0x0f) + ((data >> 4) & 0x0f) ;

       data = (data & 0x00) + ((data >> 8) & 0x00) ;
      
return data ;
    }

    速度不一定是最快的,但是想法绝对是巧妙。原理:先将data写成二进制形式, 然后将二进制位与0x55相与,然后右移移位,再与0x55相与,然后在计算下一位,一次这样计算,右移,直至data0为止。如7,转换为二进制位0000 0111,先将它和0x55(0101 0101)相与,就可以确定第0位和第2位为1,即1的个数为2,接着右移一位,继续与0x55相与,继而就确定了第11,从第一个表达式就可以确定71的个数有3个。下面第二个表达式,和上面上面的分析一样,一位一位的计算data 数中1的个数。最后得出7的中1的个数为3。大概就是这么一个过程,相信这样应该会比较好理解一点。

       查表法有分为动态建表法和静态表两种方法。

       首先说一下动态建表法,动态建表法是因为表示在程序运行过程中运行时动态创建,所以速度上肯定会慢一点。下面先说一下动态建表法的填表原理,根据奇偶性来分析,对于任意的一个整数n:

       1、如果它是偶数,那个n的二进制中1的个数与n/2中的1的个数是相同的,比如4               2的二进制中1的个数都为1,64的二进制中1的个数都为2。为什么呢??因                 n是又n/2左移以为而来,而以为运算不会增加其中1的个数。

       2、如果n是奇数,那么n的二进制中1的个数是n/21的个数加1,比如7的二进制     中有三个1,7/2=3的二进制中有两个1。为什么呢??因为当n为奇数时,n相当于         n/2左移一位再加1

       下面具体看以一下动态建表的使用:

int Bit_Count5(unsigned int n)
{

    unsigned char BitsSetTable256[256] = {0} ; // 建表
    for (int i = 0; i < 256; i++)
    {
        BitsSetTable256[i] = (i &
1) + BitsSetTable256[i / 2]; // 初始化表
    }
    unsigned
int c = 0 ;
    unsigned
char * p = (unsigned char *) &n ; // 查表
    c = BitsSetTable256[p[
0]] + 
        BitsSetTable256[p[
1]] + 
        BitsSetTable256[p[
2]] + 
        BitsSetTable256[p[
3]];
   
return c ;
}

       上面程序是实习对一个32位的无符号数的求解方法。对于任意一个32位无符号整数,将其分割为4部分,每部分8bit,对于这四个部分分别求出1的个数,再累加起来即可。而8bit对应2^8=25601的组合方式,这也是为什么表的大小为256的原因。注意其中的类型转换:unsigned char * p = (unsigned char *) &n,先取到n的地址,然后转换为unsigned char *,这样一个unsigned int对应四个unsigned char,分别取出来计算即可。如:以87654321为例,先将他写成二进制形式8bit一组,共四组,这四组中1的个数分别为4,4,3,2,所以1的个数为13。如下等式:10000111 01100101 01000011 0010001=4+4+3+2=13.

    静态表法:静态表即静态的构建一个表。对于本例,我们要求char型数据的二进制数中1的个数,一个char型数据是8位,那我们建一个大小为16的表就可以了!下面是程序的实现:

int Bit_Count6(unsigned int n)
{
    static unsigned
int table[16] =
    {
       
0, 1, 1, 2,
       
1, 2, 2, 3,
       
1, 2, 2, 3,
       
2, 3, 3, 4
    } ;
    unsigned
int count = 0 ;
   
unsigned char *p=(unsigned char * )&data;

    count=table[p[0]];
   
return count ;
}

    对于一个包含16个元素的表table,table[i]i1的个数,这里的i[0-16]之间任意一个值。

       既然有这么多得方法,肯定有一种方法是最快的。但是这个最快的也不是绝对的,对每个数求1的执行速度是不一样的。下面是两种情况:

       5种方法求同一个数的速度比较(假设程序执行10000),共测了5次,比较详情见下表 1

       1

 

普通法(usec)

快速法(usec)

位段法(usec)

平行算法(usec)

查表法(usec)

1

84

79

83

71

89

2

83

79

83

71

89

3

81

79

84

71

89

4

80

80

84

72

89

5

81

80

83

71

89

       从上表可以马虎的看出一些方法的效率。当然这不是绝对的。但是这个表格应该可以说明一些问题。

      问题补充:

       在上面的时间效率的比较中,如过在建表的时候加上static关键字,在同样的条件下,没加static关键字执行的时间是加了static关键字执行时间的一半还要多一点。Static关键字在这里到底起了上面作用?效果会有如此之大的变化?

       问题解答(20111128):

       在这里涉及到变量编译后在内存中的存储位置。Static修饰的变量是静态变量,存放在静态区,且静态变量只需要在程序开始时进行一次初始化就可以了,不需要每次调用的时候都初始化,静态的变量在程序运行过程中长期有效的。局部变量在每次程序调用的时候都要进行初始化,在程序一次调用之后其内存空间就不存在了,下次再调用的时候又要重新申请内存空门。所以,在这里使用static修饰为静态的数组空间,在程序的执行过程中,其内存空间是一直存在的。

阅读(3225) | 评论(4) | 转发(1) |
给主人留下些什么吧!~~

bill_cao2012-03-06 11:53:31

☆彼岸★花开: 什么叫运算英语啊?.....
不好意思!!写错别字了!!

☆彼岸★花开2012-03-06 09:31:31

什么叫运算英语啊?

bill_cao2012-03-05 17:13:36

其实如果数据是16位的话,那程序还是得要变动一下的!!谢谢你的评论!!

GFree_Wind2012-03-05 12:34:22

不错。比较欣赏这个方法——不为了效率,关键是思路巧妙:
    int Bit_Count4(unsigned char data)
    {
        data = (data & 0x55) + ((data >> 1) & 0x55) ;
        data = (data & 0x33) + ((data >> 2) & 0x33) ;
        data = (data & 0x0f) + ((data >> 4) & 0x0f) ;
       data = (data & 0x00) + ((data >&g