位图的实现和拓展

一:位图的介绍

①:需要位图的场景

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

要判断一个数是否在某一堆数中,我们可能会想到如下方法:

A:将这一堆数进行排序,然后通过二分查找的方法判断该数是否在这一堆数中。
B:将这一堆数插入到unordered_set容器中,然后调用find函数判断该数是否在这一堆数中。


单从方法上来看,这两种方法都是可以,而且效率也不错,第一种方法的时间复杂度是O (NlogN ) O(NlogN)O(NlogN),第二种方法的时间复杂度是O (N)

重点是,40亿个数,占用16G的空间,空间消耗是很大的,不可能用代码直接开辟出16g的空间!

所以,这时候,就需要位图了

②:位图的意义

 在上述问题中,我们只需确定某个无符号整数是否存在,即只有两种可能的状态(存在或不存在)。因此,可以用一个二进制位来表示无符号整数的状态:1表示存在,0表示不存在。如图:

无符号整数总共有232个,因此记录这些数字就需要232个比特位,也就是512M的内存空间,内存消耗大大减少。 

 ③:位图的概念及使用场景

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

位图的使用场景:
1. 快速查找某个数据是否在一个集合中
2. 排序 + 去重
3. 求两个集合的交集、并集等
4. 操作系统中磁盘块标记

二:库中的位图的使用方法

①:bitset的定义方式

// 构造一个16位的位图,所有位都初始化为0。
bitset<16> bs1; //0000000000000000//构造一个16位的位图,根据所给值初始化位图的前n位。
bitset<16> bs2(0xfa5); //0000111110100101//构造一个16位的位图,根据字符串中的0/1序列初始化位图的前n位。
bitset<16> bs3(string("10111001")); //0000000010111001

解释: bs2(0xfa5)

用十六进制数 0xfa5 初始化 bs2;0xfa5 的二进制形式是 111110100101(共 12 位)。

由于 bitset 是 16 位的,而 0xfa5 只有 12 位,因此 高位补 0,最终存储为:
0000111110100101(16 位)。

②:bieset的成员函数

#include <iostream>
#include <bitset>
using namespace std;int main()
{bitset<8> bs;bs.set(2); //设置第2位bs.set(4); //设置第4位cout << bs << endl; //00010100bs.flip(); //反转所有位cout << bs << endl; //11101011cout << bs.count() << endl; //6cout << bs.test(3) << endl; //1bs.reset(0); //清空第0位cout << bs << endl; //11101010bs.flip(7); //反转第7位cout << bs << endl; //01101010cout << bs.size() << endl; //8cout << bs.any() << endl; //1bs.reset(); //清空所有位cout << bs.none() << endl; //1bs.set(); //设置所有位cout << bs.all() << endl; //1return 0;
}

运行结果:

 bitset还有各种运算符的使用.......不再介绍了

三:位图的模拟实现

前提须知:& 和 | 的规则:

虽然位图有这么多的函数,但是我们实现,只实现set、reset、tes,已经能让我们了解bitset了!

①:构造函数

template<size_t N>
class bitset
{//构造函数bitset(){_bits.resize(N/32 + 1, 0); // 初始化所有位为 0}private:vector<int> _bits;};

解释:N/32+1的意义

我们一般从题目中得到了整形的个数后,用bitset去开辟相应个数的位出来,所以先N/32;

假设现在有50个数,那我们应该开50个位出来,但是50/32只能得到1,所以直接50/32+1等于2,开辟两个整形出来,即64个位;避免小于32的数字在构造函数里面开辟了0个空间;

②:set函数->将第 x 位设为 1

void set(size_t x)
{assert(x <= N); // 检查 x 是否越界size_t i = x / 32; // 计算 x 在哪个 int 中size_t j = x % 32; // 计算 x 在该 int 中的比特位位置_bits[i] |= (1 << j); // 将第 j 位设为 1
}

解释:_bits[i] |= (1 << j) 

旨在:通过位运算将第 j 位设为 1,且不影响其他位。

假设通过前两步得知我们的x对应的位是vector中第一个整形中的第五个二进制位,所以_bits[1] |= (1 << 5) 的效果如图:

符合预期,把第5位 置为了1

这例子很简单,如果原本的vector的其他位也有位1的,这时候进行|=操作后,照样是不影响其他位的,因为|代表一个为1则为1,两个为0才是0,所以不影响!

③:reset函数->将第 x 位设为 0

void reset(size_t x)
{assert(x <= N);size_t i = x / 32;size_t j = x % 32;_bits[i] &= ~(1 << j); // 将第 j 位设为 0
}

解释:_bits[i] &=  ~(1 << j) 

通过位运算将第 j 位设为 1,且不影响其他位。

假设通过前两步得知我们的x对应的位是vector中第一个整形中的第五个二进制位,所以_bits[1] &= ~(1 << 5) 的效果如图:

符合预期,把第5位 置为了0

这例子很简单,如果原本的vector的其他位也有位1的,这时候进行&=操作后,照样是不影响其他位的,因为&代表一个为0则为0,两个为1才是1,所以不影响!

④:test函数->检查第 x 位是否为 1

bool test(size_t x)
{assert(x <= N);size_t i = x / 32;size_t j = x % 32;return _bits[i] & (1 << j); // 返回第 j 位的值
}

解释:_bits[i] & (1 << j);

注意:这一步为何没有&= 而是 & ,因为该函数只是想看某一位为什么,不能改变该位!

