位运算

===

Index

常用位操作

在下面示例中,1s0s 分别表示一串1和一串0

x ^ 0s = x      x & 0s = 0      x | 0s = x
x ^ 1s = ~x     x & 1s = x      x | 1s = 1s
x ^ x = 0       x & x = x       x | x = x

获取和设置数位

1. 获取整型数值num二进制中的第i位。

将1左移i位,得到形如 00010000 的值,对这个值与num执行“位与”操作,从而将i位之外的所有位清0,检查结果是否位0.

bool getBit(int num, int i) {
    return (num & (1 << i)) != 0;
}

2. 将num的第i位设置位1

int setBit(int num, int i) {
    return num | (1 << i);
}

3. 将num的第i位清零

将数字 00 010 000 取反,得到类似 11 101 111 数字,对该数字与num执行与操作。

int clearBit(int num, int i) {
    int mask = ~(1 << i);
    return num & mask;
}

如果要清零最高位至第i位(包括最高位和第i位),先创建一个第i位为1 (1 « i) 的源码,将其减1的到得到第一部分全为0,第二部分全为1的数字,再与目标数执行与操作

int claerBitsLeft(int num, int i) {
    int mask = (1 << i) - 1;
    return num & mask;
}

如果要清零第i位至第0位(包括第i位和第0位),使用一串1构成的数字(-1) ,将其做一 i+1 位,得到一串第一部分全为1,第二部分全为0的数字。

int clearBitsRight(int num, int i) {
    int mask = (-1 << (i + 1));
    return num & mask;
}

4. 将num的第i位设置位v

首先将num的第i位清零,然后将v左移i位,得到一个i位为1,其他位为0的数,最后将两个结果执行或操作。

int updateBit(int num, int i, bool bit) {
    int mask = ~(1 << i);
    int v = bit ? 1 : 0;
    return (num & mask) | (value << i);
}

一些有用的位操作

  • 判断整数n是否为2的某次方: n & (n -1) == 0
  • 清除整数最右边的1:n = n & (n - 1)
  • 获得二进制中最低位的1: lowbit = n & (-n)
  • 得到一个数字的相反数(按位取反,再加一): n = (~n) + 1
  • « 左移乘二,» 除以2, 1«i = 2^i, x » i = x / 2^i.

二进制所有是1的位

例如: 13, 其二进制为 1101, 二进制中所有为1的位为0, 2, 3. 20, 其二进制为 10100, 二进制中所有为1的位为2, 4.

技巧: 对于任意在[0,35]中的k,2^k%37互不相等,且恰好取遍整数1-36 利用这个性质可以使用hash代替取log运算,提高效率。

vector<int> getAll1(int n) {
    int H[37];
    for (int i = 0; i < 36; ++i) {
        H[(1ll << i) % 36] = i;
    }
    vector<int> res;
    while (n > 0) {
        res.push_back(H[(n & -n) % 37]);
        n -= lowbit(n);
    }
}

进制转换

1.将任意2-36进制数转化为10进制数

// s是给定的radix进制字符串
int Atoi(string s, int radix) {
    int ans = 0;
    for (int i = 0; i < s.size(); ++i) {
        auto t = s[i];
        if (t >= '0' && t <= '9') 
            ans = ans * radix + t - '0';
        else 
            ans = ans * radix + t - 'a' + 10;
    }
    return ans;
}

strtol库函数 函数原型为 long int strtol(const char *nptr, char **endptr, int base) base是要转化的数的进制,非法字符会赋值给endptr,nptr是要转化的字符

例如:

string s = "10100";
char * stop;
int ans = strtol(s.c_str(), &stop, 2);
// int ans = strtol(s.c_str(), NULL, 2);

2.将10进制数转换为任意的radix进制数,结果为char型

//n是待转数字,radix是指定的进制
string intToA(int n, int radix) {
    string ans = "";
    do {
        int t = n % radix;
        if (t >= 0 && t <= 9) ans += t + '0';
        else ans += t - 10 + 'a';
        n /= radix;
    } while(n);
    reverse(ans.begin(), ans.end());
    return ans;
}

itoi库函数

可以将一个10进制数转换为任意的2-36进制字符串 函数原型:char* itoa(int value, char* string, int radix)

int num = 10;
char str[10];
itoa(num, str, 2); //将num转换为2进制,结果写在str中。

格雷编码

leetcode 89

格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。

给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。即使有多个不同答案,你也只需要返回其中一种。

格雷编码序列必须以 0 开头。

