如何清晰的理解演算法中的時間複雜度?


演算法中某個特定步驟的執行次數/對於總執行時間的估算成本,隨著「問題規模」的增大時,增長的形式。


演算法時間複雜度用來度量演算法執行時間的多少,用大O階表示,即T(n)=O(f(n)),其中n為問題規模,也就是問題的大小。

既然要理解時間複雜度,我們首先理解術語中的兩個關鍵詞——「演算法」和「時間」,理解了它倆就成功一半了。

首先看「演算法」,演算法是解決特定問題的方法,在計算機領域裡需要將演算法用計算機能聽懂的語言描述給它聽,明白之後它才能使用運算能力解決問題。計算機能聽懂的語言,當然是程序代碼了(還需編譯器將代碼翻譯成2進位流),也就是一系列的指令。

再看「時間」,此處的時間指執行演算法消耗的時間,也就是計算機執行前面所說的一系列指令的時間。這個時間受

  • 計算機執行每條指令的速度-&>硬體層面
  • 編譯產生的代碼質量-&>軟體層面
  • 演算法的好壞(演算法使用的策略)
  • 問題規模

的影響。在給定軟硬體環境下,其實就是你在自己電腦上寫演算法的時候,演算法執行時間只受演算法本身的好壞和要處理的問題的規模影響。這樣就將4個影響因素減少為2個,簡化了問題

既然執行時間受演算法好壞和問題規模n的影響,那麼執行時間就是它倆的函數。

那這個函數到是啥樣的呢?

我們繼續分析,給定問題規模n之後,優秀的演算法可能哐哐哐執行幾次就搞定了,一般的演算法可能吭哧吭哧執行很多很多次才搞定;當給定演算法時,問題規模n很小時,可能執行幾次就搞定,而n很大時,就得執行很多次了。所以演算法優劣和問題規模n改變時,執行次數(基本操作數)將改變,所以執行次數就是演算法優劣和問題規模n的函數。

到此為止,我們得出一個簡單的結論:演算法執行時間可以用執行次數表示

世界豐富多彩,同一個問題有不同的解決辦法,比如餓了,可以吃米粉也可以吃包子,可以一個人吃也可以一個人吃(hh)。對應演算法領域,同一個問題也可以用不同的演算法解決,既然這樣,那不同的演算法之間肯定有優劣之分,如何評價呢?

最簡單的評價方法是把兩個演算法拉過來比誰解決問題的速度快,誰的執行時間短,誰的執行次數少。

給定演算法時,執行次數變為問題規模n的函數。比如一個演算法是a,一個演算法是b,問題規模為n,那麼執行次數分別為Ca=f(n),Cb=g(n)。現在將比較兩個演算法的執行次數的問題轉換為比較兩個函數f(n),g(n)的問題。那比較兩個函數的什麼性質呢?當然是比較隨著問題規模n增大,執行次數的增加情況,也就是f(n),g(n)的增長情況。好比讓兩個人吃一百個包子,一個人一小時吃完,一個人一分鐘吃完,明顯一分鐘吃完的那個人能力強,效率高,吃法(演算法)更先進(hh)。

要比較兩個函數的增長情況,最好的辦法是比較函數的一階導,這樣最精確,但是考慮到很多時候只需要大體了解演算法的優劣就可以了,所以我們就直接考察對增長速度影響最大的一項,這一項就是函數的最高階數。為了說明最高階數對函數增長影響最明顯,我們看兩幅圖。

圖中4條曲線分別表示4種不同的執行次數表達式,從圖中可以看出,只要最高項的階數相同,4種表達式值受其他項的影響很小,隨著n增大,幾乎可以忽略不計,甚至可以忽略與最高項相乘的常數。

既然可以只考慮最高項的階數,以簡化問題,達到估算的目的,為何不這樣做呢?

那總得給這種情況一個恰當的表示方式吧?和其他領域一樣,還得用符號來表示,這個符號就是大名鼎鼎的大O

T(n)=O(f(n))

其中,T(n)就是演算法的時間複雜度;

f(n)表示執行與演算法優劣和問題規模有關的執行數;

