【算法&数据结构初阶篇】:位运算

曾经终败给现在 2024-03-29 14:40 247阅读 0赞

算法中很多情况下需要用到各种位运算的转换,比如>>右移、<<左移、&与等等,下面我们利用这些位运算来进行一个进制转换,将一个int整形(32位)十进制转二进制,以及其他的一些转换技巧。

目录

一、十进制转换成32位的二进制

核心点就是将1进行左移操作,从高位32位开始依次向低位0位移动,分别与所需转换的整形数值做与运算

一、代码:

二、代码解析:

二、位运算技巧

一、随用随记的特殊技巧

二、计算演练

一、负数二进制,计算转换得到其十进制:

三、用位运算实现加减乘除,不能用到+ - * /符号

一、代码演示


一、十进制转换成32位的二进制

核心点就是将1进行左移操作,从高位32位开始依次向低位0位移动,分别与所需转换的整形数值做与运算

一、代码:

  1. public void printBinary(int num){
  2. //高位从右到左遍历,打印
  3. for(int i = 31; i >= 0 ; i--){
  4. //数值第一轮是与高位1做与运算, 两者为1 与 得1,一个不为1 与 得 0
  5. System.out.print(num & (1 << i) == 0 ? 0 : 1)
  6. }
  7. }

二、代码解析:

  • 转换二进制,需要知道打印方向,从高位到低位,即从左往右依次打印
  • &与运算的技巧, 1 1 得 1,非1 得 0,所以原数值,若为1 , 与运算后仍为 1, 若为 0, 与运算后仍为0

二、位运算技巧

一、随用随记的特殊技巧

  1. System.out.println((~-5)+1); //5 -5的相反数为5 取反+1
  2. System.out.println((~5)+1); //-5 5的相反数为-5 取反+1
  3. System.out.println(-1>>>1); //2147483647 无符号右移,忽略最高位的符号位,即用0补齐最高位
  4. System.out.println(-1>>1); //-1 带符号右移,负数最高位符号位是 1, 右移后仍用1补齐
  5. printBinary(-1); //打印-1二进制 11111111111111111111111111111111
  6. System.out.println(70 & 63); // 6 即num % 64 等价与 num & 63 因为64二进制是1000000 模64后余数就是后六位的二进制数, 取后6位的技巧,与运算 111111 即 63 与运算1 1得1 这样就得到num的余数了 所以等价于模
  7. System.out.println(70 % 64); // 6
  8. System.out.println( 2 ^ 2 ) ; // 0 自己异或自己为0,异或运算,相同为0 不同为1,相当于 无进位相加

二、计算演练

一、负数二进制,计算转换得到其十进制:

例如计算机中二进制:11111111111111111111111111111011 ,表示的十进制为 -5 ,计算机内存中保存的都是补码,这个要清楚。

首先符号位即最高位为1,表示为负数,那是负几,就将存数位(即去符号位后的31位)取反,再加1,得到的就是负数的相反数,前面有提到,~(-5)+1=5,得到了相反数后,再加上符号-,即位该二进制对应的十进制:

(符号位)1 1 1 1… 1 1 0 1 1

操作1:存数位取反 1 0 0 0… 0 0 1 0 0

操作2:存数位+1 1 0 0 0… 0 0 1 0 1

这里得到的二进制,转换十进制 2^2+2^0 = 5,加入符号位,得到 -5

三、用位运算实现加减乘除,不能用到+ - * /符号