1.直接计算

    关键是搞清楚格雷编码的生成过程, G(i) = i ^ (i/2);
    如 n = 3: 
    G(0) = 000, 
    G(1) = 1 ^ 0 = 001 ^ 000 = 001
    G(2) = 2 ^ 1 = 010 ^ 001 = 011 
    G(3) = 3 ^ 1 = 011 ^ 001 = 010
    G(4) = 4 ^ 2 = 100 ^ 010 = 110
    G(5) = 5 ^ 2 = 101 ^ 010 = 111
    G(6) = 6 ^ 3 = 110 ^ 011 = 101
    G(7) = 7 ^ 3 = 111 ^ 011 = 100
    vector<int> grayCode(int n) {
        vector<int> res(1 << n);
        for (int i = 0; i < (1 << n); ++i) {
            res[i] = i ^ (i >> 1);
        }
        return res;
    }

2. DFS

    vector<int> res;
    int vis[1<<16+2], m;
    void dfs(int s, int n) {
        if (res.size() == m) return;
        res.push_back(s);
        vis[s] = 1;
        for (int i = 0; i < n; ++i) {
            int c = s ^ (1 << i);
            if (!vis[c]) dfs(c, n);
        }
    }
    vector<int> grayCode(int n) {
        if (!n) return {0};
        memset(vis, 0, sizeof(vis));
        m = 1 << n;
        dfs(0, n);
        return res;
    }

汉明距离

整数转换,确定需要改变几个位才能将整数A转成整数B。即对应二进制不同位置的数目.

示例:

  • input : 29 (11101), 15 (01111)
  • output : 2
int hammingDistance(int x, int y) {
    int n = x ^ y, cnt = 0;
    while (n) {
        n = n & (n - 1);
        cnt++;
    }
    return cnt;
}

汉明权重

汉明权重是一串符号中不同于(定义在其所使用的字符集上的)零符号(zero-symbol)的个数。对于一个二进制数,它的汉明权重就等于它 的个数(即 popcount)。

int popcount(int x) {
    int cnt = 0;
    while (x) {
        cnt += x & 1;
        x >>= 1;
    }
    return cnt;
}

求一个数的汉明权重还可以使用 lowbit 操作:我们将这个数不断地减去它的 lowbit4,直到这个数变为 0。

int popcount(int x) {
    int cnt = 0;
    while (x) {
        cnt ++;
        x -= x & -x;
    }
    return cnt;
}

或者 使用GCC用于位运算的内建函数 ` __builtin_popcount(unsigned int x)`

构造汉明权重递增的排列

在 状压 DP 中,按照 popcount 递增的顺序枚举有时可以避免重复枚举状态。这是构造汉明权重递增的排列的一大作用。

枚举 0 ~ n 按汉明权重递增的排列

    for (int i = 0; (1<<i)-1 <= n; i++) {
        for (int x = (1<<i)-1, t; x <= n; t = x+(x&-x), x = x ? (t|((((t&-t)/(x&-x))>>1)-1)) : (n+1)) {
            // 写下需要完成的操作
        }
    }

例如, 按照二进制为1的数目递增构造 0-15。

int n = (1 << 4) - 1; // 15
for (int i = 0; (1<<i)-1 <= n; i++) {
    cout <<"i = " << i << ": ";
    for (int x = (1<<i)-1, t; x <= n; t = x+(x&-x), x = x ? (t|((((t&-t)/(x&-x))>>1)-1)) : (n+1)) {
        cout << x << " ";
    }
    cout << "\n";
}

输出为:

i = 0: 0 
i = 1: 1 2 4 8 
i = 2: 3 5 6 9 10 12 
i = 3: 7 11 13 14 
i = 4: 15

不用额外变量交换整数的值

void swap(int &a, int &b) {
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
}

或者

void swap(int &a, int &b) { // a = 3, b = 4
    a = a + b;   // a = 7, b = 4
    b = a - b;   // a = 7, b = 3
    a = a - b;   // a = 4, b = 3
}

只用位运算实现整数的加减乘除

int add(int a, int b) {
    int sum = a;
    while (b) {
        sum = a ^ b;
        b = (unsigned int)(a & b) << 1;
        a = sum;
    }
    return sum;
}
int minus(int a, int b) {// a - b = a + (-b)
    return add(a, add(~b, 1));
}

乘法 a*b = a * 2^0 * b_0 + a * 2^1 * b_1 + … + a * 2^31 * b_31

int multi(int a, int b) {
    int res = 0;
    while (b) {
        if ((b & 1) != 0)
            res = add(res, a);
        a <<= 1;
        b >>= 1;
    }
    return res;
}

除法

bool isNeg(int n) {
    return n < 0;
}

int getNeg(int n) {
    return add(~n, 1);
}

int div(int a, int b) {
    int x = isNeg(a) ? getNeg(a) : a;
    int y = isNeg(b) ? getNeg(b) : b;
    int res = 0;
    for (int i = 31; i > -1; i = minus(i, 1)) {
        if ((x >> 1) >= y) {
            res |= (1 << i);
            x = minus(x, y << i);
        }
    }
    return isNeg(a) ^ isNeg(b) ? getNeg(res) : res;
}

打赏一下

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