二进制技巧

二进制位运算的技巧特别多,这里就做一份cheat sheet,希望能帮助到大家。不过过于简单的就不列举了

单行表达式

作用 表达式
把右边连续的1变成0 n & ( n + 1 )
把右边第一个1变成0 n & ( n - 1 )
把右起第一个0变成1 n | ( n + 1 )
把右起连续的0变成1 n | ( n - 1 )
取右边连续的1 n ^ ( n + 1 )
去掉右起第一个1的左边 n & -n 或 n & ( n ^ ( n - 1 ) )
高低位交换,前x位 ( n << x ) | ( x >> ( 32 - x ) )
有符号整数计算绝对值 ( n ^ (n >> 31) ) - (n >> 31)

多行的技巧

1. 两数交换不使用第三个变量

void swap(int* a, int* b)
{
    *a ^= *b;
    *b ^= *a;
    *a ^= *b;
}

2. 奇偶校验码

int check_bit(int n)
{
    n ^= n >> 1;
    n ^= n >> 2;
    n ^= n >> 4;
    n ^= n >> 8;
    n ^= n >> 16;
    return n & 1;
}

3. 计算二进制中的1的个数(popcount)

uint32_t method_1_uint32_popcount(uint32_t n)
{
	n = (n & 0x55555555) + ((n >> 1) & 0x55555555);
	n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
	n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f);
	n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff);
	n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff);
	return n;
}

uint32_t method_2_uint32_popcount(uint32_t n)
{
	n = n - ((n >> 1) & 0x55555555);
	n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
	n = (n + (n >> 4)) & 0x0f0f0f0f;
	n = n + (n >> 8);
	n = n + (n >> 16);
	return n & 0x3f;
}

uint32_t method_3_uint32_popcount(uint32_t n)
{
	n = n - ((n>>1) & 0x55555555);
	n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
	return ((n + (n >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
}

4. 计算下一组合

前提:我们使用一个整数的二进制位来代表对应集合上的元素有没有选中,从而来表达一个组合。当我们要遍历所有组合的时候,就可以用这个办法了

int next_combination(int k)
{
    int b = k & -k, t = (k + b);
    return (((t ^ k) >> 2) / b) | t;
}

5. n皇后问题位运算实现

uint64_t queen(uint32_t row, uint32_t lb, uint32_t rb)
{
    if (row == 0)
        return 1;
    uint64_t sum = 0;
    for (uint32_t r = row; r; r &= r - 1)
    {
        uint32_t p = r ^ (r & (r - 1));
        if ((p & lb) == 0 && (p & rb) == 0)
            sum += queen(row ^ p, (lb | p) << 1, (rb | p) >> 1);
    }
    return sum;
}

uint64_t nqueen(int n) // 返回值为棋盘大小n时解的个数
{
    return queen(((uint32_t)1 << n) - 1, 0, 0);
}

6. 古老的快速平方根倒数

1/sqrt(n),这段代码出自Quake-III Arena (雷神之锤3)源代码 /game/code/q_math.c,针对 IEEE 754 标准的单精度浮点格式

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck?
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

#ifndef Q3_VM
#ifdef __linux__
    assert( !isnan(y) ); // bk010122 - FPE?
#endif
#endif
	return y;
}

注:使用常数 0x5f375a86 可以使得结果误差更小,详见维基百科

7. 绘画Sierpinski三角

#include <stdio.h>
int main(void)
{
    const int n = (1 << 5);
    for (int i = 0; i < n; ++i)
    {
        for (int j = 0; j <= i; ++j)
           putchar( ( i & j ) == j ? '#' : ' ');
        printf("\n");
    }
    return 0;
}

输出结果

#
##
# #
####
#   #
##  ##
# # # #
########
#       #
##      ##
# #     # #
####    ####
#   #   #   #
##  ##  ##  ##
# # # # # # # #
################
#               #
##              ##
# #             # #
####            ####
#   #           #   #
##  ##          ##  ##
# # # #         # # # #
########        ########
#       #       #       #
##      ##      ##      ##
# #     # #     # #     # #
####    ####    ####    ####
#   #   #   #   #   #   #   #
##  ##  ##  ##  ##  ##  ##  ##
# # # # # # # # # # # # # # # #
################################
Avatar
抱抱熊

一个喜欢折腾和研究算法的大学生

Related

comments powered by Disqus