算法的時間複雜度到底如何計算

2021-03-06 IT微聯盟

我們假設計算機運行一行基礎代碼需要執行一次運算。

1int aFunc(void) {
2    printf("Hello, World!\n");      
3    return 0;       
4}

那麼上面這個方法需要執行 2 次運算

1int aFunc(int n) {
2    for(int i = 0; i<n; i++) {         
3        printf("Hello, World!\n");      
4    }
5    return 0;       
6}

這個方法需要 (n + 1 + n + 1) = 2n + 2 次運算。

我們把 算法需要執行的運算次數 用 輸入大小n 的函數 表示,即 T(n) 。
此時為了 估算算法需要的運行時間 和 簡化算法分析,我們引入時間複雜度的概念。

定義:存在常數 c 和函數 f(N),使得當 N >= c 時 T(N) <= f(N),表示為 T(n) = O(f(n)) 。
如圖:


當 N >= 2 的時候,f(n) = n^2 總是大於 T(n) = n + 2 的,於是我們說 f(n) 的增長速度是大於或者等於 T(n) 的,也說 f(n) 是 T(n) 的上界,可以表示為 T(n) = O(f(n))。

因為f(n) 的增長速度是大於或者等於 T(n) 的,即T(n) = O(f(n)),所以我們可以用 f(n) 的增長速度來度量 T(n) 的增長速度,所以我們說這個算法的時間複雜度是 O(f(n))。

算法的時間複雜度,用來度量算法的運行時間,記作: T(n) = O(f(n))。它表示隨著 輸入大小n 的增大,算法執行需要的時間的增長速度可以用 f(n) 來描述。

顯然如果 T(n) = n^2,那麼 T(n) = O(n^2),T(n) = O(n^3),T(n) = O(n^4) 都是成立的,但是因為第一個 f(n) 的增長速度與 T(n) 是最接近的,所以第一個是最好的選擇,所以我們說這個算法的複雜度是 O(n^2) 。

那麼當我們拿到算法的執行次數函數 T(n) 之後怎麼得到算法的時間複雜度呢?

我們知道常數項對函數的增長速度影響並不大,所以當 T(n) = c,c 為一個常數的時候,我們說這個算法的時間複雜度為 O(1);如果 T(n) 不等於一個常數項時,直接將常數項省略。

比如

第一個 Hello, World 的例子中 T(n) = 2,所以我們說那個函數(算法)的時間複雜度為 O(1)。
T(n) = n + 29,此時時間複雜度為 O(n)。
我們知道高次項對於函數的增長速度的影響是最大的。n^3 的增長速度是遠超 n^2 的,同時 n^2 的增長速度是遠超 n 的。 同時因為要求的精度不高,所以我們直接忽略低此項。

比如

T(n) = n^3 + n^2 + 29,此時時間複雜度為 O(n^3)。
因為函數的階數對函數的增長速度的影響是最顯著的,所以我們忽略與最高階相乘的常數。

比如

T(n) = 3n^3,此時時間複雜度為 O(n^3)。
綜合起來:如果一個算法的執行次數是 T(n),那麼只保留最高次項,同時忽略最高項的係數後得到函數 f(n),此時算法的時間複雜度就是 O(f(n))。為了方便描述,下文稱此為 大O推導法。

由此可見,由執行次數 T(n) 得到時間複雜度並不困難,很多時候困難的是從算法通過分析和數學運算得到 T(n)。對此,提供下列四個便利的法則,這些法則都是可以簡單推導出來的,總結出來以便提高效率。

對於一個循環,假設循環體的時間複雜度為 O(n),循環次數為 m,則這個
循環的時間複雜度為 O(n×m)。

1void aFunc(int n) {
2    for(int i = 0; i < n; i++) {         
3        printf("Hello, World!\n");      
4    }
5}

此時時間複雜度為 O(n × 1),即 O(n)。

對於多個循環,假設循環體的時間複雜度為 O(n),各個循環的循環次數分別是a, b, c…,則這個循環的時間複雜度為 O(n×a×b×c…)。分析的時候應該由裡向外分析這些循環。

1void aFunc(int n) {
2    for(int i = 0; i < n; i++) {         
3        for(int j = 0; j < n; j++) {       
4            printf("Hello, World!\n");      
5        }
6    }
7}

