【C++】位图(海量数据处理)

news2024/11/18 19:33:22

文章目录

  • 抛出问题:引入位图
    • 位图解决
  • 位图的概念
  • 位图的实现
    • 结构
    • 构造函数
    • 设置位
    • 清空位
    • 判断这个数是否存在
    • 反转位
    • size与count
    • 打印函数
  • 位图的应用


抛出问题:引入位图

问题:给40亿个不重复的无符号整数,没排序,给一个无符号整数,如何快速判断这个数是否在这40亿个数中。

解题思路

  1. 遍历,时间复杂度O(N);
  2. 先排序(O(NlogN)),再利用二分查找(logN)。
  3. 放到哈希表或者红黑树。

现在我们来分析一下上面的思路是否可行。首先我们要知道40亿个无符号整数占用多少空间:1G=1024MB=1024x2024KB=1024x1024x1024Byte,约等于10亿字节,所以40亿个无符号整数(一个整数需要占用4个字节)需要占用约16G的内存空间。

40亿个无符号整数需要16G的空间,那我们就只能采用归并排序了,但是二分查找算法没办法在文件中完成,所以思路1不适用。

如果放到哈希表或红黑树,我们首先要把文件中的数据加载到内存,可以采用分步加载,然后还要将这些数据插入到哈希表或红黑树中,构建完成后,再find数据。那还不如暴力查找,对吧。

综上,以上几种思路不适用的重要原因是:内存不足。

位图解决

数据是否在给定的整型数据中,结果无非两种,在或不在,那么我们就可以使用一个二进制比特位来代表数据是否存在,如果二进制比特位为1,代表存在,0代表不存在。往往我们可以将整数转换成char去存储,一个char等于1个字节=8个比特位,这样40亿个无符号整型就大概只需要0.5G的内存空间。比如:
在这里插入图片描述

位图的概念

位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的处理,通常是用来判断某个数据是否存在。

位图的实现

结构

  • 采用字符数组
  • 采用非类型模板参数,N表示要设置的bit位数
namespace zxn
{
	//模拟实现位图
	template<size_t N>
	class bitset
	{
	public:
		//构造函数
		bitset();
		//设置位
		void set(size_t x);
		//清空位
		void reset(size_t x);
		//判断数据是否存在
		bool test(size_t x);	
		//反转位
		void flip(size_t pos);	
		//获取可以容纳的位的个数
		size_t size();
		//获取被设置位的个数
		size_t count();
		//打印函数
		void Print();
	private:
		vector<char> _bits; //位图
	};
}

构造函数

N是非类型模板参数,表示我们需要设置的bit位数,因为我们采用字符数组,存储的类型是char,char类型占一个字节,所以N/8+1表示我们需要的char类型空间。利用resize函数,开空间的同时将其初始化为0。

//构造函数
bitset()
{
	_bits.resize(N / 8 + 1, 0);
}

设置位

如何将数据对应的比特位设置为1(存在)?

这里注意区分左移<<,右移>>,和虚拟内存当中的低地址,高地址。

  • 回顾大小端的概念

大端字节序:低位存在高地址
小端字节序:低位存在低地址
在这里插入图片描述

注意:读的时候是从低地址到高地址>

  • 为什么采用左移位(观察虚拟内存地址)

在这里插入图片描述