 返回值:若 _bits[i] 的第 j 位为 1,返回 true;否则返回 false。

Q:为什么能判断某一位是否为 1?


A:& 运算后,只有第 j 位可能非0(因为其他位都是 0)。

如果结果 ≠ 0 → 说明 _bits[i] 的第 j 位是 1(返回 true)。

如果结果 = 0 → 说明 _bits[i] 的第 j 位是 0(返回 false)。

四:位图总代码及测试

①:总代码

#pragma once
#include<assert.h>namespace bit
{template<size_t N>class bitset{public:bitset(){_bits.resize(N/32+1, 0);//cout << N << endl;}// 把x映射的位标记成1void set(size_t x){assert(x <= N);size_t i = x / 32;size_t j = x % 32;_bits[i] |= (1 << j);}// 把x映射的位标记成1void reset(size_t x){assert(x <= N);size_t i = x / 32;size_t j = x % 32;_bits[i] &= ~(1 << j);}bool test(size_t x){assert(x <= N);size_t i = x / 32;size_t j = x % 32;return _bits[i] & (1 << j);}private:vector<int> _bits;};
}

②:测试代码

    void test_bitset(){bitset<100> bs1;bs1.set(50);bs1.set(30);bs1.set(90);for (size_t i = 0; i < 100; i++){if (bs1.test(i)){cout << i << "->" << "在" << endl;}else{cout << i << "->" << "不在" << endl;}}bs1.reset(90);bs1.set(91);cout << endl << endl;for (size_t i = 0; i < 100; i++){if (bs1.test(i)){cout << i << "->" << "在" << endl;}else{cout << i << "->" << "不在" << endl;}}

预期效果:100个位中  第一次打印:50 30 90 位值为1;第二次打印:50 30 91 为1;

运行效果:

符合预期! 

五:位图相关题目

了解了biset的相关实现后,用库中的bitset做几道题目吧~

①:双位图找只出现一次的数字

  • 场景:从数组中找出所有只出现一次的数字(类似“单身狗”问题)。

  • 数组:int a[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6 };

  • 解决:用 two_bit_set(双位图)记录每个数字的状态:

    • 10:出现多次。

    • 01:出现一次。

    • 00:未出现。

    • 遍历数组后,输出状态为 01 的数字。

    •  two_bit_set(双位图)的成员变量就是两个位图即可

代码:

#include<iostream>
#include <bitset>
using namespace std;// 双位图:独立类,组合两个bitset
template<size_t N>
class two_bit_set
{
public:void set(size_t x){if (!_bs1.test(x) && !_bs2.test(x)){_bs2.set(x);  // 00 → 01}else if (!_bs1.test(x) && _bs2.test(x)){_bs1.set(x);  // 01 → 10_bs2.reset(x);}// 10 → 10(无需处理)}bool test(size_t x){if (_bs1.test(x) == false&& _bs2.test(x) == true){return true;}return false;}private:bitset<N> _bs1;  // 高位bitset<N> _bs2;  // 低位};
void test_bitset2()
{int a[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6 };two_bit_set<100> bs;for (auto e : a){bs.set(e);}for (size_t i = 0; i < 100; i++){//cout << i << "->" << bs.test(i) << endl;if (bs.test(i)){cout << i << endl;}}
}int main()
{test_bitset2();return 0;
}

②:求两个数组的交集

  • 场景:找出两个数组中共同存在的数字。

  • 两个数组: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 分别标记两个数组的数字。

    • 遍历所有数字,输出在两个位图中均为 1 的数字。

代码:

void test_bitset3()
{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++){if (bs1.test(i) && bs2.test(i)){cout << i << endl;}}
}
int main()
{test_bitset3();return 0;
}

运行结果:

此时回到最开始的问题:

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

思路:

1:用库中的位图开辟40亿个位出来

2:读取题目给的数据,把40亿个整形对应的位置为1

3:然后在看待查询的数对应的位是否为1即可

代码无法写出来,因为没有这个庞大的数据,但是可以了解一下40亿位如何开辟

        bitset<-1> bs2;bitset<UINT_MAX> bs3;bitset<0xffffffff> bs4;

解释:

1. bitset<-1> bs2

  • 模板参数 size_t N 接受 -1 时会发生隐式转换

  • -1 转换为 size_t 类型会变成最大值(即 2³²-1

2:bitset<UINT_MAX> bs3

标准定义:

  • UINT_MAX 是 <climits> 中定义的无符号整数最大值

  • 标准值:4,294,967,295(即 2³²-1

3:bitset<0xffffffff> bs4

十六进制解析:

  • 0xffffffff = 4,294,967,295(即 2³²-1

  • 完全等价于 bitset<UINT_MAX>

4:bitset<4294967296> bs1;

记得住数字 也可以这样

③:一些位图解题的思想

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

A:如果内存不够,分批次读是不可靠的,因为有可能在不同的批次中都出现了一次,加起来就超过了题目要求;假设要题目数据总大小1g(1024),但我们只有512MB;此时我们先读整数范围前半部分的值  再读范围为后半部分的值,先读0~2^31  再读2^31~2^31-1的范围即可。

所以空间再小一点都可以,只是要将范围分细一点罢了
 

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

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

相关文章

排序功法入门指南【江湖算法笔记】

话说江湖风云变幻&#xff0c;各路英雄好汉行走江湖&#xff0c;总得有个名号排行。若问“东邪西毒南帝北丐”谁强谁弱&#xff0c;总得排个座次不是&#xff1f;这排序之道&#xff0c;恰似武功秘籍&#xff0c;练好了能号令群雄&#xff0c;练岔了怕是要被笑掉大牙&#xff0…

【中间件】brpc_基础_用户态线程中断

bthread之用户态线程中断 源码 1 简介 interrupt_pthread 核心功能是 通过信号机制中断阻塞的 pthread 线程&#xff0c;以实现线程的协作式中断。 2 核心功能与设计 2.1 信号选择与注册 信号选择&#xff1a;使用 SIGURG 作为中断信号。 原因&#xff1a;SIGURG 通常用于…

Linux 的网络卡

#本机操作系统CentOS 10 #核心版本 rootbogon:/etc# uname -r 6.12.0-65.el10.x86_64 网卡能不能被捉到可以使用【dmesg|grep xx】来判断&#xff0c;有没有驱动则可以使用lsmod看看模块有没有加载核心&#xff01;最后&#xff0c;以ifconfig xxx测试看看 观察核心所捉到的网卡…

前端双工通信的几种方案详细描述

前端实现双工通信&#xff08;全双工或半双工&#xff09;的常见方案及详细实现如下&#xff1a; 一、WebSocket&#xff08;全双工&#xff09; 原理&#xff1a;基于 TCP 的持久化协议&#xff0c;客户端与服务端建立双向通信通道&#xff0c;支持实时双向数据传输。 // 客…

KUKA机器人快速启动设置

KUKA机器人在首次开机启动时&#xff0c;有时在示教器上需要进行投入运行等相关的设置。如以下相关的信息需要处理&#xff1a; 1、机器人系统开机后&#xff0c;选择T1运行模式&#xff1b;2、显示提示信息&#xff1a;“RDC 存储器和控制系统不一致什么被更换了”时&#xf…

游戏代码C

以下将结合不同编程语言的特点及游戏开发中的实际应用&#xff0c;展示多种语言的游戏代码示例&#xff08;以简单游戏为例&#xff0c;展示代码结构和逻辑差异&#xff09;。由于代码篇幅较长&#xff0c;我将分语言进行说明并引用相关来源&#xff1a; 1. C# Unity&#xff…

LangChain Agent核心解析:Zero-Shot-ReAct策略实现与实战指南

引言 在LangChain的Agent框架中&#xff0c;zero-shot-react-description 是一种预定义的Agent类型&#xff0c;它结合了Zero-Shot&#xff08;零样本学习&#xff09; 和 ReAct&#xff08;推理行动&#xff09; 策略&#xff0c;主要用于根据工具的描述动态选择和执行工具&a…

PyQt 或 PySide6 进行 GUI 开发文档与教程

一、官网文档 Qt 官方文档&#xff1a;Porting to Qt 6 | Qt 6.9Qt 维基&#xff1a;​​​​​​​Qt WikiQt for Python (PySide6) &#xff1a;​​​​​​​Qt for Python - Qt WikiPySide6 快速上手指南&#xff1a;​​​​​​​Getting Started - Qt for Python PyS…

2024年第十五届蓝桥杯省赛B组Python【 简洁易懂题解】

2024年第十五届蓝桥杯省赛B组Python题解 一、整体情况说明 2024年第十五届蓝桥杯省赛B组Python组考试共包含8道题目&#xff0c;分为结果填空题和程序设计题两类。 考试时间&#xff1a;4小时编程环境&#xff1a;Python 3.x&#xff0c;禁止使用第三方库&#xff0c;仅可使…

Go语言--语法基础4--基本数据类型--类型转换

Go 是一种强类型的语言&#xff0c;所以如果在赋值的时候两边类型不一致会报错。一个类型的值可以被转换成另一种类型的值。由于 Go 语言不存在隐式类型转换&#xff0c;因此所有的类型转换都必须显式的声明。 强制类型转换语法 使用 type (a) 这种形式来进行强制类型转换&am…

nginx 代理时怎么更改 Remote Address 请求头

今天工作中遇到用 localhost 访问网站能访问后台 api&#xff0c;但是用本机IP地址后就拒绝访问&#xff0c;我怀疑是后台获取 Remote Address 然后设置白名单了只能 localhost 访问。 想用 nginx 更改 Remote Address server {listen 8058;server_name localhost;loca…

LeetCode刷题链表

文章目录 链表总结 常用技巧两数相加题解代码 两两交换链表中的节点题解代码 重排链表题解代码 合并k个升序链表题解代码 K个一组翻转链表题解代码 链表总结 常用技巧 画图 直观 形象 便于理解引入虚拟头节点&#xff0c;便于处理边界情况&#xff0c;方便我们对链表进行…

ESP32S3 多固件烧录方法、合并多个固件为单一固件方法

ESP32S3 多固件烧录方法、合并多个固件为单一固件方法 文章目录 ESP32S3 多固件烧录方法、合并多个固件为单一固件方法前言1、前期准备工作2、多固件烧录方法3、单固件烧录方法总结 前言 使用正点原子的ESP32S3 BOX开发板独立烧录编译生成的xxx.bin固件无法正常运行起来&#…

Webug4.0靶场通关笔记10- 第14关链接注入

目录 第14关 链接注入 1.打开靶场 2.源码分析 3.渗透实战 &#xff08;1&#xff09;方法1&#xff1a;跳转外部网页 &#xff08;2&#xff09;方法2&#xff1a;获取cookie 4.漏洞防御 本文通过《webug靶场第14关 链接注入》来进行渗透实战。 第14关 链接注入 链接注…

SpringBoot的汽车商城后台管理系统源码开发实现

概述 汽车商城后台管理系统专为汽车4S店和经销商设计&#xff0c;提供全面的汽车管理系统解决方案。 主要内容 1. 核心功能模块 系统提供以下主要功能&#xff1a; ​​销售管理​​&#xff1a;记录销售信息&#xff0c;跟踪交易进度​​客户管理​​&#xff1a;维护客户…

VBA代码解决方案第二十四讲:EXCEL中,如何删除重复数据行

《VBA代码解决方案》(版权10028096)这套教程是我最早推出的教程&#xff0c;目前已经是第三版修订了。这套教程定位于入门后的提高&#xff0c;在学习这套教程过程中&#xff0c;侧重点是要理解及掌握我的“积木编程”思想。要灵活运用教程中的实例像搭积木一样把自己喜欢的代码…

日本IT行业|salesforce开发语言占据的地位

在日本的IT行业中&#xff0c;Salesforce 开发语言处于一个较为专业但稳步增长的细分领域&#xff0c;并不是主流开发语言&#xff08;如 Java、Python、PHP&#xff09;&#xff0c;但其在某些行业和场景中地位越来越重要。 本篇以下是详细分析&#xff1a; Salesforce开发语言…

前端开发,文件在镜像服务器上不存在问题:Downloading binary from...Cannot download...

问题与处理策略 问题描述 在 Vue 项目中&#xff0c;执行 npm i 下载依赖时&#xff0c;报如下错误 Downloading binary from https://npm.taobao.org/mirrors/node-sass//v4.14.1/win32-x64-72_binding.node Cannot download "https://npm.taobao.org/mirrors/node-sa…

基于Vue2 + Element 实现任务列表管理功能的详细教程

前言&#xff1a;本文介绍的是如何从0开始搭建Vue2项目到1实现对任务添加、删除和筛选的功能&#xff0c;&#x1f517; 相关链接Vue 入门(安装与应用超详细教程) ❤ 【作者主页—&#x1f4da;阅读更多优质文章、获取更多优质源码】 目录 一 . 项目搭建 1.1 安装node.js 1.…

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】1.4 数据库与表的基本操作(DDL/DML语句)

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 1.4 数据库与表的基本操作&#xff08;DDL/DML语句&#xff09;1.4.1 数据库生命周期管理&#xff08;DDL核心&#xff09;1.4.1.1 创建数据库&#xff08;CREATE DATABASE&…