1. 一个面试题
话说海量数据面试题火爆于各类面试环节中,动不动就 100G
数据查一个数是不是存在啥的…其实考的就是位图这个知识点,也是哈希的应用。
- 面试题
给 40 亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这 40 亿个数中。【腾讯】- 遍历,时间复杂度 O ( N ) O(N) O(N)
- 外部排序 ( O ( N l o g N ) ) (O(NlogN)) (O(NlogN)),利用多路归并、二分查找: O ( l o g N ) O(logN) O(logN)
- 位图解决:数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为 1,代表存在,为 0 代表不存在。比如:
2. 位图概念
所谓位图,就是用每一位来存放某种状态,适用于大规模数据,但数据状态又不是很多的情况。通常是用来判断某个数据存不存在的。
一个字节可以存放八种状态,值得注意的是,如果我们需要存放一个 int 范围的所有数据,即 4G
个数据,我们仅需要 4G / 8 = 512M
大小的空间就够了。
3. 位图的模拟实现
在此没有 operator[ ]
,在系统的底层实现中是重载过的。
参见代码如下:
#pragma once
#include <vector>
using namespace std;
class bitmap
{
vector<int> m_bit;
// 位图的capacity
size_t m_bitcount;
public:
bitmap(size_t bitc) :
m_bit(bitc / 32 + 1),
m_bitcount(bitc)
{}
// 位图对应位置置1
void set(size_t pos)
{
if (pos > m_bitcount)
{
return;
}
// 下标
size_t index = pos / 32;
// 左移位数
size_t bits = pos % 32;
// 不影响其它位情况下:
// |= 1 置1
// &= ~1 置0
// ^= 1 状态改变
m_bit[index] |= 1u << bits;
}
void reset(size_t pos)
{
if (pos > m_bitcount)
{
return;
}
size_t index = pos / 32;
size_t bits = pos % 32;
m_bit[index] &= ~(1u << bits);
}
bool get(size_t pos)
{
if (pos > m_bitcount)
{
return;
}
size_t index = pos / 32;
size_t bits = pos % 32;
return m_bit[index] >> bits & 1;
}
size_t size()
{
return m_bitcount;
}
size_t count()
{
// 数一的第四种方法,前三种方法在剑指-offer注意查看
// 对应数字的1有多少个
const char * pCount = "\0\1\1\2\1\2\2\3\1\2\2\3\2\3\3\4";
// 0 1 2 3 4 5 6 7 8 9 a b c d e f
size_t size = m_bit.size();
size_t count = 0;
size_t i;
for (i = 0; i < size; i++)
{
int value = m_bit[i];
int j;
for (j = 0; j < sizeof(m_bit[0]); j++, value >>= 8)
{
// char占1个字节只能取出最后的8位
char c = value;
// 与00001111进行与运算,得到对应的:0123456789abcdef
// 然后直接在对应位置上取出来1的位数即可
// 然后对c右移四位在进行如上操作
count += pCount[c & 0x0f];
c >> 4;
count += pCount[c & 0x0f];
// 至此,后8位1的个数已经统计完毕,再将数字右移八位进行统计
// 这需要进行 32 / 4 = 8 步的操作,并不快,最快的方法三是log 32 = 5的方法
// 在此统计1时也可以根据数据进来一个统计一次,设置一个计数即可
// 但是这种方法每次需要判断,当数据量大时,开销会很夸张
}
}
}
};
4. 位图的应用
- 快速查找某个数据是否在一个集合中
- 排序
- 求两个集合的交集、并集等
- 操作系统中磁盘块标记