【转】位图法应用

来源:互联网 发布:ipadpro笔记软件 编辑:程序博客网 时间:2024/05/29 11:03

编程珠玑中第一章中所提到的位图问题。下面的文章是俺在 这里 看到的啦。

一、给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中

申请512M的内存 
一个bit位代表一个unsigned int值 
读入40亿个数,设置相应的bit位 
读入要查询的数,查看相应bit位是否为1,为1表示存在,为0表示不存在

二、使用位图法判断整形数组是否存在重复

判断集合中存在重复是常见编程任务之一,当集合中数据量比较大时我们通常希望少进行几次扫描,这时双重循环法就不可取了。

位图法比 较适合于这种情况,它的做法是按照集合中最大元素max创建一个长度为max+1的新数组,然后再次扫描原数组,遇到几就给新数组的第几位置上1,如遇到 5就给新数组的第六个元素置1,这样下次再遇到5想置位时发现新数组的第六个元素已经是1了,这说明这次的数据肯定和以前的数据存在着重复。这种给新数组 初始化时置零其后置一的做法类似于位图的处理方法故称位图法。它的运算次数最坏的情况为2N。如果已知数组的最大值即能事先给新数组定长的话效率还能提高 一倍。

示例代码如下:

 package  com.sitinspring;
 
 
/** 
  * 数组重复探测,时间复杂度为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 , 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 ,中存在重复元素.

三、使用位图法进行整形数组排序

 package  com.heyang;
 
 
/** 
  * 使用位图法进行整形数组排序 
  * 
 @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; 


}


原创粉丝点击