笔试面试题目:求整数的交集(使用bitmap)

         

     T公司第三轮面试,问题如下:

      A文件有40亿个QQ号码,B文件有40万个QQ号码,所有QQ号码都是无符号整数,求A和B的交集,可用内存是600M.

      

暴力算法(鸡肋)

      先来看暴力算法,简单直接而粗暴:

#include <iostream>
#define M 5
#define N 3
using namespace std;

int main() {
  int a[M] = {3, 4, 5, 9, 2};
  int b[N] = {4, 1, 2};
  
  for (int i = 0; i < M; i++) 
  {
    for (int j = 0; j < N; j++) 
    {
      if (a[i] == b[j]) 
      {
           cout << a[i] << endl;
      }
    }
  }
  
  return 0;
}

      结果:

      这种算法挺鸡肋的,因为内层循环是线性查找,整体时间复杂度是O(MN).  为什么不用哈希查找呢?

     

哈希表(局限)

     用哈希表进行改进,hash[x]=x, 即在下标x处存x的值:

#include <iostream>
#define M 5
#define N 3
using namespace std;

int main() {
  int a[M] = {3, 4, 5, 9, 2};
  int b[N] = {4, 1, 2};
  
  int H[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  for (int i = 0; i < M; i++) 
  {
    H[a[i]] = a[i]; // hash table
  }
  
  for (int j = 0; j < N; j++) 
  {
    if (H[b[j]] == b[j]) 
    {
      cout << b[j] << endl;
    }
  }
  
  
  return 0;
}

       结果:

      可以看到,没有双重循环,时间复杂度大大降低。

flag表(依旧局限)

     受哈希表的启发,可以对元素x进行0或1标记,即为flag表:

#include <iostream>
#define M 5
#define N 3
using namespace std;

int main() {
  int a[M] = {3, 4, 5, 9, 2};
  int b[N] = {4, 1, 2};
  
  int H[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  for (int i = 0; i < M; i++) 
  {
    H[a[i]] = 1; // set a flag
  }
  
  for (int j = 0; j < N; j++) 
  {
    if (H[b[j]] == 1) 
    {
      cout << b[j] << endl;
    }
  }
  
  return 0;
}

      结果:

        

      现在比较接近正确答案了,但转念一想:A有40亿个QQ号码, B有40万个QQ号码,如果一次读到内存,肯定超过600M,所以,上述方法都失效。

      反思一下flag表,可以看到,为了存a数组中的5个整数,H数组需要10个无符号整数来记录,太浪费了。其实,用10个bit就可以了。如此一来,存储量就压缩成了原来的1/32,  所用内存在600M以内,符合要求,如下:

      我们可以看到,使用bitmap后, 1个unsigned int,就能表示32个整数的存在与否。求A和B交集的程序如下:

#include <iostream>
#include <fstream>
using namespace std;
 
#define BIT_INT 32   // 1个unsigned int可以标志32个QQ的存在与否
#define SHIFT 5
#define MASK 0x1f
#define N 4294967296 // 2的32次方, 能覆盖到所有的QQ号码
 
unsigned int *a = NULL;
 
// 必须用堆
void createArr()
{
  a = new unsigned int[1 + N / BIT_INT];
}
 
// 堆释放
void deleteArr()
{
  delete []a;
  a = NULL;
}
 
// 将所有位都初始化为0状态
void setAllZero()
{
  memset(a, 0, (1 + N / BIT_INT) * sizeof(unsigned int));
}
 
// 设置第i位为1
void setOne(unsigned int i)
{
  a[i >> SHIFT] |= (1 << (i & MASK));
}
 
// 设置第i位为0
void setZero(unsigned int i)
{
  a[i >> SHIFT] &= ~(1 << (i & MASK));
}
 
// 获取第i位的值
int getState(unsigned int i)
{
  return (a[i >> SHIFT] & (1 << (i & MASK))) && 1;
}
 
// 用bitmap记录是否存在
void setStateFromFile()
{
  ifstream cin("a.txt");
    unsigned int n;
    while(cin >> n)  
    {
        setOne(n);
    }  
}
 
// 交集
void printCommonNumber()
{
  ifstream cin("b.txt");
    unsigned int n;
    while(cin >> n)  
    {
        if(1 == getState(n))
        {
          cout << n << " ";
        }
    }  
 
  cout << endl;
}
 
int main() 
{
  createArr();
  setAllZero();
 
  // a.txt: 4 5 7 2 9 2 4 8 0 11   (a.txt中可以有40亿个无符号整数)
  // b.txt: 6 11 0 2 3             (b.txt中可以有40万个无符号整数)
 
  setStateFromFile();
  printCommonNumber(); // 交集是:11 0 2
  deleteArr();
 
  return 0;
}

      可以看到,结果符合预期。

      从最开始的暴力算法,到哈希表,再到flag表,再到bitmap, 逐渐优化,终于解决了问题。在实际开发中,bitmap的应用也是非常广泛的。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值