算法效率的評估
解決一個問題可能有很多種方法,類似要排序一個數(shù)組就有:冒泡、選擇、快排&歸并排序等。 那么這些算法效率有高有低,如何衡量?指標就是時間復雜度和空間復雜度----分別代表了實現(xiàn)該算法的程序需要消耗的時間和內(nèi)存空間。
算法時間復雜度
算法執(zhí)行時間在不同語言實現(xiàn)、不同機器環(huán)境運行都不一致;很難精確計算實際耗時。而通常我們只需要一個簡單的方式來簡易評估程序大概運行耗時,再通俗點就是程序跑完算法要執(zhí)行語句的次數(shù)。需要更專業(yè)的解釋戳知乎:https://www.zhihu.com/question/21387264
通常時間復雜度用一個Big O表示法,類似O(1)、O(n)、O(n2)等,即用一個函數(shù)來描述算法的語句執(zhí)行頻度。Big O表示法會忽略函數(shù)的系數(shù),類似O(n)即代表一個算法具有 y=kx
線性函數(shù)復雜度,隨著輸入n的變化執(zhí)行次數(shù)呈現(xiàn)線性函數(shù)的增長曲線。
常見的時間復雜度
常數(shù)級O(1)
var age = 20;
println("Hello World")
println("Hi, Joker")
/**這里無論寫多少句,只要不牽扯到循環(huán)之類的隨變量變化而不確定執(zhí)行次數(shù)的case,都屬于O(1)常數(shù)階,執(zhí)行次數(shù)為一個確定常數(shù)
**/
線性級O(n)
var count = 20; //這里的count是指外部輸入的變量 并非給定常數(shù)
println("Hello World")
val range = 1 until count
for(num in range){
println("Hi, Joker")
}
/**最簡單的加上一句for循環(huán),這段代碼執(zhí)行次數(shù)會根據(jù)count的變化而呈現(xiàn)線性增長
**/
這里若是將val range = 1 until count
--> val range = 1 until 3*count
雖然變成了y=3x
但依然是個線性也是O(n)
平方級O(n2)
var count = 20; //這里的count是指外部輸入的變量 并非給定常數(shù)
println("Hello World")
val range = 1 until count
for(num in range){
for(inex in range){
println("Hi, Joker")
}
}
/**最簡單平方階代表是雙層循環(huán)**/
拓展思考下面的代碼是什么級的時間復雜度?
val count = 5 //ex count = 5, count指代指外部輸入的變量n
println("Hello World")
for( i in 1..count) {
for( j in i..count){
println("Hi, Joker.")
}
}
//這段代碼最終的執(zhí)行次數(shù)為 1+2+3+4+5 --》
1+2+3+....+n = (n+1)*n/2 = 0.5*n2 + 0.5*n ---> 忽略系數(shù)還是O(n2)
對數(shù)級O(logn)
var count = 8; //這里的count是指外部輸入的變量 并非給定常數(shù)
println("Hello World")
var index = 1
while( index < count ){
println("Hi, Joker")
index = index * 2 //以2為基數(shù)累乘
}
/** 循環(huán)次數(shù)y=log?n 為對數(shù)關(guān)系O(logn)**/
指數(shù)階
//指數(shù)階的算法性能效率屬于較低了,最常見于遞歸:比如斐波那契數(shù)列的遞歸寫法。
fun main() {
//斐波那契數(shù)列 0、1、1、2、3、5、8、13...... (n>=1)
println(fib(7))
}
fun fib(n: Int): Int {
if(n <= 1) return n
return fib(n-1) + fib(n-2)
}
Ex:計算fib(7) --> fib(6)+fib(5)
fib(6) --> fib(5)+fib(4)
fib(5) --> fib(4)+fib(3)
每一個函數(shù)調(diào)用都會觸發(fā)兩個新的函數(shù)調(diào)用,用樹形結(jié)構(gòu)表示:
可以看到樹形結(jié)構(gòu)每增加一層節(jié)點數(shù)就是上一層的2倍而且會計算很多重復的節(jié)點值。按照樹形圖若要完整遞歸計算出fib(7)需要7層,從fib(7)--> fib(1)需要call 函數(shù)2^7 次, 所以屬于指數(shù)階復雜度O(2^n)
常見時間復雜度對比
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(2?)<O(n!)
常見算法的復雜度
二分查找 ---> O(logn)
歸并排序(merge sort) ---> O(nlogn)
節(jié)點數(shù)總數(shù)為n的下列算法:
樹的前序、中序、后續(xù)遍歷 --> O(n)
搜索算法:DFS、BFS --> O(n)
圖的遍歷 ---> O(n)
換個角度看這幾個算法都是有且僅訪問一次節(jié)點的算法,所以時間復雜度都為O(n)
空間復雜度
關(guān)于空間復雜度類似的就用來評估程序?qū)?nèi)存空間的使用情況,粗暴是只考慮程序運行期間額外申請消耗的最大內(nèi)存規(guī)模:
這里的額外指的是除了程序本身的指令存儲空間以及存儲輸入數(shù)據(jù)需要的空間等與算法本身無關(guān)的空間消耗:通常是動態(tài)申請的數(shù)組空間、臨時變量、遞歸函數(shù)??臻g等
比如算法需要申請了一個長度為n的一維數(shù)組來解決問題 ---> O(n)
或者需要申請一個n*n的二維數(shù)組,那么就是O(n^2)
另外就是使用遞歸的情況基本相當于遞歸樹的層數(shù),上述斐波拉契的算法遞歸層級為N ---> O(n)
為啥遞歸的空間復雜度是遞歸樹的層數(shù)?
遞歸斐波那契空間分析
由函數(shù)遞歸調(diào)用順序來看,每次遞歸雖然會產(chǎn)生兩次新的函數(shù)調(diào)用但是由于加法優(yōu)先級,會先計算左邊的操作數(shù),從fib(5)一直先call到fib(1),然后fib(1)計算完成返回值銷毀回收自己的函數(shù)??臻g往回調(diào)用。所以消耗最大的內(nèi)存就是最深的調(diào)用層級n*S(S代表每次調(diào)用函數(shù)消耗的函數(shù)棧空間) ,即遞歸空間復雜度等于遞歸函數(shù)最深的層數(shù)級。
參考信息
CSDN博文 算法(一)時間復雜度
如何理解算法時間復雜度的表示法,例如 O(n2)、O(n)、O(1)、O(nlogn) 等?