位运算及其应用

一 基本概念

机器码 = 符号位 + 真值

符号位:
正数:0
负数:1

1 原码

原码 = 符号位 + 真值

比如:

  • +1: 0 001
  • -1: 1 001

2 反码

  • 正数反码 = 原码
  • 负数反码 = 符号位 + 真值取反

比如:

  • +1: 0 001
  • -1: 1 110

3 补码

  • 正数反码 = 原码
  • 负数反码 = 反码 + 1

比如:

  • +1: 0 001
  • -1: 1 111

-1的补码计算过程:

-1 = 1 110 + 1 = 1 111

二 常用位运算

1 按位与

操作位同为1,则为1,否则为0

比如:

1 & 2 = 0001 & 0010 = 0000 = 0
1 & -1 = 0001 & 1111 = 0001 = 1

2 按位或

操作位同为0,则为0,否则为1

比如:

1 | 2 = 0001 | 0010 = 0011 = 3
1 | -1 = 0001 | 1111 = 1111 = -1

3 按位异或

操作位不同则为1,否则为0

比如:

1 ^ 2 = 0001 ^ 0010 = 0011 = 3
1 ^ -1 = 0001 ^ 1111 = 1110 = -2

ps: 异或也叫半加运算,其运算法则相当于不带进位的二进制加法

4 按位取反

操作位0变1,1变0

比如:

~1 = ~0001 = ~1110 = -2
~2 = ~0010 = 1101 = -3
~-1 = ~1111 = 0000 = 0

5 左移

右边补0,去掉多余的高位

比如:

1 << 1 = 0001 << 1 = 00010 = 2
2 << 2 = 0010 << 2= 001000 = 8
-1 << 3 = 1111 << 3= 1111000 = -8

6 右移

左边补符号位,去掉多余的低位

比如:

1 >> 1 = 0001 >> 1 = 0000 = 0
2 >> 1 = 0010 >> 1= 0001 = 1
4 >> 1 = 0100 >> 1= 0010 = 2
4 >> 2 = 0100 >> 2= 0001 = 1
-1 >> 1 = 1111 >> 1 = 1111 = -1

7 无符号右移

左边补0,去掉多余的低位

比如:

1 >>> 1 = 0001 >> 1 = 0000 = 0
2 >>> 1 = 0010 >> 1= 0001 = 1
4 >>> 1 = 0100 >> 1= 0010 = 2
4 >>> 2 = 0100 >> 2= 0001 = 1
-1 >>> 1 = (1...111) >> 1 = (0...111) = 最大正整数

三 位运算应用

1 判断奇偶

  • 偶数:a & 1 == 0
  • 奇数:a & 1 == 1

比如:

1 & 1 = 0001 & 0001 = 0001 = 1
2 & 1 = 0010 & 0001 = 0000 = 0
3 & 1 = 0011 & 0001 = 0001 = 1
4 & 1 = 0100 & 0001 = 0000 = 0

2 运算

  • 乘法:a * (2^n) = a << n
  • 除法:a / (2^n) = a >> n
  • 取模: a % (2^n) = a & (2^n - 1)

比如:

3 * 4 = 3 * (2 ^ 2) = 3 << 2 = 0011 << 2 = 1100 = 12
12 / 4 = 12 / (2 ^ 2) = 1100 >> 2 = 0011 = 3
3 % 4 = 3 & (2^2 - 1) = 3 & 3 = 0011 & 0011 = 0011 = 3
11 % 8 = 11 & (2^3 - 1) = 11 & 7 = 1011 & 0111 = 0011 = 3

3 记录多个状态

假如,有一个需求,要记录角色头顶的公告板要显示哪些内容,比如有名字,等级,称号,工会三个,通常我们需要三个变量,分别记录每个是否显示,使用位运算可以精简如下:

// 定义
let showFlag = 0;
const let NAME_POS = 1 << 0; // 0001 第一位表示名字
const let LV_POS = 1 << 1; // 0010 第二位表示等级
const let TITLE_POS = 1 << 2; // 0100 第三位表示称号
const let GUILD_POS = 1 << 3; // 1000 第四位表示工会

let isShowName, isShowLv, isShowTitle, isShowGuild;

