位图思想详解:用一个小小的比特征服整个世界

位图思想详解:用一个小小的比特征服整个世界

位图思想详解:用一个小小的比特征服整个世界

一、什么是位图?二、位图的形象理解三、位图的 Java 实现四、位图的算法原理剖析五、实际应用案例:网站用户活跃度统计五、真实的应用场景:布隆过滤器的基础六、算法题:判断字符是否唯一(easy)

一、什么是位图?

位图是一种超级节省空间的数据结构,他利用二进制位(0/1)来表示某个元素是否存在或某种状态是否为真。想象一下,用一个小小的比特位就能记录一个信息,这简直是数据存储界的"极简主义"!

二、位图的形象理解

想象你是一个小学班主任,班上有40个学生。周一早上,你需要记录谁到校了,谁没到。

传统方法:创建一个布尔数组boolean[] attended = new boolean[40],这要占用40个布尔值的内存。位图方法:只需要申请一个64位的long类型变量,每一个对应一个学生的出勤率。这就像是一个微型的"打卡机",每个学生占用一个小格子,来了就打个勾(1),没来就空着(0)。

三、位图的 Java 实现

public class Bitmap {

private long[] bits;

// 假设我们要表示n个元素

public Bitmap(int n) {

// 每个long有64位,所以需要 (n+63)/64 个long

this.bits = new long[(n + 63) / 64];

}

// 设置第i位为1(表示存在)

public void set(int i) {

bits[i / 64] |= 1L << (i % 64);

}

// 设置第i位为0(表示不存在)

public void clear(int i) {

bits[i / 64] &= ~(1L << (i % 64));

}

// 检查第i位是否为1

public boolean get(int i) {

return (bits[i / 64] & (1L << (i % 64))) != 0;

}

}

🏗️ 构造函数:为派对准备场地

public Bitmap(int n) {

// 每个long有64位,所以需要 (n+63)/64 个long

this.bits = new long[(n + 63) / 64];

}

想象你要举办一个有n位客人的派对,但每张桌子只能坐64人。你需要多少张桌子?当然是 ⌈n÷64⌉(向上取整)。这就是 (n + 63) / 64 的由来!

这里用了一个小技巧:整数除法会自动向下取整,所以 (n + 63) / 64 等价于 ⌈n÷64⌉。就像买桌子,9人需要2张桌子,不能买1.5张对吧?

n = 150 的情况

需要的long数组大小:(150 + 63) / 64 = 213 / 64 = 3.328125 ≈ 3

创建数组:bits = new long[3], 64 * 3 = 192

bits[0]:表示第0-63位

bits[1]:表示第64-127位

bits[2]:表示第128-191位

🔨 set() 方法:点亮你的专属小灯泡

public void set(int i) {

bits[i / 64] |= 1L << (i % 64);

}

这个方法就像一个漆黑的体育馆里,你要点亮 i 座位的灯。问题:这个座位在哪个区域?在哪个位置?

i / 64:确定在哪个long中(哪个区域)i % 64:确定哪个long中哪个bit位上(哪个位置)1L << (i % 64):创建一个只有那个位置是1的掩码(准备一个只照亮那个座位的聚光灯)|=:使用OR操作将那个位置修改成1(打开闪光灯)

假设i = 73,我们要点亮第73个灯

i / 64 = 73 / 64 = 1(在第1个long里)

i % 64 = 73 % 64 = 9(在这个long的第9位)

1L << 9 = 000...000100000000(二进制,只有第9位是1)

bits[1] |= 000...000100000000

🧹 clear() 方法:熄灭特定的灯

public void clear(int i) {

bits[i / 64] &= ~(1L << (i % 64));

}

这个方法就像是派对结束后,你要关掉特定的一盏灯。操作流程是:

i / 64:确定在哪个long(哪个区域)i % 64:确定哪个long中哪个bit位上(区域内的灯号)1L << (i % 64):创建一个只有那个位置是1的掩码~(1L << (i % 64)):反转掩码,现在除了那个位置都是1(准备一个"除了那盏灯以外都亮着"的状态)&=:使用AND操作将那一位设置为0,其他位保持不变(只关那一盏灯)

假设i = 73,我们要熄灭第73个灯

i / 64 = 73 / 64 = 1(在第1个long里)

