最近在图书馆看到本神书《算法心得:高效算法的奥秘》,主要讲解计算机算法的,强调编译器优化和计算机体系结构设计的。虽然看的不大懂,但还是给自己增长了见识和知识。少许整理些自己感兴趣的算法,以备后续温故知新。
1. 操作最右边的位元
a. 将字组中值为1且最靠右的位元置0,如果不存在值为1的位元,则全部结果为0(例如 0101 1110 => 0101 1100):
x & (x-1)
这个操作可以判断无符号证书是不是2的幂或者0.
b. 将字组中值为0且最靠右的位元置1,如果不存在值为0的位元,则全部结果的每一位为1(例如 1010 0111 => 1010 1111):
x | (x+1)
这个操作可以判断无符号证书是不是2的幂或者0.
c. 将字组尾部的1全部变成0,如果尾部没有1,则不变(例如 1010 0111 => 1010 0000)
x & (x+1)
这个操作可以判断无符号证书是不是2^n-1或者0.
d. 将字组尾部的0全部变成1,如果尾部没有0,则不变(例如 1010 1000 => 1010 1111)
x | (x-1)
e. 将字组中值为0且最靠右的位元置1,其余位置0,如果不存在值为0的位元,则全部结果的每一位为0(例如 1010 0111 => 0000 1000):
~x & (x+1)
f. 将字组中值为1且最靠右的位元置0,其余位置1,如果不存在值为1的位元,则全部结果的每一位为1(例如 1010 1000 => 1111 0111):
~x | (x-1)
g. 将字尾部所有0的位元变成1,其余位置0,如果不存在值为0的位元,则全部结果的每一位为0(例如 0101 1000 => 0000 0111):
~x & (x-1) 或 ~(x | -x)
h. 将字尾部所有1的位元变成0,其余位置1,如果不存在值为1的位元,则全部结果的每一位为1(例如 1010 0111 => 1111 1000):
~x | (x+1)
i. 将字组中值为1且最靠右的位元保留,其余位置0,如果不存在值为1的位元,则全部结果的每一位为0(例如 0101 1000 => 0000 1000):
x & (-x)
j. 将字组中值为1且最靠右的位元,以及其右方所有值为0的位元都置为1,其余位置0,如果不存在值为1的位元,则全部结果的每一位为1,而当x尾部没有值为0的位元是,运算结果是1(例如 0101 1000 => 0000 1111):
x ^ (x-1)
k. 将字组中值为0且最靠右的位元,以及其右方所有值为1的位元都置为1,其余位置0,如果不存在值为0的位元,则全部结果的每一位为1,而当x尾部没有值为0的位元是,运算结果是1(例如 0101 0111 => 0000 1111):
x ^ (x+1)
l. 将字组右侧连续出现且值为1的位元置为0,(例如 0101 1100 => 0100 0000):
( ( x | (x-1)) +1 ) & x
m. 将字组右侧连续出现且值为0的位元置为1,(例如 0100 0111 => 0111 1111):
( ( x & (x+1)) -1 ) | x
根据上面的知识规律,下面这道题就可以这样做:
例如:求给定数 x 往上增加最近的2^n 的值。如给定5时,求出8,给定4时,求出4.
unsigned int fun1(unsigned int x) { x = x<<1; while ( x & (x-1) ) x = x & (x-1); return x; } unsigned int fun2(unsigned int x) { while ( ( ( x | (x-1) ) +1 ) & x ) x = ( ( x | (x-1) ) +1 ) & x ; return x<<1; }
2. 德摩根定律
~(x&y) = ~x | ~y
~(x|y) = ~x & ~y
~(x+y) = ~x - y
~(x-y) = ~x + y
~-x = x-1
3. 位操作新式用法
给定一个数x,然后找出下一个比它大的数字y,该数字y中值为1的位元数与x中的相同。这题可以用在找出元素个数为某一定值的全部子集的算法中。
思路:给定一个表示子集位串的字组x,首先要找到连续出现在x右侧且值为1的一组位元,然后将该值“加1”,再把原来后面跟着的哪些0 补上。举例来说,如果带计算的位串是xxx0 1111 0000,那么结果就应该是xxx1 0000 0111,其中xxx这三个位元值不限。该算法首先定义 s = x&-x,算出s == 0000 0001 0000,这样就找到x中最小的那个1。然后把它与x相加,把两书只和xxx1
0000 0000存放在r 中。此时结果中的1个位元已经计算好了,想求出其他位元,还需要把位串中剩下的n-1个“1”移到右侧。要向移动到右侧,首次要计算r和x的异或,在本例中就是0001 1111 0000.
上面那个值中“1”的个数太多了,而且没有靠右对齐。为了解决此问题,要将它与s相除,这样就可以把哪些“1”靠右对齐了,除之前还要先向右移动2位,以便丢弃那2个多余的位元。此结果与r取或,就得到最终答案了。
用C语言实现就是
unsigned fun3(insigned x) { unsigned smallest, ripple, ones; //x=xxx0 1111 0000 smallest = x & -x; // xxx0 0001 0000 ripple = x +smallest; // xxx1 0000 0000 ones = x ^ ripple; // xxx1 1111 0000 ones = ( ones>>2 )/smallest; // xxx0 0000 0111 return ripple | ones; // xxx1 0000 0111 }
4. 结合逻辑操作的加减运算
-x = ~x+1
-~x = x+1
~-x = x-1
x^y = ( x|y ) - ( x&y )
x + y = ( x^y ) + 2( x&y )
= ( x|y ) + ( x&y )
x - y = ( x^y ) - 2( ~x&y )
= ( x&~y ) - ( ~x&y )
其中 x + y = ( x^y ) + 2( x&y ) 是先对两数做不进位加法x^y,然后再补上进位。
x - y = ( x^y ) - 2( ~x&y ) 是先对两数进行不进位剪发x^y,然后再把结尾的位从结果中减去。
5. 与常数相乘
要将x乘以13(二进制1101),可执行下述操作
t1 = x<<2;
t2 = x<<3;
r = x + t1 + t2;