平時設計或者閱讀乙個演算法的時候,必然會提到演算法的複雜度(包括時間複雜度和空間複雜度)。比如我們說乙個二分查詢演算法的平均時間複雜度為o(log n),快速排序可能是o(n log n)。那這裡的o是什麼意思?這樣的表達是否準確呢?
先插一句,在演算法複雜度分析中,log通常表示以2為底的對數。
演算法複雜度(演算法複雜性)是用來衡量演算法執行所需要的計算機資源(時間、空間)的量。通常我們利用漸進性態來描述演算法的複雜度。
用n表示問題的規模,t(n)表示某個給定演算法的複雜度。所謂漸進性態就是令n→∞時,t(n)中增長最快的那部分。嚴格的定義是:如果存在t˜(n)t~(n),當n→∞時,有
就說t˜(n)t~(n)是t(n)當n→∞時的漸進性態。
比如t(n) = 2 * n ^ 2 + n log n + 3,那麼顯然它的漸進性態是 2 * n ^ 2,因為當n→∞時,後兩項的增長速度要慢的多,可以忽略掉。引入漸進性態是為了簡化演算法複雜度的表示式,只考慮其中的主要因素。當比較兩個演算法複雜度的時候,如果他們的漸進複雜度的階不相同,那只需要比較彼此的階(忽略常數係數)就可以了。
總之,分析演算法複雜度的時候,並不用嚴格演算出乙個具體的公式,而是只需要分析當問題規模充分大的時候,複雜度在漸進意義下的階。記號o、ω、θ和o可以幫助我們了解函式漸高階的大小。
假設有兩個函式f(n)和g(n),都是定義在正整數集上的正函式。上述四個記號的含義分別是:
可見,記號o給出了函式f(n)在漸進意義下的上界(但不一定是最小的),相反,記號ω給出的是下界(不一定是最大的)。如果上界與下界相同,表示f(n)和g(n)在漸進意義下是同階的(θ),亦即複雜度一樣。
列舉一些常見的函式之間的漸高階的關係:
有些人可能會把這幾個記號跟演算法的最壞、最好、平均情況複雜度混淆,它們有區別,也有一定的聯絡。
即使問題的規模相同,隨著輸入資料本身屬性的不同,演算法的處理時間也可能會不同。於是就有了最壞情況、最好情況和平均情況下演算法複雜度的區別。它們從不同的角度反映了演算法的效率,各有用處,也各有侷限。
有時候也可以利用最壞情況、最好情況下演算法複雜度來粗略地估計演算法的效能。比如某個演算法在最壞情況下時間複雜度為θ(n ^ 2),最好情況下為θ(n),那這個演算法的複雜度一定是o(n ^ 2)、ω(n)的。也就是說n ^ 2是該演算法複雜度的上界,n是其下界。
接下來看看master定理。
有些演算法在處理乙個較大規模的問題時,往往會把問題拆分成幾個子問題,對其中的乙個或多個問題遞迴地處理,並在分治之前或之後進行一些預處理、彙總處理。這時候我們可以得到關於這個演算法複雜度的乙個遞推方程,求解此方程便能得到演算法的複雜度。其中很常見的一種遞推方程就是這樣的:
設常數a >= 1,b > 1,f(n)為函式,t(n)為非負整數,t(n) = a t(n / b) + f(n),則有:
比如常見的二分查詢演算法,時間複雜度的遞推方程為t(n) = t(n / 2) + θ(1),顯然有nlogba=n0=θ(1)nlogba=n0=θ(1),滿足master定理第二條,可以得到其時間複雜度為t(n) = θ(log n)。
再看乙個例子,t(n) = 9 t(n / 3) + n,可知nlogba=n2nlogba=n2,令ε取1,顯然滿足master定理第一條,可以得到t(n) = θ(n ^ 2)。
來乙個稍微複雜一點兒例子,t(n) = 3 t(n / 4) + n log n。nlogba=o(n0.793)nlogba=o(n0.793),取ε = 0.2,顯然當c = 3 / 4時,對於充分大的n可以滿足a * f(n / b) = 3 * (n / 4) * log(n / 4) <= (3 / 4) * n * log n = c * f(n),符合master定理第三條,因此求得t(n) = θ(n log n)。
運用master定理的時候,有一點一定要特別注意,就是第一條和第三條中的ε必須大於零。如果無法找到大於零的ε,就不能使用這兩條規則。
舉個例子,t(n) = 2 t(n / 2) + n log n。可知nlogba=n1nlogba=n1,而f(n) = n log n,顯然不滿足master定理第二條。但對於第一條和第三條,也無法找到大於零的ε使得nlogn=o(n1−ε)nlogn=o(n1−ε)或者nlogn=ω(n1+ε)nlogn=ω(n1+ε),因此不能用master定理求解,只能尋求別的方式求解。比如可以利用遞迴樹求出該演算法的複雜度為t(n)=o(nlog2n)t(n)=o(nlog2n)。簡單的說一下計算過程:
遞迴樹的建立過程,就像是模擬演算法的遞推過程。樹根對應的是輸入的規模為n的問題,在遞迴處理子問題之外,還需要n log n的處理時間。然後根據遞推公式給根節點新增子節點,每個子節點對應乙個子問題。這裡需要兩個子節點,每個節點處理規模為n / 2的問題,分別需要(n / 2) * log(n / 2)的時間。因此在第二層一共需要n * (log n - 1)的時間。第三層節點就是將第二層的兩個節點繼續**開,得到四個各需要(n / 4) * log(n / 4)時間的節點,總的時間消耗為n * (log n - 2)。依此類推,第k(設樹根為k = 0)層有2 ^ k的節點,總的時間為n * (log n - k)。而且可以知道,這棵樹總共有log n層(最後一層每個節點只處理規模為1的子問題,無須再分治)。最後將每一層消耗的時間累加起來,得到:
演算法複雜度分析 主定理
規模為n的問題通過分治,得到a個規模為n b的問題,每次遞迴帶來的額外計算為c n d t n at n b c n d 那麼就可以得到問題的複雜度為 可見,每次遞迴把問題分為a個規模為n b的子問題。從根節點開始,共有logb n 1層,葉子節點數為a logb n 那麼,第j層共有aj個子問題,...
演算法的複雜度 時間複雜度與空間複雜度
通常,對於乙個給定的演算法,我們要做 兩項分析。第一是從數學上證明演算法的正確性,這一步主要用到形式化證明的方法及相關推理模式,如迴圈不變式 數學歸納法等。而在證明演算法是正確的基礎上,第二步就是分析演算法的時間複雜度。演算法的時間複雜度反映了程式執行時間隨輸入規模增長而增長的量級,在很大程度上能很...
主定理與時間複雜度
只好在網上找了一篇看起來不怎麼嚴謹的部落格,不過算出來的是對的?那就預設是對的吧qwq 如果我們要解決規模為 n 的問題,通過分治,得到 a 個規模為 frac 的問題,每次的額外複雜度為 o n d t n at frac c n d begin t left n right begino lef...