日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

记一次代码优化

發(fā)布時間:2025/6/15 编程问答 12 豆豆
生活随笔 收集整理的這篇文章主要介紹了 记一次代码优化 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

題目選擇:不定方程的非負整數(shù)解解。

問題描述:問方程x+2y+5z=n,對于特定輸入的nn<=1000000),輸出其大于的整數(shù)解的個數(shù)。其時間限制是為:1000ms

選擇原因:選擇此題主要是由于自己實際作此題過程中,遇到了很多問題,這篇報告主要是來寫我對于這些問題的解決,與對代碼的不斷優(yōu)化。

解題過程:

這個問題非常簡單,是的,非常簡單,只要一個枚舉就能解決,而我為啥還要選擇這個問題來寫報告了,因為當我改了多次代碼系統(tǒng)才通過我寫的代碼,由于我下了狠心,一定要盡自己最大的努力把它寫的最好,寫的運行速度最快,甚至舉一反三的將這個問題擴散到更廣。

我拿到這個問題,想都沒想就寫了如下的代碼:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <stdio.h> void jieshu(int n) { ????int i,j,k,g; ????for(i=0;i<=n;i++) ????????for(j=0;j<=n/2;j++) ????????????for(k=0;k<=n/5;k++) ????????????{ ????????????????if(i+2*j+5*k==n) ????????????????g++; ????????????} ????printf("%d",g);?? } int main() { ????int t,n; ????while(~scanf("%d",&t)) ????{ ????????while(t--) ????????{ ????????????scanf("%d",&n) ????????????jieshu(n); ????????} ????} ????return 0; }

這就是一個暴力枚舉法,的確對于很小的n,結(jié)果完全沒有問題,完全沒有任何技術(shù)含量,這樣的代碼是個非常差勁的代碼,可想而知系統(tǒng)必然不會通過(因為超時了),簡單的分析一下,假設(shè)計算機的基本單位運算時間是t,這個t是納秒級的(10的負九次方秒),題目所要求的時間是1000ms1s,大約是109次方個t,對于這三個循環(huán)所用的時間是n*(n/2)*(n/5)*t,對于很小的n,這個時間必然不大,但是題中n的上限是一百萬,即106次方,三個106次方級的數(shù)相乘得出的時間大約是1018次方個t,也大概是109次方秒(一年大概只有108次方秒的數(shù)量級,想得出結(jié)果的話得等上幾年吧,你沒想到一個這么簡單的程序就讓你的計算機跑上幾年吧),所以這個算法的時間復(fù)雜度是o(n3),永遠不要怪數(shù)字太大,只能怪自己的算法不好,總沒有人嫌自己的錢多吧,況且一百萬不多,不然為何中國有那么多百萬富翁,之所以認為一百萬很多,是因為你總是在拿第一種算法來計算,所以你就算窮盡畢生也很難得到計算結(jié)果。

而且算法還有幾個很細微的地方帶來了額外的開支。第一,對于i=0,j=0,時,k需要枚舉到n/5,而對于i=0,j=1,k只需要枚舉到(n-2)/5,綜上第二層循環(huán)實際只需枚舉到(n-i)/2,第三層循環(huán)只需枚舉到(n-i-2*j)/5即可,所以實際進行了過多不必要的枚舉帶來額外的開支。第二,開始寫的這個算法,我犯了一個大忌,即將長循環(huán)寫在了最外層(for(i=0;i<=n;i++)要循環(huán)n次),將最短的循環(huán)寫在了最內(nèi)層(for(k=0;k<=n/5;k++)只要循環(huán)n/5次),這樣增加了cpu循環(huán)切換的次數(shù),(學(xué)過匯編的大致可以想出原因)增加了時間開支,一個好的代碼對于需要多層循環(huán)是應(yīng)該盡可能把最短的循環(huán)寫在最外層,最長的寫在最內(nèi)層,以增加cpu使用的效率(開始改進時我還是沒有將短循環(huán)寫在外層,導(dǎo)致代碼還是通不過)。

