位圖的實現和拓展

一:位圖的介紹

①:需要位圖的場景

給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?類型會變成最大值(即?232-1

2:bitset<UINT_MAX> bs3

標準定義:

  • UINT_MAX?是?<climits>?中定義的無符號整數最大值

  • 標準值:4,294,967,295(即?232-1

3:bitset<0xffffffff> bs4

十六進制解析:

  • 0xffffffff = 4,294,967,295(即?232-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.pswp.cn/news/904171.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/904171.shtml
英文地址,請注明出處:http://en.pswp.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&…