C++ 实现位图

news2025/2/23 20:01:35

引出

面试题:给出 40 亿个不重复的无符号整数,没有排过序。给定一个无符号整数,如何快速判断这个数是否在这 40 亿个无符号整数中。[ 腾讯面试题 ]

  • 想法一:将 40 亿个数据存放到 set 里面,然后再查找指定的无符号整数。
    时间复杂度:o( l o g 2 N log_2N log2N)。
  • 将 40 亿个无符号整数排序之后二分查找。
    我们先不考虑效率问题,实现上面的两种方案,都需要将 40 亿个整数加载到内存中。那么 40 亿个整数全部加载到内存中,需要多大空间呢?
    40   0000   0000   ×   4 = 160   0000   0000  字节 =   ( 160   0000   0000   ÷   1024   ÷   1024   ÷   1024 )   G B   ≈   15   G B 40\space0000\space0000 \space \times \space 4 = 160\space0000\space0000\space字节 = \space (160 \space 0000 \space 0000 \space \div \space 1024 \space \div \space 1024 \space \div \space 1024) \space GB\space \approx \space 15 \space GB 40 0000 0000 × 4=160 0000 0000 字节= (160 0000 0000 ÷ 1024 ÷ 1024 ÷ 1024) GB  15 GB

好的,我们需要的内存大概是 15 GB。
对于 set 来说,除了数据域,还要维护其他的指针信息。15 GB 的内存还不够。
普通电脑的内存也就 16 GB左右吧!但是操作系统这个软件也需要内存空间哇!所以说用 set 和 排序加二分查找都不是理想的办法!


我们再来看看问题:题目的要求是判断一个数在不在!那么一个数在或者不在,只有两种状态。因此我们可以用一个二进制位来表示一个数在不在。
因此我们可以开辟 2 32 = 42   9496   7296 2^{32} = 42 \space 9496 \space 7296 232=42 9496 7296 个比特位,用哈希表中的直接定址法(哈希表的直接定址法开辟的空间需要包含给出的 40 亿个整数的范围),将 40 亿个整数一一映射到不同的比特位。
那么我们需要的空间:
( 2 32   ÷ 8   ÷ 1024   ÷ 1024 )   M B = 512   M B (2^{32} \space \div 8 \space \div 1024 \space \div 1024) \space MB = 512 \space MB (232 ÷8 ÷1024 ÷1024) MB=512 MB
这样一来,内存的问题是不是就轻松搞定啦!
上面使用的方法就是位图

位图基本结构定义

  • 在 C++ 中我们并没有比特位的类型,因此在位图的底层,使用的数据结构就是 vecctor<char> 或者 vector<int>。只要数据类型支持位运算就没多大问题。
  • 我们应该如何确定位图中底层 vector 的空间大小呢?首先这个 vector 肯定不能是静态的。因此就需要我们传参数来决定 vector 的大小。当然你可以在 bitmap (位图) 的构造函数做文章。但是库函数中并不是这么实现的。
    你还记得模板的非类型模板参数吗?如果你需要复习,点击这里:​➡️ ​C++模板详解
    我们定义一个非类型模板的参数,这样就可以不在构造函数传参啦!
#pragma once
#include<cstddef>
#include<vector>
namespace Tchey
{
    template <size_t N>
    class bitmap
    {

    private:
        vector<char> _a;
    };
}

构造函数

我们接收了一个非类型的模板参数,根据这个非类型的模板参数,我们就能确定 vector 的大小啦!

  • 如果你实现的 vector 存储的数据类型是 int。那么构造函数中,你的 vector 需要的空间大小就是:
    ( 非类型模板参数  N   ÷   32 ) + 1 (非类型模板参数\space N \space \div \space 32 ) + 1 (非类型模板参数 N ÷ 32)+1
    这个加一是为了应对需要的空间不是 int 类型的整数倍的情况,这是必须的。
  • 如果你实现的 vector 存储的数据类型是 char。那么构造函数中,你的 vector 需要的空间大小就是:
    ( 非类型模板参数  N   ÷   8 ) + 1 (非类型模板参数\space N \space \div \space 8 ) + 1 (非类型模板参数 N ÷ 8)+1

这样看起来 vector 存储 char 更加节省内存呢?只不过这点节省好像真的没多大必要呢!

bitmap()
{
    int size = N / 8 + 1;
    _a.resize(size);
}

void set(size_t x)

这个函数将参数 x 映射的那个位置在位图中标记为 1。
那么,我们拿到参数 x 如何将这个位置标记为 1 呢?在下图中,红色矩形是一个位图,红色矩形中的黑色矩形是一个一个的 char,假设我们要将下图中 x = 12 的位置标记为 1。
在这里插入图片描述

  • 因为我们的 vector 存储的是 char ,我们要找到 x 在 vector 中的哪个下标,只需要将 x ÷ 8 x \div 8 x÷8 即可。不妨记作: i
  • 除了获取 x 在 vector 中的哪一个下标,我们还需要获取 x 在这个下标的哪一位!我们只需要将 x   %   8 x \space \% \space 8 x % 8 即可。不妨记作:j
    那要如何修改呢?只要让将 i 下标的 char1 << j 相或即可。

