您的位置:首页 > 理论基础 > 数据结构算法

数据结构与算法分析C++表述第二章编程题

2017-01-01 12:51 363 查看
  把昨天看的第二章巩固一下,做一做编程习题。

2.6:

  第一天交2元罚金,以后每一天都是前一天的平方,第N天罚金将是多少?

这个题目和2.4.4-3介绍的幂运算基本一致。若按相同的递归思路分析,比那个问题要简单,因为从1次幂开始并且指数呈2^(n-1)分布,即1,2,3,4,16……所以没有对指数是奇数时的判定。实际上用循环来求要比用递归快。在不考虑溢出的前提下,解法如下:

#include<iostream>
using namespace std;
typedef unsigned long long uint64;
uint64 r(int n){
if(n==1){
return 2;
}else{
uint64 tmp=r(n-1);
return tmp*tmp;
}
}

uint64 w(int n){
int result=2;
while(n!=1){
result*=result;
n--;
}
return result;
}

int main(){
int n;
cin>>n;
cout<<r(n)<<" "<<w(n)<<endl;
}


2.20:

确定一个正整数是否是素数。这个问题可以简化一下,因为n*n>(n-1)*(n+1)。所以只需要求n是否能被2到sqrt(n)整除就可以了,这里需要注意的就是2是最小的素数:
for(i=2;i<sqrt(n);i++)对2不能返回正确的结果,因为1.414<2不会进入循环。所以要先行判断:


#include<iostream>
#include<cmath>
using namespace std;
bool ispn(int n){
if((n%2)==0){
return false;
}else{
int i,max=int(sqrt(n));
for(i=3;i<=max;i++){
if((n%i)==0) return false;
}
}
return true;
}

int main(){
int n;
cin>>n;
cout<<ispn(n)<<endl;
}


当然,也可以if(n==2)然后for(i=2;i<=max;i++)。需要注意的是平方根(max)的条件不是小于,是小于等于。接下来有一个厄拉多塞筛的时间复杂度问题,这里仅记录厄拉多塞筛:取从2到n,n>=2之间的全部素数只需要滤掉那些非素数。根据前面讲述的原理,去掉这些非素数的方法如下:循环去掉2-sqrt(n)之间的全部非素数:

#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
typedef unsigned long long uint64;
void es(vector<int> &arr){
int i,j,n=arr.size(),sqrtnum=int(sqrt(n));
for(i=2;i<=sqrtnum;i++){        //从最小的素数m(m=2)开始遍历到sqrt(arr.size())
if(arr[i]==0){                    //找到后面第一个素数m(标记仍然为0的)
for(j=i*2;j<=n;j+=i){    //标记m*n(1<n<=arr.size/m)为非素数。
arr[j]=1;
}
}
}
}
int main(){
int i,n;
cin>>n;
vector<int> arr(n+1);
es(arr);
for(i=2;i<=n;i++){
if(arr[i]==0){
cout<<i<<endl;
}
}
}


注意以下几点(设n为上限):

1、代码中arr默认值为0,用以表示假定为素数,最终结果中被标志为1的都不是素数;其下标与数字一一对应,即若输入11,则数组最大下标为11,亦即数组元素为n+1个。

2、根据前面的结论,i只需要从2搜索到sqrt(n),包含sqrt(n)。

3、在进行标志时,从当前素数的2倍开始标志。

在chinaunix上面看到一篇类似的算法,1、2都犯了,只能低效的输出n-1以内的,漏掉了n。例如输入11,得到的是2、3、5、7,例如输入2什么也没出来。

歇一歇,2017年1月1日12:45,以上。

2.26:

  大小为N的数组A,若某个值出现次数超过N/2则称为主要元素。例如:3,3,4,2,4,4,2,4,4的主要元素是4。

  书中给出了一种解决思路:如果A0,A1相等,则把A0放入数组B,否则无操作;对后续数组A2,A3……依次做同样操作,这样得到一个数组B,对B数组进行同样操作……就可以找到A的主要元素。然后检验得到的元素是不是主要元素。这不失为一种好做法:根据主要元素的定义知道,它在数组中过半,也就是说无论如何排列,根据上述做法我们一定能找到一组相邻的主要元素,但当数组元素个数为奇数时,可以正好间隔开(此时需要把最后一个添加到B而不是省略——书中问题b)。递推可知,由B得到的C一定含有A的主要元素,即主要元素总会剩下的,反过来讲:即使数组只剩下一个元素那也一定是主要元素,那么递归终止条件就是最终的数组只有一个元素。等等,也许没有主要元素,那么最终数组中就不会剩下元素,所以递归终止条件是传入的数组中没有元素或只有一个元素——书中问题a。再等等,为什么题目中说还有第二步——检验是不是真的是主要元素呢?根据我们的思路,两两捉对,那么构建一个特例:3,4,5,5,3,4就可以回答这个问题了。

  到现在为止这个代码是这样的:

