编程珠玑_第一章_ 开篇

问题:

输入:给出至多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;
}

相关操作

编程珠玑_第一章_ 开篇_第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、位图法存数据

参考文章

http://blog.csdn.net/tianshuai11/article/details/7555563

http://blog.csdn.net/silenough/article/details/6956758

你可能感兴趣的:(编程,优化,测试,File,存储,电话)