前言
本篇文章收錄於專輯:http://dwz.win/HjK
你好,我是彤哥,一個每天爬二十六層樓還不忘讀原始碼的硬核男人。
大家都知道,資料結構與演算法解決的主要問題就是“快”和“省”的問題,即如何讓程式碼執行得更快, 如何讓程式碼更節省儲存空間。
所以,“快”和“省”是衡量一個演算法非常重要的兩項指標,也就是我們經常聽到的時間複雜度和空間複雜度分析。
那麼,為什麼需要複雜度分析呢?複雜度分析的方法論是什麼呢?
這就是我們本節要解決的問題。
好了,進入今天的學習吧。
為什麼需要複雜度分析?
首先,我們來思考一個問題:對於兩個演算法,我們如何評判誰執行得更快,誰執行時更節省記憶體?
你可能會說,這還不簡單,把這兩個演算法執行一遍,統計下執行時間和佔用記憶體不就可以了嗎?
沒錯,這確實是一種不錯的方法,而且它還有個非常形象的名字:事後統計法。
但是,這種統計方法具有非常明顯的問題:
-
不同的輸入對結果影響很大
對於一些輸入,可能演算法A執行得更快;對於另外一些輸入,可能演算法B執行得更快。比如,我們後面要學習的排序演算法,輸入的有序性對於不同的排序演算法的影響是完全不同的。
-
不同的機器對結果影響很大
對於同樣的輸入,可能在一臺機器上演算法A更快,而在另外一臺機器上演算法B更快。比如,演算法A可以利用多核而演算法B不能,那麼CPU的核數對這兩個演算法的影響將截然不同。
-
資料規模對結果影響很大
當資料規模小時,可能演算法A更快,而資料規模變大時,可能演算法B更快。比如,我們後面要學習的排序演算法,當資料規模比較小時,插入排序反而比歸併排序更快。
所以,我們需要一種可以不用實際執行演算法,就可以估計演算法執行效率的方法。
這也就是我們所說的複雜度分析。
那麼,怎麼進行復雜度分析呢?有沒有什麼方法論呢?
還真有,這個方法論叫作漸近分析法。
什麼是漸近分析法?
漸近分析法,是指將演算法執行的效率與輸入的規模進行掛鉤,隨著輸入規模的增大,演算法執行所需要的時間(或空間)將呈現一種什麼樣的趨勢,這種趨勢就叫作漸近,而這種方法就叫作漸近分析法。
概念可能比較拗口,我舉個簡單的例子,對於給定的一個有序陣列,我要查詢其中某個值所在的位置,比如,查詢8這個元素,有哪些方法呢?
簡單暴力點的方法,從頭遍歷,查詢到該元素即返回。
更友好一點的方法,採用二分法,每次定位到資料的中間位置,看其值與目標值的大小,判斷是在左邊還是右邊繼續以二分的方式查詢。
上面我們舉的例子的輸入規模是8個元素的有序陣列,目標值為8,使用第二種方法明顯比第一種方法要快很多。
但是,如果查詢的目標是1呢?
對於第一種方法,查詢一次足矣。
對於第二種方法,需要查詢3次。
此時,第二種方法又次於第一種方法了。
所以,比較兩個演算法的執行效率,不能只考慮到個別元素,而應該顧及到所有元素的感受。
我們以數學的方法來統計兩種方法的平均執行效率,假設輸入規模擴充套件到n。
對於第一種方法,1號元素查詢一次,2號元素查詢兩次,3號元素查詢三次……,而查詢每個元素的概率都是1/n。
所以,它的執行效率為:1x1/n + 2x1/n + 3x1/n + ... nx1/n = nx(n+1)/2/n = (n+1)/2。
對於第二種方法,中間的元素有一個,查詢一次,次中間的元素有兩個,查詢兩次,次次中間的元素有四個,查詢三次...,每次查詢的規模都縮小一半,而查詢每個元素的概率都是1/n。
所以,它的執行效率為:1x1x1/n + 2x2x1/n + 4x3x1/n + ... + 2^(log2(n)-2) x (log2(n)-1) x 1/n+ 2^(log2(n)-1) x log2(n) x 1/n = ?
我了個去,這個結果等於多少?
是時候展現真正的實力了:
你可能要罵娘了,對於我一個小學畢業的,難道我沒辦法學習資料結構與演算法了?
No,No,No,肯定不能這麼玩,那麼,應該怎麼玩呢?我們下一節接著講。
後記
本節,我們從演算法執行效率方面闡述了為什麼需要複雜度分析,並介紹了複雜度分析的方法,即漸近分析法,如果嚴格地遵循漸近分析法,需要大量的數學知識,這無疑增加了我們分析演算法的難度,那麼,有沒有什麼更省心地計算複雜度的方法呢?
關注公眾號“彤哥讀原始碼”,解鎖更多原始碼、基礎、架構知識!