void set(size_t x)
{
	//计算数据x映射的比特位在第i个char数组的位置
	size_t i = x / 8;
	//计算数据x映射的比特位在这个char的第j个比特位
	size_t j = x % 8;

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

清空位

reset函数的功能是将一个数据对应的比特位设置为不存在。

void reset(size_t x)
{
	//计算数据x映射的比特位在第i个char数组的位置
	size_t i = x / 8;
	//计算数据x映射的比特位在这个char的第j个比特位
	size_t j = x % 8;

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

判断这个数是否存在

test函数的功能是判断这个无符号正常是否在这40亿个不重复的数据中。如果这个数据对应的比特位状态为1,就说明这个数据存在,返回true。

bool test(size_t x)
{
	size_t i = x / 8;
	size_t j = x % 8;

	return _bits[i] & (1 << j);
	//或return (_bits[i]<<j)&1;
}

反转位

  1. 计算出数据对应的比特位位置。
  2. 将1左移j位与char中的第i个比特位进行异或运算。(相异为1,相同为0)
//反转位
void flip(size_t x)
{
	size_t i = x / 8;
	size_t j = x % 8;
	_bits[i] ^= (1 << j);//异或:相异为1,相同为0
}

size与count

//获取可以容纳的比特位个数
size_t size()
{
	return N;
}

获取位图中1的个数方法:

  1. 将原树n与n-1进行&与运算得到新的n;
  2. 判断n是否为0,若n不为0则循环进行第一步。
  3. 如此循环进行下去,直到n最终为0,该代码被执行了几次就代表二进制位中有多少个1。

原理:n&(n-1)该操作每进行一次就会消去二进制最右边的1。
在这里插入图片描述

//获取比特位为1的个数
size_t count()
{
	size_t count = 0;
		
	for (auto e : _bits)
	{
		//e是char类型的

		while (e)
		{
			e = e & (e - 1);
			count++;
		}
	}
	return count;//被设置位的个数,即位图中1的个数
}

打印函数

打印函数的功能是:遍历位图,打印每个比特位,在打印的过程中也可以统计位的个数。

void Print()
{
	int count = 0;
	size_t n = _bits.size();//便于获取数组的最后一个字符

	//先打印前n-1个char
	for (size_t i = 0; i < n - 1; i++)
	{
		for (size_t j = 0; j < 8; j++)
		{
			if (_bits[i] & (1 << j))
			{
				cout << "1";
			}
			else
			{
				cout << "0";
			}
			count++;
		}
	}
	//在打印最后一个字符的前N%8位
	for (size_t j = 0; j < N % 8; j++)
	{
		if (_bits[n - 1] & (1 << j))
		{
			cout << "1";
		}
		else
		{
			cout << "0";
		}
		count++;
	}
	cout << " " << count << endl;
}

位图的应用

  1. 快速查找某个数据是否在一个集合中;
  2. 排序+去重;
  3. 求两个集合的交集,并集;
  4. 操作系统中磁盘块标记。

位图的优缺点

  1. 优点:速度快,节省空间。
  2. 缺点:只能映射整型,其它类型,如浮点数,string等等不能存储映射。

问题一:给定100亿个整数,设计算法找到只出现一次的整数。

一个位图中的一个比特位只能表示两种状态,因此我们可以开辟两个位图,这两个位图的对应位置分别表示映射到该位置的整数的第一个位和第一个位。

可以用如下方式标记:

  1. 00表示不存在,没有出现过
  2. 01表示出现一次
  3. 10表示出现过一次以上

在这里插入图片描述
代码实现:复用bitset

template<size_t N>
class twobitset
{
public:
	//设置位:00 表示出现0次,01表示只出现一次,10表示出现过一次以上
	void set(size_t x)
	{
		//00 -> 01
		if (_bs1.test(x) == false && _bs2.test(x) == false)
		{
			_bs2.set(x);
		}
		else if (_bs1.test(x) == false && _bs2.test(x) == true)
		{
			//01 -> 10
			_bs1.set(x);
			_bs2.reset(x);
		}
	}
	void Print()
	{
		//遍历整个位图
		for (size_t i = 0; i < N; i++)
		{
			//打印出只出现一次的整数,即第一位为0,第二位为1,01
			if (_bs2.test(i))
			{
				cout << i << endl;
			}
		}
	}
private:
	bitset<N> _bs1;
	bitset<N> _bs2;
};
void test_twobitset()
{
	int a[] = { 3, 45, 53, 32, 32, 43, 3, 2, 5, 2, 32, 55, 5, 53,43,9,8,7,8};
	twobitset<100> bs;
	//将数组a中的数据映射到位图中
	for (auto e : a)
	{
		bs.set(e);
	}
	//打印出只出现一次的数据
	bs.Print();
}

注意:存储100亿个整数大概需要40G的内存空间,因此数据肯定是存储在文件中的;为了能映射所有整数,位图的大小必须开辟2^32位,即4294967295,因此开辟一个位图大概需要0.5G的内存空间,两个位图就要1G的内存空间,所以代码选择在堆区开辟空间,若是在栈区开辟会导致栈溢出问题。

问题二:给两个文件,分别有100亿个整数,我们只要1G内存,如何找到两个文件的交集。

思路一:

1.依次读取第一个文件的值,读到内存的一个位图中,再读取看一个文件,判断在不在第一个位图中,在就是交集,这个方法大约需要0.5G的内存,但是存在一个问题,就是找出的交集存在重复的值,我们还需要对其进行去重才可以。

2.改进方法:每次找到交集的值,都将第一个位图对应的值设置为0,这样就可以解决找到的交集有重复值的问题。

思路二:
1.依次读取文件1的数据映射到位图1;
2.依次读取文件2的数据映射到位图2;
3.将位图1和位图2的数据进行与&&操作,结果为1的就是交集。

问题三:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数。

这道题目的思路和题目1是一样的,我们可以用以下四种二进制表示四种状态:

  1. 00 出现0次
  2. 01 出现1次
  3. 10 出现2次
  4. 11 出现2次以上
#include <iostream>
#include <vector>
#include <bitset>
using namespace std;

int main()
{
	int a[] = { 3, 45, 53, 32, 32, 43, 3, 2, 5, 2, 32, 55, 5, 53,43,9,8,7,8,55};
	//在堆上申请空间
	bitset<4294967295>* bs1 = new bitset<4294967295>;
	bitset<4294967295>* bs2 = new bitset<4294967295>;
	for (auto e : a)
	{
		if (!bs1->test(e) && !bs2->test(e)) //00->01
		{
			bs2->set(e);
		}
		else if (!bs1->test(e) && bs2->test(e)) //01->10
		{
			bs1->set(e);
			bs2->reset(e);
		}
		else if (bs1->test(e) && !bs2->test(e)) //10->11
		{
			bs2->set(e);
		}
		
	}
	for (size_t i = 0; i < 4294967295; i++)
	{
		if ((!bs1->test(i) && bs2->test(i)) || (bs1->test(i) && !bs2->test(i))) //01或10
			cout << i << endl;
	}
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/563131.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于 MapReduce 的分布式计算系统

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 本文以 MapReduce 为基础&#xff0c;实现了一套基于浏览器实现的分布式系统。加之如今 Chrome 对各个平台近乎完美的兼容性&#xff0c;实现了一次编写&#xff0c;处处运行的目标。同时得力于个人移动设备的普及&#xff0c…

java云HIS系统源码 医院HIS管理系统源码 Java医院系统源码 SaaS医院his系统源码

技术框架&#xff1a; 1、前端&#xff1a;AngularNginx 2、后台&#xff1a;JavaSpring&#xff0c;SpringBoot&#xff0c;SpringMVC&#xff0c;SpringSecurity&#xff0c;MyBatisPlus&#xff0c;等 3、数据库&#xff1a;MySQL MyCat 4、缓存&#xff1a;RedisJ2Cac…

day6 - 使用图像运算进行图像美化

本期将了解图像的基础运算&#xff0c;包含算数运算和位运算等。我们所使用的图像处理技术其实都是靠一些简单的基础运算来完成的&#xff0c;例如加法运算、位运算等&#xff0c;这些简单运算是我们后续研究更复杂的图像处理的基础。 完成本期内容&#xff0c;你可以&#xf…

HiveSQL基础练习题

HiveSQL基础练习题 1.环境准备1.1建表语句1.2数据准备1.3插入数据 2.查询2.1 查询姓名中带“华”的学生名单2.2 查询姓“王”老师的个数2.3 检索课程编号为“04”且分数小于60的学生学号&#xff0c;结果按分数降序排列2.4 查询语文成绩 < 90分的学生和其对应的成绩&#xf…

day16 Servlet交互作用域ELJSTL

转发和重定向 **作用:**为了让jsp和servlet做到责任分离,用于web组件的跳转 **web组件:**jspservlet 转发的方法 request.getRequestDispatcher("跳转的地址").forward(request,response)**跳转的位置:**在服务端进行跳转 重定向的方法 response.sendRedirect(…

2.9 playwright之python实现

1、目录结构如下 2、main.py import os import shutilfrom playwright.sync_api import sync_playwright from config.setting import config from utils.template import Template from utils.md5 import Md5 from utils.delete import del_files import pytest from utils.d…

面试被问麻了...

前几天组了一个软件测试面试的群&#xff0c;没想到效果直接拉满&#xff0c;看来大家对面试这块的需求还是挺迫切的。昨天我就看到群友们发的一些面经&#xff0c;感觉非常有参考价值&#xff0c;于是我就问他还有没有。 结果他给我整理了一份非常硬核的面筋&#xff0c;打开…

全网最全性能测试总结,分析性能测试问题+性能调优方案...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 性能分析和优化一…

【录用案例】2区毕业快刊仅34天录用,新增8篇录用、9篇见刊、13篇检索

2023年5月13日-2023年5月19日&#xff0c;经核实&#xff0c;由我处Unionpub学术推荐的8篇论文已被期刊部录用、9篇见刊、13篇检索&#xff1a; 2区系统类SSCI 【期刊简介】IF:2.5-3.0&#xff0c;JCR2区&#xff0c;中科院4区 【检索情况】SSCI 在检&#xff0c;正刊 【征稿…

本地项目上传到Git(Gitee)仓库

一、步骤解答&#xff08;详细图解步骤见第二大点&#xff09; 1、打开我们的项目所在文件夹&#xff0c;我们发现是不存在.git文件 2、在你的项目文件夹外层【鼠标右击】弹出菜单&#xff0c;在【鼠标右击】弹出的菜单中&#xff0c;点击【Git Bash Here】&#xff0c;弹出运…

循环队列+OJ题之设计循环队列

生命不是要等待风暴过去&#xff0c;而是要学会在风暴中跳舞。 ——卡莉尔吉布朗目录 &#x1f33a;前言&#xff1a; &#x1f341;一.循环队列是什么&#xff1f; &#x1f34f;二.循环队列有什么作用&#xff1f; &#x1f340;三.OJ题之设计循环队列 1…

实战演练 | Navicat 数据生成功能

数据生成的目的是依据某个数据模型&#xff0c;从原始数据通过计算得到目标系统所需要的符合该模型的数据。数据生成与数据模型是分不开的&#xff0c;数据生成的结果应该符合某个数据模型对于数据的具体要求。所以&#xff0c;随着数据模型的发展&#xff0c;数据生成的方法相…

window 利用Qt-windeployqt打包exe程序 一个简单的实例

用一个简单的实例展示下window 如何使用QT-windeployqt打包exe程序使得其可以在别的电脑上运行 一、release模式获得exe可执行文件 新建一个QT项目 构建选择使用CMake base class选择QMainWindow Kit Selection一定要注意&#xff0c;我选的是MinGW 32-bit UI设计 mainwindow.…

手机充电宝电子充气泵方案

该充气泵产品方案的运行原理是通过电动机将电能转化为机械能&#xff0c;带动电机做往复运动&#xff0c;从而产生大量压缩空气&#xff0c;达到快速充气的效果。该充气泵可用于气垫床、汽车轮胎、自行车轮胎、足球、游泳圈等各类充气物品。产品设计以人性化为主&#xff0c;简…

VMware重新安装后没有VMnet1和VMnet8网络

问题&#xff1a; VMware重新安装后&#xff0c;没有自动生成VMnet1和VMnet8网络, 并且使用VMware自带的虚拟网络编辑器也无法生成。 导致主机无法ping通虚拟机。 如下图&#xff1a;点击该选项&#xff0c;然后应用&#xff0c;转一会圈也没有产生对应的网络适配器。 问题原…

物联网技术助力物流智能化:从货物追踪到配送优化

目录 前言 物流领域的IoT设备 物流领域的应用 二、仓库管理 三、物流配送 IoT组合应用 区块链在物流领域应用 展望 前言 随着全球贸易和物流业的快速发展&#xff0c;物流领域的智能化和自动化已成为不可避免的趋势。而物联网技术作为一种重要的数字技术&#xff0c;已经在物流…

VIsual Studio内引用Lua解释器,编译Lua源码,执行Lua脚本

前言 本篇在讲什么 在Visual Studio中引入lua的解释器 使用C调用Lua文件 本篇适合什么 适合初学Lua的小白 适合需要C/C和lua结合开发的人 本篇需要什么 对Lua语法有简单认知 对C/C语法有简单认知 依赖Lua5.1的环境 依赖VS 2017编辑器 本篇的特色 具有全流程的图文…

Shellcode分离加载实现免杀的两种方式(VT免杀率:1/68)

简介 本文详细介绍了如何通过文件加载和远程URL加载方式实现Shellcode分离加载&#xff0c;以规避安全软件的检测。文章首先描述了通过Metasploit Framework生成的shellcode文件加载的过程&#xff0c;并提供了相关的C代码。 为了避免被杀毒软件检测&#xff0c;利用动态API调…

自动化测试-DevOps如何实施?看看10年测试大佬的总结...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Selenium4自动化测…

2023最新!软件测试高频面试题基础知识点分享

近期也算是抽取出大部分休息的时间&#xff0c;为大家准备了一份通往大厂面试的小捷径&#xff0c;准备了一整套软件测试复习面试的刷题以及答案&#xff0c;我知道很多同学不知道怎么复习&#xff0c;不知道学习过程中哪些才是重点&#xff0c;其实&#xff0c;你们经历过的事…