在这里你可能会想到大小端的问题,只能说我们这样做完全没有问题呢!大小端无非是高地址存储 char 的高位还是低位的问题。我们定义了将 x 置为 1 的逻辑。只要查找和置零按照同样的逻辑来就行。只不过在内存上对应的位置并不会像上面的示意图那样按照顺序来罢了!

void reset(size_t x)

这个函数将参数 x 映射的那个位置在位图中标记为 0。
关于给定参数 x 怎么找到 x 在 vector 中的哪一个下标(不妨记作 i),在这个下标的哪一位(不妨记作 j),在 set 函数中就已经讲解完毕了。这里就不再赘述啦!
那怎么将 x 对应的那个位置的比特位置为 0 呢?其实很简单:(~(1 << j)) & _a[i]
如果真的不理解就可以举一个例子呢!
如下图我们想将橙色位置的比特位标记为 0。
在这里插入图片描述

bool test(size_t x)

这个函数可以检测 x 对应的比特位是否是 1,如果 x 对应的比特位为 1 返回 true;反之返回 false。
实现:同样的我们需要计算出 x 对应的比特位在 vector 的哪个下标(不妨记作 i ),在这个下标的哪一位(不妨记作 j)。我们只需要返回:_a[i] & (1 << j) 即可。如果 (1 << j) 与 _a[i] 向与的结果为 0,则说明 _a[i] 的第 j 为就是 0。否则就是 1。因此我们返回相与的结果就好了。

bool test(size_t x)
{
    size_t i = x / 8;
    size_t j = x % 8;
    return _a[i] & (1 << j);
}

测试代码

#include<iostream>
#include<vector>
#include<cstddef>
using namespace std;
#include "bitmap.h"

int main()
{
    Tchey::bitmap<10> bm;
    bm.set(1);
    bm.set(3);
    bm.set(5);
    bm.set(7);
    bm.set(9);

    for(int i = 0; i < 10; i++)
    {
        cout << bm.test(i) << " ";
    }
    cout << endl;

    bm.reset(1);
    bm.reset(3);
    bm.reset(5);
    bm.reset(7);
    bm.reset(9);
    
    for(int i = 0; i < 10; i++)
    {
        cout << bm.test(i) << " ";
    }
    cout << endl;

    return 0;
}

在这里插入图片描述
我们看到输出结果是没有任何问题的哈!那么我们的 bitmap 就模拟实现完成啦!

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

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

相关文章

深度学习技巧应用30-深度学习中的GPU的基本架构原理与应用技巧

大家好,我是微学AI,今天给大家介绍一下深度学习技巧应用30-深度学习中的GPU的基本架构原理与应用技巧,GPU是一种专门用于处理大量并行操作的硬件设备,它的架构设计主要是为了图形渲染。然而,由于其并行处理能力,现在广泛应用于深度学习、科学计算等领域。主要的GPU制造商…

运维高级--centos7源码安装Apache

安装必要的依赖项&#xff1a; sudo yum groupinstall "Development Tools" sudo yum install pcre pcre-devel zlib zlib-devel openssl openssl-devel这将安装编译和构建所需的基本工具&#xff0c;以及 Apache HTTP Server 所需的一些依赖项。 下载 Apache HTT…

从0到0.01入门 Webpack| 008.精选 Webpack面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【网络】DNS协议、ICMP协议、NAT技术

DNS协议、ICMP协议、NAT技术 一、DNS协议1、产生背景2、域名简介3、域名解析的工作流程4、使用dig工具分析DNS过程 二、ICMP协议1、ICMP介绍2、ICMP协议格式3、ping命令4、traceroute命令 三、NAT技术1、NAT技术背景2、NAT IP转换过程3、地址转换表4、NAPT技术5、重新理解路由器…

JsonRPC协议详解(协议介绍、请求示例、响应示例)

JsonRPC协议详解 什么是RPC&#xff1f; RPC&#xff08;远程过程调用&#xff09;是一种用于实现分布式系统中不同进程或不同计算机之间通信的技术。它允许我们像调用本地函数一样调用远程计算机上的函数&#xff0c;使得分布式系统的开发变得更加简单和高效。 什么是JsonRP…

【数据结构实验】图(三)图的深度优先搜索(DFS)生成树

文章目录 1. 引言2. 深度优先搜索生成树3. 实验内容3.1 实验题目&#xff08;一&#xff09;输入要求&#xff08;二&#xff09;输出要求 3.2 算法实现1. 数据结构2. 队列操作函数3. 广度优先搜索遍历4. 创建图5. 深度优先搜索算法6. 主函数及DFS主函数7. 输出生成树信息 3.3 …

第一百八十回 介绍两种阴影效果

文章目录 1. 概念介绍2. 实现方法3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"自定义SlideImageSwitch组件"相关的内容&#xff0c;本章回中将介绍两种阴影效果.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在…

