STL容器BitSet(位图)——1道腾讯笔试题的正确打开方式

Question


从40亿个没有排序且不重复的无符号整数中快速判断一个数的存在性。
【腾讯笔试题】


分析思路


由40亿个不重复的无符号整数,我们可以得到两点信息:

  • 最大的整数是40亿
  • 无符号整数unsigned long表示范围:0—4294967295(42亿)

接下来,我们需要利用一下数学知识:

4294967295 = 2^32字节,而2^30字节 = 1G

所以42亿约为4G
这意味着为了表示这些数,至少需要4G大小内存。
因此正常情况下,内存是不够存储这些数的。

位图(BitSet)能够解决这个问题。


位图(BitSet)


由上所述,由于内存不够导致数据无法存储,也就不能判断某数的存在性。

让我们把视线放在关键的问题上:数据的存在性

试想一下,数据的存在性本身是一个原子问题,所以我们可以用二进制来表示,即1表示存在。0表示不存在。

想通了这一点,我们就应该想到可以用一个比特位来表示数据,而非数据本身。而这种思想可以完美的解决数据存储的问题。

再拓展一下,我们就可以总结出问题的解决办法:

创建一个数组,数组中每个数据的每一个二进制位用来表示一个数据,0表示数据不存在,1表示数据存在。

这就是位图(BitSet)

根据位图的思想,我们就可以算出在此方法下数据所占内存大小:

2^32/2^5=2^27整型

2^27*4=2^29=512M,所以,有了位图,我们只需要开辟500M的内存,就可以存储每一个数据,与之前的4G比较,位图确实大大降低了内存消耗。

位图本身也是C++模板库STL中一个重要的容器

1

有了位图,我们就可以解决刚开始的那个问题。


Solution


接下来就是根据思想编写代码了。

为了方便对数据操作,使用STL另一个重要容器vector来生成数组。

首先,我们应该实现Set接口来存储数据,用位运算计算数据在数组中的下标,然后计算该数在此下标数据中具体的比特位位置,最后将对应比特位置为1。

其次,还应该实现ReSet接口方便实现数据的删除和位置重置,同样用位运算来完成。

最后,实现TestExist接口测试数据的存在性。

基于上述方法,编写C++代码如下:

#pragma once
#include 

class BitSet
{
public:
    BitSet(size_t range)
    {
        _a.resize((range >> 5), 0);//构造位图并将每位初始化为0
    }

    void SetNum(size_t num)//存放数据
    {
        size_t index = num >> 5;//计算该数在整形数组中的下标
        size_t pos = num % 32;//计算该数在32个bite位中的位置

        _a[index] |= (1 << pos);//将对应的比特位置为1
    }

    void ResetNum(size_t num)//重置数据
    {
        size_t index = num >> 5;
        size_t pos = num % 32;

        _a[index] &= ~(1 << pos);//将对应的比特位置为0
    }

    bool TestExistNum(size_t num)//判断某数的存在性
    {
        size_t index = num >> 5;
        size_t pos = num % 32;
        if (_a[index] & (1 << pos))
        {
            printf("该数存在\n");
            return true;
        }
        else
        {
            printf("该数不存在\n");
            return false;
        }

    }

protected:
    vector <int> _a;
};

写完了代码,接下来就该测试了,根据代码编写如下测试用例:

void TestBitSet()
{
    BitSet m1(-1);//-1的补码就是4294967295,此处用了强转

    m1.SetNum(1);
    m1.SetNum(5);
    m1.SetNum(42900000000);
    m1.ResetNum(5);
    m1.TestExistNum(42900000000);
    m1.TestExistNum(5);
}

调试过程:
1.初始化数组STL容器BitSet(位图)——1道腾讯笔试题的正确打开方式_第1张图片

2.分别插入1和5
STL容器BitSet(位图)——1道腾讯笔试题的正确打开方式_第2张图片
STL容器BitSet(位图)——1道腾讯笔试题的正确打开方式_第3张图片

3.重置数据

STL容器BitSet(位图)——1道腾讯笔试题的正确打开方式_第4张图片

4.边界值插入及判断数据存在性
STL容器BitSet(位图)——1道腾讯笔试题的正确打开方式_第5张图片


The End


位图的思想可以应用在很多方面,它可以减少内存消耗,是创新思维的体现。类似这种问题还有很多,比如在100亿个整数中找到出现一次的数,也可以通过位图来解决。

这个问题可以用两个比特位来表示一个数据,因为数据有3种状态,分别是不存在0,存在一次1,存在多次10。

当然,位图也不是万能的,当数据类型发生变化,如整型变成字符型。就要用位图的另一个变种方法,布隆过滤器(Bloom Filter)来解决。

你可能感兴趣的:(数据结构)