1.位图法介绍
普通的位图法,一般指开辟一个大型的位空间如int、char、long数组等,具体的数值存储在数组的下标中。比如在java中一个普通的整型变量可以存储32个数值,如果是整型数组能存储的数值呈指数增长,当int数组长度为N时,则能存储的数值范围为0~(1<<32-1)。这里的位图存储的数值要求是无重复的,故可用于判重复的算法或者对无重复的数字序列排序中,详细介绍请参见点击打开链接 而有些时候需要对有重复的数字序列排序或者其他重复数字序列上的其他操作需求时,前文所述的这种不允许重复的位图无能为力,下面介绍一种允许重复数值的位图。
2.允许重复数值的位图
这里要求提前知道每个数值的最多重复次数。若已知每个数值的最多重复次数为10,则可以考虑采用4位来存储每个数值,这里存储数值所需要的空间将会原来普通的位图的4倍。因为每个数值占用了多个空间,一个数值的存储很有可能会跨越多个数组中的多个下标,故每次增加和删除都需要进行相应的判断。本文实现的位图要求每个数组最多重复次数不超过256个,因为一旦超过一个数值的存储将跨越三个下标及以上。因为最多重复次数不超过256个,故最多跨越两个下标及以上,也是基于此思想在3.3小节设计了相应的增加、删除函数的实现。
3.实现代码
3.1 基本数据结构
private int SHIFT=5;// 因为是用int来存储
private int[] word= null;//存储位图数据
private int MASK = 0x1F;
private int K;//每个数值需要占据的位的个数
private int wordInUse;//表示使用的word个数
3.2采用Location结构来存储边界位置
//该内部类是用于存储所在的word中的索引以及在当前int型数据中的位置
private class Location {
int index;
int loc;
public Location(int index, int loc) {
super();
this.index = index;
this.loc = loc;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public int getLoc() {
return loc;
}
public void setLoc(int loc) {
this.loc = loc;
}
@Override
public String toString() {
return "Location [index=" + index + ", loc=" + loc + "]";
}
}
3.3 公共函数
private Location getLowIndex(int i) {
// 这里表示当前数值的存储上边界在word中的索引以及对应的int型中的位置
if(i<0||i>wordInUse<<SHIFT) return null;
int leftLoc = i*K;
int leftIndex = leftLoc>>SHIFT;//找到word中的索引
int loc = leftLoc&MASK;
return new Location(leftIndex,loc);
}
private Location getHighIndex(int i) {
if(i<0||i>wordInUse<<SHIFT) return null;
int rightLoc = (i+1)*K-1;
int rightIndex = rightLoc>>SHIFT;//找到word中的索引
int loc = rightLoc&MASK;
return new Location(rightIndex,loc);
}
3.4操作函数
// 增加一个数值
public void add(int i) {
Location low = this.getLowIndex(i);
Location high = this.getHighIndex(i);
if(low != null && high != null) {
if(low.getIndex() == high.getIndex()) {
// 考虑直接在当前的int中加1
word[low.getIndex()] += 1<<low.getLoc();//在对应位置上加1
}else {
// 如何在上下界中进行加1
// 先考虑上界部分是否全是1
int lowValue = word[low.getIndex()]|(~(1<<high.getLoc()));
if(lowValue == 0xffff) {
// 考虑在下界部分加1
word[high.getIndex()]++;
}else {
word[low.getIndex()] += 1<<low.getLoc();
}
}
}else {
throw new RuntimeException();
}
}
// 删除一个数值
public void delete(int i) {
if(test(i)<1) throw new RuntimeException();
Location low = this.getLowIndex(i);
Location high = this.getHighIndex(i);
if(low != null && high != null) {
if(low.getIndex() == high.getIndex()) {
word[low.getIndex()] -= 1<<low.getLoc();//在对应位置上减1
}else {
// 考虑下界全0,从上界的高位借1再进行计算
int lowValue = word[low.getIndex()]>>low.getLoc();
if(lowValue == 0) {
// 考虑在左边部分减1,右边部分变成fff样式
word[high.getIndex()] -- ;
int tmp = 1<<SHIFT-high.getLoc()+1;
word[low.getIndex()] |= (~(1>>tmp))<<low.getLoc();
}else {
word[low.getIndex()] -= 1<<low.getLoc();//在对应位置上加1
}
}
}else {
throw new RuntimeException();
}
}
// 测试当前数值的个数
public int test(int i) {
Location low = this.getLowIndex(i);
Location high = this.getHighIndex(i);
if(low != null && high != null) {
if(low.getIndex() == high.getIndex()) {
int tmp = word[low.getIndex()];
tmp>>=low.getLoc();
tmp&=~(1<<(K+1));
return tmp;
}else {
// 将两端的数值加起来
int lowValue = word[low.getIndex()]>>low.getLoc();
int highValue = word[high.getIndex()]&(~(1<<(high.getIndex()+1)));
return highValue<<(1<<SHIFT-low.getLoc())+lowValue;
}
}else {
throw new RuntimeException();
}
}
3.4 测试
public static void main(String[] args) {
MyBitSet set = new MyBitSet(5000,10);
set.add(100);
set.add(100);
set.add(1000);
set.add(1000);
set.delete(1000);
set.delete(1000);
System.out.println(set.test(100)); //结果:2
System.out.println(set.test(1000));//结果:1
}