位图的设计与实现
位图的设计与实现
作者:Grey
原文地址:
说明#
本文内容使用的编程语言是 Java。其他语言有类似的数据结构。
原理#
在 Java 中,使用HashSet
可以实现如下操作:
add(T v)
加入一个元素到
HashSet
中,重复则覆盖。
contains(T v)
判断一个元素是否加入过
HashSet
。
remove(T v)
从
HashSet
中删除一个元素。
如果数据范围固定,使用位图比使用HashSet
省空间。
在 Java 中,一个int
类型的整数可以表示 32 个bit
,所以,如果数据范围是int
类型的数来完成上述三个操作。
例如:
int
类型数(二进制表示,初始化全为0)中,把第 4 号位置设置为 1:
继续执行int
类型数(二进制表示)中,第 7 号位置设置为 1。如下图
如果数据范围是 0 ~ 1023
, 则可以用一个int
类型数组来表示,这个数组只需要 32 个元素即可。因为 32 个int
类型元素,可以表示 1024 位,正好可以覆盖数据范围中的所有数字。对于0 ~ 1023
中任意一个数num
,num
在数组中存在第num / 32
个元素中的第num % 32
位中。
举例说明:
num = 37
,客观上,num
应该在如下位置:
在 1 号(即:37 / 32
)数组元素的第 5 个bit
(即:37 % 32
)位置上。
实现#
为了扩大表示范围,我们可以使用long
类型来替代int
类型,因为long
类型可以表达 64 个bit
,思路还是和上述过程一样。现在说明如何实现上述三个方法,
先把位图的数据结构和相关方法定义好
public static class BitMap {
// 使用每个bit位置的信息。
private final long[] bits;
public BitMap(int max) {
// TODO
// 位图初始化
}
public void add(int num) {
// TODO
// 添加一个元素
}
public void remove(int num) {
// TODO
// 删除一个元素
}
public boolean contains(int num) {
// TODO
// 判断一个元素是否在位图中
}
}
注:这里只需要考虑非负数,对于负数的情况,也可以转换成正数来处理,比如:-3~6
,可以转换成0~9
。
首先是位图的初始化,即:如何根据数据范围确定位图应该开辟多大的数组?
由于是long
类型,所以,对于
(x + 64) / 64
这么大的long
类型数组。
位图中增加一个元素,比如我们要增加 53 这个元素,先定位它是数组中的哪个元素,即53 / 64 = 0
,第 0 号位置的元素,再定位是这个元素中的第几个bit
位,即:53 % 64 = 11
,即第 11 个bit
位,我们可以用 1L << 11
后的值与(|)
上bit[0]
即可,代码实现如下
public void add(int num) {
bits[num / 64] |= (1L << (num % 64));
}
由于 num / 64
其实就是 num >> 6
,
num % 64
其实就是num & 63
,
由于位运算比算术运算效率要高,所以
public void add(int num) {
// bits[num / 64] |= (1L << (num % 64));
// num % 64 ---> num & 63
// 只适用于 2 的 n 次方
bits[num >> 6] |= (1L << (num & 63));
}
位图中删除一个元素,其实就是把对应位置的二进制位置为 0,其他位置保持不变,通过
~((1L << (num & 63)))
可以预先得到一个除目标位置是 0,其他位置都是 1 的数。
然后通过这个数去与(&
)数组目标位置的元素,即可把对应位置的 1 改为 0,其他位置不变。
public void remove(int num) {
bits[num >> 6] &= ~(1L << (num & 63));
}
位图中是否包含某个元素,其实就是判断对应位置是0还是1, 如果是0 ,就说明存在,不是0 , 则不存在。
public boolean contains(int num) {
return (bits[num >> 6] & (1L << (num & 63))) != 0;
}
位图的完整代码见
// 位图
class BitMap {
private final long[] bits;
// 初始化
public BitMap(int max) {
// 准备多少个整数? 0 ~ 63 需要1个整数
// >> 6 就是 除以 64 >> 效率比除法高
bits = new long[(max + 64) >> 6];
}
public void add(int num) {
// bits[num / 64] |= (1L << (num % 64));
// num % 64 ---> num & 63 即:0b111111
// 只适用于 2 的 n 次方
// 注意:这里是1L非1,如果是1,因为要管64位
bits[num >> 6] |= (1L << (num & 0b111111));
}
public void remove(int num) {
bits[num >> 6] &= ~(1L << (num & 0b111111));
}
public boolean contains(int num) {
return (bits[num >> 6] & (1L << (num & 0b111111))) != 0;
}
}
测试#
通过实现的位图和Java
自带的HashSet
进行对比测试,可以判断我们写的位图是否正确,测试代码如下:
注:需要引入junit
包
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
package git.snippet.bit;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.HashSet;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("位图测试")
public class BitMapTest {
@Test
void testBitMap() {
// System.out.println("test begin");
int max = 70000;
BitMap bitMap = new BitMap(max);
Set<Integer> set = new HashSet<>();
int testTime = 90000000;
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.remove(num);
set.remove(num);
} else {
assertEquals(bitMap.contains(num), set.contains(num));
}
}
for (int num = 0; num <= max; num++) {
assertEquals(bitMap.contains(num), set.contains(num));
}
}
}
运行,未打印报错信息,说明我们的算法正确。
本文所有图例见:ProcessOn:位图的使用和实现
更多#
参考资料#
作者:GreyZeng
出处:https://www.cnblogs.com/greyzeng/p/16634282.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
你可以在这里自定义其他内容
本文来自博客园,作者:Grey Zeng,转载请注明原文链接:https://www.cnblogs.com/greyzeng/p/16634282.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南