一、代码演示

  1. package class05;
  2. // 测试链接:https://leetcode.com/problems/divide-two-integers
  3. public class Code03_BitAddMinusMultiDiv {
  4. // 加法逻辑,理解举例
  5. // a = 00001110
  6. // b = 00000111
  7. // a + b = a和b的无进位相加(^) + a和b的进位信息(&之后左移一位)
  8. // a和b的无进位相加(^),假设为c
  9. // a和b的进位信息(&之后左移一位),假设为d
  10. // c = 00001001
  11. // d = 00001100
  12. // c + d = c和d的无进位相加(^) + c和d的进位信息(&之后左移一位)
  13. // c和d的无进位相加(^),假设为e
  14. // c和d的进位信息(&之后左移一位),假设为f
  15. // e = 00000101
  16. // f = 00010000
  17. // e + f = e和f的无进位相加(^) + e和f的进位信息(&之后左移一位)
  18. // e和f的无进位相加(^),假设为g
  19. // e和f的进位信息(&之后左移一位),假设为h
  20. // g = 00010101
  21. // h = 00000000
  22. // h为0了,g就是结果
  23. // 而且,00001110 + 00000111你直接自己来计算,最后结果也确实为00010101
  24. public static int add(int a, int b) {
  25. int sum = a;
  26. // 进位信息什么时候消失,什么时候停止
  27. while (b != 0) {
  28. // sum为无进位相加
  29. sum = a ^ b;
  30. // b为进位信息
  31. b = (a & b) << 1;
  32. a = sum;
  33. }
  34. return sum;
  35. }
  36. public static int negNum(int n) {
  37. return add(~n, 1);
  38. }
  39. // 实现了加法,那么a-b,就是a+(-b),就是a + (~b + 1)
  40. public static int minus(int a, int b) {
  41. return add(a, negNum(b));
  42. }
  43. // 乘法逻辑:
  44. // a = 00001100
  45. // b = 00001101
  46. // b的第0位是1,所以最终结果包含1个00001100
  47. // b的第1位是0,所以最终结果又包含0个00001100
  48. // b的第2位是1,所以最终结果又包含4个00001100,
  49. // 4个00001100 = 00001100左移2位 =00110000
  50. // b的第3位是1,所以最终结果又包含8个00001100
  51. // 8个00001100 = 00001100左移3位 =01100000
  52. // 然后b就没有1了,最终结果 =
  53. // 00001100 + 00110000 + 01100000
  54. public static int multi(int a, int b) {
  55. int res = 0;
  56. // 如果b里还有1,就继续
  57. // 如果b里没有1,就停止
  58. while (b != 0) {
  59. // 当前位是1,就加上此时的a
  60. // 当前位是0,就不加上此时的a
  61. if ((b & 1) != 0) {
  62. res = add(res, a);
  63. }
  64. // 每次a左移1位
  65. // 每次b右移1位
  66. // 有1就加,没有就不加
  67. a <<= 1;
  68. b >>>= 1;
  69. }
  70. return res;
  71. }
  72. public static boolean isNeg(int n) {
  73. return n < 0;
  74. }
  75. // 除法逻辑
  76. // 默认a和b都是正数
  77. // 如果不是正数,转化成正数
  78. // a = 0101010 42
  79. // a = 0001010
  80. // b = 0000110 6
  81. // a向右移动30位,啥都没了,当然 < b,说明如果b向左移动30位,必然大于a
  82. // a向右移动29位,啥都没了,当然 < b,说明如果b向左移动29位,必然大于a
  83. // a向右移动28位,啥都没了,当然 < b,说明如果b向左移动28位,必然大于a
  84. // .....
  85. // a向右移动6位,啥都没了,当然 < b,说明如果b向左移动6位,必然大于a
  86. // a向右移动5位,状态是0000001,还是 < b,说明如果b向左移动5位,必然大于a
  87. // a向右移动4位,状态是0000010,还是 < b,说明如果b向左移动4位,必然大于a
  88. // a向右移动3位,状态是0000101,还是 < b,说明如果b向左移动3位,必然大于a
  89. // a向右移动2位,状态是0001010,首次 >= b,说明如果b向左移动2位,开始 <= a
  90. // 所以a / b的结果,必然包含一个00000100 4
  91. // 然后让,a = a - (b<<2),也就是让a扣掉已经算出来的部分 42 - 6*4 = 18 10010
  92. // 继续讨论,a向右移动1位 01001 、 res |= (1 << 1) =>4|2 = 6 18 - 6*1 =12 1100
  93. // a向右移动0位的后续情况 1100 res | 1 => 6|1 = 7 12 - 6 =6 110
  94. public static int div(int a, int b) {
  95. int x = isNeg(a) ? negNum(a) : a;
  96. int y = isNeg(b) ? negNum(b) : b;
  97. int res = 0;
  98. for (int i = 30; i >= 0; i = minus(i, 1)) {
  99. if ((x >> i) >= y) {
  100. res |= (1 << i);
  101. x = minus(x, y << i);
  102. }
  103. }
  104. return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
  105. }
  106. // 除法逻辑主函数
  107. // a / b
  108. // 先把a和b转化成正数来做
  109. // 但是Integer.MIN_VALUE是转化不了的,所以单独讨论
  110. public static int divide(int a, int b) {
  111. if (a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
  112. // 如果a和b都是Integer.MIN_VALUE
  113. // 结果当然是1
  114. return 1;
  115. } else if (b == Integer.MIN_VALUE) {
  116. // 如果b是Integer.MIN_VALUE
  117. // 结果当然是0
  118. return 0;
  119. } else if (a == Integer.MIN_VALUE) {
  120. if (b == negNum(1)) {
  121. // 如果a是Integer.MIN_VALUE,b是-1
  122. // 根据题目要求,返回Integer.MAX_VALUE
  123. return Integer.MAX_VALUE;
  124. } else {
  125. // 如果a是Integer.MIN_VALUE,且b不是-1
  126. // 根据我们课上讲的,用补偿的方法
  127. // 例子1 :
  128. // 假设整数最小是-9,当前要除以3
  129. // -9无法转化成+9,因为整数最大只能到8
  130. // 所以,先让-9+1 = -8,让-8除以3,得到-2
  131. // 注意到,-2 * 3 = -6,而我们当初应该用-9来除
  132. // 所以还差着 -9 - (-6) = -3
  133. // 这就是应该补偿结果的,所以最终结果为
  134. // -2 + (-3 / 3) = -3得到正确结果
  135. // 例子2 :
  136. // 假设整数最小是-9,当前要除以4
  137. // -9无法转化成+9,因为整数最大只能到8
  138. // 所以,先让-9+1 = -8,让-8除以4,得到-2
  139. // 注意到,-2 * 4 = -8,而我们当初应该用-9来除
  140. // 所以还差着 -9 - (-8) = -1
  141. // 这就是应该补偿结果的,所以最终结果为
  142. // -2 + (-1 / 3) = -2得到正确结果
  143. int c = div(add(a, 1), b);
  144. // minus(a, multi(c, b)) -> a - b*c
  145. // a - b*c 就是就是应该补偿结果的,那个剩余
  146. // 剩余 / b -> 就是结果应该加上去的补偿
  147. return add(c, div(minus(a, multi(c, b)), b));
  148. }
  149. } else {
  150. return div(a, b);
  151. }
  152. }
  153. }

