你不知道的按位运算

先来看LeetCode上的Divide Two Integers题目要求:html

Divide two integers without using multiplication, division and mod operator.python

就是说不用乘法,除法,求模运算来实现两个整数相除,看起来很简单,我能够用除数减去被除数,直到除数小于被除数,记录减法操做的次数便可。假设是计算m/n,那么时间复杂度为O(m/n)。用Python实现后,Time Limit Exceeded。咱们考虑有没有更加优化的算法呢?git

若是很难想获得,那就先来回忆下二进制数按位运算的一些知识。github

二进制数按位运算

计算机里面全部数据都存储为0,1串,全部的运算归根到底都转为二进制数的运算。相信你们都知道二进制数按位运算的规则:面试

二进制位运算规则

来看一些简单的例子:算法

1010 & 1100 = 1000
1010 | 1100 = 1110
1010 ^ 1100 = 0110
1010 << 2   = 101000
1010 >> 2   = 10
~1010       = 0101

单纯的二进制位之间的这些运算至关简单,但对咱们实际编程并无直接帮助,由于编程过程当中须要的常常是数字间的运算,好比 5*(2^4) 。真的是这样吗?接着往下看!编程

计算机中数字的存储方式

咱们都知道计算机中万物皆为0、1,将万物变为0、1的过程叫作编码,这里咱们只讨论将数字编码为0、1的过程。windows

计算机中对数字的表示有三种方式:原码,反码,补码:数组

  • 原码表示法在数值前面增长了一位符号位(即最高位为符号位):正数该位为0,负数该位为1。好比十进制3若是用8个二进制位来表示就是 00000011, -3就是 10000011。编程语言

  • 反码表示方法:正数的反码是其自己;负数的反码是在其原码的基础上,符号位不变,其他各个位取反。

  • 补码表示方法:正数的补码是其自己;负数的补码是在其原码的基础上,符号位不变,其他各位取反,最后+1。 (即在反码的基础上+1)

原码容易被人脑直接识别并用于计算,可是对于计算机来讲并不友好。因此在计算机系统中,数值一概用补码来表示、运算和存储。使用补码,能够将符号位和数值域统一处理,将加法和减法统一处理。此外,补码与原码相互转换,其运算过程是相同的,不须要额外的硬件电路。详细的解释能够参考原码, 反码, 补码详解

数字的按位运算

计算机中数字存储为补码形式,各个数之间的运算也是对它们的补码作运算,并且获得的结果也是补码,以下图:

补码运算

各类编程语言都提供了对补码的二进制位直接进行运算的方法。以Python为例:

>>> 0b1010 & 0b1100
8   #1000
>>> 0b1010 | 0b1100
14  #1110
>>> 0b1010 ^ 0b1100
6   #0110
>>> 0b1010 << 2
40  #101000
>>> 0b1010 >> 2
2   #10
>>> ~0b1010
-11 #10000000 00000000 00000000 00001011
>>> type(0b1010)
<type 'int'>

上面0b开头的0、1串表示整型数字,在32位操做系统中,Python中int类型通常占32个二进制位,以最后一个求反运算为例子,1010的补码为

00000000 00000000 00000000 00001010

求反操做后为:

11111111 11111111 11111111 11110101

即为-11(原码为:10000000 00000000 00000000 00001011)的补码。(对一个数的补码求补码便可获得该数的原码)

另辟蹊径的按位运算

那么按位运算在实际编程中能够扮演哪些角色呢?简单点地,能够用来判断奇、偶数:num & 0x1,或者对一个数变换符号:~num + 1;复杂点的能够用来交换两个数,求绝对值等等。

> 不用额外的变量实现两个数字互换。

def swap(num_1, num_2):
    num_1 ^= num_2
    num_2 ^= num_1
    num_1 ^= num_2
    return num_1, num_2

证实很简单,咱们只须要明白异或运算知足下面规律:

  • 0^a = a;

  • a^a = 0;

  • a^b^c = a^c^b;

巧妙运用异或能够高效解决不少问题,好比 找出数组中只出现了一次的数(除了一个数只出现一次外,其余数都是出现两次),以及它的升级版:数组中只出现1次的两个数字(百度面试题)

> 不用判断语句来实现求绝对值。

def bit_abs(num):
    negative = num >> 31
    return (num ^ negative) - negative

这里假设程序运行环境中操做系统为32位,int型整数(不考虑整数溢出)用32位存储,所以能够用 num>>31 取出符号位,后面的部分留给大伙证实。

Leetcode 题目思路

回到文章开始提到的题目中,咱们对除数减去被除数的过程稍做改进。假设求m/n,咱们不一次次的 m-n,而是找到n的一个倍数,使得m-x*n尽量小,这样能减小循环减法的次数,进而提升效率。咱们知道在按位操做中,n << k至关于 n * 2^k,所以能够用2^k 来找合适的x。

咱们须要这样的一个数字k,它使得n 2^k < m < n 2^(k+1), 而后用m - n*2^k,获得新的m'。再找相应的k',作减法,如此循环便可。代码放在这里

原文地址

相关阅读
Pyhon wiki: BitwiseOperators
位操做基础篇之位操做全面总结

相关文章
相关标签/搜索