位运算(3)-- 高级运用
注:此文内容来自于对【数据结构与算法之位运算】课程所做的笔记
一、二进制中1的个数
问题:给定一个无符号整型变量,求其二进制表示中“1”的个数。
相似问题:判断整数A转换成整数B需要的次数。(A ^ B
,再求“1”的个数)
分析:很容易想到右移,然后判断奇偶性,复杂度与最左侧的1的位置相关。
int oneCount(unsigned int n){int c = 0;while(n){c += n & 1;n >>= 1;}return c;
}
优化1.:利用判断一个整数是否是2的幂采用的方法,每一次判断都会找到一个1,直到整数变为0为止。复杂度与1的个数相关。
int oneCount1(unsigned int n){int c = 0;while(n){n &= (n - 1); // 清除最低位的1c++;}return c;
}
优化2.:采用并行化规约思想进行优化。在GPU编程中,为了加速求和操作,会采用并行化规约技术使多个线程参与计算,从而将复杂度由O(n)将为O(logn). 位运算可以看成不同 bit 之间的线程并行。
int oneCount2(unsigned int n){/*获得偶数位的值*/ /*获得奇数位的值*/ // 常数的二进制表示n = (n & 0x55555555) + ((n >> 1) & 0x55555555); // 0101010101010101...n = (n & 0x33333333) + ((n >> 2) & 0x33333333); // 0011001100110011...n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f); // 0000111100001111...n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff); // 0000000011111111...n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff); // 0000000000000000...return n;
}
优化3.:编译器内置位运算,GCC下是__builtin_popcount
,VS下是__popcnt
。
int oneCount3(unsigned int n){return __builtin_popcount(n); // VS: __popcnt(n)
}
二、最左侧1问题
问题:给定一个整数,判断该整数最左侧1的位置 (most significant bit or count leading zeros problem)
way 1.:通过逐次移位获得最高位(左移右移皆可),复杂度较高。
int left_most_one_1(int n){int pos = -1;while(n){n >>= 1;pos++;}return pos;
}
way 2.:通过二分法寻找最左侧1,复杂度较way1降低很多。
int left_most_one_2(int n){if(n == 0) return -1;int exp = 4;int pos = (1 << exp); // 从16的位置开始二分while(exp > 0){exp--;if(n >> pos) pos += (1 << exp);else pos -= (1 << exp);}return n >> pos != 0 ? pos : pos - 1;
}
way 3.:原问题等价于将整数转化成科学记数法的幂。例如,01101000 = 1.101 * 2 ^ 6
。首先将整数强转为浮点数(必须用double,防止精度损失),此时浮点数中就包含了指数部分(想想计算机是怎么表示浮点数的),将其解析出来即是最左侧1。解析方法是地址强转:获得浮点数的地址,将地址强转成int型地址,然后移位即可获得指数。
int left_most_one_3(int n){if(!n) return -1;double b = n;/* 右移52位获得双精度浮点数的指数部分,再与上2047(0b11111111111)只获得指数部分,* 最后减去偏移量(移码),就是最终的指数部分的十进制值*/return ((*(long long*)&b) >> 52 & 2047) - 1023;
}
way 4.:编译器内置位运算,GCC下是__builtin_clz
,VS下是__lzcnt
。
int left_most_one_4(int n){return 31 - __builtin_clz(n); // VS: __lzcnt
}
三、Single number
I
问题:给定n个整数,除了一个数出现一次之外,其余的整数均出现了两次,找到只出现一次的整数。
分析:利用异或的性质,x ^ x = 0
,x ^ 0 = x
,异或满足交换律。将所有的整数异或,出现两次的整数就变为0,出现一次的整数保持不变。
int singleNumberI(int* A, int len){int res = 0;for(int i = 0; i < len; ++i){res ^= A[i];}return res;
}
II
问题:给定n个整数,除了一个数出现一次之外,其余的整数均出现了三次,找到只出现一次的整数。
分析:将每一个整数看成是一个长度为32位 的数组,然后统计32位中每一位出现1的次数。如果一个数出现了3次,则其出现1的位肯定也是3次,这时如果某位出现了4次,则意味着出现1次的数在该位也为1。时间复杂度:O(32n)
int singleNumberII(int* A, int len){int count[32], res;memset(count, 0, sizeof(count));for(int i = 0; i < len; ++i){for(int j = 0; j < 32; ++j){count[j] += A[i] >> j & 1;}}for(int i = 0; i < 32; ++i){res |= count[i] % 3 << i; // 模3再放置到对应的位}return res;
}
优化:构造真值表,计算逻辑函数表达式。
进一步优化:
int singleNumberII2(int* A, int len){int ones = 0, twos = 0;for(int i = 0; i < len; ++i){ones = (ones ^ A[i]) & ~twos;twos = (twos ^ A[i]) & ~ones;}return ones;
}
III
问题:给定n个整数,除了两个数出现一次外,其余的整数均出现了两次,找到只出现了一次的这两个整数。
分析:将所有的整数异或可获得这两个整数的异或值,还需要有其他的操作把它们分开。两个整数不同,它们的异或值非零,0表示这两个整数在该位相同,1表示不同。我们可以利用它们某一个为1的位对所有的整数进行划分,划分成该位为1的一组和该位为0的一组,进而可以区分这两个数。
int* singleNumberIII(int* nums, int numSize, int* returnSize){int* res = (int*)malloc(sizeof(int) * 2);memset(res, 0, sizeof(int) * 2);int xorsum = 0;for(int i = 0; i < numSize; ++i){xorsum ^= nums[i];}int low_one = xorsum & -xorsum; // 获得最低有效位,即最低位1的位置for(int i = 0; i < numSize; ++i){/* 分成两类 */if(low_one & nums[i]){res[0] = res[0] ^ nums[i];}else{res[1] = res[1] ^ nums[i];}}*returnSize = 2;return res;
}
四、N皇后问题
利用位运算提升回溯法的效率。”利用当前已放置皇后的位置,我们可以推测出下一行哪些位置可行,哪些位置不可行,从而可以避免遍历很多无效的位置。”
如图:
其中,row表示准备放置的行的纵列可以放置的位置,ld表示该行左斜对角线可以放置的位置,rd表示该行右斜对角线可以放置的位置。
那么,该位置即为:~(row | ld | rd)
const int QUEEN_NUM = 14; // 皇后的个数
int position[QUEEN_NUM]; // 每个皇后在每一行的位置
static int all_queen = (1 << QUEEN_NUM) -1;
static int sum = 0; // 放法// 假设在前 t - 1 行没有冲突的情况下,第t行放置的位置是否冲突
bool check(int t){for(int i = 0; i < t; ++i){// 行 - 行 == 列 - 列 或者 在同一列if(abs(t - i) == abs(position[t] - position[i]) || position[i] == position[t])return false;}return true;
}// 回溯法
void n_queen_backtrace(int row){ // 在第row行放置一个皇后if(row == QUEEN_NUM){sum++;return;}for(int i = 0; i < QUEEN_NUM; ++i){position[row] = i;if(check(row)) n_queen_backtrace(row + 1);}
}/*
* 采用位运算的解法
* row, ld和rd, 分别表示在纵列和两个对角线方向的限制条件下该行的哪些地方不能放
*/
void n_queen_bit(int row, int ld, int rd){if(row == all_queen){sum++;return;}// pos 表示当前行可以放置皇后的位置int pos = all_queen & ~(row | ld | rd);// 挑出这些位置while(pos){int p = pos & -pos;pos -= p;n_queen_bit(row + p, (ld + p) << 1, (rd + p) >> 1);}
}int main(){n_queen_backtrace(0);//n_queen_bit(0, 0, 0);printf("%d\n", sum);return 0;
}
五、格雷码
问题 I:给定一个二进制数n,获得其对应的格雷码,或者反过来。
分析:二进制转为格雷码: Gi=Bi⊕Bi+1(n−1≥i≥0) G_i = B_i \oplus B_{i + 1} (n - 1 \ge i \ge0)。即从最高位开始将前两位异或,可转化成公式:G = B ^ (B >> 1)
unsigned int binary2gray(unsigned int n){return n ^ (n >> 1);
}
格雷码转为二进制: Bi=Gi⊕Bi+1(n−1≥i≥0) B_i = G_i \oplus B_{i + 1} (n - 1 \ge i \ge 0)。
unsigned int gray2binary(unsigned int g){unsigned int b = g;while(g >>= 1){b ^= g;}return b;
}
格雷码的特点:最高为在转换前后不变,其余位上下呈镜面对称。
问题 II:把0
到2 ^ (n + m) - 1
的数写成2 ^ n * 2 ^ m
的矩阵,使得上下左右位置相邻两数的二进制表示只有一位之差。
分析:所有数都是由m位格雷码和n位的格雷码拼接而成,只需要用左移操作和或运算完成。
实现:
void print_binary_format(int g){int bit[32] = {0};int i = 31;while(g){bit[i--] = g % 2;g /= 2;}for(int j = 0; j < 32; ++j){printf("%d", bit[j]);}
}
void gen_gray_matrix(int m, int n){for(int i = 0; i < (1 << m); ++i){ // 行int high = (i ^ (i >> 1)) << n; // 高位部分格雷码for(int j = 0; j < (1 << n); ++j){ // 列int low = j ^ (j >> 1); // 低位部分格雷码print_binary_format(high | low);printf(" ");}printf("\n");}
}
位运算(3)-- 高级运用相关推荐
- C语言位运算的高级应用(尤其适合单片机和嵌入式编程)
位运算加速技巧 本方法可以让c语言指令进一步接近汇编指令的执行效率,提高单片机,嵌入式系统的速度和稳定性, 但编程时应采取函数化的编程法--例如使用swap()函数时,必要时加注释. 注:本例涉及一些 ...
- 位运算以及位运算的应用
位运算 什么是位运算 在计算机系统中,所有数据都是以二进制的形式进行存储,位运算就是对二进制中进行操作.所有的计算都是通过位运算进行实现的. 整数在计算机中 原码 原码是最简单的表示法,对于一个整数而 ...
- 位运算模块mBit.bas
'File: mBit.bas 'Name: 位运算模块 'Author: zyl910 'Version: V2.0 'Updata: 2006-4-29 'E- ...
- c语言位运算负数的实例_一招教你学会C语言中位运算
程序中的所有数在计算机内存中都是以二进制的形式储存的.位运算说穿了,就是直接对整数在内存中的二进制位进行操作.注意,位运算只针对于整数进行操作. 运算符号 运算规则 1.&与运算:对应两个二进 ...
- php位运算重要吗,PHP位运算的用途
下面为大家带来一篇PHP位运算的用途.现在就分享给大家,也给大家做个参考.一起过来看看吧 在实际应用中可以做用户权限的应用 我这里说到的权限管理办法是一个普遍采用的方法,主要是使用到"位运行 ...
- Java位运算优化:位域、位图棋盘等
快速小测试:如何重写下面的语句?要求不使用条件判断语句交换两个常量的值. if (x == a) x= b; else x= a; 答案: x= a ^ b ^ x; //此处变量x等于a或者等于b ...
- C语言截取整数的某些位编程,C语言中位运算的巧用(转)
一 .位运算实例 1.用一个表达式,判断一个数X是否是2的N次方(2,4,8,16.....),不可用循环语句. X:2,4,8,16转化成二进制是10,100,1000,10000.如果减1则变成0 ...
- [GO语言基础] 四.算术运算、逻辑运算、赋值运算、位运算及编程练习
作为网络安全初学者,会遇到采用Go语言开发的恶意样本.因此从今天开始从零讲解Golang编程语言,一方面是督促自己不断前行且学习新知识:另一方面是分享与读者,希望大家一起进步.前文介绍了Golang的 ...
- 第七周 位运算、布隆过滤、LUR和排序
[TOC] 一.位运算 XOR-异或 ^ x ^ 0 = x x ^ 1s= ~x //1s = ~0 x ^ (~x) = 1s x ^ x = 0 c = a ^ b ==> a ^ c = ...
最新文章
- 汇编语言--iret指令
- mysql1440秒未活动_phpMyAdmin登陆超时1440秒未活动请重新登录
- CF666B. World Tour
- 月薪多少_教师月薪多少?全国各地教师工资表来了
- .net core websocket
- StringUtils测试
- c#垂直投影法_形象理解“梯度”与“法向量”的关系
- 据说:一个线程性能相当于30%核心
- 计算机视觉知识点-人脸对齐
- C语言也能干大事第六节(如鹏基础)
- charles 本地IP地址
- python如何手动编写开根号的算法_手动开根号方法
- 腾讯云搭建 CentOS 可视化界面startx无效解决方法
- 教你怎么一下哄好赌气的女朋友​
- x的y次方python表达式怎么写_x 的 y 次方(xy) 以下表达式正确的是________
- 【收藏】如何优雅的在 Python设置报警通知(邮件、短信、电脑外放声音)
- Win10命令大全通用
- Arcmap地理配准png
- js逆向 | 七麦数据analysis加密逻辑分析(最新)
- MX25L1635D spi-flash芯片的读写记录