位图加位运算实现加减乘除
简单来说明一下什么是我们的位图
首先 我们在进行数据处理的时候 很多情况下避免不了储存数据,而这个时候我们就会开辟内存 创建数组进行存储我们的数据
但是我们以整形为例 int 占4个字节的空间,
假设我想要表示0-32的数据 大家要知道的是1个字节是8个bit位 是,我们完全可以通过一个整形,来表示我们的32位数,假设我们想要表示的数据是0-31之间 那么我们用一个整形就可以搞定
要是想要表示更多的数据那么 我们可以创建一个数组进行存储我们的数据进入,这样就会节省我们的空间
我们要是相要表示0-1023 那就是1024个数
1024/32=32 我们准备一个32长度的数组 我们就能表示1024个数
那么第一个数组里面的元素可以表示一下 我们的0-31之间有没有出现过数据
位图的功能就是可以做出一个集合
0-63 a%64===a&63
因为我们的一个num的数据其实是%64就是 获取到他们二进制的后7位
假设我们要表示的数据的范围是0-1023 那么就是1024个数
1024/32=32 那么我们可以安排一个32长度的数组
这样进行操作其实我们是将我们空间复杂度大大降低了32倍,就是减少了32倍几乎是
1.创建一个位图
public BitMap(int max) {
bits = new long[(max + 64) >> 6];//这里确定我们的大小是多大 确定我们的数据有多大
}
2.添加一个数据
public void add(int num) {
//如何把170放到我们的数组里面去?
//num%64(这个就是获得)===num&63
//我们想办法将那个要放的数据的位置放置为1 代表这个数据进入了
//|=就是为了我们的数据是设置成为1 从而进行的操作进行处理
bits[num >> 6] |= (1L << (num & 63));//这一步就是把我们的数据设置成为1 代表出现过
//因为我们的二进制的位置就是 后7位就是我们的2进制的数据 但是其实上 我们的数据不止止是那些 我们只需要获取到我们的后七位就ok
首先第一步我们要进行的是找到我们的170是位于什么位置 首先 我们可以定位出来的是我们的这个数据是位于第几个位置的 这个是毋庸置疑的 然后我们要将具体位置的数据变成1 就是那一位变成1
简单说一下 num&63====num%64
为什么那 因为一个数据&63的话就会把那个数的二进制的后7位原封不动的保留下来了,这样我们也就获取到了我们的余数前面的位都会变成0 只有后7位是原封不动的保留,
那为什么要采取&63而不是%64那 这个是因为位运算的速度相比于我们的正常的%速度要快的多
那么添加一个数据的核心就是将我们确定位置的数从0变成1
那么如何删除一个数据
首先也是要先定位到那个位置 把他变成0就行
public void delete(int num) {
//如何删掉 就是把我们刚刚改成1的位置改成0就行了 我只管这一位 只要设置成为0就ok
bits[num >> 6] &= (~(1L << (num & 63)));
}
3.如何判断一个数据有没有存在
//我们可以先确定的是 我们要从哪个位置进行处理 然后说实话
public boolean contains(int num) {
return (bits[num >> 6] & (1L << (num & 63))) == 0 ? false : true;
}
这个也是处理 我们就先进行判断一下 找到那个位置如果为0 那么就是不存在 否则存在
加法
如何使用位运算实现我们的加法那
首先我们要知道一个操作就是 a^b就是无进位的加法
(a&b)<<1这个就是获取到我们的进位的信息 他们两个相加就行 但是注意我们进行操作处理的过程中不能出现加法 所以说 我们要重复刚刚的动作 一直到没有进位
public static int add(int a, int b) {
int sum = a;
while (b != 0) {//当不存在进位的时候
sum = a ^ b;//这个结果就是我们的不进位相加
b = (a & b) << 1;//这个就是我们的进位的数据b->b'
a = sum;//a->a'
}
return sum;//返回我们的sum值就ok了
}
2.如何实现我们减法??
我们把b换成-b不就好啦
但是您忘了 我们不能出现-号
那么怎么操作 首先 我们要知道一个小常识就是
一个数的负数等于~a+1
等于这个数进行按位取反之后加上1就OK啦
首先先写一个求相反数的一个函数
public static int negNum(int n) {
return add(~n, 1);//一个数的负数等价于什么? 等价于我们的~n+1
}
减法函数登场
public static int sub(int a, int b) {
return add(a, negNum(b));
}
乘法
首先我们先回顾一下正常二进制直接的乘法就是
你可能已经懵逼了 但是不用慌张 慢慢看 我们一步步进行解析一下
什么意思那 就是说 我们先进行的是对b数据进行观察 如果是1的话 就将a原封不定的加入,但是第二次加的时候a就要想左移动 因为这个是规律 下一次a继续想左移动 于此b会向右进行移动 但是进行的是无符号的移动 因为一旦涉及到右移 如果右移带符号的话 我们的这个循环就结束不了。
下面上代码
public static int mul(int a, int b) {
int res = 0;//这个代表我们的结果
while (b != 0) {
if ((b & 1) != 0) {
res = add(res, a);//
}
a <<= 1;//这个就是将我们的a向左移动
b >>>= 1;//这个就是将我们的b向右进行移动 获取它的下一位这个是用0来补 是因为>>>
}
return res;
}
除法
这里我们可以简单的进行的是除法的处理
public static int divide(int a, int b) {
//但是注意一下就是我们的系统最小值是不能转化为我们的正数的其实是
// 我们只能转化为我们的不是系统最小值的数据 我们只能转化非系统最小值才行
int x = isNegative(a) ? negNum(a) : a;
int y = isNegative(b) ? negNum(b) : b;
int res = 0;
//是x/y
//011000
//000011
for (int i = 30; i >= 0; i = sub(i, 1)) {//这个for循环也是遵循的是我们的减减的原则进行处理的
if ((x >> i) >= y) {//你直接右移30位的话 就直接不可能大于我们的y就还不行 因为数据就直接溢出啦
res |= (1 << i);//这个就是让我们的数据变成1
x = sub(x, y << i);//然后我们的x就把b数据往左移动相应的位置
}
}
return isNegative(a) ^ isNegative(b) ? negNum(res) : res;//这里再次进行一次判断的处理如果说是负数的话
//我们就会进行的是负数的处理实现 实现我们的流程 如果不是的话 就会进行的是我们相反数的进行处理
}
说一下我们的思路
a/b=c
我们如何获取到c的值
a=1001
b=0010
c=?
我们首先将我们的a向右移动 看看什么时候会大于b
首先可以想右移动30位 29位。。。。。。
我们会发现向右移动2位
发现这个时候我们的数据是大于的
那么这个时候我们就可以把b像左移动2位进行处理
这就是一个相反的过程而已
然后a就变成
0001这个时候怎么移动都不会相减成功
那么
c=0100
只有第二位是1
那么我们的操作流程就结束了
这个的实现思路就是我们先进行的流程就是说如何将我们的数据进行对我们的流程进行完善处理
我们先具体的举个例子处理进行展示我们的数据流程
假设
b=00110
c=01110
a/b=c
那么我们的a=b×21+b×22+b×2^3
我们就是通过我们的数据不断的移动获取到上边的那个指数是多少
从而确定到底什么位置是我们的结果数据
0
只有第二位是1
那么我们的操作流程就结束了
这个的实现思路就是我们先进行的流程就是说如何将我们的数据进行对我们的流程进行完善处理
我们先具体的举个例子处理进行展示我们的数据流程
假设
b=00110
c=01110
a/b=c
那么我们的a=b×21+b×22+b×2^3
我们就是通过我们的数据不断的移动获取到上边的那个指数是多少
从而确定到底什么位置是我们的结果数据