算法录 之 复杂度分析。
一個算法的復雜度可以說也就是一個算法的效率,一般來說分為時間復雜度和空間復雜度。。。
注意接下來說的均是比較YY的,適用與ACM等不需嚴格分析只需要大致范圍的地方,至于嚴格的算法復雜度分析的那些數學證明,主定理什么的在《算法導論》這本書上有十分詳細的講解,網上應該也會有人寫過,這里就不多說了(其實,是我不會而已o(╯□╰)o。。。)。
— 到底啥是復雜度呢?先來個栗子。
小明有10個蘋果,有一天他餓了,然后準備吃掉一個蘋果,但是小明有中二病,他要吃里面重量最大的那個,于是。。。他需要一個找到那個最大的,可是這應該怎么找呢?
小明要先量一下第一個蘋果多重,然后第二個多重,然后量第三個。。。一直到第十個,量的時候記錄當前最重的那個,然后當找完了這十個就好了。。。
下面來看看這個神奇的找蘋果算法的復雜度,有10個蘋果,所以需要測量10次才能找到,如果有100個蘋果,顯然需要測量100次,1000個呢,1000次,n個也就需要n次。
下面設n為問題的規模,f(n)為運算次數,那么對于小明這個問題來說 f(n)=kn,k是一個常數,這個例子 k=1。(看到如此眼熟的高中數學氣息有沒有啥感覺。。。)
?
— 不用多說就知道如果需要的運算次數多的話,需要花費的時間也就多,所以這就是時間復雜度了,對于上面那個問題的時間復雜度就是 f(n) 了。
— 但是。。。對于一個問題的常數 k 是比較難搞定的,可能稱一下蘋果的重量需要一步操作,也可能兩步,也可能好幾步。所以對于時間復雜度的分析一般是去找漸進復雜度。簡單說就是函數 f(n) 省略了常數和低次項,只留下最高次項。比如函數 6n^3+3n^2+2n+10 變成了 n^3 ,因為當n很大很大的時候,變化趨勢就是 n^3 型的,再比如 2^n+n^5 就變成了 2^n,因為指數爆炸嘛。。。
— 這里還要說說符號 O,o 和?Θ,這三個應該高數課會講。。。O( f(n) ) 表示函數的上界,也就是一個一直比 f(n) 大的函數,然后 o 就是下界,至于 ?Θ 的話,叫做中界(我YY的一個名字)?也就是和 f(n) 的增長速度一樣。。。不過一般來說之后的分析都是用 O 的,因為這個字母好寫。。。餓,其實可以理解為因為O是上界,所以算法遇到再壞的情況也不會超過這個函數。
?
?
— 對于一個算法來說一般常用的漸進復雜度函數有 O( n ) ?O( n^2 ) ?O( n^3 ) ?O(1) ?O( 2^n ) ?O( n!) ?O( log n) ?O( n*logn ) ?O( n*2^n ) 差不多這些,注意 log 指以2為底的對數。。。函數圖就像下面這樣:
— 然后分別比較看看哪種復雜度更好,顯然 O(1)是最好的,因為不管問題的規模有多大,都能一下子得到答案。。。然后看看 O(n),小明的問題就是這個復雜度的,如果問題規模是n,需要運行n次,顯然已經不錯了,挺快的了。。。但是還有一個更快的,O(log n)的,如果n=1000000 那么才只需要運行 20次不到就能得到答案,但是 O(n)的卻需要運行1000000次,你說哪個快。
?
舉個栗子:
要求輸入一個n,然后算 1^2+2^2+3^2+4^2+...+n^2的值。
那么先說一種做法,直接for循環,代碼如下:
#include <iostream>using namespace std;int main() {int n;int sum=0;cin>>n;for(int i=1;i<=n;++i)sum+=i*i;cout<<sum;return 0; }顯然這種算法的復雜度是 O (n) 的,因為運行了 n 次嘛。(注意 n 比較大的時候結果會超過 int 的表示范圍,具體看 ACM錄 常識與錯誤那篇文章有說。)
?
然后看看第二種做法:推出公式來。1^2+2^2+。。。+n^2 = n*(n+1)*(2n+1)/6
所以第二種做法就是
#include <iostream>using namespace std;int main() {int n;cin>>n;cout<<n*(n+1)*(2*n+1)/6<<endl;return 0; }這就是 O ( 1 ) 的復雜度了,那么哪個快就不說了。。。
?
— 至于后面的 O( n^2 ) ?O( n^3 ) ?O( 2^n ) 啊啥的,也是這樣算,上面那些里面復雜度最高,效率最差的應該是 O(n!),如果 n 是10,就需要運行 3628800 次,這效率,不多說了。。。
— 上面說的是運行的次數,然后說說時間,對于現在的個人計算機來說,速度大約是一秒能運行10000000(7個0)次多,這個可以自己寫個程序感受一下,for循環10000000次或者更多看看。。。一般來說如果只有加減法 100000000(8個0)次也很快,但是如果有了除法或者其他東西會慢一點。。。
#include <iostream>using namespace std;int main() {int x;for(int i=0;i<1000000000;++i)x=123*321; // 對比 x=123/321;return 0; }?
— 然后對于一個題目來說一般限制了1秒啊2秒啊啥的,那么如果題目的數據規模是100000,那么如果采用的算法是 O(n^2)的,那么就需要運行 10000000000次,也就需要大約1000秒,顯然超時了。。。如果算法是 O(n log n)的,那么大約是 1700000 次還是可以接受的。。。至于 O(n) O(1) O(log n)啥的就更不用說了。。。所以說解一個題目的話要注意數據范圍和時間限制。。。
?
— 這里順便說下常數吧,之前都是把常數忽略了然后看的是一個大致的增長函數。但是如果常數很大,比如算法只有一個 for 循環,但是里面有1000次運算,那么雖然他的復雜度是 O(n)的,但是對于100000的規模需要運行100000000次,也就很可能會超時了。。。所以當常數比較大的時候要注意看看。。。
— 至于空間復雜度的話,也就是用的空間的多少和數據規模n的函數,但是因為這個不是很常用而且和時間復雜度幾乎差不多,就不多說了。。。
?
///
?
— 那么怎么算復雜度呢,在一些算法和數學的書上有十分嚴格的數學方法。。。然而,平時不需要的。。。
— 其實靠肉眼看看就差不多知道了,看看有幾個循環,大致估算一下運行多少次,然后就知道了。。。這些當寫代碼前想算法的時候其實就已經大致了解了。。。
— 當然這里對于存在遞歸的算法,就比較麻煩了,這里建議大家學了一些基本的算法之后再來看,因為這里實在是找不到簡單的例子。這里有一個主定理(名字就叫主定理),用來計算遞歸的函數的復雜度計算。比如說一個算法是把問題分成兩個小問題,然后在花費 g(n) 的復雜度來合并兩個小問題得到大問題的解。那么函數差不多是 f(n)=2*f(n/2)+g(n) 至于 f(n) 怎么推,就可以用上面的那個主定理(這里可以去網上找找這個定理學習一下),其實。。。也可以先猜一下,然后帶入那個等式看看行不行。。。
— 經典的遞推比如 f(n)=2f(n/2)+n 的復雜度就是 O(n log n)。。。所以如果 g(n) 如果比 O(n) 要好的話,顯然最后的復雜度會比 O(n log n)更好。。。
?
— 另外還有一些復雜度分析比如均攤分析就更喪心病狂了,這種分析是找平均值,期望值啥的,通過概率或者是其他什么東西證明運行比如100次的平均復雜度一定不會高于一個數,所以平均就是多少多少的,這個的話就不詳細多說了,之后學習比較高級的算法的時候才會接觸到。。。(其實是我不會。。。)
?
?
復雜度分析感覺就這些東西,其實大部分算法的復雜度是一眼就能看出來,當然也有一些需要仔細分析這個算法的各種情況才行。。。
轉載于:https://www.cnblogs.com/whywhy/p/4865609.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的算法录 之 复杂度分析。的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php session 跨页失效问题
- 下一篇: StringWriter/PrintWr