1. 程式人生 > >時間複雜度和空間複雜度及其計算方法詳解

時間複雜度和空間複雜度及其計算方法詳解

在電腦科學中,演算法的時間複雜度是一個函式,它定量地描述了一個演算法的執行時間。時間複雜度常用一個大 O 符號(不是零)來表示,不包括這個函式的低階項和首項係數。

時間複雜度是漸近的,考慮的是這個值趨於無窮時的情況。比如一個演算法的執行時間為 3n2+2n+3,這裡我們用大 O 符號來表示時,不考慮低階項,也就是隻考慮最高階項 3n2,也不考慮首項的係數,所以我們會直接將這個演算法的時間複雜度表示為 O(n2)。

一般我們在計算時間複雜度時,需要考慮演算法是否會有多重巢狀迴圈(即程式碼中包含的迴圈內部還有一個迴圈操作),因為巢狀迴圈勢必會使時間複雜度升階。而對於一個列表進行迴圈有限次數的操作,則無須考慮,因為我們會忽略首項的係數。

我們在計算一個演算法的時間複雜度時,首先需要找出演算法的核心部分,然後根據程式碼確認時間複雜度。

一般的時間複雜度按照效能從差到好有這麼幾種:O(n3
)、O(n2)、O(nlogn)、O(n)、O(logn)、O(1)。當然,效能差的情況可能還有 O(n4) 甚至更高的冪數,但是當演算法的時間複雜度達到 O(n2) 以上時,效能就會相當差,我們應該尋找更優的方案。當然,對於某些比較特殊的演算法,可能最優的效能也不會很好。

另外,O(nlogn)、O(logn) 內部的內容在數學裡是錯誤的,一般應該是 log2n 等,但是這裡的係數並不在我們的考慮範圍之內,所以我們一般在計算複雜度時直接將其表示為 O(nlogn) 和 O(logn)。

下面我們看看這段程式碼示例。
for(int I = 0; i < n; i++){
        //some code here
    for(int j=0; j < n; j++){
        //some code here
        for(int k=0; k<n; k++){
            //some code here;
        }
    }
}
這段程式碼是個三重巢狀迴圈程式碼(且每重迴圈都執行了完整的 n 遍),n 一般指演算法的規模,很容易推斷出這段程式碼的時間複雜度是 O(n3)。

所以如果是兩重的巢狀迴圈,那麼時間複雜度是 O(n2);如果只有一重迴圈,那麼時間複雜度是 O(n)。什麼時候會出現 O(nlogn) 呢?我們接著看一段程式碼。
for(int i =0; i < n; i++){
    //some code here
    for(j=i; j < n; j++){
        //some code here
    }
}
我們發現,在內層迴圈中 j 的起始量是 i,而隨著每次外層迴圈i的增加,j 的一層迴圈執行的次數將會越少。對於這種情況,我們把時間複雜度稱為 O(nlogn)。

一般我們把下面這段程式碼的時間複雜度稱為 O(logn) 的時間複雜度,並將這種情況稱為對數階,效能要優於 O(n)。
for(int i=0; i < n; i*=2){
    //some code here
}
效能最好的演算法的時間複雜度為 O(1),也就是在執行有限次的操作之後達到目標。比如一些計算型別的程式碼或者交換值的程式碼等。
int a = 10;
int b = 100;
//1.計算
int sum = a * b;
//2.交換
int temp = a;
int a = b;
int b = temp;
當然,一個演算法能不能達到 O(1) 的時間複雜度,要看具體情況,我們當然希望程式的效能能夠達到最優,所以演算法的時間複雜度能夠低於 O(n2) 一般來說已經很不錯了。不要忘了,演算法的效能除考慮時間複雜度外還要考慮空間複雜度,在大多數情況下往往需要在時間複雜度和空間複雜度之間進行權衡。

我們在上面提到的情況都只有一個規模引數,有時規模引數也可能有兩個。比如兩層巢狀迴圈的規模不一樣,我們假設分別為 m 和 n,這時我們一般會把時間複雜度寫為 O(m×n),但是我們自己需要明確,如果 m 和 n 非常相近,則這個時間複雜度趨於 O(n2);如果 m 通常比較小(也就是說我們能夠明白 m 的範圍是多少),則這個時間複雜度趨於 O(n)。在這兩種情況下,雖然時間複雜度都是 O(m×n),但是真實的時間複雜度可能相差很大。

實際上,一個演算法的執行時間是不可能通過我們的計算得出的,必須到機器上真正執行才能知道,而且每次的執行時間不一樣。但是我們沒必要將每個演算法都到機器上執行和測試,並且對於很多演算法,我們通過簡單的分析就能知道其效能的好壞,而沒有必要詳細地寫出來,所以時間複雜度的計算還是非常有用的。

時間複雜度其實還分為平均時間複雜度、最好時間複雜度和最壞時間複雜度。對於一個演算法來說,往往有很多特殊情況,一般而言,我們所說的時間複雜度都指最壞時間複雜度,因為在最壞的情況下,我們才能夠評估一個演算法的效能最差會到什麼地步,這樣我們才能更好地選擇相應的演算法去解決問題。

空間複雜度

其實我們在做演算法分析時,往往會忽略空間複雜度,可能是因為現在計算機的空間已經越來越便宜了,成本很低,而一臺計算機的 CPU 的效能始終很難得到太大的提升。但是空間複雜度作為另一個演算法效能指標,也是我們需要掌握的,這能夠讓程式在時間和空間上都得到優化,成為一個好的演算法。

空間複雜度的表示其實和時間複雜度是一樣的,都用大 O 符號表示。空間複雜度是對一個演算法在執行過程中所消耗的臨時空間的一個度量。

空間複雜度的計算方式和時間複雜度一樣,也不包括這個函式的低階項和首項係數。

一般我們認為對於一個演算法,本身的資料會消耗一定的空間,可能還需要一些其他空間,如果需要的其他空間是有限的,那麼這個時間複雜度為 O(1)。相對地,也有 O(n)、O(nlogn)、O(n2)。

在學會了時間複雜度的相關內容之後,學習空間複雜度其實就沒有什麼難點了,對於更多的內容,我們會通過後面的演算法慢慢地瞭解。