// 名字操作
showFlag = showFlag | NAME_POS; // 0001 显示名字
isShowName = showFlag & NAME_POS = 0001 & 0001 = 0001 = 1;
showFlag = showFlag & ~NAME_POS; // 0000 隐藏名字
isShowName = showFlag & NAME_POS = 0000 & 0001 = 0000 = 0;

// 等级操作
showFlag = showFlag | LV_POS; // 0010 显示等级
isShowLv = showFlag & LV_POS = 0010 & 00010 = 0010 = 2;
showFlag = showFlag & ~LV_POS; // 0000 隐藏等级
isShowLv = showFlag & LV_POS = 0000 & 0010 = 0000 = 0;

// 显示名字和称号
showFlag = showFlag | NAME_POS; // 0001 显示名字
showFlag = showFlag | TITLE_POS; // 0101 显示等级
isShowName = showFlag & NAME_POS = 0101 & 0001 = 0001 = 1;
isShowTitle = showFlag & TITLE_POS = 0101 & 0100 = 0100 = 4;
......

这段代码涉及到几个点:

  1. 将a第k设为0: a = a & ~(1<<k)

  2. 将a第k设为1: a = a | (1<<k)

  3. 取a第k位的值: (a>>k) & 1

例如:


let a = 10;// 1010

// 第3位设为0
a = a & (1 << 3) = 1010 & ~(0001 << 3) = 1010 & ~1000 = 1010 & 0111 = 0010 = 2;

// 取3位的值
(a >> 3) & 1 = (0010 >> 3) & 0001 = 0000 & 0001 = 0

// 第2位设为1
a = a | (1 << 2) = 0010 | (0001 << 2) = 0010 | 0100 = 0110 = 6;

// 取2位的值
(a >> 2) & 1 = (0110 >> 2) & 0001 = 0001 & 0001 = 1

2019/11/04更新

4 计算加法

function add(x, y) {
  let result;
  while (x != 0) {
      result = x ^ y;
      x = (x & y) << 1;
      y = result;
  }
  return y;
}

5 xor加密

原理:

b ^ a ^  a == b ^ (a ^ a) == b ^ 0 == b

例如,我们要加密一段文本 txt,密钥为 key,那么:
加密:encrypt_txt = txt ^ key
解密:txt = encrypt_txt ^ key


6 查找数组中只出现一次的数

初级版

一个整型数组里除了1个数字之外,其他的数字都出现了两次。请写程序找出这个数字。

class Solution {
public:
    int FindNumsAppearOnce(vector<int> data) {
		// 两个相同数字异或=0,一个数和0异或还是它本身。
		int len = data.length;
        int res = 0;
        for (int i = 0; i < len; i++) {
            res ^= data[i];
        }
        return res;
    }
};

进阶版

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        int len = data.size();
        if (len < 2) {
            return;
        }
		// 对所有元素进行异或运算,得到num1 ^ num2的结果
        int res = 0;
        for (int i = 0; i < len; i++) {
            res ^= data[i];
        }

		// 异或结果1所在的最低位
        int mask = 1;
        while ((res & mask) == 0) {
            mask = mask << 1;
        }

		// 将数组分成两部分,然后分别异或得到对应的数字(类似初级版)
		*num1=*num2=0;
        for(int i = 0; i < len; i++) {
            if((data[i] & mask)) {
                *num1 = *num1 ^ data[i];
            } else {
                *num2 = *num2 ^ data[i];
            }
        }
    }
};

终极版

一个整型数组里除了一个数字之外,其他的数字都出现了三次。请写程序找出这两个只出现一次的数字。

class Solution {
public:
    int FindNumsAppearOnce(vector<int> data) {
    	// 记录每一位1出现的次数
		int[] bits = new int[32];
        int len = data.length;
        for(int i = 0; i < len; i++){
            for(int j = 0; j < 32; j++){
                bits[j] = bits[j] + ( (data[i]>>>j) & 1);
            }
        }

		// 统计1出现的次数不为3的整数倍位
        int res = 0;
        for(int i = 0; i < 32; i++){
            if(bits[i] % 3 !=0){
                res = res | (1 << i);
            }
        }
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值