数据结构-bitmap剖析

本文深入探讨了位图数据结构的原理与应用,特别是在内存受限环境下处理大量整数的高效解决方案。通过具体实例,详细解释了如何利用位图进行整数的插入、查询和删除操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        最近在看《编程珠玑》这本书。 第1章中引入了bitmap(位图)的数据结构。以前没有接触过, 抽空研究了一下,记录下来。

        书中描述的情景:  

         1. 最多1000万个7位数电话号码(号码不重复,实际大概800万个),保存在文本中 

         2. 每隔一段时间要对号码进行排序

         3.程序模块最多可用1M Bytes的内存, 磁盘空间充足


          分析:

           通常方案:7位电话号码可以用uint32_t (4个字节)来存储,  4 * 8 * 10^6 Bytes约为32M Bytes,一次性排序显然内存不满足。

                               1M Bytes 内存可以存放 10^6/4 = 250万个号码, 1000万个约要分40趟进行读取排序写入文件,在归并到一起

                               此方案,需要40次读原始文本的代价,效率低下。

           位图方案: 1个字节(8Bit)可以存放8个整数,  实际800万个号码,刚好1M Bytes的内存空间可以使用,符合要求。


        一、原理

        通常存储方案, 每个整数(假如4个字节), 占用内存多, 在内存不足或海量数据时, 导致处理方法复杂。

        位图, 可以理解用位(Bit)来表示数据, 1个字节是8位, 可以存储8个连续的整数, 内存空间充分利用。 每一位 1表示有数据, 0 表示没有数据。

        举例说明:   

        整数集合 {1 , 11,  9 , 7 , 10 }

        5个整数,用通常存储方案,需要 5  * 4 = 20 字节 

        位图只需要  12位, 不到2个字节(通常是用2个字节来存放,方便管理和字节对齐)

        这5个整数用位图表示如下:

        

0100000101110000
0
123456789101112131415

         第1个字节,表示的数据范围  0 ~ 7; 第2个字节, 表示的数据范围 : 8 ~ 15


       二、 一个非负整数(假设为N)位图的表示方法

         (负数表示的方法类似, 在此只以非负数来分析)

1. 计算N属于哪个单元

            1个字节(8Bit)可以存放8个连续的整数,一个位图可以划分为多个这样的小单元。

             整数N除以8 的值可以判断在哪个单元中。

              如例子中: 整数 9,   9 / 8 =  1(从0 开始计算的),可以知道在第2个字节单元中。

         2. 位图中N的插入

             每一个字节中,可以表示8个连续整数,  要确定N在单元中的确切位置

              插入9之前:          

00000000
89101112131415
              插入9之后:

01000000
89101112131415
              假设插入之前,单元数值为X,

              插入9后可以表示为:  X  |  (0X80 >> 9 % 8)

              公式可以表示为:   X  |  ( 0X80 >> N % 8)

              推算过程: 0X80, 二进制表示 1000  0000 ,右移 9 % 8= 1位, 变为 0 100   0000,和 插入之前的数 “或”运算,就可以把9的位置设为1了。


           3. 位图中N的查询

                 假设N 所在的划分单元的数值为 X,

                公式可以表示为:  X   &  ( 0X80 >> N % 8)

                推算过程:0X80 右移 N % 8 位, 与X 进行“交”运算, 如果结果为0, 则整数N不在位图中, 反之,在位图中。


            4. 位图中N的删除

               假设N所在的划分单元的数值为X,

                公式可以表示为: X & ~(0X80 >> N % 8)

                推算过程:0X80 右移 N % 8 位,求反会把对应的为置为0,其他位为1, 再与X 进行“交”运算, 只把N所在单元的位置设为0


             通过以上的分析,写代码就方便了。 我用c语言写了一个简单的测试代码:

 _bitmap.h         


 
  1. #ifndef _BITMAP_H
  2. #define _BITMAP_H
  3. #include <stdint.h>
  4. static inline void bitmap_add(char* p, int64_t n)
  5. {
  6. p += n / 8;
  7. *p |= ( 0x80 >> n % 8);
  8. }
  9. static inline void bitmap_del(char* p, int64_t n)
  10. {
  11. p += n / 8;
  12. *p &= ~( 0x80 >> n % 8);
  13. }
  14. // yes: > 0, no: 0
  15. static inline int bitmap_lookup(char* p, int64_t n)
  16. {
  17. p += n / 8;
  18. return *p & ( 0x80 >> n % 8);
  19. }
  20. #endif /*_BITMAP_H*/

test.c


 
  1. #include <stdio.h>
  2. #include "_bitmap.h"
  3. int main(int argc, char* argv[])
  4. {
  5. uint32_t num[] = { 7999, 6000, 0, 1, 13, 11, 12, 99, 88, 55, 77, 800, 5000};
  6. #define SIZE 1000
  7. char a[SIZE] = { 0}; // Can store 0 ~ 7999
  8. int i = 0;
  9. for (i = 0; i < sizeof(num) / sizeof( uint32_t); i++)
  10. {
  11. bitmap_add(a, num[i]);
  12. }
  13. //print bitmap
  14. char* p = a;
  15. for (i = 0; i < SIZE; i++)
  16. {
  17. int j = 0;
  18. for (j = 0; j < 8; j++)
  19. {
  20. if ((*p & ( 0x80 >> j)))
  21. printf( "%d\t", i * 8 + j);
  22. }
  23. p++;
  24. }
  25. printf( "\n");
  26. // delete
  27. bitmap_del(a, 99);
  28. bitmap_del(a, 0);
  29. // print bitmap
  30. p = a;
  31. for (i = 0; i < SIZE; i++)
  32. {
  33. int j = 0;
  34. for (j = 0; j < 8; j++)
  35. {
  36. if ((*p & ( 0x80 >> j)))
  37. printf( "%d\t", i * 8 + j);
  38. }
  39. p++;
  40. }
  41. printf( "\n");
  42. int x = 99;
  43. if (bitmap_lookup(a, x))
  44. {
  45. printf( "%d is in bitmap\n", x);
  46. }
  47. else
  48. {
  49. printf( "%d is not in bitmap\n", x);
  50. }
  51. x = 7999;
  52. if (bitmap_lookup(a, x))
  53. {
  54. printf( "%d is in bitmap\n", x);
  55. }
  56. else
  57. {
  58. printf( "%d is not in bitmap\n", x);
  59. }
  60. return 0;
  61. }

输出结果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值