位运算在查找重复元素中的秒用
在大部分算法中,默认给定的数据量都很小的,例如只有几个或者十几个元素,但是如果将数据量提高到百万甚至十几亿,那处理逻辑就会发生很大差异,这也是算法考查中,经常出现的类问题。
在海量数据中,此时普通的数组、链表、Hash、树等等结构有无效了,因为内存空间放不下了。而常规的递归、排序,回溯、贪心和动态规划等思想也无效了,因为执行都会超时,必须另外想办法。这类问题该如何下手呢? 这里介绍三种非常典型的思路:
1. 使用位存储,使用位存储最大的好处是占用的空间是简单存整数的1/8。例如一个40亿的整数数组,如果用整数存储需要16GB左右的空间,而如果使用位存储,就可以用0.5GB的空间,这样
很多问题就能够解决了。
2. 如果文件实在太大,无法在内存中放下,则需要考虑将大文件分成若干小块,先处理每个块最后再逐步得到想要的结果,这种方式也叫做外部排序。这样需要遍历全部序列至少两次,是典型的用时间换空间的方法。
3. 堆,如果在超大数据中找第K大、第K小,K个最大、K个最小,则特别适合使用堆来做。而且将超大数据换成流数据也可以,而且几乎是唯一的方式,口诀就是“查小用大堆,查大用小堆”
1. 用4KB内存寻找重复元素
给定一个数组,包含从1到N的整数,N最大为32000,数组可能还有重复值,且N的取值不定,若只有4KB的内存可用,该如何打印数组中所有重复元素。
分析: 者是一道海量数据问题的热身题,如果去掉“只有4KB”的要求,我们可以先创建一个大小为N的数组,然后将这些数据放进来,但是整数最大为32000。如果直接采用数组存,则应该需要32000*4B=128KB的空间,而题目有4KB的内存限制,我们就必须先解决该如何存放的问题。
如果只有4KB的空间,那么只能寻址8*4*2^10个比特,这个值比32000要大的,因此我们可以创建32000比特的位向量(比特数组),其中一个比特位置就代表一个整数利用这个位向量,就可以遍历访问整个数组。如果发现数组元素是v,那么就将位置为V的设置为1,碰到重复元素,就输出一下。具体实现代码如下:
pubilc class FindDuplicateIn32000 {
// 将数用位存储到比特数组并查看是否有重复
public void checkDuplicates (int[] array){
// 创建大小为32000 < 4KB 的比特数组
BitSet bs = new BitSet(32000);
for (int i = 0; i < array.length; i++){
int num = array[i]; // 要存储的数
int num0 = num - 1; // 要存储的位置索引
if (bs.get(num0)) {
System.out.println(num); // 若该元素已存在则打印
} else {
bs.set(num0); // 若不存在则将改为设为1以存储该元素
}
}
}
// 定义比特数组
class BitSet {
int[] bitset;
public BitSet(int size){
this.bitset = new int[size >> 5];
}
// 判断数组中是否有重复元素
boolean get(int pos) {
int wordNumber = (pos >> 5); // 除以32 --- 数大小
int bitNumber = (pos & 0×1F); // 除于32 --- 存储的位位置
// 查看对应位位置上是否为1,即存在该元素
return (bitset[wordNumber] & (1 << bitNumber)) != 0;
}
// 将该位设为1
void set(int pos){
int wordNumber = (pos >> 5); // 除以32 --- 数大小
nt bitNumber = (pos & 0×1F); // 除于32 --- 存储的位位置
// 若不存在则将对应位赋为1
bitset[wordNumber] |= 1 << bitNumber;
}
}
}