【服务器能干什么】二十分钟搭建一个属于自己的 RSS 服务

如果大家不想自己捣鼓,只是想尝尝鲜,可以在下面留言,我后台帮大家开几个账号玩一玩。 哔哩哔哩【高清版本可以点击去吐槽到 B 站观看】:【VPS服务器到底能干啥】信息爆炸的年代,如何甄别出优质的内容?你可能需要自建一个RSS服务!_哔哩哔哩_bilibili 前言 RSS 服务 市…

面试:双线程交替打印奇偶数

代码如下&#xff1a; package practice1;/*** 0-100的奇数偶数打印* 1、通过对象的wait和notify进行线程阻塞* 2、通过对num%2的结果进行奇数偶数的判断输出**/ public class JiOuOne {private static volatile int num 0;private static final int max 100;public static …

#define例题

我们已经学了#define的所有知识&#xff0c;让我们来看这道题&#xff0c;可不要又陷入陷阱 题目要求&#xff1a; #define N 4 #define Y(n) ((N2)*n) int main() {int z 2 * (N Y(5 1));printf("z%d\n", z);return 0; } 求这个z的值是多少&#xff1f; 我们直接…

Stable-Diffusion——Windows部署教程

Windows 参考文章&#xff1a;从零开始&#xff0c;手把手教你本地部署Stable Diffusion Webui AI绘画(非最新版) 一键脚本安装 默认环境安装在项目路径的venv下 conda create -n df_env python3.10安装pytorch&#xff1a;&#xff08;正常用国内网就行&#xff09; python -…

Sublime Text 3 安装离线插件 anaconda

1 下载 Sublime Text 3 免安装版 Download - Sublime Text 2 下载 Package Control&#xff0c;放到 Sublime Text Build 3211\Data\Installed Packages 目录下。 Installation - Package Control 3 页面搜索 anaconda anaconda - Search - Package Control Anaconda - Pac…

车载通信架构 —— 传统车内通信网络MOST总线(光纤传输、专精多媒体)

车载通信架构 —— 传统车内通信网络MOST总线(光纤传输、专精多媒体) 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都…

自建私有化证书颁发机构(Certificate Authority,CA)实战之 《0x02 Nginx 配置 https双向认证》

自建CA实战之 《0x02 Nginx 配置 https双向认证》 上一章节我们已经实现了Nginx上配置https单向认证&#xff0c;主要场景为客户端验证服务端的身份&#xff0c;但是服务端不验证客户端的身份。 本章节我们将实现Nginx上配置https双向认证&#xff0c;主要场景为客户端验证服…

Day41力扣打卡

打卡记录 第 N 位数字&#xff08;找规律&#xff09; 链接 class Solution:def findNthDigit(self, n: int) -> int:count, digit, start 9, 1, 1while n > count:n - countdigit 1start * 10count start * 9 * digitnum start (n - 1) // digitreturn int(str(n…

详解#define

我们要知道&#xff0c;#define后面定义的标识符只进行替换而不进行计算&#xff0c;我们不能根据惯性自动给它计算了&#xff0c;这样可能会出错。 目录 1.关于#define 1.1#define定义标识符 1.2#define定义宏 1.3#define的替换规则 2.#和## 1.# 2.## 3.带副作用的宏参…

1.4 8位加法器

1.半加器 2.全加器 半加器: 完整模拟1位加法 1.A,B 接受端,接受1或0 , 2个电信号 2.异或门 做为结果: 1^10, 0^00, 1^01, 0^11 与编程中的: 异或一致 3.与门 做为进位: 1&11,1&00,0&10, 0&01 与编程中的: 与一致 4.半加器实现1位的加法运算,比如:A端: …

11 月 25 日 ROS 学习笔记——3D 建模与仿真

文章目录 前言一、在 ROS 中自定义机器人的3D模型1. 在 rviz 里查看3D模型2. xacro 二、Gazebo1. urdf 集成 gazebo2. 综合应用1). 运动控制及里程计2). 雷达仿真3). 摄像头信息仿真4). kinect 深度相机仿真5). 点云 前言 本文为11 月 25 日 ROS 学习笔记——3D 建模与仿真&am…

Vue框架学习笔记——键盘事件

文章目录 前文提要键盘事件&#xff08;并不是所有按键都能绑定键盘事件&#xff09;常用的按键不同的tab和四个按键keyCode绑定键盘事件&#xff08;不推荐&#xff09;Vue.config.keyCode.自定义键名 键码 神奇的猜想div标签和click.enterbutton标签和click.enter 前文提要 …

redhat9.3配置国内yum阿里源

由于新建的Redhat9.3在未注册激活之前是没有yum源的配置文件的&#xff0c;所以需要我们自己新建一个yum源文件的配置文件 vim /etc/yum.repos.d/aliyun_yum.repo 内容如下&#xff1a; [ali_baseos] nameali_baseos baseurlhttps://mirrors.aliyun.com/centos-stream/9-str…