位运算和比特数组

枚举查找和对分查找的优劣

枚举查找的劣势简直无需多言,有n个元素,那复杂度就是o(n),查找一个元素是如此地费劲!倘若我需要你做m次查找,复杂度就是o(n*m)。当然,我们可以用对分查找来优化,但是对分查找需要一个有序的数组,通过每次对折一半的方法,最终的复杂度是o(logn),小于线性。但是这还是不能让我们满意。
在解决图论问题时,我们用到了DFS。DFS要求不能经过重复的点,所以我们需要创建一个数组来记录走过的点,那么我们DFS到一个点时会用查找算法么…
写一个for循环…遍历整个数组?还是说,加入一个元素就来一个排序,最后用对分?线段树?

哈希散列表

别看这名字专业,其实这是一个很常用的技巧。
使用这种方法,可以使查找一个元素的复杂度变为o(1)。真的是o(1)!!!
在做DFS的时候,为了标记这个点有没有被搜索过,我们会用一个bool类型的数组vis来记录搜到的每一个点,我们如果搜到了点5,那就记录vis[5]=true(初始化都是false)。所以我们下次搜到一个点,比如搜到了点5,我们只需要if(vis[5])就好了。如果是true,就说明被搜过。
这是典型的牺牲空间换时间的做法(回忆一下桶排序)
当然,代价是你需要很大的空间来满足。
因此真正的哈希散列表还会对存入的元素取模,这里不细讲,但是,不论他怎么取模,都不会胜过我们接下来要讲的神奇的数组——比特数组!
在此之前,先普及一个知识:位运算

位运算

我给你一个提纲吧。
每一个数都有自己的二进制,计算机中,一个1或者0的二进制位都是占1byte(比特)的。一个int变量最大可以存到21亿,所以她的二进制位差不多有32个,占4kb左右。比如一个int类型的1,它在计算机的二进制里就是00000000000000000000000000000001,前面有31个0!
设二进制数a=1101,b=1001

  • !是按位取反的意思,就是把a的每一位二进制位都取反,例如1取反就是0。所以!a=0010那就是10
  • ~是数字取负,在加法中的应用如 ~11-1=-11-1=-12
  • ^是按位异或,说道这个,曾几何时,千万C++初学者在写一道题目:计算平面上两点的距离时,统统把
    “ ^”符号当做平方……然后不停地抱怨着编译器出问题了……然而这个符号其实是这么用滴:就是两个数对应的二进制位相同的时候,就变成0,否则是1,例如a ^b,结果是0100也就是100的意思,a和b的第二位不同,所以变1。
  • &这个是按位与,也就是说两个数对应的二进制位都是1的时候,结果的二进制位才会是1,否则是0,例如a&b,结果是1001。这个符号有一个妙用,比如c&1=1时,c是奇数,c&1=0时,c是偶数。因为奇数的最后一位二进制必然是1,所以和1相与,结果就会是1。(1可以看做0000001,前面有31个0)
  • | 这个是按位或。两个数对应的二进制位只要有一个,两个数随便哪一个是1的时候,结果的二进制位是1,否则是0。如a | b,结果是1101
  • <<这个是左移符号,就是把所有的1都往左移。移出来的位由0来补上。比如101<<2,就是把101左移两位,假如我们限制它的二进制位长度只能为3,那么移动结果就是100。
  • 和左移相反的是右移,就是这个>>,就是把所有的1都往右移。移出来的位由0来补上。比如101>>2,假如我们限制它的二进制位长度只能为3,那么移动结果就是001。就是1。

比特数组

这个数组也是一个int类型数组,然而它一个单位可以储存31个数。比如说a[1]可以存31个数字!
我们已经知道,一个int可以存32个2进制位,那么我们可以把每一个2进制位都利用起来存储数据!比如一个int的第24个二进制位我们设置为1,就代表存在24这个数了,是不是跟上文的vis很像呢?
我们这么存储一个数据:

void addnum(int x){
	data[x/32]|=1<<(x%32);
	return;
}

正如我们所言,每一个data对应的存储单位都可以达到31个。
当我们读入40的时候,它会存在data[1]中,存在data[1]中的第八位。那么他就左移8位,也就是把这个1放到第八位上。然后取或运算,保留了原来的数据,原来里面存的1还是1。
然后我们这样读取一个数据:

bool find(int x){
	return data[x/32]>>(x%32)&1;
}

确切的说是询问这个数存在与否。例如我们询问40,那么他就会找到data[1],右移那么多位然后与一个1,如果真的存在这个数,那与的结果必然是1,也就是返回了true了。
完整代码:

#include<iostream>
using namespace std;
#define maxm 10001
int data[maxm],n,m,num;

void addnum(int x){
	data[x/32]|=1<<(x%32);
	return;
}

bool find(int x){
	return data[x/32]>>(x%32)&1;
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>num;
		addnum(num);
	}if(find(m)) cout<<"yes";
	else cout<<"no";
	return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值