位图
位图原理
其实就是用bit组成的数组来存放值,用bit状态1、0代表存在、不存在,取值和存值操作都用位运算
限制是必须为连续范围且不能过大。好处是极大的节省空间,因为1个数字只占用1个bit的空间。
位图的实现
- Bitset(int n):初始化位图的大小,只支持0~n-1所有数字的增删改查
- void add(int num):把num加入到位图
- void remove(int num):把num从位图中删除
- void reverse(int num):如果位图里没有num,就加入;如果位图里有num,就删除
- boolean contains(int num):查询num是否在位图中
// 位图的实现
// 使用时num不要超过初始化的大小
public static class Bitset {
public int[] set;
// n个数字 : 0~n-1
public Bitset(int n) {
// a/b如果结果想向上取整,可以写成 : (a+b-1)/b
// 前提是a和b都是非负数
set = new int[(n + 31) / 32];
// 0~31:一个int 32~63:一个int
}
public void add(int num) {// 用1或进去
set[num / 32] |= 1 << (num % 32);// 1来到num所在位置上,把1或进去
}
public void remove(int num) {// 用0消除
set[num / 32] &= ~(1 << (num % 32));// 1101 1111
}
public void reverse(int num) {// 在/不在->不在/在: 与1异或
set[num / 32] ^= 1 << (num % 32);
}
public boolean contains(int num) {// 看这一位是1是0: 提取该位
return ( ( set[num / 32] >> (num % 32) ) & 1) == 1;
}
}
- 对数器测试
public static void main(String[] args) {
int n = 1000;// 0~999这些数字
int testTimes = 10000;// 一万次
System.out.println("测试开始");
// 实现的位图结构
Bitset bitSet = new Bitset(n);
// 直接用HashSet做对比测试
HashSet<Integer> hashSet = new HashSet<>();
System.out.println("调用阶段开始");
for (int i = 0; i < testTimes; i++) {
double decide = Math.random();
// number -> 0 ~ n-1,等概率得到
int number = (int) (Math.random() * n);
if (decide < 0.333) {
bitSet.add(number);
hashSet.add(number);
} else if (decide < 0.666) {
bitSet.remove(number);
hashSet.remove(number);
} else {
bitSet.reverse(number);
if (hashSet.contains(number)) {
hashSet.remove(number);
} else {
hashSet.add(number);
}
}
}
System.out.println("调用阶段结束");
System.out.println("验证阶段开始");
for (int i = 0; i < n; i++) {
if (bitSet.contains(i) != hashSet.contains(i)) {
System.out.println("出错了!");
}
}
System.out.println("验证阶段结束");
System.out.println("测试结束");
}
测试链接
https://leetcode-cn.com/problems/design-bitset/
-
位图的实现
- Bitset是一种能以紧凑形式存储位的数据结构
- Bitset(int n) : 初始化n个位,所有位都是0
- void fix(int i) : 将下标i的位上的值更新为1
- void unfix(int i) : 将下标i的位上的值更新为0
- void flip() : 翻转所有位的值
- boolean all() : 是否所有位都是1
- boolean one() : 是否至少有一位是1
- int count() : 返回所有位中1的数量
- String toString() : 返回所有位的状态
-
思路
- 用变量标识0,1,能够表示的数的个数 用reverse表示是否经过反转
- 每一次反转都将ones zeros交换,revserse取反
- 在进行插入,删除操作时, 要根据是否反转写出不同的操作
-
代码
-
class Bitset { private int[] set; private final int size;// 一共要支持多大的范围 private int zeros;// 0的数量 private int ones;// 1的数量 private boolean reverse;// 是否反转 public Bitset(int n) {// 初始化 set = new int[(n + 31) / 32];// 分配空间 size = n;// 大小 zeros = n; ones = 0; reverse = false;// 标志 } // 把i这个数字加入到位图 public void fix(int i) { int index = i / 32;// 属于哪个整数 int bit = i % 32;// 属于该整数的第几位 if (!reverse) { // 位图所有位的状态,维持原始含义 // 0 : 不存在 // 1 : 存在 if ((set[index] & (1 << bit)) == 0) {// 如果i这个数原本不存在位图中,加入i zeros--; ones++; set[index] |= (1 << bit); } } else { // 位图所有位的状态,翻转了 // 0 : 存在 // 1 : 不存在 if ((set[index] & (1 << bit)) != 0) { zeros--;// 个数的标识不变 ones++; set[index] ^= (1 << bit);// 0,1所代表的状态改变 } } } // 把i这个数字从位图中移除 public void unfix(int i) { int index = i / 32; int bit = i % 32; if (!reverse) { if ((set[index] & (1 << bit)) != 0) {// 存在 ones--; zeros++; set[index] ^= (1 << bit);// 反转 } } else { if ((set[index] & (1 << bit)) == 0) { ones--; zeros++; set[index] |= (1 << bit); } } } public void flip() { reverse = !reverse; int tmp = zeros; zeros = ones; ones = tmp; } public boolean all() { return ones == size; } public boolean one() { return ones > 0; } public int count() { return ones; } public String toString() { StringBuilder builder = new StringBuilder(); for (int i = 0, k = 0, number, status; i < size; k++) {// i用以判断最后一个int中的状态是否到达size number = set[k];// 取出每一个整数 for (int j = 0; j < 32 && i < size; j++, i++) {// 每个数的每一个状态 status = (number >> j) & 1;// 取出每一个数 status ^= reverse ? 1 : 0;// 看看是否被反转过 builder.append(status); } } return builder.toString(); } }
-