经过了大一学年C和C++的接触和学习,进入大二后,学校安排的课程便对编程能力提出了更高的要求。这学期开设的数据与算法的课程,内容涵盖了数据结构和算法两大方面。为了能从本学期的编程中整理出更多的思路、算法和注意事项,我将特意抽取时间对每次编程的思路及优化加以总结。毕竟只是一名普通的大二学生,因而我在算法肯定不是最优,只是按照自己思考和理解,加上一点点技巧。另外自己在内存的利用上也欠佳。但相信整理一下编程的过程,肯定还是比糊里糊涂、已通过测试为目的的编程要有更大的收获。所以,特开设此博客。还麻烦各路大神有时间的继续优化,共同努力寻找到更好的算法。
【数据与算法】之哥德巴赫数问题
【问题】哥德巴赫数
[Description]
定义哥德巴赫数为可以表示为两个质数之和的数。给定正整数N,找出第N个歌德巴赫数。约定1不是质数。
[InputDescription]
一个正整数N,不超过10^7。
[OutputDescription]
第N个哥德巴赫数。
[Input Sample]
6
[Output Sample]
9
Time Limit : 1000 ms Memory Limit : 40000 KiB
【分析问题】
【哥德巴赫猜想】任意一个大于2的偶数都可以写成两个质数之和。
本题目设置在哥德巴赫猜想之上。这一猜想在纯数学理论层面上还没有完全被证出。(即使有不少人声称自己已经证出,但数学界仍没有统一的结论。)不过从计算机科学方面,目前所有的测试结果显示,并没有找到任何不符合哥德巴赫猜想的情况。因而,这道题的重点之一就是默认哥德巴赫猜想是成立的(至少在目前计算机可及的范围内正确)。因此,所有大于2的偶数都是哥德巴赫数。
除此之外,我们意识到质数分为2和奇质数。两个奇质数之和必定为偶数,所以包括在第一类哥德巴赫猜想之中。另外就是一个质数为2,另一个质数为奇质数。这样的书也哥德巴赫数。
所以,经过分析,我们可以得出,所要寻找的哥德巴赫数分为两大类:
① 所有大于2的偶数;
② 2+任一奇质数。
找偶数没有难度,所以关键问题在于奇质数的寻找。网上有很多筛法,都是从古至今不少智慧者寻找到的绝佳方法。但因为我是在编程的初始阶段,还是应该以个人思考为重点;前人的方法一定很厉害,不过可以留到日后继续学习,目前阶段需要的是自己的思考。于是我没有过多的上网寻找找质数的方法。
【解决问题】
经过分析,问题的关键在于寻找质数(奇质数)。我采用的是小时候老师讲质数时用到的方法,说高端一些也是一种筛法。主要思路是,在一个数组中,将2的倍数、3的倍数、5的倍数、7的倍数、11的倍数等等全部“筛去”,最后剩下的就是质数(因为不是2、3、5、7等数的倍数)。
在实现过程中,需要优化和加以确认的有:
①关于数组类型的选择:
这道题目设定了输入的数字N不超过10^7,以10^7作为最坏情况。如果选用int型数组存储这10^7个整型,则光是存储就需要10^7*4B=40000KB,已经达到最大内存的限制。因而绝对不能使用int型变量数组。
那么我们仅从数据存储上考虑最佳的情况(每个数据只占用一字节)。这时候可以被考虑的有bool(C++)和char型,均占1字节。所以这两种数据类型是比较合适的。
之后考虑到在寻找素数的过程中肯定需要对质数和合数分别进行标记,bool适合只有两种不同情况的标记,char适用于多种情况的标记。因此在这道题目背景下,bool型已经足够了。我在算法的实现(编程)时,选用了char,后面也用bool型改变了一下,结果完全相同,代码也只是大同小异。
②关于申请动态数组的大小:
寻找素数时必须限定一个范围,而且这个范围能够普遍地被表示成与N有关。我们可以考虑,输入任意一个整数N,2N+2的范围内必定有N个大于2的偶数,也就是偶合数。因为大于2的偶数都是哥德巴赫数,数量已经达到所要求的N个,因此可以把最大上限设为2N+3(编程时往往用<2N+3)。所以申请的char型动态数组大小为2N+3。
③关于寻找素数:
我的算法是在所有奇数中去除2、3、5、7、11……的倍数。这样最终剩下的即为素数。然而在本道题中是要寻找“质数+2”,所以需要将找到的质数加2才能标记为哥德巴赫数。
【C++】
①初始版本
#include<iostream>
#include<cmath>
usingnamespace std;
int main()
{
int n,i,j,m,count=0;
cin>>n;
m=(int)(sqrt(double(2*n+3)));
char *num=newchar[2*n+3];
for(i=3;i<2*n-3;i++)
{
num[i]='y';
}
for(i=3;i<=m;i=i+2)
{
for(j=i;i*j<2*n+3;j=j+2)
{
num[i*j]='n';
}
}
if(n==1)
cout<<4;
elseif(n==2)
cout<<5;
else
{
for(i=4;i<2*n+3;i++)
{
if(num[i]=='y')
{
count++;
}
if(count==n-2)
{
cout<<i+2;
break;
}
}
}
}
这次实现的过程中并没能完全实现个人思考的算法。在筛去奇合数的时候,我不是精简地筛去3、5、7、11、13、17……的倍数,而是重复性的筛去了3、5、7、9、11、13、15等的倍数。这导致原本已经被3筛去的9、15的倍数,又在9、15时再次被筛去,导致了大量的重复。虽然内存和时间均没有超出限制,但是感觉仍能进行简化。
后来想到在筛去奇数m的倍数时,对奇数m进行一个判断。假如奇数m已经在被标记为之前的奇数的倍数,则跳过m,对下一个奇数执行操作。例如,筛去3的倍数的时候,已经将9标记为非哥德巴赫数,那么当对9的倍数进行筛去之前,判断得到9在3的时候已经被筛去,因而不执行筛去9的倍数的操作,直接跳至奇数11。这样会大大减少重复。
②修改版。
/*经过分析,哥德巴赫数可分为两个大类,即①偶数(由哥德巴赫猜想);
②偶质数+任一奇质数;所以该题目的难点在于寻找一定范围的质数*/
#include<iostream>
#include<cmath>
usingnamespace std;
int main()
{
int iN,i,j,iRoot,Count=0;
cin>>iN;//输入整数iN(第iN个哥德巴赫数)
iRoot=(int)(sqrt(double(2*iN+3)));//求得算术平方根,作为寻找质数的上限
char *Num=newchar[2*iN+3];//申请char型数组的动态内存。【注意】之所以申请*iN+3个char,是因为n+3里已经包含了n个非偶数,已满足了要寻找的哥德巴赫数的量
for(i=0;i<2*iN+3;i++)//先将所有大于的正整数标记为‘y’,表示“是哥德巴赫数”
{
Num[i]='y';
}
for(i=3;i<=iRoot;i=i+2)//将奇数中的合数标记为'n'
{
if(Num[i]=='y')
{
for(j=i;i*j<=2*iN+3;j=j+2)/*对于每一个大于等于的质数的奇数倍(该奇倍数大于等于质数本身)
标记为合数,奇数中剩下的即质数*/
{
Num[i*j]='n';
}
}
}
if(iN==1)//输入和为特殊情况,可单独罗列
cout<<4;
elseif(iN==2)
cout<<5;
else
{
for(i=4;i<2*iN+3;i++) //遍历整个iN+3个char型,遇到哥德巴赫数,累加器Count加
{
if(Num[i]=='y')
{
Count++;
}
if(Count==iN-2) //判断累加器中的哥德巴赫数量与输入iN相等,则输出此时的哥德巴赫数
{
cout<<i+2;
break;
}
}
}
}
经过修改,时间缩短了不少,完全达到了题目的所有要求。但是肯定还是会有更节省内存和时间的算法。我所实现的算法中还是有不少重复筛去的数;做到无重复筛除时,肯定效率还会提升很多,还有很大的进步空间。未来仍将继续思考。