【C++补充】第一弹---位图技术揭秘:内存优化与快速访问

发布于:2025-02-10 ⋅ 阅读:(68) ⋅ 点赞:(0)

 ✨个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1 位图

1.1 位图相关面试题

1.2 位图的设计及实现

1.3 C++库中的位图 bitset

1.4 位图的模拟实现

1.5 位图的优缺点

1.6 位图相关考察题目


1 位图

1.1 位图相关面试题


1. 面试题
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。(本题为腾讯/百度等公司出过的⼀个⾯试题)

解题思路1:暴力遍历,时间复杂度O(N),太慢

解题思路2:排序+⼆分查找。时间复杂度消耗 O(N*logN) + O(logN)

深入分析:解题思路2是否可⾏,我们先算算40亿个数据⼤概需要多少内存。
1G = 1024MB = 1024*1024KB = 1024*1024*1024Byte 约等于10亿多Byte
那么40亿个整数约等于16G,说明40亿个数是无法直接放到内存中的,只能放到硬盘文件中。而⼆分查找只能对内存数组中的有序数据进行查找。
解题思路3:数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用⼀个⼆进制比特位来代表数据是否存在的信息,如果⼆进制比特位为1,代表存在,为0代表不存在。那么我们设计⼀个用位表示数据是否存在的数据结构,这个数据结构就叫位图

比如:

1.2 位图的设计及实现


位图本质是⼀个直接定址法的哈希表,每个整型值映射⼀个bit位,位图提供控制这个bit的相关接⼝。

实现中需要注意的是,C/C++没有对应位的类型,只能看int/char这样整形类型,我们再通过位运算去控制对应的比特位。比如我们数据存到vector<int>中,相当于每个int值映射对应的32个值,比如第⼀个整形映射0-31对应的位,第⼆个整形映射32-63对应的位,后⾯的以此类推,那么来了⼀个整形值x,i = x / 32;j = x % 32;计算出x映射的值在vector的第i个整形数据的第j位

解决给40亿个不重复的⽆符号整数,查找⼀个数据的问题,我们要给位图开2^32个位,注意不能开40亿个位,因为映射是按大小 映射的,我们要按数据大小范围开空间,范围是是0-2^32-1,所以需要开2^32个位。然后从文件中依次读取每个set到位图中,然后就可以快速判断⼀个值是否在这40亿个数中了。

注意:C++库中的位图不能直接开辟2^32个位的空间,需要通过指针new这么大的空间,代码如下:

#include<iostream>
#include<bitset>
using namespace std;

int main()
{
	// 开2^32个位的位图
	//std::bitset<-1> bs1;// C++库开不了这么大空间
	//std::bitset<0xffffffff> bs2;
	// INT_MAX ->2,147,483,647 ->7FFF FFFF
	// UINT_MAX -> 0xffffffff
	//std::bitset<UINT_MAX> bs3;
	std::bitset<-1>* ptr = new std::bitset<-1>();// C++库能够new这么多空间
	return 0;
}

开辟成功 

开辟失败 

快速判断⼀个值是否在这40亿个数中 ?

代码演示

#include<iostream>
#include<bitset>
using namespace std;

int main()
{
	std::bitset<-1>* ptr =  new std::bitset<-1>();
	int a[] = { 5,7,9,2,5,9,5,5,7,5,3,9,2,5,1,5,6,6,6,6,7,9 };
	for (auto e : a)
	{
		ptr->set(e);
	}
	// 判断一个数是否在上面的数组中
	for (size_t i = 0; i < 10; i++)
	{
		if (ptr->test(i))
		{
			cout << i << "->" << "在" << endl;
		}
		else
		{
			cout << i << "->" << "不在" << endl;
		}
	}
	return 0;
}

运行结果 

1.3 C++库中的位图 bitset

可以看到核心接⼝还是set/reset/和test,当然后面还实现了⼀些其他接⼝,如to_string将位图按位转成01字符串,再包括operator[]等⽀持像数组⼀样控制⼀个位的实现

代码演示

#include<iostream>
#include<bitset>
using namespace std;

int main()
{
	bitset<100> bs1;
	// 返回位数,非类型参数N
	cout << bs1.size() << endl;
	// 位数为1个总个数
	cout << bs1.count() << endl;
	bitset<10> bs2(5);
	cout << bs2.count() << endl;
	cout << bs2 << endl;
	// 将第一位设置为1
	bs2.set(1);
	cout << bs2 << endl;
	// 将第零为设置为0
	bs2.reset(0);
	cout << bs2 << endl;
	// 检查第二位是否为1
	cout << bs2.test(2) << endl;
	return 0;
}

