数据结构--位图(位图的模拟实现及STL里bitset的使用)

位图的引入

1.对于下面的题

  • 给40亿个不重复的无符号整数,没排过序。任给一个无符号整数,如何快速判断一个数是否在这40亿个数中。 【腾讯】

2.分析
(1)我们首先可以想到将这40亿个整数先进行排序,然后再进行二分查找;
(2)但是40亿个整数在内存中大约占16G的内存;
(3)我们一般的计算机的内存很难达到16G,也就是说如果没有特殊的方式,我们无法在计算机的内存里存放这么多的数据;
(4)所以对于上面的想法很难实现,我们需要思考其它的方式;
(5)由题目,我们只用知道一个数是否存在这40亿个数,在或者不在可以用1个比特位来表示(0表示不在,1表示在)。所以可以将一个数用一个位来进行表示,这样一个数以前用32位表示,现在就缩短到用一个位来表示,这样的话,对于40亿个整数现在只用500M就可以存下,大大缩短了空间。
(6)这种用位来表示一个数在或是不在的数据结构称为位图,位图利用的是哈希直接定址法的思想。

自己模拟实现一个位图(BitSet)

1.位图的数据结构可以用数组来实现,STL里面有一个容器是vector就是动态增长的数组,所以位图的底层就用一个vector。

class BitSet
{
public:

private:
      vector<size_t > _bits;
};

2.构造函数

  • 构造函数需要为BitSet开空间,并且将里面的数据都清零(也就是将位图的每一位都置0)
  • 我们知道vector里面用两个函数可以用来开辟空间,一个是reserve ,一个是resize。(reserve只改变容量不改变size,一般和push_back搭配使用;而resize不仅改变容量,也会改变size),所以这里用resize。
  • 如果想要开辟range个位,因为当前位图用vector实现,而vector里面的数据为size_t类型,也就是1个size_t类型可以存放32个位,range个比特位就需要开range/32个size_t的空间,为了防止当range<32时,range/32=0的情形,所以需要多开一个size_t的空间

综上,构造函数如下:

    BitSet(size_t range)
      {
           _bits.resize(range / 32 + 1, 0);     //resize开range/32+1的空间,并且将所有的数据初始化为0
      }

3.Set(将位图的某一位设置为1,也就是将给定的数x对应的位设为1)

  • 将某一位设置成1,首先要知道该位 位于位图的哪个位置上(即该位对应的数组的下标);(因为该数组一个位置对应32个位,所以用该数除32可以算出该位对应数组的下标是哪一个);
  • 然后还要知道该位对应数组那个位置的哪一位上;
  • 如果要将某一位设置为1,由逻辑运算符知道,1|x=1(1与任意数按位或都是1)0|x=x(0与任意数按位或都是任意数),则我们可以将该位与1进行按位或,但是又不能影响别的位(即别的位可以或上0);
  • 如果将pos位变成1其它位不变,则将1左移pos位,再或等上数组_bits对应位置上的数。(对应位置:该位所在的数组的下标)。
void Set(size_t x)
      {
           size_t num = x / 32;  //计算x位于_bits的哪个下标处
           size_t pos = x % 32;  //计算x位于对应下标的哪一位上
           _bits[num] |= (1 << pos);
      }