此時時間複雜度為 O(n × n × 1),即 O(n^2)。

對於順序執行的語句或者算法,總的時間複雜度等於其中最大的時間複雜度。

1void aFunc(int n) {
2    
3    for(int i = 0; i < n; i++) {
4        for(int j = 0; j < n; j++) {
5            printf("Hello, World!\n");
6        }
7    }
8    
9    for(int j = 0; j < n; j++) {
10        printf("Hello, World!\n");
11    }
12}

此時時間複雜度為 max(O(n^2), O(n)),即 O(n^2)。

對於條件判斷語句,總的時間複雜度等於其中 時間複雜度最大的路徑 的時間複雜度。

1void aFunc(int n) {
2    if (n >= 0) {
3        
4        for(int i = 0; i < n; i++) {
5            for(int j = 0; j < n; j++) {
6                printf("輸入數據大於等於零\n");
7            }
8        }
9    } else {
10        
11        for(int j = 0; j < n; j++) {
12            printf("輸入數據小於零\n");
13        }
14    }
15}

此時時間複雜度為 max(O(n^2), O(n)),即 O(n^2)。

時間複雜度分析的基本策略是:從內向外分析,從最深層開始分析。如果遇到函數調用,要深入函數進行分析。

最後,我們來練習一下

一. 基礎題

求該方法的時間複雜度

1void aFunc(int n) {
2    for (int i = 0; i < n; i++) {
3        for (int j = i; j < n; j++) {
4            printf("Hello World\n");
5        }
6    }
7}

參考答案:
當 i = 0 時,內循環執行 n 次運算,當 i = 1 時,內循環執行 n - 1 次運算……當 i = n - 1 時,內循環執行 1 次運算。
所以,執行次數 T(n) = n + (n - 1) + (n - 2)……+ 1 = n(n + 1) / 2 = n^2 / 2 + n / 2。
根據上文說的 大O推導法 可以知道,此時時間複雜度為 O(n^2)。

二. 進階題

求該方法的時間複雜度

1void aFunc(int n) {
2    for (int i = 2; i < n; i++) {
3        i *= 2;
4        printf("%i\n", i);
5    }
6}

參考答案:
假設循環次數為 t,則循環條件滿足 2^t < n。
可以得出,執行次數t = log(2)(n),即 T(n) = log(2)(n),可見時間複雜度為 O(log(2)(n)),即 O(log n)。

三. 再次進階

求該方法的時間複雜度

1long aFunc(int n) {
2    if (n <= 1) {
3        return 1;
4    } else {
5        return aFunc(n - 1) + aFunc(n - 2);
6    }
7}

參考答案:
顯然運行次數,T(0) = T(1) = 1,同時 T(n) = T(n - 1) + T(n - 2) + 1,這裡的 1 是其中的加法算一次執行。
顯然 T(n) = T(n - 1) + T(n - 2) 是一個斐波那契數列,通過歸納證明法可以證明,當 n >= 1 時 T(n) < (5/3)^n,同時當 n > 4 時 T(n) >= (3/2)^n。
所以該方法的時間複雜度可以表示為 O((5/3)^n),簡化後為 O(2^n)。
可見這個方法所需的運行時間是以指數的速度增長的。如果大家感興趣,可以試下分別用 1,10,100 的輸入大小來測試下算法的運行時間,相信大家會感受到時間複雜度的無窮魅力。

