一、给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中
申请512M的内存
一个bit位代表一个unsigned int值
读入40亿个数,设置相应的bit位
读入要查询的数,查看相应bit位是否为1,为1表示存在,为0表示不存在
判断集合中存在重复是常见编程任务之一,当集合中数据量比较大时我们通常希望少进行几次扫描,这时双重循环法就不可取了。
位图法比较适合于这种情况,它的做法是按照集合中最大元素max创建一个长度为max+1的新数组,然后再次扫描原数组,遇到几就给新数组的第几位置上1,如遇到5就给新数组的第六个元素置1,这样下次再遇到5想置位时发现新数组的第六个元素已经是1了,这说明这次的数据肯定和以前的数据存在着重复。这种给新数组初始化时置零其后置一的做法类似于位图的处理方法故称位图法。它的运算次数最坏的情况为2N。如果已知数组的最大值即能事先给新数组定长的话效率还能提高一倍。
示例代码如下:
/**
* 数组重复探测,时间复杂度为O(n)
* @author: sitinspring(junglesong@gmail.com)
* @date: 2008-6-18-上午03:24:07
*/
public class DuplicatedArrayTest {
public static void main(String[] args){
int[][] arr={
{1,2,3,5,3,5,56,534,3,32},
{1,2,3,5},
{1,2,3,5,3,5},
{0,0,1,2,3,5,56,534,78,32},
};
for(int i=0;i<arr.length;i++){
System.out.print("数组:");
for(int temp:arr[i]){
System.out.print(temp+",");
}
System.out.print("中");
System.out.print(hasDuplicatedItem(arr[i])?"存在":"不存在");
System.out.print("重复元素./n");
}
}
/**
* 判断整形数组中是否有重复数据,时间复杂度为O(n)
* @param arr
* @return
*/
public static boolean hasDuplicatedItem(int[] arr){
// 扫描数组找最大值
int max=arr[0];
for(int i=1;i<arr.length;i++){
if(arr[i]>max){
max=arr[i];
}
}
// 按最大值创建一个新数组
int[] bitArray=new int[max+1];
// 按值向新数组中添值,如value为3则bitArray[3]=1
for(int value:arr){
if(bitArray[value]!=0){
// 如果value指向的位置已经不是零,说明之前已经给这一块置1了,立即返回true表示数组有重复
return true;
}
else{
// value指向的位置是零,则置为1表示这一位已经有数存在了
bitArray[value]=1;
}
}
return false;
}
}
输出:
数组: 1 , 2 , 3 , 5 ,中不存在重复元素.
数组: 1 , 2 , 3 , 5 , 3 , 5 ,中存在重复元素.
数组: 0 , 0 , 1 , 2 , 3 , 5 , 56 , 534 , 78 , 32 ,中存在重复元素.
/**
* 使用位图法进行整形数组排序
* @author 何杨(heyang78@gmail.com)
*
* @since 2009-2-11 上午08:51:24
* @version 1.00
*/
public class BitmapSorter {
public static void main(String[] args){
int[] arr={1,7,-3,0,0,6,6,9,-11};
bitmapSort(arr);
for(int i:arr){
System.out.print(i+",");
}
}
/**
* 使用位图法进行排序
* @param arr
*/
public static void bitmapSort(int[] arr){
// 找出数组中最值
int max=arr[0];
int min=max;
for(int i:arr){
if(max<i){
max=i;
}
if(min>i){
min=i;
}
}
// 得到位图数组
int[] newArr=new int[max-min+1];
for(int i:arr){
int index=i-min;
newArr[index]++;
}
// 重整arr中的元素
int index=0;
for(int i=0;i<newArr.length;i++){
while(newArr[i]>0){
arr[index]=i+min;
index++;
newArr[i]--;
}
}
}
}
在8K字节的内存空间内,如何存unsigned short类型数据?
一般做法:
定义一个数组:unsigned short arrNormal[4096];
这样做,最多只能存4K个unsigned short数据。
利用位图法:
定义一个数组:unsigned char arrBit[8192];
这样做,能存8K*8=64K个unsigned short数据。
写数据元素:计算待写入数据在arrBit存放的字节位置和位位置(字节0~8191,位0~7)
比如写1234,字节序:1234/8 = 154; 位序:1234 & 0b111 = 2,那么1234放在arrBit的下标154字节处,把该字节的2号位(0~7)置为1
字节位置:int nBytePos = 1234/8 = 154;
位位置: int nBitPos = 1234 & 7 = 2;
//把数组的154字节的2位置为1
unsigned short val = 1<<nBitPos;
arrBit[nBytePos] = arrBit[nBytePos] | val; //写入1234得到arrBit[154]=0b00000100
此时再写入1236,
字节位置:int nBytePos = 1236/8 = 154;
位位置: int nBitPos = 1236 & 7 = 4
.// /把数组的154字节的4位置为1
val = 1<<nBitPos;
arrBit[nBytePos] = arrBit[nBytePos] | val; //再写入1236得到arrBit[154]=0b00010100
读数据元素:按位读取arrBit,取得位为1的字节位置和位位置。元素值为8* nBytePos + nBitPos
for (i=0; i<8192; i++)
{
for (j=0; j<8; j++)
{
if (arrBit[i] & (1<<j))
{
cout << "arrBit:" << i << " " << j << " " << 8*i+j << endl;
}
}
}
会输出:
arrBit:154 2 1234
arrBit:154 4 1236
删除元素:计算待删除元素的字节位置和位位置:arrBit[nBytePos] &= ~(1<< nBitPos);
比如删除1234:arrBit[154] &= ~(1<<2);
位图法的缺点:
1.可读性差
2.位图存储的元素个数虽然比一般做法多,但是存储的元素大小受限于存储空间的大小。
位图存储性质:存储的元素个数等于元素的最大值。
比如,1K字节内存,能存储8K个值大小上限为8K的元素。(元素值上限为8K,这个局限性很大!)
比如,要存储值为65535的数,就必须要65535/8=8K字节的内存。要就导致了位图法根本不适合存unsigned int类型的数(大约需要2^32/8=5亿字节的内存)。
3.位图对有符号类型数据的存储,需要2位来表示一个有符号元素。这会让位图能存储的元素个数,元素值大小上限减半。
比如8K字节内存空间存储short类型数据只能存8K*4=32K个,元素值大小范围为-32K~32K。
综上所诉:位图法的适用范围很特殊。
五、模拟实现用位图法管理文件存储空间的分配与回收
// bitmap.cpp : Defines the entry point for the console application.
#include <stdio.h>
#include <malloc.h>
void display(int disk[8][8])//显示位示图
{
int i,j;
printf(" 磁道0记录块 磁道1记录块/n");
printf("记录块序号 0 1 2 3 4 5 6 7/n/n" );
for(i=0;i <8;i++)
{
printf("柱面%2d: ",i);
for(j=0;j <8;j++)
printf("%4d",disk[i][j]);
printf("/n");
}
printf("/n");
}
int maxspace(int disk[8][8])//返回最大的连续空间
{
int i=0;
int j=0;
int num=0;
int max=0;
for(i=0;i <8;i++)
for(j=0;j <8;j++)
if(disk[i][j]==0)
{
num++;
if(num>max)
max=num;
}else
num=0;
return max;
}
void fenpei(int disk[8][8])//分配程序
{
int i=0;int j=0;int num=0;
int space,max,ii,jj,m,n;
int g,gg,k,kk,z,c,x;
printf("请输入请求分配的空间大小:");
B: scanf("%d",&space);
while(space>=0)
{
if(space <0){
printf("/n输入非法,你输入的空间大小是负数!请重输:");
scanf("%d",&space);
}
//goto B;
max= maxspace(disk);
if(max==0){
printf("/n已经没有空闲区可以分配了!");
return;
}
else
{
if(space>max)
{
printf("/n要求空间太大,没有满足要求的空间。请重输:");
goto B;
}else{
for(i=0;i <=7;i++)
for(j=0;j <=7;j++)
if(disk[i][j]==0)
{
num++;
ii=i;
jj=j;
}else{
if(num>=space)
{
m=ii*8+jj-num+1;
g=m/8;//记录的是要分配的空间的首地址在二维数组中的行号;
k=m%8;//记录的是要分配的空间的首地址在二维数组中的列号;
z=g;//记录的是分配的空间的物理地址的柱面号
c=k/4;//记录的是分配的空间的物理地址的磁盘号
x=k%4;//记录的是分配的空间的物理地址的记录号
n=g*8+k+space-1;
gg=n/8;//记录的是要分配的空间的末地址在二维数组中的行号;
kk=n%8;//记录的是要分配的空间的末地址在二维数组中的列号;
for(i=g;i <=gg;i++)
for(j=k;j <=kk;j++)
disk[i][j]=1;
}
}//if(disk[i][j]==0)
}//if(space>max)
}//if(max==0)
return;
}//while(space <=0)
}
void huishou(int disk[8][8])//回收程序
{
#if 0
int z;int c;int x;int l;int g;int k;int m;int ii;int jj;
printf("请输入要回收的空间系数:/n");
printf("柱面号:");
scanf("%d",&z);
while(z>7||z <0)
{printf("非法输入!请输入0到7之间的数字:");
scanf("%d",&z);}
printf("/n请输入磁盘号:");
scanf("%d",&c);
while(c>1||c <0)
{ printf("非法输入!请输0或1:");
scanf("%d",&c);}
printf("请输入记录块号:");
scanf("%d",&x);
while(x>3||x <0)
{ printf("非法输入!请输0到3之间的数:");
scanf("%d",&x);}
printf("请输入回收空间长度:");
scanf("%d",&l);
while(l <=0)
{ printf("非法输入!请输大于零的正数:");
scanf("%d",&l);}
g=z;//g记录的是要回收的物理块的起址在二维数组中的行
k=c*4+x;//k记录的是要回收的物理块起址在二维数组中的列
m=g*8+k+l-1;
ii=m/8;//记录的是要回收的物理块的末尾地址在二维数组中的行
jj=m%8;//记录的是要回收的物理块的末尾地址在二维数组中的列
if(m <=63)//如果要回收的起址加长度小于等于数组的长度的话就按要求回收
{for(i=g;i <=ii;i++)
{for(j=k;j <=jj;j++)
disk[i][j]=0;}}
else//如果要回收的起址加长度大于数组的长度的话,就回收到数组结束的地方
{ for(i=g;i <=7;i++)
{for(j=k;j <=7;j++)
disk[i][j]=0;}
}
}
#endif
return;
}
int main()//主程序
{
char a=0;
int i,j;
int disk[8][8];//={{0,1,1,0,0,0,1,0},{0,0,0,0,1,0,0,1},{0,0,0,1,1,1,0,0},{0,1,0,1,0,1,1,0},{0,0,0,0,1,0,0,0},{1,1,0,0,0,1,0,0,},{1,0,0,0,0,1,1,0},{0,0,1,1,0,1,0,0}};
for(i=0;i <8;i++)
for(j=0;j <8;j++)
disk[i][j]=0;
display(disk);
while(a!=3)
{
printf("请选择你的操作:");
printf("1:分配空间 2:回收空间 3:退出/n");
printf("/n/n");
scanf("%d",&a);
switch(a)
{
case 1 :
printf("分配空间:/n");
fenpei(disk);
printf("分配后的主存分配情况:/n/n");
display(disk);
break;
case 2 :
printf("回收磁盘空间:/n");
huishou(disk);
printf("回收后的主存分配情况:/n/n");
display(disk);
break;
case 3 :
printf("/n退出");
break;
default:
break;
}
}
}