四、异或运算

1.不使用额外变量实现交换两个元素

注意i j 不能相等,相等就等于指向数组同个索引,是同个地址值 相等异或为0 就导致i j都为0了

  1. //注意i j 不能相等,相等就等于指向数组同个索引,是同个地址值 相等异或为0 就导致i j都为0了
  2. public static void swap (int[] arr, int i, int j) {
  3. arr[i] = arr[i] ^ arr[j];
  4. arr[j] = arr[i] ^ arr[j];
  5. arr[i] = arr[i] ^ arr[j];
  6. }

2.一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数

偶数个相同数,异或之后值为0,即可以全部依次异或,最后就是0与奇数个的值异或,为自身值

  1. // arr中,只有一种数,出现奇数次
  2. public static void printOddTimesNum1(int[] arr) {
  3. int eor = 0;
  4. for (int i = 0; i < arr.length; i++) {
  5. eor ^= arr[i];
  6. }
  7. System.out.println(eor);
  8. }

3.怎么把一个int类型的数,提取出最右侧的1来

  1. // eor != 0
  2. // eor最右侧的1,提取出来
  3. // eor : 00110010110111000
  4. // rightOne :00000000000001000
  5. int rightOne = eor & (-eor); // 提取出最右的1

演示:

eor : 00110010110111000 => rightOne :00000000000001000

~eor: 11001101001000111

~eor+1 11001101001001000 取反+1 等同于 相反数 -eor

eor & (~eor+1 ) 00000000000001000 ,这里就把最右侧的1取出来了,其他位都为0

取反+1 等同于 相反数 -eor,所以把eor & (~eor+1 ) 转换为eor & (-eor)

发表评论

表情:
评论列表 (有 0 条评论,247人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java进运算

    序中的所有数在计算机内存中都是以二进制的形式存储的。位运算就是直接对整数在内存中的二进制位进行操作。比如,and运算本来是一个逻辑运算符,但整数与整数之间也可以进行and...

    相关 数据结构算法--运算

    位运算 记得大学编译原理刚开始面对的就是机器语言,而位运算就是讲数字用二进制标识后,对每一位上的0, 或者1 进行运算。二进制以及位运算都是计算机学科的基础,很多底