相關焦點

  • 評估算法及算法的時間複雜度
    解決一個問題有多種方法,也就有多種算法。每一種算法都可以達到解決問題的目的,但花費的成本和時間不盡相同,從節約成本和時間的角度考慮,需要找出最優算法。那麼,如何衡量一個算法的好壞呢?顯然,選用的算法應該是正確的(算法的正確性不在此論述)。
  • 算法的時間複雜度
    算法的時間與空間複雜度事後分析法缺點:不同的數據規模,不同的機器下算法運行的時間不同,無法做到計算運行時間事前分析法大O時間複雜度漸進時間複雜度 隨著n的增長,程序運行時間跟隨n變化的趨勢幾個原則去掉常數項2(n^2) =n^2一段代碼取時間複雜度最高的test(n) { //時間複雜度n^3 for(int i = 0; i < n ; i++){ for(int i = 0; i
  • 時間複雜度的分析及排序算法總結
    算法時間複雜度是一個函數,它定性描述該算法的運行時間(也就是所花費時間的消耗)。排序算法中各個算法時間複雜度為:O(n2), O(nlogn), O(n)。這些我們之前都學習過,可以先從排序算法來理解時間複雜度。
  • 算法的時間複雜度和空間複雜度-總結
    第一是從數學上證明算法的正確性,這一步主要用到形式化證明的方法及相關推理模式,如循環不變式、數學歸納法等。而在證明算法是正確的基礎上,第二部就是分析算法的時間複雜度。算法的時間複雜度反映了程序執行時間隨輸入規模增長而增長的量級,在很大程度上能很好反映出算法的優劣與否。因此,作為程式設計師,掌握基本的算法時間複雜度分析方法是很有必要的。
  • 時間複雜度的表示、分析、計算方法……一文帶你看懂時間複雜度!
    作者 | OverRedMaple責編 | Carol來源 | CSDN 博客封圖 | CSDN付費下載於東方 IC如果你還在發愁究竟怎麼計算時間複雜度和空間複雜度名詞解釋:在計算機科學中,時間複雜性,又稱時間複雜度,算法的時間複雜度是一個函數,它定性描述該算法的運行時間。這是一個代表算法輸入值的字符串的長度的函數。時間複雜度常用大O符號表述,不包括這個函數的低階項和首項係數。
  • 從經典算法題看時間複雜度
    算法的時間複雜度(Time complexity)是一個函數,用於定性描述算法的運行時間。提出時間複雜度的目的是:分析與比較完成同一個任務而設計的不同算法。分析算法的結果意味著算法需要的資源,雖然有時我們關心像內存,通信帶寬或者計算機硬體這類資源,但是通常我們想要度量的是計算時間。
  • 【皮皮灰】考研數據結構中關於時間複雜度的計算
    考研中考察重點:選擇題中關於時間複雜度的計算,算法題中對算法有時間複雜度的要求。本文共4350字,通過閱讀本文你可以get:1.時間複雜度的概念以及皮皮灰的一些看法。2.非遞歸函數時間複雜度的計算。3.遞歸函數時間複雜度的計算。
  • Python編程之算法複雜度簡介
    在智慧型手機上,用戶會關係啟動一個應用程式需要多長時間,而生物學家則關心系統進化推理計算會持續多久。編寫高效程序並不容易,最簡單直接的方法一般都不是最有效率的。有效的算法一般都會使用一些巧妙的小技巧,這就使得它們非常難以理解。結果經常就是,程式設計師努力地減少了計算複雜度,但是卻增加了概念複雜度。
  • 原創 | 時間複雜度不理解?看這個一定懂!
    讓人迷惑的複雜度小白: 慶哥啊,這個複雜度到底是個啥啊,我在上大學的時候學這塊的時候就很懵😂,不知道是個啥,理解起來很費勁,所以當時也沒有好好學習,自己的數據結構與算法這塊一直比較薄弱,準備好好再學學數據結構與算法嘞,這一個複雜度都難住我了🤣慶哥: 的確啊,雖然就三個字,但是理解起來也確實有點費勁
  • Web前端面試題:如何分析時間複雜度-開課吧
    問題:如何分析時間複雜度?O(1) 傳說中的常數階的複雜度,這種複雜度無論數據規模n如何增長,計算時間是不變的。const increment = n => n++舉個簡單的例:不管n如何增長,都不會影響到這個函數的計算時間,因此這個代碼的時間複雜度都是O(1)。
  • 用機器學習構建O(N)複雜度的排序算法,可在GPU和TPU上加速計算
    排序一直是計算機科學中最為基礎的算法之一,從簡單的冒泡排序到高效的桶排序,我們已經開發了非常多的優秀方法。但隨著機器學習的興起與大數據的應用,簡單的排序方法要求在大規模場景中有更高的穩定性與效率。中國科技大學和蘭州大學等研究者提出了一種基於機器學習的排序算法,它能實現 O(N) 的時間複雜度,且可以在 GPU 和 TPU 上高效地實現並行計算。
  • 報導 | 複雜度O(n log n)?整數乘法新算法!
    作者:運籌OR帷幄整理編者按:在小學時,大家就學會如何用列豎式的方法計算乘法。程序設計競賽的選手們對於如何進行大數乘法計算想必也不陌生——這個利用快速傅立葉變換的Schönhage–Strassen算法早在上個世紀就已經被發明。
  • 通過一道面試題目,講一講遞歸算法的時間複雜度!
    ,修煉編程內功)來源:代碼隨想錄相信很多同學對遞歸算法的時間複雜度都很模糊,那麼這篇來給大家通透的講一講。那麼我通過一道簡單的面試題,模擬面試的場景,來帶大家逐步分析遞歸算法的時間複雜度,最後找出最優解,來看看同樣是遞歸,怎麼就寫成了O(n)的代碼。面試題:求x的n次方想一下這麼簡單的一道題目,代碼應該如何寫呢。
  • 關於時間複雜度,你不知道的都在這裡!
    那麼該如何估計程序運行時間呢,通常會估算算法的操作單元數量來代表程序消耗的時間,這裡默認CPU的每個單元運行消耗的時間都是相同的。假設算法的問題規模為n,那麼操作單元數量便用函數f(n)來表示,隨著數據規模n的增大,算法執行時間的增長率和f(n)的增長率相同,這稱作為算法的漸近時間複雜度,簡稱時間複雜度,記為 O(f(n))。
  • ​隨機算法(randomized algorithm)概述——概念、方法、類型、複雜度、設計方法和計算舉例(14k字)
    本文概述隨機算法(randomized algorithm)的概念、方法、類型、複雜度、設計方法和計算舉例。關鍵詞:隨機算法(randomized algorithm)、隨機、計算。>概念、方法、類型、複雜度、設計方法和計算舉例文|秦隴紀,數據簡化DataSimp©20190703Wed維基百科上,隨機算法是一種採用一定程度的隨機性作為其邏輯的算法。
  • 時空複雜度(時間複雜度/空間複雜度)O(1)、O(n)、O(n^2)、O(log n)、O(n log n)是什麼意思?
    它描述了時空複雜度.大O符號是我在大學裡學過的東西之一,我了解過這個算法的概念。我知道的不算多,可以回答一些基本的問題,僅此而已。從大學畢業以後,我對這個算法的了解基本沒有改變,因為自從我開始工作以來,我沒有使用過它,也沒有聽到任何同事提到過它。所,我想我應該花點時間回顧一下它,並在這篇文章中總結大O符號的基礎知識,以及一些代碼示例來幫助解釋它。什麼是大O符號?
  • 我們到底該如何學習《數據結構與算法》
    一、算法與數據結構到底是個什麼東東?在這裡我不想去解釋哪些常見的名詞了,像什麼是數據項、數據對象、元素等等這些概念。稍微有點基礎的人,對這些概念都應該很清楚,畢竟都是中國人。我主要想說一下,我們到底該如何理解數據結構與算法。1、什麼是數據結構?
  • PNAS:如何定量描述複雜度?新方法來了
    基於重整化群計算圖片複雜度的方式 如何計算上面這幅圖片的複雜度?重整化群的方法,是將該圖分為16份,再將圖中的某一份放大之後分為16份,將其中像素平均後,得出該子圖的不同區域像素值。將原圖的16個子圖依次排列,得到A,再將子圖分割排列得出圖B,計算圖A和圖B之間的差異,得出圖O。
  • 時間複雜度中的log(n)底數是多少?
    其實這裡的底數對於研究程序運行效率不重要,寫代碼時要考慮的是數據規模n對程序運行效率的影響,常數部分則忽略,同樣的,如果不同時間複雜度的倍數關係為常數,那也可以近似認為兩者為同一量級的時間複雜度。現在來看看為什麼底數具體為多少不重要?
  • 通過 JavaScript 學習算法複雜度
    // 每日前端夜話 第474篇// 正文共:1700 字// 預計閱讀時間:10 分鐘在本文中,我們將探討 「二次方」 和 「n log(n)」 等術語在算法中的含義。當你進一步了解算法時,就會發現這非常有用,因為在理解這種關係的同時去編寫代碼,就能知道時間都花在了什麼地方。當你了解更多有關 Big O 表示法的信息時,可能會看到下圖中不同的變化。我們希望將複雜度保持在儘可能低的水平,最好避免超過 O(n)。