经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 其他 » 计算机原理 » 查看文章
位运算与二进制表示集合
来源:cnblogs  作者:Cattle_Horse  时间:2023/2/20 15:46:18  对本文有异议

位运算与二进制表示集合

位运算

运算符

运算 运算符 数学符号表示 解释
& &、\(and\) 只有两个对应位都为 \(1\) 时才为 \(1\)
\(|\) \(|\)\(or\) 只要两个对应位有一个 \(1\) 时就为 \(1\)
异或 ^ \(\oplus\)\(xor\) 只有两个对应位不同时才为 \(1\)
取反 ~ 二进制位均 全部取反(\(0\) 变为 \(1\)\(1\) 变为 \(0\)
左移 << num << i 表示将 \(num\) 的二进制位向左移动 \(i\) 位所得的值
带符号右移 >> 正数右移后,高位补 \(0\),负数右移后,高位补 \(1\)
无符号右移(\(java\)等部分语言) >>> 无论正负,高位均补 \(0\)

原码、反码、补码

最高位是符号位,以下以 \(8\) 位的二进制举例

编码方式 解释 举例
原码 原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值 \(+1_{原}=(0000\ 0001)_{原}\)\(-1_{原}=(1000\ 0001)_{原}\)
反码 正数的反码是其本身。负数的反码是在其原码的基础上,符号位不变,其余各个位取反. \(+1_{反}=(0000\ 0001)_{原}=(0000\ 0001)_{反}\)\(-1_{反}=(1000\ 0001)_{原}=(1111\ 1110)_{反}\)
补码 正数的补码就是其本身。负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后+1。(即在反码的基础上+1) \(+1_{补}=(0000\ 0001)_{原}=(0000\ 0001)_{反}=(0000\ 0001)_{补}\)\(-1_{补}=(1000\ 0001)_{原}=(1111\ 1110)_{反}=(1111\ 1111)_{补}\)

计算机采用补码的编码方式

应用

一个数乘除 \(2\) 的非负整数次幂

计算 \(n\times 2^m\)n << m

计算 \(n\div 2^m\)n >> m

操作一个数的二进制位

操作 实现 举例
获取 \(a\) 的二进制倒数 \(b\) 位(编号从 \(0\) 开始) a >> b & 1 4 >> 2 & 1 等于 1
\(a\) 的二进制倒数 \(b\) 位设置为 \(0\) a & ~(1 << b) 5 & ~(1 << 2)等于1
\(a\) 的二进制倒数 \(b\) 位设置为 \(1\) \(a | (1 << b)\) \(1 | (1 << 2)\) 等于5
\(a\) 的二进制倒数 \(b\) 位取反 a ^ (1 << b) 1 ^ (1 << 2)等于5
获取 \(a\) 的二进制最后一个 \(1\) 的值 a & -a(或a & (~a + 1) 6 & -6等于2
获取 \(a\) 的二进制位最后一个 \(1\) 设置为 \(0\) 的值 a & (a - 1) 6 & (6 - 1)等于4

一些自带的二进制方法

大多通过二分查找的方式实现

C++(头文件stdlib.h中) Java(均可通过使用Long类代替Integer类)
十进制转换其他进制(\(2\sim36\) 范围内) itoa(数字, char[] ans, 进制),ltoa(数字, char[] ans, 进制)同时返回值均为char[]类型的答案 Integer.toString(数字, 进制)
任意进制转换十进制(\(2\sim36\) 范围内) strtol(原进制字符数组, 接受剩余非法字符, 进制)返回十进制数,strtol返回long类型,strtoll返回long long类型,strtoul返回unsigned long类型。还有double等类型函数 Integer.parseInt(字符串String, 进制)
二进制中 \(1\) 的个数 int __builtin_popcount(unsigned int x)int __builtin_popcountll(unsigned long long x)等等 Integer.bitCount(数字)
二进制末尾连续 \(0\) 的个数 int __builtin_ctz(unsigned int x)long等其他类型同上,当 \(x\) 等于 \(0\) 时,行为未定义 Integer.numberOfTrailingZeros(数字),当 \(x\) 等于 \(0\) 时,返回 \(32\)
二进制前导零的个数(可用来求最高位的 \(1\) 的位置) int __builtin_clz(unsigned int x)long等其他类型同上,当 \(x\) 等于 \(0\) 时,行为未定义 Integer.numberOfLeadingZeros(数字),当 \(x\) 等于 \(0\) 时,返回 \(32\)
获取符号 暂未了解 Integer.signum(数字)。当 数字大于 \(0\) 时返回 \(1\)。当 数字等于 \(0\) 时返回 \(0\)。当 数字小于 \(0\) 时返回 \(-1\)
获取二进制最后一个 \(1\) 的值 暂未了解 Integer.lowestOneBit(数字)通过 x & -x 实现
获取二进制第一个 \(1\) 的值(也就是小于等于该数的最大的 \(2\) 的幂的值) 暂未了解 Integer.highestOneBit(数字)通过调用 numberOfLeadingZeros(数字) 实现
二进制循环左移(低位缺少的位通过高位消去的位补充) 暂未了解 Integer.rotateLeft(数字, 移动位数)
二进制循环右移(高位缺少的位通过低位消去的位补充) 暂未了解 Integer.rotateRight(数字, 移动位数)
二进制按位反转 暂未了解 Integer.reverse(数字)返回二进制按位反转后的十进制值

更多位数

Int类型的二进制位只有 \(32\) 位,Long类型的二进制位也只有 \(64\) 位,如果需要更多的二进制位,就需要使用位图这个类了

bitset - OI Wiki

下述表格待补全...

c++bitset(头文件bitset中) JavaBitSet

例题

231. 2 的幂 - 力扣

  1. class Solution {
  2. public boolean isPowerOfTwo(int n) {
  3. if (n <= 0) return false;
  4. return (n & (n - 1)) == 0;
  5. }
  6. }

342. 4的幂 - 力扣

  1. class Solution {
  2. public boolean isPowerOfFour(int n) {
  3. if (n <= 0) return false;
  4. if ((n & (n - 1)) != 0) return false;
  5. return (0b10101010101010101010101010101010 & n) == 0;
  6. }
  7. }

461. 汉明距离 - 力扣

  1. class Solution {
  2. public int hammingDistance(int x, int y) {
  3. return Integer.bitCount(x ^ y);
  4. }
  5. }

剑指 Offer 15. 二进制中1的个数 - 力扣

  1. public class Solution {
  2. // you need to treat n as an unsigned value
  3. public int hammingWeight(int n) {
  4. return Integer.bitCount(n);
  5. }
  6. }

190. 颠倒二进制位 - 力扣

  1. public class Solution {
  2. // you need treat n as an unsigned value
  3. public int reverseBits(int n) {
  4. // 或者直接调用
  5. //return Integer.reverse(n);
  6. int ans = 0;
  7. for (int i = 0; i < 16; ++i) {
  8. ans |= (n >> i & 1) << (31 - i);
  9. ans |= (n >> (31 - i) & 1) << i;
  10. }
  11. return ans;
  12. }
  13. }

405. 数字转换为十六进制数 - 力扣

每四位二进制数对应一位十六进制数

  1. class Solution {
  2. char[] ch = "0123456789abcdef".toCharArray();
  3. final int mask = 0b1111;
  4. public String toHex(int num) {
  5. if (num == 0) return "0";
  6. StringBuilder ans = new StringBuilder();
  7. while (num != 0) {
  8. ans.append(ch[num & mask]);
  9. num >>>= 4; // 注意要无符号右移
  10. }
  11. return ans.reverse().toString();
  12. }
  13. }

477. 汉明距离总和 - 力扣

数列横着看和竖着看是两种方式,有时另一种会十分简便

  1. class Solution {
  2. public int totalHammingDistance(int[] nums) {
  3. int n = nums.length, ans = 0;
  4. for (int i = 0; i < 32; ++i) {
  5. int sum = 0;
  6. // 计算第i位1的个数
  7. for (int v : nums) {
  8. sum += v >> i & 1;
  9. }
  10. ans += sum * (n - sum);
  11. }
  12. return ans;
  13. }
  14. }

693. 交替位二进制数 - 力扣

  1. class Solution {
  2. public boolean hasAlternatingBits(int n) {
  3. n ^= n >> 1;
  4. return n != 0 && (n & (n + 1)) == 0;
  5. }
  6. }

401. 二进制手表 - 力扣

  1. class Solution {
  2. // 预处理小时位和分钟位的二进制1的个数
  3. static LinkedList<String>[] hours = new LinkedList[4];
  4. static LinkedList<String>[] minutes = new LinkedList[9];
  5. static {
  6. for (int i = 0; i < 4; ++i) hours[i] = new LinkedList<String>();
  7. for (int i = 0; i < 9; ++i) minutes[i] = new LinkedList<String>();
  8. for (int i = 0; i < 12; ++i) {
  9. hours[Integer.bitCount(i)].add(Integer.toString(i));
  10. }
  11. for (int i = 0; i < 10; ++i) {
  12. minutes[Integer.bitCount(i)].add(":0" + i);
  13. }
  14. for (int i = 10; i < 60; ++i) {
  15. minutes[Integer.bitCount(i)].add(":" + i);
  16. }
  17. }
  18. public List<String> readBinaryWatch(int turnedOn) {
  19. List<String> ans = new LinkedList<String>();
  20. if (turnedOn >= 9) return ans;
  21. // 枚举小时位灯的个数
  22. for (int i = Math.min(3, turnedOn); i >= 0; --i) {
  23. for (String hour : hours[i]) {
  24. for (String minute : minutes[turnedOn - i]) {
  25. ans.add(hour + minute);
  26. }
  27. }
  28. }
  29. return ans;
  30. }
  31. }

二进制表示集合

集合操作

操作 集合表示 位运算符
交集 \(a\cap b\) a & b
并集 \(a\cup b\) \(a | b\)
补集 \(\bar{a}\) ~a(全集为二进制位均为 \(1\)
差集 \(a\setminus b\) a & (~b)
对称差 \(a\bigtriangleup b\) a ^ b

遍历子集

若遍历的是二进制表示除前导 \(0\) 外均为 \(1\) 的集合(如 111111),则可以通过下述方式遍历

  1. int n = 1;
  2. int S = (1 << n) - 1;
  3. for (int i = 1; i <= S; ++i) {
  4. for (int j = 0; j < n; ++j) {//遍历二进制每一位
  5. if ((i >> j & 1) == 1) {//判断第j位是否存在
  6. // do something;
  7. }
  8. }
  9. }

但如果要屏蔽某一位置的遍历(如111110011),若仍选择通过上述方式遍历,就需要一些判断,更推荐如下做法(逆序遍历)

  1. /*
  2. // 这种写法不会遍历空集
  3. int n = 1;
  4. int S = (1 << n) - 1;
  5. for (int i = S; i != 0; i = (i - 1) & S) {
  6. for (int j = 0; j < n; ++j) { // 遍历二进制每一位
  7. if ((i >> j & 1) == 1) { // 判断第j位是否存在
  8. //do something;
  9. }
  10. }
  11. }
  12. */
  13. int n = 1;
  14. int S = (1 << n) - 1;
  15. int i = S;
  16. do {
  17. for (int j = 0; j < n; ++j) { // 遍历二进制每一位
  18. if ((i >> j & 1) == 1) { // 判断第j位是否存在
  19. //do something;
  20. }
  21. }
  22. i = (i - 1) & S;
  23. } while (i != S);

原理:

  1. \(1\) 是为了遍历所有比 \(S\) 小的数,减 \(1\) 的实质就是去掉二进制数的最后一个 \(1\),并在其后面的位上补上 \(1\),如\((10100)_2-1=(10011)_2\)
  2. & 操作是让原来 \(S\) 二进制上是 \(0\) 的位均保持 \(0\)
  3. \(i\) 变为空集 \(0\) 时,继续减一会变成 \(-1\),而 \(-1=(111\cdots111)_2\),他与 \(S\) 做 & 运算就会重新变为 \(S\),此时循环终止

例题

784. 字母大小写全排列 - 力扣

  1. class Solution {
  2. public List<String> letterCasePermutation(String s) {
  3. int len = s.length();
  4. char[] t = s.toCharArray();
  5. List<String> ans = new LinkedList<>();
  6. ans.add(s);
  7. int S = 0;
  8. for (int i = 0; i < len; ++i) {
  9. if (Character.isDigit(t[i])) continue;
  10. S |= 1 << i;
  11. }
  12. for (int i = S; i != 0; i = (i - 1) & S) {
  13. t = s.toCharArray();
  14. for (int j = 0; j < len; ++j) {
  15. if ((i >> j & 1) == 1) t[j] ^= 32; // 英文字母异或32代表大小写转换
  16. }
  17. ans.add(new String(t));
  18. }
  19. return ans;
  20. }
  21. }

78. 子集 - 力扣

  1. class Solution {
  2. public List<List<Integer>> subsets(int[] nums) {
  3. int len = nums.length;
  4. List<List<Integer>> ans = new ArrayList<>(1 << len);
  5. for (int i = 0, S = 1 << len; i < S; ++i) {
  6. List<Integer> t = new LinkedList<>();
  7. for (int j = 0; j < len; ++j) {
  8. if ((i >> j & 1) == 1) {
  9. t.add(nums[j]);
  10. }
  11. }
  12. ans.add(t);
  13. }
  14. return ans;
  15. }
  16. }

90. 子集 II - 力扣

  1. class Solution {
  2. public List<List<Integer>> subsetsWithDup(int[] nums) {
  3. Arrays.sort(nums);
  4. int len = nums.length;
  5. List<List<Integer>> ans = new ArrayList<>(1 << len);
  6. for (int i = 0, S = 1 << len; i < S; ++i) {
  7. List<Integer> t = new LinkedList<>();
  8. boolean mark = true;
  9. for (int j = 0; j < len; ++j) {
  10. if ((i >> j & 1) == 1) {
  11. if (j > 0 && nums[j] == nums[j - 1] && (i >> (j - 1) & 1) == 0) {
  12. mark = false;
  13. break;
  14. }
  15. t.add(nums[j]);
  16. }
  17. }
  18. if (mark) ans.add(t);
  19. }
  20. return ans;
  21. }
  22. }

1178. 猜字谜 - 力扣

  1. class Solution {
  2. public List<Integer> findNumOfValidWords(String[] words, String[] puzzles) {
  3. HashMap<Integer, Integer> map = new HashMap<>();
  4. for (String s : words) {
  5. // 二进制映射
  6. int mask = 0;
  7. for (int i = 0; i < s.length(); ++i) {
  8. mask |= 1 << (s.charAt(i) - 'a');
  9. }
  10. // 题目保证puzzle字符串长度为7
  11. // 只加入个数小于等于7的减少空间消耗
  12. if (Integer.bitCount(mask) <= 7) {
  13. map.put(mask, map.getOrDefault(mask, 0) + 1);
  14. }
  15. }
  16. List<Integer> ans = new ArrayList<>(puzzles.length);
  17. for (String s : puzzles) {
  18. // 二进制映射
  19. int mask = 0;
  20. // 跳过首字母,之后处理集合的时候单独加上,保证首字母存在
  21. for (int i = 1; i < s.length(); ++i) {
  22. mask |= 1 << (s.charAt(i) - 'a');
  23. }
  24. int cnt = 0;
  25. int begin = s.charAt(0) - 'a';
  26. for (int i = mask; i != 0; i = (i - 1) & mask) {
  27. // 保证首字母存在
  28. cnt += map.getOrDefault(i | (1 << begin), 0);
  29. }
  30. // 处理空集(只有首字母的情况)
  31. cnt += map.getOrDefault(1 << begin, 0);
  32. ans.add(cnt);
  33. }
  34. return ans;
  35. }
  36. }

参考资料

位运算 - OI Wiki

二进制集合操作 - OI Wiki

Java Integer.highestOneBit(i)代码品读 - JessenPan的博客

二进制位运算遍历所有子集 - kokoro的博客

原文链接:https://www.cnblogs.com/Cattle-Horse/p/17137689.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号