“算法复杂度”——其实并没有那么复杂
算法是用于解決特定問題的一系列的執(zhí)行步驟。使用不同算法,解決同一個問題,效率可能相差非常大。為了對算法的好壞進(jìn)行評價,我們引入?“算法復(fù)雜度”?的概念。
?
1、引例:斐波那契數(shù)列(Fibonacci sequence)
已知斐波那契數(shù)列:,求它的通項公式。
求解斐波那契數(shù)列的方法有很多種,這里只介紹兩種:遞歸法和平推法。
package com.atangbiji;public class Main {public static void main(String[] args) {// 輸出通項F(n)System.out.println(fib1(1));System.out.println(fib1(2));System.out.println(fib1(3));System.out.println(fib1(4));System.out.println(fib1(5));System.out.println(fib2(70));}/** 求斐波那契數(shù)列(Fibonacci sequence)* F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 3,n ∈ N*)的通項F(n).*//** 方法一:遞歸法* 最高支持 n = 92 ,否則超出 Long.MAX_VALUE* @param n * @return f(n) * */public static long fib1(int n) {if (n < 1 || n > 92)return 0;if (n < 3)return 1;return fib1(n - 1) + fib1(n - 2);}/** 方法二:平推法* 最高支持 n = 92 ,否則超出 Long.MAX_VALUE* @param n * @return f(n) * */public static long fib2(int n) {if (n < 1 || n > 92)return 0;//n: 1 2 3 4 5 ……//F(n): 1 1 2 3 5 ……long first = 1;long second = 1;for (int i = 3; i <= n; i++) {long sum = first + second;first = second;second = sum;}return second;}}通過測試,我們可以發(fā)現(xiàn):當(dāng)n的取值較大時(如:n = 60),若采用遞推法計算則會發(fā)現(xiàn)遲遲不出結(jié)果,若采用平推法計算則可以秒出結(jié)果。由此可見,?平推法的效率明顯高于遞推法。
?
2、如何評估算法的好壞?
-
正確性
-
可讀性
-
健壯性:對不合理輸入的反應(yīng)能力和處理能力。
-
時間復(fù)雜度(time complexity):?估算程序指令的執(zhí)行次數(shù)(執(zhí)行時間)。
-
空間復(fù)雜度(space complexity):?估算所需占用的存儲空間。
注:一般情況下,我們主要考慮算法的時間復(fù)雜度。?(因為目前計算機的內(nèi)存一般都比較大)
?
3、時間復(fù)雜度的估算
我們可以用程序指令的執(zhí)行次數(shù)來估算時間復(fù)雜度。例如:
(1)函數(shù)test1
public static void test1(int n) {//總執(zhí)行次數(shù) = 14// 1(判斷語句可以忽略)if (n > 10) {System.out.println("n > 10");} else if (n > 5) {System.out.println("n > 5");} else {System.out.println("n <= 5");} // 1 + 4 + 4 + 4 for (int i = 0; i < 4; i++) {System.out.println("test"); }}(2)函數(shù)test2
public?static?void?test2(int?n)?{//總執(zhí)行次數(shù) = 1 + 3n//1 + n + n + nfor (int i = 0; i < n; i++) {System.out.println("test");} }(3)函數(shù)test3
public static void test3(int n) {//總執(zhí)行次數(shù) = 48n + 1// 1 + 2n + n * (1 + 45)for (int i = 0; i < n; i++) {for (int j = 0; j < 15; j++) {// 1 + 15 + 15 + 15System.out.println("test");} }}(4)函數(shù)test4
public?static?void?test4(int?n)?{//總執(zhí)行次數(shù) = 3n^2 +3n +1// 1 + 2n + n * (1 + 3n)for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) { // 1 + n + n + nSystem.out.println("test");}} }(5)★ 函數(shù)test5
public static void test5(int n) {//總執(zhí)行次數(shù) = log2(n)/** n = 2 , 執(zhí)行 1 次* n = 4 , 執(zhí)行 2 次* n = 8 , 執(zhí)行 3 次 * */while ((n = n/2) > 0) { // 倍速減小System.out.println("test"); // 只考慮這一句的執(zhí)行次數(shù)} }(6)函數(shù)test6
public?static?void?test6(int?n)?{//總執(zhí)行次數(shù) = log5(n)while ((n = n/5) > 0) {System.out.println("test"); // 只考慮這一句的執(zhí)行次數(shù)} }(7)函數(shù)test7
public?static?void?test7(int?n)?{//總執(zhí)行次數(shù) = 3n*log2(n) + 3log2(n) + 1// 1 + 2 * log2(n) + log2(n) * (1 + 3n)/** n = 2 , 執(zhí)行 1 次* n = 4 , 執(zhí)行 2 次* n = 8 , 執(zhí)行 3 次 * */for (int i = 1; i < n; i += i) { // i = i + i = i * 2(倍速增大)for (int j = 0; j < n; j++) { // 1 + n + n + nSystem.out.println("test");}} }?
4、大O表示法
為了進(jìn)一步簡化復(fù)雜度的計算,我們一般使用大O表示法來描述時間(或空間)復(fù)雜度。它表示的是?數(shù)據(jù)規(guī)模為n?時算法所對應(yīng)的復(fù)雜度。
大O表示法的性質(zhì):
(1)可以忽略常數(shù)、常系數(shù)和低階項。
-
9 >> O(1)
-
2n + 3 >> O(n)
-
3n^2?+ 2n + 6 >> O(n^2)
(2)對數(shù)階一般省略底數(shù),統(tǒng)稱 log n。
- log2 n = log2 9 * log9 n
注:大O表示法僅僅只是一種粗略的分析模型,是一種估算。?它能幫我們快速了解一個算法的執(zhí)行效率。
?
5、常見的復(fù)雜度
其中:
-
當(dāng)數(shù)據(jù)規(guī)模較小時,?各復(fù)雜度對應(yīng)的曲線如下圖所示。
?
-
當(dāng)數(shù)據(jù)規(guī)模較大時,?各復(fù)雜度對應(yīng)的曲線如下圖所示。
所以,當(dāng)數(shù)據(jù)規(guī)模比較大時,復(fù)雜度為??我們就很難接受了。
?
6、斐波那契數(shù)算法復(fù)雜度分析
(1)遞歸法
public?static?long?fib1(int?n)?{if (n < 1 || n > 92) return 0; if (n < 3) return 1; return fib1(n - 1) + fib1(n - 2);}假設(shè)計算時和的值已經(jīng)得到,我們可以發(fā)現(xiàn)該函數(shù)每次執(zhí)行的時間主要取決于求和運算。因此,該算法函數(shù)指令的執(zhí)行次數(shù)等價于該函數(shù)被遞歸調(diào)用次數(shù)。
當(dāng)時,該函數(shù)的調(diào)用過程如下圖所示。
所以,該函數(shù)被遞歸調(diào)用的次數(shù)??二叉樹的節(jié)點數(shù)。
即:。
因此,該算法的復(fù)雜度為。
注:?細(xì)心的同學(xué)可能會發(fā)現(xiàn),當(dāng)時,函數(shù)被遞歸調(diào)用的次數(shù)并不完全等于。
這里需要說明的是:復(fù)雜度是一種估算,我們關(guān)心的不是具體的數(shù)值,而是量級和趨勢。?所以,呈指數(shù)級增長的趨勢是毋庸置疑的。
(2)平推法
public static long fib2(int n) {if (n < 1 || n > 92)return 0;//n: 1 2 3 4 5 ……//F(n): 1 1 2 3 5 ……long first = 1;long second = 1;for (int i = 3; i <= n; i++) {long sum = first + second;first = second;second = sum;}return second; }顯然,平推法的時間復(fù)雜度為。
?
7、算法的優(yōu)化方向
(1)用盡量少的執(zhí)行步驟(運行時間)。
(2)用盡量少的存儲空間。
(3)根據(jù)情況,空間換時間或者時間換空間。
更多關(guān)于復(fù)雜度的知識,我們會在后續(xù)數(shù)據(jù)結(jié)構(gòu)和算法的設(shè)計與實現(xiàn)過程中穿插講解。
總結(jié)
以上是生活随笔為你收集整理的“算法复杂度”——其实并没有那么复杂的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云原生时代,Java还是Go?
- 下一篇: 为什么我恨Scrum?