《算法心得》一点整理
近期在图书馆看到本神书《算法心得:高效算法的奥秘》。主要解说计算机算法的,强调编译器优化和计算机体系结构设计的。尽管看的不大懂。但还是给自己增长了见识和知识。少许整理些自己感兴趣的算法,以备兴许温故知新。
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-13. 位操作新式使用方法
给定一个数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;