C++ 实现位图

引出

面试题:给出 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.mfbz.cn/a/189247.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PyCharm 安装插件Vue

一、打开PyCharm工具 File -> Settings -> Plugins 二、在项目中添加Vue.js的依赖项。 npm install vue 三、页面应用

Innux(特殊位与权限)

特殊位与权限 目录&#xff1a; 1. SUID 2. SGID 3. SBIT 4. 文件系统属性chattr权限 5. 管理员权限sudo 1. SUID 1.1 什么是SUID SUID只对二进制可执行文件才有效&#xff08;文件必须具备x权限&#xff09; 执行者对该程序有 x 权限 当前程序拥有SUID时&#xff0…

MySQL性能优化,SQL调优,SQL调优的手段

文章目录 对MySQL性能的优化的理解硬件和操作系统层面的优化架构设计层面的优化MySQL程序配置优化SQL优化 SQL调优有哪几种方式1.EXPLAIN2.SQL语句中IN包含的值不应过多3.SELECT语句务必指明字段名称4.当只需要一条数据的时候&#xff0c;使用limit 15.如果排序字段没有用到索引…

android系统新特性——用户界面以及系统界面改进

用户界面改进 Android用户界面改进最明显的就是MD了。MD是Google于2014年推出的设计语言&#xff0c;它是一套完整的设计系统&#xff0c;包含了动画、样式、布局、组件等一系列与设计有关的元素。通过对这些行为的描述&#xff0c;让开发者设计出更符合目标的软件&#xff0c…

Percepio Tracealyzer 4.8.1 视觉跟踪诊断解决方案

Percepio Tracealyzer 4.8.1 视觉跟踪诊断解决方案&#xff0c; 是使嵌入式软件开发人员能够深入了解其运行时系统。这样可以更轻松地调试系统级问题、查找软件设计缺陷以及测量软件时序和资源使用情况。确保您的代码可靠、高效且响应迅速。 视觉运行时洞察 在运行时将 X 射线视…

计算器的模拟实现

计算器的模拟实现 一、实验题目&#xff1a;计算器二&#xff1a;实验目的&#xff1a;三&#xff1a;实验内容与实现1&#xff1a;【实验内容】2&#xff1a;【实验实现】1.计算器界面的实现&#xff0c;如下图所示&#xff1a;2&#xff1a;各项功能的实现&#xff0c;如下图…

Linux常用命令——bg命令

在线Linux命令查询工具 bg 用于将作业放到后台运行 补充说明 bg命令用于将作业放到后台运行&#xff0c;使前台可以执行其他任务。该命令的运行效果与在指令后面添加符号&amp;的效果是相同的&#xff0c;都是将其放到系统后台执行。 在Linux系统中执行某些操作时候&…

基于YOLOv5的视频计数 — 汽车计数实现

在视频中计数对象可能看起来有挑战性&#xff0c;但借助Python和OpenCV的强大功能&#xff0c;变得令人意外地易于实现。在本文中&#xff0c;我们将探讨如何使用YOLO&#xff08;You Only Look Once&#xff09;目标检测模型在视频流或文件中计数对象。我们将该过程分解为简单…

HCIA题目解析(1)

1、【多选题】关于动态 MAC 地址表说法正确的是&#xff1f; A、通过报文中的源MAC地址学习获得的动态MAC表项会老化 B、通过查看指定动态MAC地址表项的个数&#xff0c;可以获取接口下通信的用户数 C、在设备重启后&#xff0c;之前的动态表项会丢失 D、在设备重启后&…

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

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

C#,《小白学程序》第十九课:随机数(Random)第六,随机生成任意长度的大数(BigInteger)

1 文本格式 using System; using System.Linq; using System.Text; using System.Collections.Generic; /// <summary> /// 大数的&#xff08;加减乘除&#xff09;四则运算、阶乘运算 /// 乘法计算包括小学生算法、Karatsuba和Toom-Cook3算法 /// 除法运算为 Truffer…

【腾讯云云上实验室】向量数据库相亲社交应用实践

快速入口 &#x1f449;向量数据库_大模型知识库_向量数据存储_向量数据检索- 腾讯云 (tencent.com) 文章目录 前言1. 向量数据库概念及原理1.1 向量数据库概念1.2 向量数据库核心原理1.3 向量数据库优缺点1.4 向量数据库与传统数据库的区别 2. 腾讯云向量数据库的基本特性及优…

[数据结构]-红黑树

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、红黑树的…

可观测性建设实践之 - 日志分析的权衡取舍

指标、日志、链路是服务可观测性的三大支柱&#xff0c;在服务稳定性保障中&#xff0c;通常指标侧重于发现故障和问题&#xff0c;日志和链路分析侧重于定位和分析问题&#xff0c;其中日志实际上是串联这三大维度的一个良好桥梁。 但日志分析往往面临成本和效果之间的权衡问…

css加载会造成阻塞吗??

前言 前几天面试问到了这个问题&#xff0c;当时这个答得不敢确定哈哈&#xff0c;虽然一面还是过了 现在再分析下这个&#xff0c;总结下&#xff0c;等下次遇到就能自信得回答&#xff0c;666 准备工作 为了完成本次测试&#xff0c;先来科普一下&#xff0c;如何利用chr…

【UnLua】在 Lua 中定义 UE 反射类型

【UnLua】在 Lua 中定义 UE 反射类型 UEnum C UENUM(BlueprintType) enum class ETest : uint8 {Walking,Running,Sprinting,ALS_MAX UMETA(DisplayName"ALS MAX") };Test.generated.h #include "UObject/ObjectMacros.h" #include "UObject/Scri…

人工智能-注意力机制之Transformer

Transformer 比较了卷积神经网络&#xff08;CNN&#xff09;、循环神经网络&#xff08;RNN&#xff09;和自注意力&#xff08;self-attention&#xff09;。值得注意的是&#xff0c;自注意力同时具有并行计算和最短的最大路径长度这两个优势。因此&#xff0c;使用自注意力…

红队攻防实战系列一之metasploit

百目无她&#xff0c;百书质华&#xff0c;君当醒悟&#xff0c;建我中华 本文首发于先知社区&#xff0c;原创作者即是本人 前言 在红队攻防中&#xff0c;我们主要在外网进行信息收集&#xff0c;通过cms或者其他漏洞拿到shell&#xff0c;之后通过免杀木马将windows或lin…

学习.NET验证模块FluentValidation的基本用法(续2:其它常见用法)

FluentValidation模块支持调用When和Unless函数设置验证规则的执行条件&#xff0c;其中when函数设置的是满足条件时执行&#xff0c;而Unless函数则是满足条件时不执行&#xff0c;这两个函数的使用示例如及效果如下所示&#xff1a; public AppInfoalidator() {RuleFor(x>…

C#,《小白学程序》第八课:列表(List)其二,编制《高铁列车时刻表》与时间DateTime

1 文本格式 /// <summary> /// 车站信息类 class /// </summary> public class Station { /// <summary> /// 编号 /// </summary> public int Id { get; set; } 0; /// <summary> /// 车站名 /// </summary&g…
最新文章