1. 程式人生 > >[資料結構與演算法]-常見演算法時間複雜度(程式執行時間)計演算法則

[資料結構與演算法]-常見演算法時間複雜度(程式執行時間)計演算法則

本文歡迎轉載,轉載前請聯絡作者,經允許後方可轉載。轉載後請註明出處,謝謝! http://blog.csdn.net/colton_null 作者:喝酒不騎馬 Colton_Null from CSDN

一.引言

演算法(Algorithm)是為求解一個問題需要遵循的、被清楚指定的簡單指令的集合。估算分析演算法所消耗的資源是一個理論問題,所以需要一套規範一套系統架構在幫助我們分析。

二.四個定義

定義1:如果存在正常數c和n0使得當N≥n0時,T(N)≤cf(N),則記為T(N)=O(f(N))。
定義2:如果存在正常數c和n0使得當N≥n0時,T(N)≥cf(N),則記為T(N)=Ω(g(N))。
定義3:T(N)=θ(h(N))當且僅當T(N)=O(h(N))和T(N)=Ω(h(N))。
定義4:如果對每一正常數c都存在常數n0使得當N>n0時T(N) <
cp(N),則T(N)=o(p(N))。有時也可以說,如果T(N)=O(p(N))且T(N)≠θ(p(N)),則T(N)=o(p(N))。

解釋:
定義1:總會存在某個點n0從它以後cf(N)最小與T(N)一樣大,如果忽略常數因子c則,則說明f(N)最小與T(N)一樣大。也就是說T(N)的增長率小於等於f(N)的增長率。例如,T(N)=100N,f(N)=N³,則c為1,n0為10,所以100N=O(N³)(N的立方級)。我們稱這種記法為大O標記法。
定義2:T(N)的增長率大於等於g(N)的增長率。
定義3:T(N)的增長率等於h(N)的增長率。
定義4:T(N)的增長率小於p(N)的增長率。與大O不同的是,沒有增長率相等的可能性。

三.執行時間計算原則

對與程式執行時間的運算,一般我們有兩種方式。一種是事後統計法,一種是事前分析估演算法。

對於前者,當然不是一個好方法。第一,如果想使用這個方法,我們需要把程式執行至少一次以上,進而統計時間,而在某些情況下,我們甚至沒有條件去執行某些程式。其次,這個方法的統計依賴硬體環境、配置等因素,對統計結果會產生一定的誤差。

所以,我們這裡採用後者,來對程式執行時間進行預估。為了簡化分析,我們通常採用如下的約定:不存在特定的時間單位。所以我們要摒棄前導的常數以及摒棄低階項。這麼做就是要計算大O執行時間。因為大O是一個上界(可以理解為最壞的情況),所以我們要分析最壞的情況,即不能低估程式的執行時間。程式可能提前結束,但是絕不可能比大O時間還要長。

四.常見法則

1.for迴圈

一個for迴圈的執行時間至多是該迴圈內部語句的執行時間乘以迴圈次數。

public void do1(int n) {
    for (int i = 0; i < n; i++) {
        num++;
    }
}

num++佔用2個單元時間,迴圈次數為n次,所以一共是2N個單元時間。摒棄前導常數,最後得到該方法的時間複雜度為O(N)。

2.巢狀for迴圈

從裡向外分析這些迴圈。迴圈內部的語句執行時間乘以所有for迴圈大小的乘積。

public void do2(int n) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            num++;
        }
    }
}

該方法的運時間為O(N²)。

3.順序語句

將各個語句執行時間求和。記得摒棄常數和低階項。

public void do3(int n) {
    for (int i = 0; i < n; i++) {
        num++;
    }
    num++;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            num++;
        }
    }
}

這個方法,總時間單元為2N + 2 + 2N*N。最終得到O(N²)。

4.if-else語句

一個if-else語句的執行時間為判斷時間加上條件分支中執行時間最長的那個分支的執行時間。雖然有些時候這麼估計會可能導致時間過長,但是根據我們提到的規約,決不能估計過低。

public void do4(int n) {
    if(num == 0) {
        for (int i = 0; i < n; i++) {
            num++;
        }
    }else {
        num++;
    }
}

由於num == 0的情況下的執行時間長於num++,所以該方法的執行時間為O(N²)。

5.對數計算

我們用經典的折半查詢例子來說明這種情況。

public boolean Search(int k) {
    int left = 0;// 左邊界變數  
    int right = N - 1;// 右邊界變數  
    int middle;// 中位數變數  
    while (left <= right) {
        middle = (left + right) / 2;
        if (k < data[middle]) {
            right = middle - 1;// 查詢前半段  
        } else if (k > data[middle]) {
            left = middle + 1;// 查詢後半段  
        } else if (k == data[middle]) {
            System.out.println("Data[" + middle + "] = " + data[middle]);
            return true;
        }
        count++;
    }
    return false;
}

迴圈從right - left = n - 1開始 ,並保持right - low ≥ -1。每次迴圈後right - left的值至少將該次迴圈前的值折半,所以迴圈次數最多為{log(N - 1)} + 2。其中{log(N - 1)}表示大於log(N - 1)的最小整數。所以,執行時間為O(logN)。

五.常見時間複雜度排序

常見的時間複雜度從小到大排序依次為:Ο(1)<Ο(logN)<Ο(N)<Ο(NlogN)<Ο(N²)<Ο(N³)<…<Ο(2^N)<Ο(N!)
這裡寫圖片描述

六.結果準確性分析

通常來說,有時分析的值會過大。如果是這樣,要麼就進一步細化分析,要不可能是平均執行時間顯著小於最壞的情形。對於許多複雜的演算法,最壞的情況是可以成立的,雖然它在實踐過程中通常過大。所以一般來說,最壞的情形就是最好的分析結果。

站在前人的肩膀上前行,感謝以下部落格及文獻的支援。
演算法的時間複雜度和空間複雜度-總結
用java實現折半查詢
《資料結果與演算法分析 機械工業出版社》