网站首页 > 基础教程 正文
假设现在有一亿个8位以内的数要进行排序,应该怎么处理?
想一想,如果是使用普通的排序算法,那需要将这一亿个数全部都读到内存中,然后再进行排序计算的话,如果全部按照 int类型来说,一个 int32位,4字节,那一千万个数就是 400M,再加上运行时的一些额外内存,肯定是不会少于400M 的。
内存可是服务器的宝贵资源,这实在是有点浪费了,而且快速排序的时间复杂度是 ,这样一来,这一亿个数光排序计算的时间就会很长,还不包括将这一亿个数读入内存的时间。
那有没有什么又快有省内存的方法呢?今天咱们来介绍一种方法叫做位图排序法。
什么是位图
位图(Bitmap)是一种用于表示和存储数据的数据结构,一种数据类型。
实际上位图就是一组二进制位(0或1)的序列,每个二进制位表示一个数据元素的状态。
在位图中,每个数据元素对应位图中的一个位,位图中的每一位都可以看作是一个开关,表示某个数据元素是否存在或满足某个条件。如果某个数据元素存在,对应的位值为1;如果不存在,对应的位值为0。
位图的大小通常由位数来决定,比如8位(一个字节)、16位、32位或64位等。每个位的取值只有0或1,所以位图的存储空间相对于存储实际数据本身要小得多。
为什么位图的存储空间要比实际存储数据本身要小呢?
首先来举个例子。假设你有排列为从 0 到 9 的10个待办事项,假定待办事项有只有两种状态,就是未完成和已完成。在开发过程中,我们很容易想要可以用用布尔值来表示完成和未完成的状态。
那我们可以这样声明这10个待办事项的集合,一个状态集合。
List<Boolean> statusList = new ArrayList();
那这样一来,这10个状态会占用多少空间呢,List本身也占用空间的,这个咱们先不算,单纯算这10个布尔值。在 Java 中一个 Boolean 占用1个字节,那10个呢,就是10个字节。
而我们把它换成用位图来表示呢?
把从 0 到 9 的10个待办事项只用 10个比特位就可以表示了。
上面的顺序表示从0到9的待办事项排序,下面每一个蓝色的表示一个比特,0 表示未完成,1表示已完成。
这样一来,想看第 0 个事项的状态,就获取第0个比特位的值,想看第1个事项的状态,就取第1个比特位的值,依此类推,可以获取每一个事项的状态。
位图排序
通过以上的分析,我们就知道用位图如何表示简单的数据了,但一时间好像和排序扯不上什么关系啊。
假设现在有5个数用位图法排序,分别是1、3、5、7、10,怎么做呢?一个比特位要么是0要么是1,怎么表示1、3、5、7、10 呢?
其实总结下来就是两点:
- 把位图想象成一个比特数组,数组索引下标可以表示数值,例如3这个数字,就是索引为3的位置;
- 每一个比特位的值表示这个索引位置上有没有数值存在,因为待排序的集合中有3这个数字,所以在索引3的位置上应该是1,集合中没有2这个数字,所以在索引2的位置上应该是 0 。
根据以上两点,可以将这5个待排序数的分布变换成下面的位图形式。
变换的过程
1、首先找到排序集合中的最大数,因为最大的数决定了位图的长度。
位图排序的一个特点就是最大长度就是最大的那个数,所以说,位图排序不适合那种范围、跨度特别大的情况,比如排1万个数,最小的是0,最大的一百亿,那这样一来,这个位图的长度就是一百亿比特了,还不如用其他排序算法。
这个集合中最大的数是10,所以位图的长度是11,因为包含0了。
2、将位图中所有比特位的默认值设置为 0 。
3、遍历集合中的每一个元素,根据元素的数值,将此元素所在索引位的值设置为1。
这样一来呢,只要从头开始遍历这个位图,依次找出值为1的比特位,这个比特位的索引就是实际的值,顺序一下就出来了。
接近一亿个8位以内的排序
回到最开始说的那个问题,将近一亿个8位以内的数排序,差不多就是一亿个数的全排列了,那最大的数就按照一亿算,差不多是12.5M,比快速排序算法全放到内存中的400M要小的多了。
public static void main(String[] args) {
String inputFile = "random_numbers.txt";
int maxRandomNumber = 999999999;
// 从文本文件中读取随机数
Set<Integer> randomNumbers = new HashSet<>();
try (BufferedReader reader = new BufferedReader(new FileReader(inputFile))) {
String line;
while ((line = reader.readLine()) != null) {
int randomNumber = Integer.parseInt(line);
randomNumbers.add(randomNumber);
}
} catch (IOException e) {
return;
}
// 将随机数转换为位图
BitSet bitmap = new BitSet(maxRandomNumber + 1);
for (int num : randomNumbers) {
bitmap.set(num);
}
//输出到一个文件
String outputFile = "sorted_random_numbers.txt";
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
for (int i = 0; i <= maxRandomNumber; i++) {
if (bitmap.get(i)) {
writer.write(String.format("%08d", i));
writer.newLine();
}
}
System.out.println("随机数已成功从文件中位图排序并写入文件:" + outputFile);
} catch (IOException e) {
System.err.println("写入文件时出现错误:" + e.getMessage());
}
}
另外说一点,这接近一亿个数应该是没有重复数据的情况,如果重复数据过多,用位图排序就要用很多额外的手段来标记重复,到最后可能得不偿失。
位图的使用场景
排序小范围整数:
位图排序对于排序小范围的非负整数非常高效。当待排序的整数范围相对较小,并且整数数量较大时,传统的比较排序算法如快速排序、归并排序等可能会有较大的开销,而位图排序则能够在较短的时间内完成排序。
刚才这个接近一亿个数是个比较极端的例子,很多时候其实位图占用的空间还是比较大的,只要有一个特别大的数,就会把整个空间放大。
去重操作:
位图排序可以用于去除重复的元素。通过位图排序,可以快速识别是否有重复元素,并对重复元素进行去重操作。
查找元素:
位图排序可以用于快速查找元素是否存在。在位图中,每个整数对应一个位,如果位为1表示该整数存在,如果位为0表示该整数不存在。通过查找对应的位,可以快速判断一个整数是否存在。
统计频率:
位图排序可以用于统计整数出现的频率。通过在位图中记录整数的出现次数,可以快速计算每个整数的频率。
集合操作:
位图排序可以用于进行集合操作,如并集、交集、差集等。通过对两个位图进行位运算,可以快速得到集合操作的结果。
来源:https://mp.weixin.qq.com/s?__biz=MzAxMjA0MDk2OA==&mid=
猜你喜欢
- 2024-12-18 吊打 ThreadLocal,谈谈FastThreadLocal为啥能这么快?
- 2024-12-18 分布式锁中的王者方案 - Redisson
- 2024-12-18 你管这玩意儿叫高并发? 什么叫高并发
- 2024-12-18 系统数据实时同步方案一落地 系统间数据同步解决方案
- 2024-12-18 分布式锁工具:Redisson 分布式锁 redis zookeeper
- 2024-12-18 Spring Cloud Circuit Breaker快速入门Demo
- 2024-12-18 BitMap是啥?脑袋一下空白? bitmap文件头
- 2024-12-18 Java基础-数据类型和数据结构,初阶小白看过来~
- 2024-12-18 10张图带你搞定高并发之网络IO模型
- 2024-12-18 「微服务架构」分布式锁Redission官方文档
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)