綜上所述,第一種算法的時間開支實在太高,算法實在太差勁了,如果這題一百分的話,這樣的代碼最多能得30分。

第一次改進思路:對于經(jīng)過兩次對x,y循環(huán)的循環(huán)枚舉后,實際要使得x+2y+5z=n有正整數(shù)解,只要有n-x-2y5的倍數(shù)即可,即不需要再進行第三次循環(huán),就可以通過x,y唯一確定z,通過改進減少循環(huán)的總數(shù),減少時間開支,根據(jù)這樣的思路,我進行了如下的改進,jieshu函數(shù)變?yōu)榱?#xff1a;

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void jieshu(int n) { ????int i,j,k,g; ????for(i=0;i<=n;i++) ????????for(j=0;j<=(n-i)/2;j++) ????????????{ ???????????????if((n-i-2*j)%5==0) ???????????????g++; ????????????} ????printf("%d",g);?? }

循環(huán)次數(shù)大大縮減,時間復(fù)雜度變?yōu)?span style="padding:0px; margin:0px">o(n2),但是這個代碼必然還是很悲哀的通不過的,因為對于一百萬的平方,也有1012次方之多,換算成秒,所要的時間變成1000秒的數(shù)量級左右,雖然還是很悲催的通不過,但是對比第一種算出結(jié)果要花幾年,第二種只需花不到一個小時就可以算出來了,這個速度是火箭般的飛躍。嗯,雖然還是不及格,對于第一種算法應(yīng)該有20分的提升吧,這個可以打50分。這個時候,我才想到要把短循環(huán)放外層,于是有第二次改進。