运行结果

1.4 位图的模拟实现

 基本结构

template<size_t N>
class bitset
{
public:
    // 构造函数
	bitset();
	// x映射的位标记成1
	void set(size_t x);
	// x映射的位标记成0
	void reset(size_t x);
	// x映射的位是1返回真,0返回假
	bool test(size_t x);
private:
	vector<int> _bs;
};

构造函数 

bitset()
{
	// 扩容,不管能否整除都多开一个空间
	_bs.resize(N / 32 + 1);
}

set() 

// x映射的位标记成1
void set(size_t x)
{
	size_t i = x / 32;
	size_t j = x % 32;

	_bs[i] |= (1 << j);
}

分析 

reset() 

// x映射的位标记成0
void reset(size_t x)
{
	size_t i = x / 32;
	size_t j = x % 32;

	_bs[i] &= (~(1 << j));
}

分析

 

test()

// x映射的位是1返回真,0返回假
bool test(size_t x)
{
	size_t i = x / 32;
	size_t j = x % 32;

	return _bs[i] & (1 << j);
}

分析 

1.5 位图的优缺点

优点:增删查改快,节省空间
缺点:只适用于整形


1.6 位图相关考察题目


• 给定100亿个整数,设计算法找到只出现⼀次的整数?
虽然是100亿个数,但是还是按范围开空间,所以还是开2^32个位,跟前面的题目是⼀样的。
• 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
把数据读出来,分别放到两个位图,依次遍历,同时在两个位图的值就是交集
• ⼀个文件有100亿个整数,1G内存,设计算法找到出现次数不超过2次的所有整数 ?

根据上面的题意,我们需要通过位图实现一个能表示出现0次,1次,2次,多次的位图,因此可以实现一个两个位图成员的类。

基本类

namespace lin
{
	template<size_t N>
	class twobitset
	{
	public:
		void set(size_t x)
		{
			bool bit1 = _bs1.test(x);
			bool bit2 = _bs2.test(x);

			if (!bit1 && !bit2) // 00->01
			{
				_bs2.set(x);
			}
			else if (!bit1 && bit2) // 01->10
			{
				_bs1.set(x);
				_bs2.reset(x);
			}
			else if (bit1 && !bit2) // 10->11
			{
				_bs1.set(x);
				_bs2.set(x);
			}
		}

		// 返回0 出现0次数
		// 返回1 出现1次数
		// 返回2 出现2次数
		// 返回3 出现2次及以上
		int get_count(size_t x)
		{
			bool bit1 = _bs1.test(x);
			bool bit2 = _bs2.test(x);

			if (!bit1 && !bit2)
			{
				return 0;
			}
			else if (!bit1 && bit2)
			{
				return 1;
			}
			else if (bit1 && !bit2)
			{
				return 2;
			}
			else
			{
				return 3;
			}
		}

	private:
		bitset<N> _bs1;
		bitset<N> _bs2;
	};
}

1、 只出现⼀次的整数?

代码演示

void test_twobitset1()
{
	lin::twobitset<100> tbs;
	int a[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6,6,6,6,7,9 };
	for (auto e : a)
	{
		tbs.set(e);
	}
	// 只出现一次的整数
	for (size_t i = 0; i < 100; ++i)
	{
		if (tbs.get_count(i) == 1)
		{
			cout << i << endl;
		}
	}
}

运行结果 

2、找到两个文件交集? 

代码演示

void test_bitset()
{
	int a1[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6 };
	int a2[] = { 5,3,5,99,6,99,33,66 };

	bitset<100> bs1;
	bitset<100> bs2;

	for (auto e : a1)
	{
		bs1.set(e);
	}

	for (auto e : a2)
	{
		bs2.set(e);
	}
	// 两个文件的交集
	for (size_t i = 0; i < 100; i++)
	{
		// 第i的位置都是1就是交集
		if (bs1.test(i) && bs2.test(i))
		{
			cout << i << endl;
		}
	}
}

运行结果 

 3、出现次数不超过2次的所有整数(出现1次和2次)?

代码演示

void test_twobitset2()
{
	lin::twobitset<100> tbs;
	int a[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6,6,6,6,7,9 };
	for (auto e : a)
	{
		tbs.set(e);
	}
	// 出现次数不超过2次的所有整数
	for (size_t i = 0; i < 100; ++i)
	{
		//cout << i << "->" << tbs.get_count(i) << endl;
		if (tbs.get_count(i) == 1 || tbs.get_count(i) == 2)
		{
			cout << i << endl;
		}
	}
}

运行结果 


网站公告

今日签到

点亮在社区的每一天
去签到