引入:位图是什么?
位图(Bitmap)是一种数据结构,用于表示和处理二进制图像或图形。它的作用是在有限的空间中表示和存储图像或图形的像素信息。每个像素都用一个位或几个位来表示其颜色或其他属性。
应用场景:
图像处理:位图广泛用于图像处理和计算机图形学领域。它可以表示真彩色图像、灰度图像或黑白图像,以及透明度、图层和特定效果等。
图像压缩:位图可以通过压缩算法来减小文件大小。一些常见的位图压缩算法包括JPEG和PNG。压缩后的位图在网络传输和存储方面更具效率。
图像编辑和绘图软件:许多图像编辑和绘图软件使用位图作为内部数据结构。用户可以对位图进行编辑、绘制、调整和处理,以创建或修改图像。
扫描仪和打印机:扫描仪用位图来捕捉图像或文档,并将其转换为数字形式。打印机则使用位图来生成图像或文档的输出。
图形界面(GUI):在计算机图形用户界面中,位图用于绘制图标、按钮、窗口等界面元素。位图还用于绘制和渲染图形效果,如过渡、阴影和纹理等。
游戏开发:位图在游戏开发中扮演重要角色。它可以表示游戏中的角色、场景、纹理、动画等图像元素。
需要注意的是,位图相对于矢量图形(Vector Graphics)具有一些限制,例如在放大时可能失去清晰度,文件大小可能较大等。因此,在选择使用位图还是矢量图形时,需要根据具体的应用场景和需求进行权衡。
一、前置知识点:左移、右移、无符号右移
<<:左移 左边最高位丢弃,右边补0(高位向左移动,低位补零)
若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。
>>:右移 最高位是0,左边补0;最高为是1,左边补1 ,即高位补符号位
为非负数时,>> 1和/ 2的结果是一样的
为负数且还是偶数时,>> 1和/ 2的结果是一样的
为负数且还是奇数时,>> 1和/ 2的结果是不一样的
>>>:无符号右移 无论最高位是0还是1,左边补0
二、位图的功能和实现
引入:假设有一个集合可以帮我们收集数字,既可以add,也可以去contains。但是存在空间问题:每个整型数字(起码)占4Byte(字节)。若集合中此时只有一个整数,而一个整数有32位(4Byte*8bit),足以表示0~31之间的数字在集合中有没有。
初始是:0000...00000000 --> 一共32位
把5位置标为1即可以表示集合中有5:0000...00100000
结论:只需要4个字节即32个bit即可表示0~31之间数字是否出现过。
推广:则int[32]-> 可以表示32*32=1024个数。
功能:位图可以做出一个集合,在数字范围(最大值)确定情况下,可使用位图收集数字并判断某个数存不存在。优点就在于它可以极大压缩空间。
实现:
public static class BitMap {
private long[] bits; //一个long类型可表示64个数
public BitMap(int max) {//在数字范围确定情况下传入最大值
//(max + 64) >> 6 -> (max + 64) / 64 即一共需要几位存数字
bits = new long[(max + 64) >> 6];
}
public void add(int num) {
/*
1.首先num/64可以定位到是哪个整数 num >> 6 -> num/64 -->找到第几个整数
2.(num & 63) -> (num % 64) 63即000..111111,&操作把前面全变成0了,剩下的等同于num%64 -->找到第几位用于描述num
3.why? 因为+ - * / %比位运算>> << & | ^慢多了
4. 1L<<(num & 63) -> 把1移到描述num的位置通过或运算标到位图里
例如170: 170/64=2,170%64=42, 1L<<42然后和000...0000进行或运算来完成num的add操作
*/
bits[num >> 6] |= (1L << (num & 63));
}
public void delete(int num) {
/*
例如删170,先170/64=2再170%64=42,然后到bit[2]中描述num的位置改成0;
不管描述num的位置是0还是1,和0进行&操作都会变成0->等同于删掉
*/
bits[num >> 6] &= ~(1L << (num & 63));
}
public boolean contains(int num) {
//描述num的位置和1进行&操作-> 若是1: 1&1->1 !=0 返回true即存在
return (bits[num >> 6] & (1L << (num & 63))) != 0;
}
}
三、使用位运算实现加法
两数相加思路:
int a = 46; //0101110
int b = 20; //0010100
1.异或运算也叫无进位相加
0101110 ^ 0010100 -> 0111010
2.得到a、b的进位信息:先&再左移1位
0101110 & 0010100 -> 0000100 -> 0001000
3. a+b=a^b+进位信息
0111010 + 0001000 = 1000010 -> 66
代码
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;
}
四、使用位运算实现减法
a-b即a+b的相反数
代码
public static int negNum(int n) {
return add(~n, 1);
}
public static int minus(int a, int b) {
return add(a, negNum(b));
}
五、使用位运算实现乘法
a=0110
b=0111
则a*b=ans=0110+01100+011000
代码
public static int multi(int a, int b) { //支持正负
int res = 0;
while (b != 0) {
if ((b & 1) != 0) { //等于0则不加此数
res = add(res, a); //结果接收a
}
a <<= 1; //左移右边补0
b >>>= 1; //无符号右移左边补0
}
return res;
}
六、使用位运算实现除法
小例:正数除以正数
a:01101100 b:00001100
2.小例:a/b=c 若按乘法思路: a=b*c
b=01110 c=00110
a=b*2¹+b*2² (c在第1、2位上是1) 反过来求c:
3.小例: a=01101100 b=00000011 求a/b
得到最终结果:100100(5位、2位有1)
4.小例:
代码
public static boolean isNeg(int n) {
return n < 0;
}
public static int div(int a, int b) { //会向下取整
//在正式逻辑开始之前一定都转成正数
int x = isNeg(a) ? negNum(a) : a; //得到正数绝对值形式
int y = isNeg(b) ? negNum(b) : b;
int res = 0;
// x/y
for (int i = 30; i >= 0; i = minus(i, 1)) {
//代码上是x去右移匹配y->因为y左移到头可能改变符号位
if ((x >> i) >= y) { //30->0位
res |= (1 << i); //移动到某个位置可以减掉时把结果设置上
x = minus(x, y << i); //此时y左移被x减掉
}
}
//如果a、b符号不一样,取个负号返回
return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
}
怎么解决系统最小值转绝对值
例子1:假设-10(最小)~9(最大)
问题:当使用-10/2无法转成10/2,因为系统最大没有10。
第一步: -10+1=-9
第二步:求-9/2=-4
第三步:再乘回去看差多少--> -4*2=-8 -->发现和-10相差-2
第四步:-2/2=-1
第五步:最后让-4加上-1等于-5
例子2:-15~14 问题-15/3=?
第一步:-15+1=-14
第二步:-14/3=-4
第三步:-4*3=-12 --> -15-(-12)=-3
第四步:-3/3=-1
第五步:-1+(-4)=-5
成功绕过-15无法转成绝对值的问题!
代码
public static int divide(int a, int b) {
//在所有整数中只有系统最小值是没法转成绝对值的->怎么解决系统最小值转绝对值?
if (a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
return 1; //1.ab都是系统最小,除完返回1
} else if (b == Integer.MIN_VALUE) {
return 0; //2.a不是最小,b系统最小,除完返回0 因为b的绝对值最大:a/b返回0
} else if (a == Integer.MIN_VALUE) { //3. a系统最小,b不是最小->再谈论
if (b == negNum(1)) {
//如果是系统最小除以-1则返回系统最大(力扣的约定)
return Integer.MAX_VALUE;
} else {
// a/b
// (a + 1) / b = c
// a - (b * c) = d
// d / b =e
// c + e
int c = div(add(a, 1), b);
return add(c, div(minus(a, multi(c, b)), b));
}
} else { //4. a、b都不是系统最小-->div方法直接用
return div(a, b);
}
}
七、完整代码
位图:
public class Code02_BitMap2 {
// 这个类的实现是正确的
public static class BitMap {
private long[] bits;
public BitMap(int max) {
bits = new long[(max + 64) >> 6];
}
public void add(int num) {
bits[num >> 6] |= (1L << (num & 63));
}
public void delete(int num) {
bits[num >> 6] &= ~(1L << (num & 63));
}
public boolean contains(int num) {
return (bits[num >> 6] & (1L << (num & 63))) != 0;
}
}
public static void main(String[] args) {
System.out.println("测试开始!");
int max = 10000;
BitMap bitMap = new BitMap(max);
HashSet<Integer> set = new HashSet<>();
int testTime = 10000000;
for (int i = 0; i < testTime; i++) {
int num = (int) (Math.random() * (max + 1));
double decide = Math.random();
if (decide < 0.333) {
bitMap.add(num);
set.add(num);
} else if (decide < 0.666) {
bitMap.delete(num);
set.remove(num);
} else {
if (bitMap.contains(num) != set.contains(num)) {
System.out.println("Oops!");
break;
}
}
}
for (int num = 0; num <= max; num++) {
if (bitMap.contains(num) != set.contains(num)) {
System.out.println("Oops!");
}
}
System.out.println("测试结束!");
}
}
加减乘除
// 测试链接:https://leetcode.com/problems/divide-two-integers
public class Code03_BitAddMinusMultiDiv {
public static int add(int a, int b) {
int sum = a;
while (b != 0) {
sum = a ^ b;
b = (a & b) << 1;
a = sum;
}
return sum;
}
public static int negNum(int n) {
return add(~n, 1);
}
public static int minus(int a, int b) {
return add(a, negNum(b));
}
public static int multi(int a, int b) {
int res = 0;
while (b != 0) {
if ((b & 1) != 0) {
res = add(res, a);
}
a <<= 1;
b >>>= 1;
}
return res;
}
public static boolean isNeg(int n) {
return n < 0;
}
public static int div(int a, int b) {
int x = isNeg(a) ? negNum(a) : a;
int y = isNeg(b) ? negNum(b) : b;
int res = 0;
for (int i = 30; i >= 0; i = minus(i, 1)) {
if ((x >> i) >= y) {
res |= (1 << i);
x = minus(x, y << i);
}
}
return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
}
public static int divide(int a, int b) {
if (a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
return 1;
} else if (b == Integer.MIN_VALUE) {
return 0;
} else if (a == Integer.MIN_VALUE) {
if (b == negNum(1)) {
return Integer.MAX_VALUE;
} else {
int c = div(add(a, 1), b);
return add(c, div(minus(a, multi(c, b)), b));
}
} else {
return div(a, b);
}
}
}