第一章 开篇(2)

习题 5. 那个程序员说他有1MB的可用存储空间,但是我们概要描述的代码需要1.25MB的空间。他可以不费力气的索取到额外的空间。如果1MB空间是严格的边界,你会推荐如何处理呢?你的算法的运行时间又是多少?

    用位向量表示10 000 000个整数需要10 000 000个位,10000000 bits = 1.192MB.所以程序员可利用的1MB空间是显然不够的,可以采用两趟算法,第一趟利用5000000个位来排序0~4999999之间的整数,然后在第二趟中排序排序5000000~9999999之间的整数。代码如下:

#include <stdlib.h>
#include <time.h>
#include <iostream>
#include <boost/timer.hpp>
#include <boost/progress.hpp>

using namespace std;
using namespace boost;

#define  BITSPERDWORD 32          //表示一个整形变量具有32个位
#define  SHIFT         5          //单次位移量,右移一位相当于除以2,左移一位相当于乘以2
#define  MASK         0x1f        //掩码,其十进制即为31
#define  N            10000000    //表示10000000个7为非负整数

//将位数组从0开始的第i个位置为0,即将整数i在位向量中对应位置的位置为false
void resetBits(int * arry, int i)  
{
	arry[i >> SHIFT] &= ~(1 << (i & MASK));  //m%n,n = 2^x时,m%n = m & (n-1)
}

//将位数组从0开始的第i个位置为1,即将整数i在位向量中对应位置的位置为true
void setBits(int * arry, int i)
{
	arry[i >> SHIFT] |= (1 << (i & MASK));
}

//检测i是否在集合中
int testBits(int * arry, int i)
{
	return arry[i >> SHIFT] & (1 << (i & MASK));
}

//返回[l, u]范围内的一个随机整数
int randint(int l, int u)
{
	return (l + rand() % (u - l + 1));
}


int * geneRand2(int m ,int n)
{
	int i, j;
	int *pArry = new int[n];
	for (i = 0; i < n; i++)  //生成数组
	{
		pArry[i] = i;
	}

	//筛选随机数
	srand((unsigned)time(NULL));
	for (i = 0; i < m; i++)
	{
		j = randint(i, n - 1);
		int t = pArry[i];
		pArry[i] = pArry[j];
		pArry[j] = t ;
	}
	return pArry;
}


void mySort(int m ,int n)
{
	int i, j = 0, k;
	int *pBitsArry = new int[1 +n / (2 * BITSPERDWORD)]; //使用空间减半
	int * p = geneRand2(m ,n);
	int *pOutArry = new int[m];  //模拟输出文件

	progress_timer t;

	for (k = 0; k < 2; k++)
	{
		for (i = 0; i < (n / 2); i++)
		{
			resetBits(pBitsArry, i);
		}
		if (0 == k)
		{
			for (i = 0; i < m; i++)
			{
				if (p[i] < (n / 2))
				{
					setBits(pBitsArry, p[i]);
				}
			}
			for (i = 0; i < (n / 2); i++)
			{
				if (testBits(pBitsArry, i))
				{
					pOutArry[j++] = i;
				}
			}
		} 
		else
		{
			for (i = 0; i < m; i++)
			{
				if (p[i] >= (n / 2))
				{
					setBits(pBitsArry, p[i]-(n / 2));
				}
			}
			for (i = n / 2; i < n; i++)
			{
				if (testBits(pBitsArry, (i - n / 2)))
				{
					pOutArry[j++] = i;
	
				}
			}
		}
	}
	cout << t.elapsed()<<endl;     // 生成位于0至n-1之间m个有序且不重复的随机整数所用的时间
/*	cout<<"..........................."<<endl;
	for (i = 0; i < m; i++)
	{
		cout << pOutArry[i]<<endl;
	}
	cout<<"..........................."<<endl;*/
	delete [] p;
	delete [] pBitsArry;
	delete [] pOutArry;

}

int _tmain(int argc, _TCHAR* argv[])
{
	mySort(10000000,10000000);
	return 0;
}

排序计算时间为(m = n = 1000 0000):0.859s

    k趟算法可以在kn的时间开销和n/k的空间开销内完成对最多n个小于n的无重复正整数的排序。

习题 6. 如果那个程序员说的不是每个整数做多出现一次,而是每个整数最多出现10次,你又如何建议他呢?你的解决方案如何随着可用存储空间总量的变化而变化呢?

     因为10等于二进制的1010, 2^3<10<2^4,所以可以用4位的半字节来统计它的出现次数,即需要4*1000 0000个位来表示1000 0000个正整数。利用习题五,可以使用4000 0000/k个位在k趟内完成对整个文件的排序。

习题 7. 本书1.4节中描述的程序存在一些缺陷。首先是假设在输入中没有出现两次的整数。如果是假定在输入中没有出现两次的整数。如果某个数出现超过一次的话,会发生什么?在这种情况下,如何修改程序来调用错误处理函数?当输入整数小于零或者大于等于n时,又会发生什么?如果某个输入不是数值又如何?在这些情况下,程序该如何处理?程序还应该包含哪些明智的检查?描述一些用以测试程序的小型数据集合,并说明如何正确处理上述以及其他不良情况。

   1) 如果某个数出现超过一次的话,那么该数在排序后的文件中只会出现一次,因为该数对应的位被重复置位为1.在调用setBits()函数时,首先检查该数在位向量中对应的的位是否为0,如果是0表示该数第一次出现,那么将其置为1;否则则表示该数是重复出现的,可抛出异常进行错误处理。

    2)  因为负数对于是个有符号数,其二进制的最高位为1,但计算机存储负数并不是按照其二进制形式直接存储的,而是对其取反加1再存储的,所以当输入小于零时会导致移位运算符和位运算符产生错误的值,从而导致访问数组越界等问题。如:

#include <iostream>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	int a = 10, b = -10;
	cout << (a >> 5) << endl;     
	cout << (a & 31)<<endl;

	cout << (b >> 5) << endl;
	cout << (b & 31)<<endl;
	return 0;
}

输出结果为:0

                  10

                   -1

                   22

    对于任何一个整数A,若A>0,则其补码等于其本身;若A<0,则其补码为其符号位不变,其他位按位取反再加1。

      b = 10000000000000000 00000000 00001010        //b的原码
      b' = 11111111 11111111 11111111 11110110        //b的补码

   所以,b>>5 = 1111111111111111 11111111 11111111 (算术右移,即对有符号数右移时,左边移入该数的符号位,原来为1就移入1,原来为0就移入0),也就等于-1. b & 31 = 00000000 0000000000000000 00010110 = 22

    若输入整数大于或小于n时,会发生位向量数据访问越界。对于输入不是数值时,应首先检查输入数据是否为整数。

    

    

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值