编程珠玑第一章课后习题

1、使用库函数来进行排序

这个不同语言有不同的库函数排序c有qsort,java有sort排序,具体就不贴代码了。


2、用位逻辑实现位运算

这个道题的核心就在于想要把某bit置0,将该位直接和0做与操作,想要保持某bit位不变,将该位与1做与操作,想要将某bit位置1,将该位与1做或操作。

#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];

void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }

原书中的例子i>>SHIFT相当于得到i除32(int是4个字节,32bit)的商得到第i位属于i/32的int的bit位范围内,

i&MASK相当于获取i/32的余数,即刚刚我们所说的int的bit范围的第几位。


4、生成k个[0,n)的不重复数字,k<n

#include <stdio.h>
#include <windows.h>
#include <algorithm>
#include <stdlib.h>
#include <iostream>
#include <time.h>
#define MAXNUM 10000000

void swap(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
int main(void)
{

	int i = 0;
	int *tmp = new int[MAXNUM+1];
	//在1-MAXNUM范围里生成LENGTH个不重复数字,先按顺序得到1-MAXNUM,然后从第一位开始到第MAXNUM位,每位生成
	//一个随机数r,并让tmp[i]与tmp[r]交换,这样就生成位置随机的随机数了
	for(i = 0; i < MAXNUM; i++)
	{
		tmp[i] = i;
	}
	for(i = 0;i < MAXNUM; i++)
	{
		int p = rand();
		//因为rand()最大只能产生到RAND_MAX,这个值似乎远小于10000000,为了避免每次都是和前RAND_MAX的数交换,采用swap(&tmp[i],&tmp[MAXNUM - p])保证数字也有机会被交换到后面
		if(i%2 == 0)
		{
			swap(&tmp[i],&tmp[p]);
		}
		else
		{
			swap(&tmp[i],&tmp[MAXNUM - p]);
		}
	}
	delete[] a;
	delete[] tmp;
	return 0;
}

3、位图排序(现在第四题的基础上获得不重复数字再来做这题)

#include <stdio.h>
#include <windows.h>
#include <algorithm>
#include <stdlib.h>
#include <iostream>
#include <time.h>
#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define MAXNUM 10000000
int a[1 + MAXNUM/BITSPERWORD];

void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }
void swap(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
int main(void)
{

	int i = 0;
	int *tmp = new int[MAXNUM+1];
	//在1-MAXNUM范围里生成LENGTH个不重复数字,先按顺序得到1-MAXNUM,然后从第一位开始到第MAXNUM位,每位生成
	//一个随机数r,并让tmp[i]与tmp[r]交换,这样就生成位置随机的随机数了
	for(i = 0; i < MAXNUM; i++)
	{
		clr(i);
		tmp[i] = i;
	}
	srand((unsigned)time(NULL));
	for(i = 0;i < MAXNUM; i++)
	{
		int p = rand();
		
		if(i%2 == 0)
		{
			swap(&tmp[i],&tmp[p]);
		}
		else
		{
			swap(&tmp[i],&tmp[MAXNUM - p]);
		}
	}
	
	DWORD start, stop;
    start = GetTickCount();
    

	for(i = 0; i < MAXNUM;i++)
		set(i);
	for(i = 0; i < MAXNUM; i++)
	{
		if(test(i))
		{
			printf("%d\n",i);
		}
	}
	stop = GetTickCount();
    printf("time: %lld ms\n", stop - start);
	delete[] tmp;
	return 0;
}
我运行后的时间是这么多

426040ms

如果不做排序后的输出操作时间只有

438ms

5、空闲内存有1M,但实际代码有1.25M,如何将代码控制在只有1M以内。

该题核心在于一次性读入10000000个数将导致内存不够,因此需要对着10000000进行分组读入,假设分成4组,则先读入[0,25000000)范围的书,对他们排序输出,在读入[25000000,50000000)的数再排序,以此类推。既然知道了方法,现在首先考虑的第一个问题是,要分几组,这个问题相对简单1M内存为8388608bit,一个int 32bit 8388608/32=261456,接近250000,因此分成40组。现在的第二个问题是如何读入这40组数据,代码如下


 
 
#include <stdio.h>
#include <windows.h>
#include <algorithm>
#include <stdlib.h>
#include <iostream>
#include <time.h>
#define GROUPNUM 40
#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define MAXNUM 10000000
int a[MAXNUM/GROUPNUM+1];
void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }
void swap(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
int main(void)
{

	int i,j,n = 0;
	int range = MAXNUM/GROUPNUM;
	int *tmp = new int[MAXNUM+1];
	//在1-MAXNUM范围里生成LENGTH个不重复数字,先按顺序得到1-MAXNUM,然后从第一位开始到第MAXNUM位,每位生成
	//一个随机数r,并让tmp[i]与tmp[r]交换,这样就生成位置随机的随机数了
	for(i = 0; i < MAXNUM; i++)
	{
		tmp[i]=i;
	}
	srand((unsigned)time(NULL));
	for(i = 0;i < MAXNUM; i++)
	{
		int p = rand();
		
		if(i%2 == 0)
		{
			swap(&tmp[i],&tmp[p]);
		}
		else
		{
			swap(&tmp[i],&tmp[MAXNUM - p]);
		}
	}
	DWORD start, stop;
    start = GetTickCount();
	//空间不够的情况下
	for(i = 0; i < GROUPNUM; i++)
	{
		//先清空存储空间
		for(j = 0; j < range; j++)
		{
			clr(j);
		}
		//分组出在[range*i,range*i+range)范围内的数并排序
		for(j = 0; j < MAXNUM; j++)
		{
			if(range*i <= tmp[j]  && tmp[j] < range*(i+1))
			{
				set(tmp[j]%range);
			}
		}
		//输出[range*i,range*i+range)范围的数
		for(j = 0; j < range; j++)
		{
			if(test(j))
			{
				printf("%d\n",j);
			}
		}
	}
	
	stop = GetTickCount();
    printf("time: %lld ms\n", stop - start);
	delete[] tmp;
	return 0;
}

若是去掉输出排序结果

printf("%d\n",j);

运行时间只有3250ms

分组输入虽然解决了内存不够的问题,但在时间上对所有数据进行了总共40此遍历,属于牺牲时间换取空间。

6如果每个数字不止可以出现一次,可以出现10,该怎么办

这题其实是第五题的升级版,相当于分出一部分内存用来存储次数,剩下的内存按照第五题的存储方式来分组排序数字,一个数字最多出现10次,相当于4bit位存一个数字的出现次数,则分出4*10^7空间存次数即可。

7、输入情况判断

如果一个数字输入多次,判断了test(i)得到true后就放弃读取这个数,至于小于0大于n,做输入判断的时候就可以排除,而非数字就不用读取进来就行了。

8、如果免费号码的区号有800,877,888,内存依然只有1M,如何排序

这个问题我能想到的也就只有每次读取一个区号下的免费号码进行排序,这样就跟前面的第四题差不多意思了。


9、如何省去对数组初始化的时间,保证每次初次访问该向量就初始化。

这题是网上google才理解了答案的意思。具体操作是声明两个数组from to以及一个变量top=0;

if(from[i] < top && to[from[i]] == i)
{
	printf("has used!\n")
}
else
{
	a[i] = 1;
	from[i] = top;
	to[top] = i;
	top++;
}

top变量用来记录已经初始化过的元素个数,from[i]=top,相当于保持a[i]是第几个初始化过的元素,to[top]=i,用来致命第top个初始化的元素在data里的下标是多少。因此每次访问一个data元素时,判断from[i] < top,即data[i]元素是否被初始化过,但当top很大时,from[i]里被内存随便赋予的初始化值可能真的小于top,这时候我们就还需要to[from[i]] == i 的判断了来保证from[i]<top不是因为内存随意赋给from[i]的值本身就小于top而是初始化后小于的。


10、在成本低廉的隔日送达时代之前,商店允许顾客通过电话订购商品,并在几天后上门自取。商店数据库使用客户的电话号码作为其检索的主关键字(客户知道他们的电话号码,并且这些关键字几乎都是唯一的)。你如何组织商店的数据库,以允许高效的插入和检索操作?

根据电话号码的最后两位作为客户的哈希索引,进行分类,当顾客打电话下订单的时候,它被放置在一个合适的位置。然后当顾客抵达进行检索商品时,营业员按顺序检索订单,这是经典的解决哈希冲突的解决方法:通过顺序检索。电话号码的最后两位数字是相当随机的,而电话号码的前两位作为索引行不通,因为很多电话号码的前两位是相同的。


11、选择一个数据传输方式减小成本

答案给飞鸽传输,个人觉得鸽子拿着这么重的图纸能飞得起来吗?这题好像有点像发散性思维题。


12、在太空环境书写流畅的笔

这题好像很久前就听说过了,铅笔。


你可能感兴趣的:(排序,编程珠玑)