O()表示一種運算符號,和+-*/類似。作用就是去除其他項,包括與最高項相乘的常數,只保留最高項,比如f(n)=2n^2+1,O(f(n))=O(n^2)

好了,我們已經推導出了時間複雜度的大O表示,應該對演算法時間複雜度有個不錯的認識了吧。

最後回顧一下推導過程:

演算法執行時間-&>4種因素影響-&>去除軟硬體因素,考慮演算法優劣和問題規模n兩種因素-&>演算法執行次數-&>簡化:忽略其他項-&>大O表示法表示演算法時間複雜度

PS:文章里有許多自己的思考,初學演算法,不一定完全正確。


時間複雜度就是執行時間隨問題規模擴大而增大的規律,具體的數學定義查查就知道了。不過,我不知道樓主到底想知道什麼。


可以近似認為對某一規模為n的輸入數據,執行運算的次數


因為剛好對這個問題有點興趣,藉此表達一點個人拙見,出於定義考慮:定義原文 "存在有正常數C、n0,使得當n≧n0時都有0≦T(n)≦C*f(n),則T(n)=O(f(n))。" 這表明演算法經歷過程這個時間複雜度是一個近似擬合過程,類似於數理統計里的線性相關、線性回歸之類的,有的演算法可以清晰地表示(執行n次)有的只能表以抽象性,於是就以一種模式加以標籤。


時間複雜度,其實只是一個度量,不是真正的運行時間的投影。

也就是說只是給了你一個尺子去量一下這個演算法的耗時,不是這個演算法實現以後真的會耗時多少,也不是兩個不同的演算法的耗時比例真的可以這麼比。

時間複雜度的定義是以一個演算法基本操作重複執行的次數來作為度量單位。

先找出演算法的基本操作,然後根據相應的各語句確定它的執行次數 ,再把這個次數的隨著演算法的初始值數量擴大產生的變化歸納為一個函數。

假設初始值的數量是n,執行次數的結果是T(n)

如果執行次數始終是初始值數量的一倍,比如有序遍歷一次,那麼就是
T(n) =n

如果執行次數始終是初始值數量*初始值數量,那麼就是
T(n) =n^{2}

不知道我說清楚沒有


問對人了,看我的總結:演算法複雜度分析概要

第一部分辨析了 O,Ω,Θ 三種符號的區別以及它們的性質。
第二部分介紹了兩種常用的計算時間複雜度方法,即主定理和分治法。


基本上數循環次數就能看出時間複雜度來。對於複雜的,還需要一些數學知識,級數什麼的。knuth那套經典的taocp注重分析,我覺得實際上沒必要分析得如此精確。推薦看那套書——《計算機成學設計藝術》。對於空間複雜度,好像比較簡單,直接看要存下多少東西就行了,主要是map,set,vector之類容器內東西的多少,以及數組的大小等。還是那總感覺對於實際程序,可能沒必要那麼精確。

也可以看一些計算複雜性之類的內容。


O(n),這裡的n是說問題規模,也就是函數參數,如果n是單值,那麼把n無限加大,如果n代表集合,那麼把集合元素數目無限加大,這就是問題規模的意思。

結果是估算的,平均每次執行函數調用的大致時間

比如一個函數,總是把參數除以2,既:n/2,折半查找的核心步驟就是這個。那麼最終函數運行時間,應該是:log n,也就是O(log n)


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

int aFunc(void) {
printf("Hello, World!
"); // 需要執行 1 次
return 0; // 需要執行 1 次
}

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

int aFunc(int n) {
for(int i = 0; i&

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

我們把 演算法需要執行的運算次數 用 輸入大小n 的函數 表示,即 T(n) 。

此時為了 估算演算法需要的運行時間 和 簡化演算法分析,我們引入時間複雜度的概念。

定義: 存在常數 c,使得當 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) 。

可以參考 十分鐘從零搞定時間複雜度


推薦閱讀:

如何將一個[10^3,10^15]上的整數儘可能多地表示成n個互不相同的因數乘積?
有隨著n規模增大,所用時間反而減小的演算法么?
如何證明 a-b≤ a xor b ?

TAG:演算法 | 演算法設計 |