經過了大一學年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;
}
}
}
}
經過修改,時間縮短了不少,完全達到了題目的所有要求。但是肯定還是會有更節省內存和時間的算法。我所實現的算法中還是有不少重複篩去的數;做到無重複篩除時,肯定效率還會提升很多,還有很大的進步空間。未來仍將繼續思考。