第二次改進,是把短循環(huán)放外層:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void jieshu(int n) { ????int i,j; ????long long g=0;? //這時才想到對于百萬時,解的數(shù)目應(yīng)該超出了int的范圍 ????for(i=0;i<=n/5;i++) ????????{ g+=(n-5*i)/2+1; ????????} ????printf("%lld\n",g); } 注:對于for(i=0;i<=n/5;i++) ??????????for(j=0;j<=(n-5*i)/2;j++) ????????????????g++;

可以直接把第二次循環(huán)g加的個數(shù)求出來,就是(n-5*i)/2+1,所以就寫成了g+=(n-5*i)/2+1,再次省去一層循環(huán)。這時算法的時間復(fù)雜度就變成了o(n),當然這個時候算一百萬所要的時間也就1ms左右了,又是一次火箭般的飛躍,當然這個時候系統(tǒng)也終于通過了,可以及格了,這時的算法可以得60分了,及格萬歲!一般做到這里通過了就可以滿足了,畢竟及格萬歲,但是對于被這樣簡單的題拖累了這么長時間的我不太甘心,畢竟對于一個時間復(fù)雜度為o(n)的算法,當輸入的大小為100億時又有等待了,一百億大嗎?看看比爾蓋茨的資產(chǎn)去吧。

第三次改進,

注意到第二次改進后的算法:

for(i=0;i<=n/5;i++)

g+=(n-5*i)/2+1;

實際上是一個這樣的數(shù)列的和:n/2+(n-5)/2+(n-10)/2+…+(n%5)/2,然后加上n/5,對于一個數(shù)列求和實際是可以手工求出其通項的表達的,這樣就可以省去最后一層循環(huán),使得時間更短且與輸入數(shù)的大小幾乎無關(guān)。雖然對于這樣的數(shù)列的求和,不管是高中還是大學(xué),都沒有學(xué)過怎么解,慢慢來分情況討論吧,先根據(jù)n%5分為5種不同情況,即:

Switch(n%5)

Case 0:

Case 1:

Case 2:

Case 3:

Case 4:

我們先看看case 0的這組(n-5*i)數(shù)列是這樣的。

0,5,10,15,20,25,30,35,40,45,50,55,60,65,……????記為數(shù)列1

輸入的n對應(yīng)的為它的第x項,得x=n/5+1。讓它的每個數(shù)做/2運算得到另外一組數(shù)列,有(n-5*i/2的數(shù)列:

0,2,5,7,10,12,15,17,20,22,25,27,30,32,……???????記為數(shù)列2

這組數(shù)列即我們要求和的數(shù)列,對數(shù)列2的前x項求和,然后再加上n/5即可得到對于任意n該不定方程解的個數(shù)。注意到數(shù)列2的個位數(shù)是每4個數(shù)一循環(huán)(0,2,57,0,2,5,7……),每4個數(shù)進10,將0,2,5,7相加為0+2+5+7=14,可以先把x之前剛好被4整除的項相加,剩下的x%4項另作討論,這樣有:(x/4)*14+(0+1+2+…+(x-1)/4)*40加上剩下的x%4項就可以了,這樣再加個判斷:

Swich(x%4)

Case 0:?? break;//因為這是整除的情況,不加任何數(shù)之間跳出就行了。

Case 1:?? …+(x/4)*10+0;

Case 2:?? …+(x/4)*20+0+2;

Case 3:?? …+(x/4)*30+0+2+5;

這樣對于case 0的情況就可以談?wù)撏耆恕?span style="padding:0px; margin:0px">

接著對于case 1的情況進行討論有, n-5*i的數(shù)列為:

1,6,11,16,21,26,31,36,31,36,41,46,51,56,61,……???記為數(shù)列3

所以(n-5*i/2數(shù)列為:

1,3,5,8,11,13,15,18,……? 記為數(shù)列4

同樣個位也是每四個一循環(huán),每四個一進位。1+3+5+8=16,:(x/4)*16+(0+1+2+…+(x-1)/4)*40

Swich(x%4)

Case 0:?? break;//因為這是整除的情況,不加任何數(shù)之間跳出就行了。

Case 1:?? …+(x/4)*10+1;

Case 2:?? …+(x/4)*20+1+3;

Case 3:?? …+(x/4)*30+1+3+5;

對于case 1的情況的討論就完了。

同樣對于case 2,case 3,case 4都可以完整的談?wù)摗?span style="padding:0px; margin:0px">

于是寫出代碼有:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 void jieshu(int n) { ????long long x=n/5+1; ????long long g=0; ???g+=(x/4-1)*(x/4)*20; //這里表示(0+1+2+…+(x-1)/4)*40的和 ????g+=x; ????switch(n%5) ????{ ????case 0: ????????{ ????????????g+=(x/4)*14; ????????????switch(x%4) ????????????{ ????????????case 0: ????????????????break; ????????????case 1: ????????????????g+=(x/4)*10; ????????????????break; ????????????case 2: ????????????????g+=(x/4)*20+2; ????????????????break; ????????????case 3: ????????????????g+=(x/4)*30+7; ????????????????break; ????????????} ????????} ????????break; ????case 1: ????????{ ????????????g+=(x/4)*16; ????????????switch(x%4) ????????????{ ????????????case 0: ????????????????break; ????????????case 1: ????????????????g+=(x/4)*10; ????????????????break; ????????????case 2: ????????????????g+=(x/4)*20+3; ????????????????break; ????????????case 3: ????????????????g+=(x/4)*30+8; ????????????????break; ????????????} ????????} ????????break; ????case 2: ????????{ ????????????g+=(x/4)*18; ????????????switch(x%4) ????????????{ ????????????case 0: ????????????????break; ????????????case 1: ????????????????g+=(x/4)*10+1; ????????????????break; ????????????case 2: ????????????????g+=(x/4)*20+4; ????????????????break; ????????????case 3: ????????????????g+=(x/4)*30+9; ????????????????break; ????????????} ????????} ????????break; ????case 3: ????????{ ????????????g+=(x/4)*20; ????????????switch(x%4) ????????????{ ????????????case 0: ????????????????break; ????????????case 1: ????????????????g+=(x/4)*10+1; ????????????????break; ????????????case 2: ????????????????g+=(x/4)*20+5; ????????????????break; ????????????case 3: ????????????????g+=(x/4)*30+11; ????????????????break; ????????????} ????????} ????????break; ????case 4: ????????{ ????????????g+=(x/4)*22; ????????????switch(x%4) ????????????{ ????????????case 0: ????????????????break; ????????????case 1: ????????????????g+=(x/4)*10+2; ????????????????break; ????????????case 2: ????????????????g+=(x/4)*20+6; ????????????????break; ????????????case 3: ????????????????g+=(x/4)*30+13; ????????????????break; ????????????} ????????} ????????break; ???????default: ??????????????break; ????} ????printf("%lld\n",g); }

雖然很長,但是求解過程只有幾次運算,加幾次判斷,求解速度又完成了一次飛躍,從前面的與n大小相關(guān)到與n無關(guān),運算速度直接降到10~100ns,缺陷就是代碼太長,太繁瑣,是不是可以繼續(xù)改進,這樣會被很多人罵作多余其事。

第四次優(yōu)化,注意第三次優(yōu)化的代碼中,注意到:

Case 0:? g+=(x/4*14……

Case 1:? g+= (x/4) *16……

Case 2:? g+= (x/4) *18……

Case 3:? g+= (x/4) *20……

Case 4:? g+= (x/4) *22……

可以用一個表達式來說明這個判斷即:?g+= (x/4) *(14+2*(n%5)).

然后對于其后的x%4判斷,可以用一個必小于4次的for循環(huán)來替代,

有:for(i=0;i<x%4;i++)

????????? g+=(n%5+5*i)/2;

可以將整個代碼縮減。

第四次優(yōu)化代碼如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void jieshu(int n) {??????? ?????????????long long x=n/5+1; ????????g+=(x/4-1)*(x/4)*20+(x+(x/4)*(14+(n%5)*2)+(x/4)*(x%4)*10); ??????????for(i=0;i<x%4;i++)?? //由于x%4永遠小于等于4,循環(huán)最多四次 ?????????????g+=(n%5+5*i)/2; ???????????printf("%lld\n",g); }

現(xiàn)在精簡了很多,而且省去了幾次判斷,不僅運算速度比第三次優(yōu)化后的代碼更短了,而且代碼的長度甚至比最開始還短得多,然而

g+=(x/4-1)*(x/4)*20+(x+(x/4)*(14+(n%5)*2)+(x/4)*(x%4)*10);

這個表達式是不是太長了,而且其中重復(fù)算了幾次x/4,n%5等運算。

于是第五次優(yōu)化,給這些要重復(fù)計算的表達式單獨開辟空間,用空間換取簡略,換取計算時間更改如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void jieshu(int n) { ????????????int x,y,z,r; ???????x=n/5+1; ???????y=x%4; ???????z=n%5; ???????r=x/4; ??????????g+=(r-1)*r*20+(x+r*(14+z*2)+r*y*10); ??????????for(i=0;i<y;i++) ?????????????g+=(z+5*i)/2; ???????????printf("%lld\n",g); }

這樣修改后,對于x/4,n/5+1,n%5,x%4,均只需要計算一次,以后的計算只需要從儲存空間中調(diào)取即可,繼續(xù)增進計算速度。

???于是,優(yōu)化到這步好像沒有什么要優(yōu)化了,運算速度基本已經(jīng)達到最快了,但是一個好的代碼不只是能解決一個問題,而是要能解決一類問題,并舉一反三解決最多的問題才能算一個好代碼。

? ?總結(jié):對于求一個多元一次不定方程的正整數(shù)解的個數(shù)求法,可以首先嘗試使用暴力枚舉法來進行枚舉即可求得枚舉出其所有的,第一步優(yōu)化可以采取將某個系數(shù)單位化的策略進行減去一層循環(huán)的操作,第二步根據(jù)可以根據(jù)前面的枚舉又省去一層循環(huán),將其改成一個判斷,加快計算速度,第三步,可以通過第二步的表達求出之前運算的數(shù)學(xué)表達式,求出通項即可使算法最優(yōu)。

總結(jié)

以上是生活随笔為你收集整理的记一次代码优化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。