问题:
输入:给出至多10,000,000个正整数的序列
特征:每个数都小于10,000,000、数据不重复 且 数据之间不存在关联关系(相互独立)
输出:增序输出序列
约束:内存容量1MB,磁盘空间充足,运行时间至多几分钟,10是最适宜的时间
分析:我们需要10,000,000个数表示10,000,000个位。1MB的包含8*1024*1024个位
则,所需要的内存容量为:10,000,000/(8*1024*1024) = 1.20MB
这里和要求的差不多,我们可以使用位图排序,之后再进行优化,使之在1MB内存上运行
思路:
/*第一步,初始化这个比特位集合中的所有位为0*/
for i = [0, 10,000,000)
bit[i] = 0
/*第二步,读入待排序的正整数*/
for each i in the input file
bit[i] = 1
/*第三步,输出排序结果*/
for i = [0, 10,000,000)
if bit[i] == 1
write i on the output file
程序
使用字节存储位向量
#include <iostream>
using namespace std;
const int BITRERBYTE = 8;//位向量使用字节存储存储
const int SHIFT = 3; //右移3位,相当于除去8
const int MASK = 0x7; //16进制下的7
const int MAXNUM = 10000000;//最大的数为1千万
const int NUMCOUNT = 5;//实际待排序的数个数
unsigned char bit[MAXNUM/BITRERBYTE + 1];//位向量
/*置0*/
void InitBit(int i)
{
bit[i>>SHIFT] &= ~(1 << (i&MASK));
}
/*置1*/
void SetBit(int i)
{
bit[i>>SHIFT] |= (1 << (i&MASK));
}
/*检查是否1*/
int TestBit(int i)
{
return bit[i>>SHIFT] & (1 << (i&MASK));
}
int main()
{
int arr[NUMCOUNT] = {3,2,5,4,9};
//关闭所有位
memset(bit,0,sizeof(bit));
//读入待排序的整数
for (int i = 0;i < NUMCOUNT;i++)
{
SetBit(arr[i]);
}
//输出数据
for (int i = 0;i < MAXNUM;i++)
{
if (TestBit(i))
{
cout<<i<<" ";
}
}
cout<<endl;
system("pause");
return 1;
}
使用整形存储位向量
#include <iostream>
using namespace std;
const int BITPERWORD = 32;//位容器使用整形数据存储
const int SHIFT = 5; //右移5位,相当于除去32
const int MASK = 0x1F; //16进制下的31
const int MAXNUM = 10000000;//最大的数为1千万
const int NUMCOUNT = 5;//实际待排序的数个数
int bit[MAXNUM/BITPERWORD+ 1];//位向量
/*置0*/
void InitBit(int i)
{
bit[i>>SHIFT] &= ~(1 << (i & MASK));
}
/*置1*/
void SetBit(int i)
{
bit[i>>SHIFT] |= (1 << (i & MASK));
}
/*检查是否1*/
int TestBit(int i)
{
return bit[i>>SHIFT] & (1 << (i & MASK));
}
int main()
{
int arr[NUMCOUNT] = {3,2,5,4,9};
//关闭所有位
memset(bit,0,sizeof(bit));
//读入待排序的整数
for (int i = 0;i < NUMCOUNT;i++)
{
SetBit(arr[i]);
}
//输出数据
for (int i = 0;i < MAXNUM;i++)
{
if (TestBit(i))
{
cout<<i<<" ";
}
}
cout<<endl;
system("pause");
return 1;
}
注意:
0、位图排序使用范围:
(1)非负整数 (2)每个整数最多出现一次 (3)最大整数小于 N (4)整数之间相互独立
1、使用int和char存储位图原理上是没有区别的,上述代码的区别仅仅是几个常量BITPERWORD、SHIFT、MASK不同,其他代码全部相同
2、给出一个数,怎么设置其对应位图的位置
unsigned char bit[2];(这里使用字节存储位图,如果使用int存储,原理一样)
此时,可用字节有两个0、1字节,一个字节中有八位
也就是说,我们只能表示16个数(0~15)
问题:给出一个数,怎么判断其对应位图的位置
步骤:找到该数对应的字节 + 再找到该数对应的位
举例:14对应的字节:14/8 = 1
14对应的位:第14%8位
即,14是存储在第1个字节上的第6号位
8是存储在第1个字节上的第0号位
确定num所在字节的方法
num/8 或 num>>3
确定num所在上述字节的几号位
num % 8
num & 0b111(二进制下的7,0b表示二进制)
//注意:num MOD n = num & (n-1) (n = 2^m,n-1表示低几位为全1)
找到待处理数所在的字节和位后,我们怎么设置它为1或0呢
思想:针对其所在字节的所在位,进行 & 和 | 操作,设置为0或1
/*置1*/
bitmap[num>>3] |= (1<<(num & 0b111));
处理时,我们针对其所在字节整体进行操作,即找到其所在字节 bitmap[num>>3]
之后,我们要把其对应位设为1(则使用或操作),同时又不能影响该字节中的其他位,即只改动一位
方法:我们可以利用一个新的字节且把该字节上,num对应的位设为1,其他全为0(则找到num对应的位置num & 0b111,之后对1进行右移操作)。之后和num所映射的字节进行或操作
/*置0*/
bitmap[num>>3] &= ~(1<<(num & MASK));
同理,置0操作可以一样设置
由于,我们要把一个位设置为0,我们可以使用与操作
/*判断是1或0*/
if( bitmap[num>>3] & (1<<(num & MASK)) )
判读是0还是1,可以让其和1进行与操作。如果是0,结果为0。是1,结果为1
优化:假如严格限制为1MB,可以采用的策略:
1、两次遍历待排序列
2、观察待排序列的特征,判断是否含有不可能出现的整数,缩减范围
两次遍历待排序列是指我们可以把数据分成两部分,
第一次对1-4,999,999之间的数排序,需要存储空间为:5,000,000/(8*1024*1024) = 0.596MB,之后再对5,000,000 -10,000,000 之间的数排序。总体上消耗的空间为0.596MB。
#include <iostream>
using namespace std;
const int BITPERWORD = 32;//位容器使用整形数据存储
const int SHIFT = 5; //右移5位,相当于除去32
const int MASK = 0x1F; //16进制下的31
const int MAXNUM = 10000000;//最大的数为1千万
const int NUMCOUNT = 5;//实际待排序的数个数
int bit[MAXNUM/(2 * BITPERWORD) + 1];//位向量
/*置0*/
void InitBit(int i)
{
bit[i>>SHIFT] &= ~(1 << (i & MASK));
}
/*置1*/
void SetBit(int i)
{
bit[i>>SHIFT] |= (1 << (i & MASK));
}
/*检查是否1*/
int TestBit(int i)
{
return bit[i>>SHIFT] & (1 << (i & MASK));
}
int main()
{
int j = 0;
int arr[NUMCOUNT] = {3,2,5,4,9};
int arrTmp[NUMCOUNT]; //只是测试结果,实际过程可以直接输出
//关闭所有位
memset(bit,0,sizeof(bit));
//对1-4,999,999之间的数排序
for (int i = 0;i < NUMCOUNT;i++)
{
if (arr[i] < MAXNUM/2)
{
SetBit(arr[i]);
}
}
//收集排好序的结果
for (int i = 0;i < MAXNUM/2;i++)
{
if (TestBit(i))
{
arrTmp[j++] = i;
}
}
//关闭所有位
memset(bit,0,sizeof(bit));
//对5,000,000 -10,000,000 之间的数排序
for (int i = 0;i < NUMCOUNT;i++)
{
if (arr[i] >= MAXNUM/2)
{
SetBit(arr[i]);
}
}
//收集排好序的结果
for (int i = MAXNUM/2;i < MAXNUM;i++)
{
if (TestBit(i))
{
arrTmp[j++] = i;
}
}
//测试--输出所有结果
for (int i = 0;i < j;i++)
{
cout<<arrTmp[i]<<" ";
}
cout<<endl;
system("pause");
return 1;
}
也就是说,当内存充足时,我们遍历一遍。
当内存不足以把整个bitset全部搬入内存时,我们可以放入一部分,遍历多遍数据
注意,每一遍处理的数据范围是不同的
需要的遍历的趟数k = 排序用的总的内存/实际使用的内存(往上取整)
时间开销 = k*一趟遍历数据的时间
空间开销 = 总的内存消耗/趟数k
习题六、改变题目的限制条件,现在数据可以重复且最多重复十次,该怎么使用位图排序
方法:我们需要使用四位表示一个数出现的次数。此时消耗的空间=1.25*4 = 5M
这里我们仍然使用上面的方法,进行k趟遍历数据
即可以把数据分成5部分,一部分使用1MB进行排序
习题八:免费电话的区号为800,878,888等,七位数字表示电话号码
要求:
问题1、现在给出1MB空间,对所有免费号码进行排序
方法1:把整个区间放入内存
若一千万个电话号码都可能成为免费号码,那么需要的空间1.25MB * (免费号码前缀个数)。
方法2:省空间,多次扫描文件:
1、首先扫描整个文件,看有哪个免费号码前缀 以及 每个免费号码前缀下的号码个数。
2、设置区间映射表:比如800前缀有125个免费号码,找到最大的数、最小的数,差值做为bit长度。
问题2、如何存储免费号码,使得给一个免费电话号码,快速确定该免费电话是可用还是被占用
使用散列存储电话号码,实现快速查询
方法:使用7位的电话作为关键字,后面区号(800,878)作为散列值。
给出一个区号 + 电话号码,我们使用电话号码迅速获取其有关散列值,在检查区号是否出现在这些值。
在STL中,bitset实现了相同的功能
#include <iostream>
#include <bitset>
using namespace std;
const int MAXNUM = 10;//最大能出现的数,用于设定bitset的大小
const int NUMCOUNT = 5;//实际待排序的数个数
int main()
{
int arr[NUMCOUNT] = {3,2,5,4,9};
bitset<MAXNUM + 1> bitmap;//10位,初始化为0
//把待排序元素放入bitset中
for (int i = 0;i < NUMCOUNT;i++)
{
bitmap.set(arr[i]);
}
//输出结果
for (int i = 0;i < MAXNUM;i++)
{
if (bitmap.test(i))
{
cout<<i<<" ";
}
}
cout<<endl;
system("pause");
return 1;
}
相关操作

