Fork me on GitHub

算法-10 | 位运算

 

1. 二进制计数法

机器里的数字表示方式和存储格式就是 二进制

 十进制 < — > 二进制 : 如何转换?

  4(d): 0100  (0*2^0 + 0*2^1 + 1*2^2)
  8(d): 01000
  5(d): 0101
  6(d): 0110

2. 二进制的位操作-位运算

位运算符: 

左移(分为逻辑左移和计算机左移)

下图是以计算机左移,以4位为例,现在计算机大多都是64位。

  常见的二进制位操作包括向左移位和向右移位的移位操作,以及“或”“与”“异或”的逻辑操作。

2.1 向左移位

    二进制 110101 向左移一位,就是在末尾添加一位 0,因此 110101 就变成了 1101010。( 110101 <<  1101010 )

数字溢出,就是二进制数的位数超过了系统所指定的位数。目前主流的系统都支持至少 32 位的整型数字。如果进行左移操作的二进制已经超出了 32 位,左移后数字就会溢出,需要将溢出的位数去除。

二进制左移一位,其实就是将数字翻倍。

2.2 向右移位

二进制 110101 向右移一位,就是去除末尾的那一位,因此 110101 就变成了 11010(最前面的 0 可以省略) (110101 >> 11010)

二进制右移一位,就是将数字除以 2 并求整数商的操作。

移位 1 次相当于乘以或除以 2,而移位 3 次就相当于乘以或除以 8(即 2 的 3 次方)

  左移位是 <<,那右移位为什么是 >>> 而不是 >> 呢?  >> 也是右移操作。之所以有这两种表达方式,根本原因是 Java 的二进制数值中最高一位是符号位。

    当符号位为 0 时,表示该数值为正数;

    当符号位为 1 时,表示该数值为负数。

 

例如数字 53 的二进制为 110101,从右往左数的第 32 位是 0,表示该数是正数,通常将其省略。

       数字是 -53 呢,那么第 32 位就不是 0,而是 1。  那么这个时候向右移位,就会产生一个问题:对于符号位(特别是符号位为 1 的时候),我们是否也需要将其右移呢?

              因此,Java 里定义了两种右移,逻辑右移 >>> 算术右移 >> 

                  • 逻辑右移>>> 1 位,左边补 0 即可,不考虑符合位。
                  • 算术右移>> 保持符号位不变,除符号位之外的右移一位并补符号位 1。补的 1 仍然在符号位之后。

            

2.3 位的“或”  | 

 逻辑“或”的意思是,参与操作的位中只要有一个位是 1,那么最终结果就是 1,也就是“真”。如果我们将二进制 110101 和 100011 的每一位对齐,进行按位的“或”操作,就会得到 110111。

2.4 位的“与” &

 “与”的意思是,参与操作的位中必须全都是 1,那么最终结果才是 1(真),否则就为 0(假)。如果我们将二进制 110101 和 100011 的每一位对齐,进行按位的“与”操作,就会得到 100001。

2.5 位的“异或”XOR  ^ 

       相同为0不同为1, "不进位加法"

  位的“异或”逻辑“异或”和“或”有所不同,它具有排异性,也就是说如果参与操作的位相同,那么最终结果就为 0(假),否则为 1(真)。所以,如果要得到 1,参与操作的两个位必须不同,这就是此处“异”的含义。

  我们将二进制 110101 和 100011 的每一位对齐,进行按位的“异或”操作,可以得到结果是 10110。

 “异或”操作的本质其实就是,所有数值和自身进行按位的“异或”操作之后都为 0。而且要通过“异或”操作得到 0,也必须通过两个相同的数值进行按位“异或”。这表明了两个数值按位“异或”结果为 0,是这两个数值相等的必要充分条件,可以作为判断两个变量是否相等的条件。

异或:相同为 0,不同为 1。也可用“不进位加法”来理解。
异或操作的一些特点:
x ^ 0 = x
x ^ 1s = ~x // 注意 1s = ~0   0s表示一串0,1s表示一串1
x ^ (~x) = 1s
x ^ x = 0
c = a ^ b => a ^ c = b, b ^ c = a // 交换两个数
a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c // associative

2.6 按位取反 ~

   0011 -------- =>  1100

2.7 指定位置的位运算

指定位置的位运算:
    1. 将 x 最右边的 n 位清零:x & (~0 << n)
    2. 获取 x 的第 n 位值(0 或者 1): (x >> n) & 1
    3. 获取 x 的第 n 位的幂值:x & (1 << (n -1))
    4. 仅将第 n 位置为 1:x | (1 << n)
    5. 仅将第 n 位置为 0:x & (~ (1 << n))
    6. 将 x 最高位至第 n 位(含)清零:x & ((1 << n) - 1)
    7. 将第 n 位至第 0 位(含)清零:x & (~ ((1 << (n + 1)) - 1))

 

2.8常用的运算要点

实战运算要点:
• 判断奇偶:
    x % 2 == 1 —> (x & 1) == 1
    x % 2 == 0 —> (x & 1) == 0
• x >> 1 —> x / 2
    即: x = x / 2; —> x = x >> 1;
         mid = (left + right) / 2; —> mid = (left + right) >> 1;
• X =
X & (X-1) => 二进制清零最低位的 1, X & (X-1) 结果不是 X - 1 • X & -X => 1   得到最低位的 1 • X & ~X => 0

3. 位运算的应用

 N皇后的位运算解法

python

def totalNQueens(self, n):
    if n < 1: return []
    self.count = 0
    self.DFS(n, 0, 0, 0, 0)
    return self.count
def DFS(self, n, row, cols, pie, na):
    # recursion terminator
    if row >= n:
        self.count += 1
        return
    bits = (~(cols | pie | na)) & ((1 << n) — 1) # 得到当前所有的空位
    while bits:
        p = bits & —bits # 取到最低位的1
        bits = bits & (bits — 1) # 表示在p位置上放⼊皇后
        self.DFS(n, row + 1, cols | p, (pie | p) << 1, (na | p) >> 1)
        # 不需要revert cols, pie, na 的状态    

Java

class Solution {
    private int size;
    private int count;
    private void solve(int row, int ld, int rd) {
        if (row == size) {
        count++;
        return;
        }
        int pos = size & (~(row | ld | rd));
        while (pos != 0) {
            int p = pos & (-pos);
            pos -= p; // pos &= pos - 1;
            solve(row | p, (ld | p) << 1, (rd | p) >> 1);
            }
        }
        public int totalNQueens(int n) {
            count = 0;
            size = (1 << n) - 1;
            solve(0, 0, 0);
            return count;
    }
}

 

posted @ 2020-07-21 23:02  kris12  阅读(845)  评论(0编辑  收藏  举报
levels of contents