i % 64 = 73 % 64 = 9(在这个long的第9位)

1L << 9 = 000...000100000000(二进制,只有第9位是1)

~(1L << 9) = 111...111011111111(二进制,只有第9位是0)

bits[1] &= 111...111011111111

🔍 get() 方法:查看灯是否亮着

public boolean get(int i) {

return (bits[i / 64] & (1L << (i % 64))) != 0;

}

这个方法就像是派对组织者在检查:第i号座位有人坐着吗?

i / 64:确定在哪个long(哪个区域)i % 64:确定在这个long的哪一位(区域内的座位号)1L << (i % 64):创建一个只有那个位置是1的掩码(准备一个只照亮那个座位的手电筒)bits[i / 64] & (1L << (i % 64)):使用AND操作检查那一位是否为1(用手电筒照,看看有没有人)!= 0:如果结果不是0,说明那一位是1(座位上有人)

假设我想要检查第73个灯是否被设置

i / 64 = 73 / 64 = 1(在第1个long里)

i % 64 = 73 % 64 = 9(在这个long的第9位)

1L << 9 = 000...000100000000(二进制,只有第9位是1)

假设 bits[1] = 000...000100010000(第9位和第4位是1)

bits[1] & (1L << 9) = 000...000100010000 & 000...000100000000 = 000...000100000000 ≠ 0

所以返回 true

四、位图的算法原理剖析

位图的核心原理其实也很简单:利用二进制位的0和1来记录信息

索引映射:每一个需要记录的元素映射到位图中的一个特定的位置状态表示:使用0表示不存在/否,1表示存在/是位操作:通过位运算可以高效的设置和获取状态

位图的数学原理可以表示为

元素 i 映射到数组索引 i / 64(假设用long数组)在该数组元素中的位偏移量为 i % 64设置该位:array[i / 64] |= (1L << (i % 64))清除该位:array[i / 64] &= ~(1L << (i % 64))检查该位:(array[i / 64] & (1L << (i % 64))) != 0

五、实际应用案例:网站用户活跃度统计

假设你在追踪一个拥有1000万用户的网站,需要记录每个用户在过去30天是否登录过。

传统方法:使用布尔数组,需要约300MB内存。位图方法:只需约36MB!

public class UserActivityTracker {

private Bitmap[] dailyActivity; // 30天的活跃记录

// 创建3个位图,每个表示一天的用户活跃度

public UserActivityTracker() {

dailyActivity = new Bitmap[30];

for (int i = 0; i < 30; i++) {

// 每一天的用户容量都得达到 1千万

dailyActivity[i] = new Bitmap(1_000_000);

}

}

// 记录用户在某天登录

public void recordLogin(int userId, int dayOffset) {

dailyActivity[dayOffset].set(userId);

}

// 检查用户在30天内是否活跃(至少有一天是在线的)

public boolean isActiveInLast30Days(int userId) {

for (int i = 0; i < 30; i++) {

if (dailyActivity[i].get(userId)) {

return true;

}

}

return false;

}

// 计算某天的用户活跃度

public int countActiveUsersOnDay(int dayOffset) {

int count = 0;

for (int i = 0; i < 1_000_000; i++) {

if (dailyActivity[dayOffset].get(i)) {

count++;

}

}

return count;

}

}

五、真实的应用场景:布隆过滤器的基础

这个位图实现是布隆过滤器(Bloom Filter)的基础,布隆过滤器是一种高效判断元素是否在集合中的概率数据结构。

// 使用位图实现的简单布隆过滤器

public class SimpleBloomFilter {

private Bitmap bitmap;

private int size;

public SimpleBloomFilter(int size) {

this.size = size;

this.bitmap = new Bitmap(size);

}

public void add(String item) {

int hash1 = Math.abs(item.hashCode() % size);

int hash2 = Math.abs((item.hashCode() * 31) % size);

int hash3 = Math.abs((item.hashCode() * 37) % size);

bitmap.set(hash1);

bitmap.set(hash2);

bitmap.set(hash3);

}

public boolean mightContain(String item) {

int hash1 = Math.abs(item.hashCode() % size);

int hash2 = Math.abs((item.hashCode() * 31) % size);

int hash3 = Math.abs((item.hashCode() * 37) % size);

return bitmap.get(hash1) && bitmap.get(hash2) && bitmap.get(hash3);

}

}