习题四、随机生成k个[0, n)之间不重复的随机数
思路:先顺序生成所有的可能数,之后打乱顺序
要求:整数范围:[a,b)、k个整数唯一、次序随机、不重复
for i = [0,n)
x[i] = i;
for i = [0,k)
swap(i,randint(i,n-1));
扩展:整数范围:[a,b)、k个整数唯一、次序随机、不重复
int arr[b];
//初始化数据
for (int i = a ;i < b;i++)
{
arr[i] = i;
}
//随机数代表数据的位置,
srand((unsigned)time(NULL));
for (int i = 0;i < k;i++)
{
int loc = rand()%(b - i) + i;//随机数的区间[i,b)
swap(arr[i],arr[loc])
}
相关应用
1、给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中
2、使用位图法判断整形数组是否存在重复
3、位图法存数据
参考文章
本文介绍了如何使用位图排序处理不超过10,000,000个正整数的序列,探讨了在内存有限(1MB)的情况下如何通过遍历和优化策略进行排序。内容涉及位图排序的原理、设置与判断位图位置的方法、优化方案以及处理数据重复和电话号码排序的问题。"
133534618,19671764,Windows Subsystem for Android:微软开启Windows上的Android应用开发新时代,"['microsoft', 'android', '编程', 'Windows开发', '应用程序开发']

859

被折叠的 条评论
为什么被折叠?