4.Reset(将位图的某一位设置为0,也就是说将该位对应的数删除)

  • 仍然需要先算出该位对应数组的哪个位置;
  • 如果要将某一位变为0,则可以将该位 按位与上0,但是其它位不变,所以其它位应与上1;
  • 如何将pos位变为0,其它位变为1呢?(可以将1左移pos位,就会使得pos位为1,其它位为0,然后再将左移后的数按位取反,则pos位为0,其它位为1.
  void ReSet(size_t x)
      {
           size_t num = x / 32;
           size_t pos = x % 32;
           _bits[num] &= ~(1 << pos);
      }

5.Test(测试某个数是否存在,也就是测试该数对应的位是0还是1)

  • 如果该数对应的位为1说明该数存在,为0说明不存在在
  • 因为 1&1=1,1&0=0,所以可以将该数对应的位与上1,将其它位与上0,则结果为0说明当前位为0即表示该数不在,不为0说明该数存在
bool Test(size_t x)
      {
           size_t num = x / 32;
           size_t pos = x % 32;
           return _bits[num] & (1 << pos);
      }

6.总的代码如下:

#include<vector>   //注意用到vector,就必须包含vector的头文件

class BitSet
{
public:
      BitSet(size_t range)
      {
           _bits.resize(range / 32 + 1, 0);
      }

      void Set(size_t x)
      {
           size_t num = x / 32;  //计算x位于_bits的哪个下标处
           size_t pos = x % 32;  //计算x位于对应下标的哪一位上
           _bits[num] |= (1 << pos);
      }

      void ReSet(size_t x)
      {
           size_t num = x / 32;
           size_t pos = x % 32;
           _bits[num] &= ~(1 << pos);
      }

      bool Test(size_t x)
      {
           size_t num = x / 32;
           size_t pos = x % 32;
           return _bits[num] & (1 << pos);
      }
private:
      vector<size_t > _bits;
};

void TestBitSet()
{
      BitSet s(1000);
      s.Set(100);
      s.Set(15);
      s.Set(34);
      s.Set(815);

      cout << s.Test(815) << endl;
      cout << s.Test(34) << endl;
      cout << s.Test(69) << endl;
      cout << s.Test(125) << endl;
      cout << s.Test(150) << endl;
}

C++标准库中位图的使用(bitset)

1.在C++标准库里位图(bitset)是一个容器,采用模板实现,有一个非类型的模板参数,这个非类型的模板参数用来表示我们需要的比特位的位数;
2.使用:
(1)bitset的定义及初始化

      bitset<4> bs1;   //位图bs1包含4个位,每个位初始值为0

      bitset<4> bs2(11);     //bs2包含4个位,由于11的二进制为1011,该二进制的低位对应位图的低位置处,高位对应位图的高位置处,即位图的第0位为1,第一位为1,第2位为0,第3位为1

      bitset<4> bs3("0011");  //可以将一个string对象初始化一个位图,string对象的值与位图位置相反,string的最右边的字符对应位图的最低位,最左边的字符对应位图最高位

      string s = "1111";
      bitset<4> bs4(s);   

      bitset<17> bs5(0xffff);   //在32位机器上,0xffff为16进制数,表示16个1,所以会将bs5里0~15位都置为1,第16位为0

      string s2 = "00110011110";
      bitset<32> bs6(s2, 3, 4);   //bs6有32个位,将s2从第3位(从0开始)开始包含4个字符用来初始化bs6的0~3位

(2)将某位设置为1 (set( ))

     bitset<8> bs1;    //bs1包含8个位,第0~第7位,初始时每位都为0
      bs1.set();   //set里面不带任何参数,则会将所有位都设置位1

      bitset<8> bs2;
      bs2.set(5);   //将bs2的第5位设置为1

(3)将某位设置为0 (reset( ))

      bitset<8> bs1("00011111");
      bs1.reset(3);   //将bs1的第3位设置为0

(4)测试某一位是1还是0 (test())

    bitset<8> bs1("00011111");
      bs1.reset(3);
      cout << bs1.test(3) << endl;   //输出0
      cout << bs1.test(2) << endl;   //输出1

(5)统计位图里面为1的个数

     bitset<8> bs1("00011111");
      cout << bs1.count() << endl;    //输出5

(6)统计位图的size

  bitset<8> bs1("00011111");
      cout << bs1.size() << endl;   //输出8

(7)访问位图的某一位(通过[ ])

     bitset<8> bs1("00011111");
      cout << bs1[3] << endl;

(8)按位取反 (flip( ))

      bitset<8> bs1("00011111");
      bs1.flip();   //不带参数,表示将所有位都按位取反

      bs1.flip(3);    //表示将bs1的第三位按位取反

(9)输出该位图
这里写图片描述

猜你喜欢

转载自blog.csdn.net/xu1105775448/article/details/80551637