这就像是一个高效的俱乐部入场系统:

当有人加入俱乐部时,在多个特定位置留下标记当有人要进入时,检查这些位置是否都有标记可能会有误判(误认为某人是会员),但绝不会错过真正的会员

想要了解更多关于布隆过滤器的内容,请点击🔍该链接

六、算法题:判断字符是否唯一(easy)

请点击🔍:该题目链接

代码示例:

class Solution {

public boolean isUnique(String astr) {

// 利用鸽巢原理 来做优化

if (astr.length() > 26) {

return false;

}

int bitMap = 0; // 位图原理:比特位 有 1 表示已经存在 0 表示还未存在

for (int i = 0; i < astr.length(); i++) {

// 判断 bit 位是否已经标志上 1 了

int x = astr.charAt(i) - 'a';

if (((bitMap >> x) & 1) == 1) {

// 表示 bitMap >> x == 0 此时 未标志上

return false;

}

// 把当前字符加⼊到位图中

bitMap |= (1 << x);

}

return true;

}

}

代码解析:

鸽巢原理:派对人数超过座位数,直接拒之门外

首先,小写字母只有26个,如果字符串长度超过26,那肯定有人得站着(重复)。所以代码开头就用了这个优化:

if (astr.length() > 26) {

return false;

}

这就像保安大哥看了一眼派对名单,发现人数超过26,直接说:“别来了,里面没位置了!”

位图:每个字符对应一个比特位,0是空位,1是有人

接下来的重头戏是位图。我们用整数bitMap的每一位代表一个字母是否出现过。比如:

a → 第0位b → 第1位…z → 第25位

假设bitMap是二进制数000...101,表示a和c已经到场(第0位和第2位是1)。

操作步骤:查座位、占座位

查座位: 当字符c(x=2)到来时,我们想知道第2位是否已被占用。 把bitMap右移2位,让第2位变成最低位,然后和1做&运算:

(bitMap >> x) & 1 // 结果为1表示座位被占

占座位: 如果座位是空的,就用bitMap |= (1 << x)把对应位设为1。 这相当于给字符c发了一张VIP卡,标记这个座位已有人。

举个栗子 🌰

假设字符串是"abc":

处理a(x=0):

bitMap初始为0(二进制全0)。检查(0 >> 0) & 1 → 0,座位空。更新bitMap = 1(二进制000...001)。 处理b(x=1):

检查(1 >> 1) & 1 → 0(1右移1位是0)。更新bitMap = 1 | (1<<1) = 3(二进制000...011)。 处理c(x=2):

检查(3 >> 2) & 1 → 0(3是11,右移2位是0)。更新bitMap = 3 | (1<<2) = 7(二进制000...0111)。

派对圆满结束,所有字符都有独立座位,返回true!

冲突时刻:当字符试图抢占已占座位

如果字符串是"abca",处理最后一个a时:

x=0,此时bitMap是7(二进制000...0111)。(7 >> 0) & 1 → 1,座位已被占!保安立刻将其赶走,返回false。

相关推荐

从 Siri 到 FaceID……被苹果收购的这 10+ 家服务和公司现在怎么样了
炖兔肉🐇
365游戏注册

炖兔肉🐇

📅 10-10 👁️ 3544
拔牙紧张害怕怎么办
精准原创123656官方网

拔牙紧张害怕怎么办

📅 07-08 👁️ 2479
photoshop 怎么换图?
365bet体育在线赌场

photoshop 怎么换图?

📅 10-22 👁️ 8034
700毫米等于多少厘米?
365bet体育在线赌场

700毫米等于多少厘米?

📅 11-14 👁️ 9168
王者荣耀梦奇重做废了是真的吗 梦奇重做强度分析
精准原创123656官方网

王者荣耀梦奇重做废了是真的吗 梦奇重做强度分析

📅 07-19 👁️ 6977
翾忆名字寓意及打分
精准原创123656官方网

翾忆名字寓意及打分

📅 10-06 👁️ 4806
你们最近做的新站一般都多久收录
365bet体育在线赌场

你们最近做的新站一般都多久收录

📅 08-18 👁️ 286
地下城堡4最强阵容推荐最新2025:对单boss/对群T0阵容怎么搭配?