第五课-位运算

第五课-位运算
大约3500字,仔细阅读需要15分钟,如果上手执行,推理步骤,并书写代码大约需要50分钟。内容若存在错误请与我联系。

这一块内容是我认为比较重要的一个内容。其实更应该说是自己的一个盲区吧,今天算是填了一个小洞,心里还是十分高兴的。本来是想把这个内容也放在第四课讲的,但是真的发现位运算的魅力真的真的是蛮大的。这一块内容算是我写了这么多帖子以来,理解得最深刻的一部分了。

参考书书籍:
《算法笔记》第二版-曾磊,胡凡编著
《数据结构》第二版-高等教育出版社-陈越编著
《计算机组成与设计-硬件软件接口》

参考视频:国防科技大学-计算机原理

简单介绍一些运算符,这些运算在组成原理的数字运算上会讲到,过多的题目我在这里不去深究。但是为了做题目,我还是会尽量去举例子子。
^(异或):都为1,才为1
&(与):比如位上的数位A,那么A&1=A,A&0=0。那么可以通过与操作保留数位上的数
~(取反):1变成0,0编程1.
右移动:>>所有位向右移动若干位,高位溢出丢弃,低位补0
左移动:<<所有位向右移动若干位,高位溢出丢弃,低位补1

位运算题目 来源:leetcodeAcwing
leetcode 201. 数字范围按位与
leetcode 260. 只出现一次的数字 III
leetcode 461. 汉明距离
Acwing 801. 二进制中1的个数

位运算:
其实大部分题目的基本两个操作:
1.求n的第k位数字:n>>k&1

2.返回n的最后一位1:lowbit(n)=n&-n ,lowbit是树状数组的基本操作
lowbit返回的是x的最后(最右边)的一位1
如:x=1010, 那么lowbit(x)=10
x=101000, 那么lowbit(x)=1000

如何理解lowbit(n)=n&-n 呢?
比如

lowbit的应用:统计一个数x对应的二进制数,其中1出现的次数 ,比如底下的Acwing-801- 二进制中1的个数。但是这一题,我还是希望从原理上能够解答出这一题。让这篇博客的读者不仅仅拘泥于lowbit的应用。
题解如下:
它的模板做法如下:
我不妨去举例:
不妨求数字x=110110110中其中1出现的次数。其实还是很简单,lowbit提供一个较好的方法而已。
第一个在循环中,计算出lowbit(x)=110。x-lowbit=x=110110000,cnt++,此时cnt=1
第一个在循环中,计算出lowbit(x)=110000。x-lowbit=x=110000000,cnt++,此时cnt=2
第一个在循环中,计算出lowbit(x)=110000000。x-lowbit=x=110000000,cnt++,此时cnt=3.

#include <iostream>
using namespace std;
int lowbit(int x){
	return x&(-x);
}
int main(){
	int m;//代表输出的数个数
	cin>>m;
	while(m--){
		int x;
		cin>>x;
		int cnt=0;
		while(x) x-=lowbit(x),cnt++;//每次减去x的最后一位1 
		cout<<cnt<<" ";
	} 
	return 0;
} 

但是我更希望从原理上能够做出来这一道题目:
主要的实现功能在函数上:虽然传递的参数是十进制n,但是在计算机中会进行转化,变成二进制。n&1.比如n二进制的为:110110110,那么n&1为:000000001.
不管有没有计算出,n进行右移动,n=011011011



重复操作,就能得出结果。
代码如下:

#include <iostream>
using namespace std;
int count(int n){
    int cnt=0;
    while(n>0){
        if((n&1)==1){
            cnt++;
        }
        n=(n>>1);
    }
    return cnt;
}
int main(){
    int m;
    cin>>m;
    while(m--){
        int n;
        cin>>n;
        int res=count(n);
        cout<<res<<" ";
    }
    return 0;
}

但是,又但是有更好的方法,分而治之。这个有点负载,大概说一下思路。
以为x=11001100为例子。以两个数为一对。然后与四个连续的01过滤器)对相与(有点复杂)。
步骤如下:
1.用11001100&01010101=01000100,设结果为a1
2.将x右移动:此时x=01100110,重复步骤一,此时为01000100.设结果为a2
3.a1&a2:01000100+01000100=10001000分队分成四对:10 00 10 00
对应的十进制为2 0 2 0
所以1出现的次数为=2+0+2+0=4

同样:可以分成两对:1100 1100
那么过滤器就要设置为0011 0011
步骤还是一样的。
这样,在时间复杂度上面节约很多。因为不涉及++,>>等操作。
当电脑配置为64位时,这样做效率上就会显得十分高效,而且逻辑真的是很清晰。

题目二:201. 数字范围按位与

说一说我的做法:

#include <iostream>
// bitwise operation
using namespace std;
int rangeBitwise(int m, int n) {
	int result=m;
	for(int i=m;i<=n;i++){
		result=(result&i);
	}
	return result;
}
int main(){
	int m,n;
	cin>>m>>n;
	int outcome=rangeBitwise(m,n);
	cout<<outcome;
	return 0;

显然这样的做法是不可能过去的,O(n)的复杂度在这一题显得不那么允许了。
在这里插入图片描述
但是通过位运算就可以了。
我们不妨分析一下位运算的过程:然后给的区间的是【m,n】,显然m<n。不妨假设
m=10000011
n=10001001

那么m~n之间的数应该是
10000011
10000100
10000101
10000110
10000111
10001000
10001001
进行与操作之后,我们发现结果为10000000.很容易发现前四位是相同的,而且前面又讲到了**&操作的性质:A&1=A,A&0=0**
那么只要存在0,那么一整列计算出来就是0,而且数字的进位会产生0.我们从总体观上去观察。从10000011到10001000.肯定经历了10000111,10001000。所以我们只要观察前缀相等即可。
所以结果应该为10001001
代码编写如下:

#include <iostream>
// bitwise operation
using namespace std;
int rangeBitwise(int m, int n) {
	int result=0;
	while(m!=n){
		m>>=1;
		n>>=1;
		result++;
	}
	return m<<result;
}
int main(){
	int m,n;
	cin>>m>>n;
	int outcome=rangeBitwise(m,n);
	cout<<outcome;
	return 0;
} 

在这里插入图片描述

关于上面空余的两题,我一定会在空闲时间把代码上传上去。并且位运算的题目以及位运算本身的内容,之后的日子里,我一定还会进行补充。

猜你喜欢

转载自blog.csdn.net/weixin_44110100/article/details/107008204