【数据结构(邓俊辉)学习笔记】向量06——位图

本文介绍了位图数据结构的原理,包括其结构、如何在O(1)时间复杂度下执行test(),set(),clear()操作,以及在去重和素数筛选中的应用。通过Bitmap类实例展示了如何在O(n)时间内处理字符去重和在O(nlogn)时间内计算素数。
摘要由CSDN通过智能技术生成

0.概述

位图(Bitmap)是一种特殊的序列结构,可用以动态地表示由一组(无符号)整数构成的集合。
在这里插入图片描述

  1. test() 判断k 是否存在集合S中。
  2. set() 将k 加入到集合S中。
  3. clear() 将k从集合S中移除。

若是32位,则长度U为 2 32 2^{32} 232,且其中每个元素的取值均为布尔型(初始值均为 false)。

1.结构

在这里插入图片描述
注意:bit的最小操作单位是8位,n+ 7是在做ceiling。

算法思想:

  • test算法:
    第一步:找bit所在基本单位区间,即以8个bit为单位的,k >> 3 即(k/8),即为入口。
    第二步:根据第一步找到的入口,计算offset值,k & 0x07 即(k%8),再生成掩码去操作single bit。
  • set算法 / clear算法:
    与test算法基本相同,将 & 改成 |= 或 &=~。
    在这里插入图片描述
    综上:
  • 就可在O(1)时间完成test set clear操作。
  • 位图向量所占的空间线性正比于集合的取值范。

2.实现

上述接口实现

class Bitmap { //位图Bitmap类
private:
   unsigned char* M;
   Rank N, _sz; //位图空间M[],N*sizeof(char)*8个比特中含_sz个有效位
protected:
   void init( Rank n )
      { M = new unsigned char[N = ( n + 7 ) / 8]; memset( M, 0, N ); _sz = 0; }
public:
   Bitmap( Rank n = 8 ) { init( n ); } //按指定容量创建位图(为测试暂时选用较小的默认值)
   Bitmap( char* file, Rank n = 8 ) { //按指定或默认规模,从指定文件中读取位图
      init( n );
      FILE* fp = fopen( file, "r" ); fread( M, sizeof( char ), N, fp ); fclose( fp );
      for ( Rank k = 0, _sz = 0; k < n; k++ ) _sz += test(k);
   }
   ~Bitmap() { delete[] M; M = NULL; _sz = 0; } //析构时释放位图空间

   Rank size() { return _sz; }
   void set   ( Rank k ) { expand( k ); _sz++; M[k >> 3] |=   ( 0x80 >> ( k & 0x07 ) ); }
   void clear ( Rank k ) { expand( k ); _sz--; M[k >> 3] &= ~ ( 0x80 >> ( k & 0x07 ) ); }
   bool test  ( Rank k ) { expand( k ); return M[k >> 3] &    ( 0x80 >> ( k & 0x07 ) ); }

   void dump( char* file ) //将位图整体导出至指定的文件,以便对此后的新位图批量初始化
   { FILE* fp = fopen( file, "w" ); fwrite( M, sizeof ( char ), N, fp ); fclose( fp ); }
   char* bits2string( Rank n ) { //将前n位转换为字符串——
      expand( n - 1 ); //此时可能被访问的最高位为bitmap[n - 1]
      char* s = new char[n + 1]; s[n] = '\0'; //字符串所占空间,由上层调用者负责释放
      for ( Rank i = 0; i < n; i++ ) s[i] = test( i ) ? '1' : '0';
      return s; //返回字符串位置
   }
   void expand( Rank k ) { //若被访问的Bitmap[k]已出界,则需扩容
      if ( k < 8 * N ) return; //仍在界内,无需扩容
      Rank oldN = N; unsigned char* oldM = M;
      init( 2 * k ); //与向量类似,加倍策略
      memcpy_s( M, N, oldM, oldN );
      delete[] oldM; //原数据转移至新空间
   }
};
  • 提供了一个dump()接口,可以将位图整体导出至指定的文件,以便对此后的新位图批量初始化。
  • 与可扩充向量一样,一旦即将发生溢出,这里将调用expand()接口扩容。可见,这里采用的也是“加倍”的扩容策略。

3. 应用

3.1 去重

在这里插入图片描述

  • 利用 Bitmap 类设计算法,在 O(n)时间内剔除 n 个 ASCII 字符中的重复字符,各字符仅保留一份

 1. 将非重复的ASCII字符视作一个集合,并将其组织为一个Bitmap结构——ASCII编码为k的字符,对应于其中第k个比特位。
 2. 初始时,该集合为空,Bitmap结构中的所有比特位均处于0状态。以下,只需在O(n)时间内遍历所有的输入字符,并对
ASCII编码为k的字符,通过set(k)接口将其加入集合。
 3. 请注意,这里使用的Bitmap结构只需128个比特位。因此,最后只需再花费O(128) = O(1)时间遍历一趟所有的比特位,
 并输出所有通过test()测试的比特位,即可完成字符集的去重。

3.2 筛法

  • 求素数
    算法思想:0和1自然不是,若2是,则认为2的倍数4,6,8均是,依次类推。最后留下来的则是素数。
    在这里插入图片描述
    实现思想:Bitmap B(n),记录待处理的数,0和1自然不是set掉,查看数i是否被set,若没有则以i为步长,set掉取余数。
    在这里插入图片描述
  • 利用 Bitmap 类设计算法,快速地计算不大二 10^8 的所有素数。
#include "../Bitmap/Bitmap.h" //引入Bitmap结极
/******************************************************************************************
* 筛法求素数
* 计算出不大于n的所有素数
* 不计内循环,外循环自身每次仅一次加法、两次判断,累计O(n)
* 内循环每趟迭代O(n/i)步,由素数定理至多n/ln(n)趟,累计耗时不过
* n/2 + n/3 + n/5 + n/7 + n/11 + ...
* < n/2 + n/3 + n/4 + n/6 + n/7 + ... + n/(n/ln(n))
* = O(n(ln(n/ln(n)) - 1))
* = O(nln(n) - nln(ln(n)) - 1)
* = O(nlog(n))
* 如下实现做了进一步优化,内循环从i * i而非i + i开始,迭代步数由O(n / i)降至O(max(1, n / i - i))
******************************************************************************************/
void Eratosthenes ( int n, char* file ) {
	Bitmap B ( n ); B.set ( 0 ); B.set ( 1 ); //0和1都不是素数
	for ( int i = 2; i < n; i++ ) //反复地,从下一
		if ( !B.test ( i ) ) //可认定的素数i起
			for ( int j = 2*i; j < n; j += i ) //以i为间隔
				B.set ( j ); //将下一个数标记为合数
	B.dump ( file ); //将所有整数的筛选标记统一存入指定文件,以便日后直接寻入
}

该算法可在O(nlogn)时间内计算出不超过n的所有素数。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值