const int INF=0x7fffffff;
int me(const int a[],int n){
if(n==0){
return INF;
}else if(n==1){
return a[0];
}
int i,bidx=0,b[(n+1)/2];
for(i=0;i<n-1;i+=2){
if(a[i]==a[i+1]){
b[bidx]=a[i];
bidx++;
}
}
if((n%2)==1){
b[bidx]=a[n-1];
bidx++;
}
return me(b,bidx);
}

bool test(const int a[],int n,int v){
int cnt=0;
for(int i=0;i<n;i++){
if(a[i]==v){
cnt++;
}
}
if(cnt*2>n){
return true;
}else{
return false;
}
}

int main(){
int a[]={3,3,4,2,4,4,2,4,4};
int alen=sizeof(a)/4;
int val=me(a,alen);
cout<<test(a,alen,val)<<"  "<<val<<endl;
}


  以上代码我只进行了简单的测试,可能存在问题,敬请指正。

  这个问题实际上还有更好的解决方法,能够达到线性时间——用一次循环即可。但是在谈这种解决方案之前,我想先说一个类似问题:最大子序列和问题。这两个问题的解决思想非常相似。最大子序列和是这样一个问题:

  求一个数组中连续部分的最大和。例如数组:-1,4,-3,5,-2,-1,2,6,-2的最大子序列和为11(从4开始到6结束)。这个问题可以有很多解法,但其中较好的解法无一不是根据这样的特性:达到最大和的子序列不可能以负数开头;优秀的解法会更充分的利用这一点的推论:达到最大和的子序列任意前n个只和不可能以负数开头。若某一段序列的和为0,我们忽略它。

  现在根据我们的推论来考虑一下实现:从头遍历求和,当和小于0时,放弃之前的一段重新开始;当和大于0时,比较当前和是否大于已记录的最大和,若比记录的最大和还大,则替换最大和为当前值——所以即使后面加了若干负数导致和小于0被舍弃,也不会丢失这个最大和,编码如下:

#include<iostream>
using namespace std;
int maxsum(const int arr[],int arrcnt){
int i,maxsum=0,cursum=0;
for(i=0;i<arrcnt;i++){
cursum+=arr[i];                //计算当前和
if(cursum>maxsum){        //如果当前和大于最大和,替换最大和
maxsum=cursum;
}else if(cursum<0){        //如果当前和小于0,说明之前加的是一个负数,并且之前的序列已经没有作用了(大于0的最大和序列如果大于maxsum则已经被记录了)
cursum=0;                    //所以重新开始记录和
}
}
return maxsum;
}

int main(){
int a[8]={4,-3,5,-2,-1,2,6,-2};
cout<<maxsum(a,8)<<endl;
}


代码真的很少,而且时间复杂度为O(N)。所以,任何优秀的代码都是建立在对问题的深入剖析基础上的,而这往往需要敏锐的观察。怎么能敏锐呢?个人觉得就是遇到问题多思考,多动笔,多写代码尝试;对问题多类比,多对比,多归纳总结。

  那么,让我们继续剖析数组的主要元素这个问题:依然用上面的数组3,3,4,2,4,4,2,4,4,根据主要元素的定义我们知道它的个数超过任何其他元素个数的总和,即主元素个数至少比其他元素总和多1个。向上面最大子序列和靠拢:我们把数组中的元素分成两派:主元素派和其他元素派,然后进行计数:主元素使得计数+1,其他元素使得计数-1。可以预见的是,最终计数大于等于1。现在,问题已经基本和最大子序列和相同,只是最大子序列和中求的是元素和,而这里我们求的是计数和——其他元素计数-1,主元素计数+1而已。所以我们是不是可以考虑,从头开始遍历,假定a[0]是主元素,那么我们数一数,当到达a[4]时,计数和变为-1——从这里继续,假定a[4]为主元素,数到数组结尾计数为3。那么我们认为a[4]就是主元素。等等,这还不一定正确,让我们检验一下,如果开始我们找到的就是主元素,但是和前面的a[0]一样,被若干其他元素消除了计数结论还正确吗?这显而易见,把计数变负数时需要的其他元素个数多于前面的主元素个数,这导致后面会堆积至少相同个数个主元素。所以,如果存在主元素,那么我们的结论是正确的。现在,考虑不存在主元素时,我们的扫描结果的正确性:数组3,3,1,2,5扫描结果是5。所以,不存在主元素时我们的算法返回了错误结果,同样需要用test函数来检验。而这种算法本身的编码很简单:

int me1(const int a[],int n){
int i,cnt=1,curval=a[0];
for(i=1;i<n;i++){
if(a[i-1]==a[i]){
cnt++;
}else{
cnt=1;
curval=a[i];
}
}
return curval;
}


在之前的main函数结尾添加以下代码来测试它:

val=me1(a,alen);
cout<<test(a,alen,val)<<"  "<<val<<endl;


2017-1-1 22:40 以上
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: