重新开张 && two pieces of fascinating code

写下这个题目,我的脸皮红得在发烧。我知道我很懒,但是远远低于Blog更新的平均周期仍然使我感到羞愧。然后是我在文章统计里看到了这个:

文章标题页面/聚合访问
英文版WindowsXP下中文名Chm文件无法打开的解决方法1662 /15
呵呵,发现了一个破解NTFS加密文件夹的另类方法4273 /14

两篇奇技淫巧文章占去了我一半的访问量,让我十分怀疑自己是否退化到了高中时的水平(那个时候最为痴迷的杂志是《电脑爱好者》)。因此,我想我应该写点符合一名CS的学生身份的东西。最近看了些面试题,有些东西还值得记录下来,如果我没有再次半途而废,这个系列会继续下去~~

--

先贴两段代码:

unsigned  int  bit_reverse(unsigned  int  n)
{
       n 
= ((n >> 1& 0x55555555| ((n << 1& 0xaaaaaaaa);
       n 
= ((n >> 2& 0x33333333| ((n << 2& 0xcccccccc);
       n 
= ((n >> 4& 0x0f0f0f0f| ((n << 4& 0xf0f0f0f0);
       n 
= ((n >> 8& 0x00ff00ff| ((n << 8& 0xff00ff00);
       n 
= ((n >> 16& 0x0000ffff| ((n << 16& 0xffff0000);
 
       
return n;
}

 
int  count_ones( int  n)
{
       n 
= (n & 0x55555555+ ((n & 0xaaaaaaaa>> 1);
       n 
= (n & 0x33333333+ ((n & 0xcccccccc>> 2);
       n 
= (n & 0x0f0f0f0f+ ((n & 0xf0f0f0f0>> 4);
       n 
= (n & 0x00ff00ff+ ((n & 0xff00ff00>> 8);
       n 
= (n & 0x0000ffff+ ((n & 0xffff0000>> 16);
 
       
return n;
}

 第一次看到是在C PUZZLES, Some interesting C problems,完全不懂它是在做什么,从函数名称上推断,bit_reverse是将一个unsigned int的二进制位反序排列,count_ones是计算一个unsigned int中二进制位1出现的次数。运行一下,的确结果是对的!今天在这里看到一贴回复,又演算了一下,方才明白其中的奥妙。

首先看bit_reverse的一个使用8bit的位串的简化示例,假定从左向右为每个位编号为1 2 3 4 5 6 7 8,

第一步:

((n >> 1) & 0x55) 即 (n >> 1) & 0101,0101),将位串上奇数位右移到偶数位上,得到0 1 0 3 0 5 0 7

((n << 1) & 0xaa) 即 ((n << 1) & 1010,1010),将位串上偶数位左移到奇数位上,得到2 0 4 0 6 0 8 0

两者相与,结果即得到奇数位和偶数位相互交换的位串:1 2 3 4 5 6 7 8 => 2 1 4 3 6 5 8 7

第二步:

分别将相邻的4位编为一组,左侧与右侧相互交换:2 1 4 3 6 5 8 7 => 4 3 2 1 8 7 6 5

第三步:

分别将相邻的8位编为一组,左侧与右侧相互交换:4 3 2 1 8 7 6 5 => 8 7 6 5 4 3 2 1.  ~End~

这段代码像是杂耍一样就完成工作了,呵呵。事实上,在Programming Pearls里Chapter 2里提到的字符串旋转的算法与此有异曲同工之妙。效率O(logn),比起阳春的O(n)算法要高得多。

再来看count_ones,依然使用8bit的位串的简化示例,这次假定位串值位1001,0110

第一步,将相邻的2位编为一组:

(n & 0x55) 即 (n & 0101,0101) = 0001,0100:计算出每一组中左侧位上1的个数,并保存在结果中该组对应的位置上;

((n & 0xaa) >> 1) 即 (n & 1010,1010) = 0100,0001:计算出每一组中右侧位上1的个数,并保存在结果中该组对应的位置上;

两者相加,得到该组中1的个数,并保存在结果中该组对应的位置上;

第二步,将相邻的4位编为一组,根据第一步中的算法,计算每组中1的个数,并保存在结果中该组对应的位置上:

(n & 0x33) + ((n & 0xcc) >> 2) 即 0001,0001 + 0001,0001 即 0010,0010;

第三步,将相邻的8位编为一组,根据第一步中的算法,计算每组中1的个数,并保存在结果中该组对应的位置上:

(n & 0x0f) + ((n & 0xf0) >> 4) 即 0000,0010 + 0000,0010 即 0000,0100(二进制4).  ~End~

如果上个算法像是杂耍,那这个算法就像是魔术了:-) (当然还有更加惊为天人鬼斧神工的,看Quake3中的快速平方根算法)。对于一般情况,这个算法的复杂度也是O(logn),相对于阳春的O(n)算法当然要高。但是在某些情况下,如果只有很少的位为1(考虑常见的标志位),那么还有下面的代码:  

int  CountBits(unsigned  int  x)
{
    
int count=0;
    
while(x)
    
{
        count
++;
        x 
= x&(x-1);
    }

    
return count;
}

 How does it work? Try to figure it out:-)

这只是关于位运算一点小trick,茶余饭后娱乐一下,搞嵌入式的大牛们就不必费心看过来了。有自虐倾向者请继续看向Algorithms for Programmers里的Chapter 1.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值