来源:https://blog.youkuaiyun.com/dQCFKyQDXYm3F8rB0/article/details/82864223
思路:先使用bitmap算法进行标记,再遍历一遍标记即可找出缺失的
扩展:如果用一个较小的内存对大量数据进行排序,如使用10M内存1G数据排序,也可以采用这种思路。bitmap算法使用二进制标识一个数据是否出现,一个long类型64位,可以标记64个不同的数据,但只占8Byte。例如一个long可以标记0~63,那么2个long可以标记0~127,即x个long数据可以标记2exp(x+5)个数据。1G的数据如果范围确定,比如int范围2exp(32),则只需要2个long即可标记,27*8=216Byte,这远小于10M。对于排序,如果要求相同的数据仍然出现在排序后的结果中,可以用一个二维数组记录个数,如果相同的可以虑掉,则更简单,使用一维数组就可以解决了。
package algorithm;
/*
* @author weijie
* 使用bitmap算法实现了查找0~100的数据中缺失的数据
* 实现过程可优化为更通用的,如查找整数x~y的数据中缺失的数据
* 同样可以优化为对大量数据进行排序
* 关键的是标记过程
*/
public class BitMap {
public static void main(String[] args)
{
int[] array = {0,1,64,31,32,63,64,65,100,101,127,128,135};
bitMap(array);
}
public static void bitMap(int[] array)
{
long[] check = new long[2];
check = mark(array);
int[] result = new int[101];
System.out.println(Long.toBinaryString(check[0])+" "+Long.toBinaryString(check[1]));
result= searchMissingNumber(check);
for(int i=0;i<result.length;i++)
{
if(result[i]!=200) System.out.println(result[i]);
}
}
public static long[] mark(int[] data)
{
if (data.length <= 0) {
long[] mark_result = { 0l, 0l };
return mark_result;
}
long[] check = { 0l, 0l };
int min = 0;
int max = 100;
long num1 = 1l;
int[] result;
for(int i=0;i<data.length;i++)
{
long temp=0;
if(data[i]<64&&data[i]>=min)
{
temp = num1<<data[i];
if((check[0]&temp)!=temp) check[0]+=temp;
}
else if(data[i]>=64&&data[i]<=max)
{
temp = num1<<(data[i]-64);
if((check[1]&temp)!=temp) check[1]+=temp;
}
}
return check;
}
/*如果定义一个数组,数组元素初始化为0,那么怎么知道一个0是来自需要识别的0还是初始化的0呢?
*思路,初始化为范围之外的数*
*/
public static int[] searchMissingNumber(long[] array)
{
if(array.length==0)
{
int[] result = {};
return result;
}
final int max=100;
int[] result = new int[max+1];
//初始化数组为一个范围之外的数
for(int i=0;i<max+1;i++) result[i]=200;
long num1=1;
//数组的索引
int index=0;
for(int i=0;i<array.length;i++)
{
long temp =1;
for(int j=0;j<64;j++)
{
temp = num1<<j;
if((array[i]&temp)!=temp)
{
//如果超出范围,则这一轮循环不需要再算下去了
if((j+64*i)>max) break;
result[index] = j+64*i;
index++;
}
}
}
return result;
}
}
测试结果:
经过优化,变得更加通用,待识别的数据不需要从0开始,最大值在int范围即可,标记数组的长度根据实际数据范围确定。标记位仍然从0开始,只是对应的不是数据0,而是第一个数据。这个标记方法mark()可用于对大量数据的排序,只不过要分批调用这个方法,将标记数组markArray作为参数传入
package algorithm;
/*
* @author weijie
* 使用bitmap算法实现了查找0~100的数据中缺失的数据
* 实现过程可优化为更通用的,如查找整数x~y的数据中缺失的数据
* 同样可以优化为对大量数据进行排序
* 关键的是标记过程
*/
public class BitMap {
public static void main(String[] args)
{
int[] array = {0,1,64,31,32,63,64,65,67,83,95,98,100,101,127,128,135};
bitMap(array,1,100);
}
public static void bitMap(int[] array,int min, int max)
{
if(max<min) return;
long[] check;
//标记给定数组中出现的数据
check = mark(array,min,max);
int[] result ;
for(int i =0;i<check.length;i++)
System.out.print(Long.toBinaryString(check[i])+" ");
System.out.println();
result= searchMissingNumber(check,min,max);
for(int i=0;i<array.length;i++) System.out.print(array[i]+" ");
System.out.println();
for(int i=0;i<result.length;i++)
{
if(result[i]<=max) System.out.print(result[i]+" ");
}
}
/*给定的数据如果不从0开始,标记仍然从0开始,但是比较时需要处理原始数据
* 即如果从5开始,则标记位的0位表示5
*/
public static long[] mark(int[] data, int min, int max)
{
if (data.length <= 0) {
long[] mark_result = { 0l };
return mark_result;
}
//计算标记min到max范围的数据需要的long类型的个数
int lengthOfMarkArray = 0;
if((max-min+1)%64==0) lengthOfMarkArray = (max-min+1)/64;
else lengthOfMarkArray = (max-min+1)/64+1;
long[] markArray = new long[lengthOfMarkArray];
for(int i=0;i<markArray.length;i++) markArray[i] = 0l;
long num1 = 1l;
for(int i=0;i<data.length;i++)
{
long temp=0;
//超出范围的数据不需要标记,本身也没有定义那么长的标记数组
//待标记的数据未必从0开始,但是标记位仍然从0开始
if(min<=data[i]&&data[i]<=max)
{
temp = num1<<(data[i]-min);
if((markArray[(data[i]-min)/64]&temp)!=temp) markArray[(data[i]-min)/64]+=temp;
}
}
return markArray;
}
/*如果定义一个数组,数组元素初始化为0,那么怎么知道一个0是来自需要识别的0还是初始化的0呢?
*思路,初始化为范围之外的数*
*返回到是从0开始搜索的,如果待判断的数据从n开始,则取数据可以在返回的数据上加n
*/
public static int[] searchMissingNumber(long[] array, int min, int max)
{
if(array.length==0)
{
int[] result = {};
return result;
}
//final int max=100;
int[] result = new int[max-min+1];
//初始化数组为一个范围之外的数,这里如果max为int的最大值,则有问题
for(int i=0;i<max-min+1;i++) result[i]=max+1;
long num1=1;
//数组的索引
int index=0;
for(int i=0;i<array.length;i++)
{
long temp =1;
//if(i*64>max) break;可以不需要,因为array数组的长度是按需(min~max)定义的
for(int j=0;j<64;j++)
{
//如果超出范围,则这一轮循环不需要再算下去了
if((j+64*i)>max) break;
temp = num1<<j;
if((array[i]&temp)!=temp)
{
result[index] = j+64*i+min;
index++;
}
}
}
return result;
}
}
测试如下: