1. 程式人生 > >資料結構與算法系列二(複雜度分析)

資料結構與算法系列二(複雜度分析)

1.引子

1.1.為什麼要學習資料結構與演算法?

有人說,資料結構與演算法,計算機網路,與作業系統都一樣,脫離日常開發,除了面試這輩子可能都用不到呀!

有人說,我是做業務開發的,只要熟練API,熟練框架,熟練各種中介軟體,寫的程式碼不也能“飛”起來嗎?

於是問題來了:為什麼還要學習資料結構與演算法呢?

#理由一:
    面試的時候,千萬不要被資料結構與演算法拖了後腿
#理由二:
    你真的願意做一輩子CRUD Boy嗎
#理由三:
    不想寫出開源框架,中介軟體的工程師,不是好廚子

1.2.如何系統化學習資料結構與演算法?

我想好了,還是需要學習資料結構與演算法。但是我有兩個困惑:

1.如何著手學習呢?

2.有哪些內容要學習呢?

學習方法推薦:

#學習方法
1.從基礎開始,系統化學習
2.多動手,每一種資料結構與演算法,都自己用程式碼實現出來
3.思路更重要:理解實現思想,不要背程式碼
4.與日常開發結合,對應應用場景

學習內容推薦:

資料結構與演算法內容比較多,我們本著實用原則,學習經典的、常用的資料結構、與常用演算法

#學習內容:
1.資料結構的定義
2.演算法的定義
3.複雜度分析
4.常用資料結構
    陣列、連結串列、棧、佇列
    散列表、二叉樹、堆
    跳錶、圖
5.常用演算法
    遞迴、排序、二分查詢
    搜尋、雜湊、貪心、分治
    動態規劃、字串匹配

2.考考你

在開篇中提到了複雜度分析,與大O表示法的概念。具體要如何進行復雜度分析,以及大O表示法的公式推導,我們在這一篇詳細來看。

#考考你:
1.你知道大O表示法,公式是如何來的嗎?
2.你知道時間複雜度分析的常用原則嗎?
3.你知道常見覆雜度的度量級嗎?

3.案例

3.1.大O表示法公式推導

3.1.1.案例程式碼

我們根據以下案例程式碼推導大O表示法的公式。程式碼很簡單,有沒有?

// 傳入引數n,求解1..n的累加和
public int sum(int n){
    int sum = 0;
    for(int i = 1; i <= n; i++){
        sum += i;
    }
    
    return sum;
}

3.1.2.推導過程

簡述:

1.對於程式程式碼中的每一行程式碼,從cpu的角度來看,執行的時候都有:讀資料->運算->寫資料過程

2.我們假定每一行程式碼的執行時間都相同,都是一個單位時間:unit_time

3.那麼sum方法中,程式碼執行的總時間是多少呢

1 public int sum(int n){
2    int sum = 0;// 第二行程式碼執行,需要1 個unit_time
3    for(int i = 1; i <= n; i++){// 第三行程式碼執行,需要n 個unit_time
4        sum += i;// 第四行程式碼執行,需要n 個unit_time
5    }   
6    return sum;// 第五行、第六行程式碼暫時忽略,不影響
7 }

4.根據3推導,sum方法的總執行時間是:

T(n)=(n + n + 1) * unit_time = (2n +1) * unit_time=O(f(n))

5.結論:所有程式碼的執行時間T(n),與每行程式碼的執行次數成正比

6.提取出公式即:T(n) = O(f(n))

#公式解讀:
 T(n):代表程式碼執行時間
 n:代表資料規模
 f(n):代表每行程式碼執行的次數總和
 O:表示程式碼執行時間T(n),與程式碼執行次數f(n)成正比

7.這就是大O表示法的公式來源,表示程式碼的執行時間T(n),與程式碼的執行次數f(n)成正比

8.進一步理解:

1.大O表示法:時間複雜度,表示資料規模n的增長,與演算法執行時間的增長趨勢
2.大O表示法:空間複雜度,表示資料規模n的增長,與演算法儲存空間的增長趨勢

 

3.1.3.約定

大O表示的公式,以及含義我們已經推匯出來了。它表示的是資料規模n的增長,與演算法執行時間,或者儲存空間的增長趨勢。這裡需要關注兩個字:趨勢。

根據常理我們知道,常量、係數、低階不會影響趨勢,因此在實際複雜度分析中,往往忽略常量、係數、低階。

那麼上面案例的時間複雜度大O表示法公式,省略係數、常量:

可以從:T(n) = O(f(n)) = O(2n+1)

簡化成:T(n) = O(n)

3.2.時間複雜度案例

在複雜度分析中,有時間複雜度分析和空間複雜度分析。它們是從兩個維度來衡量演算法的優劣。實際分析方式類似,我們以時間複雜度分析為例。

時間複雜度分析,有幾個基本的原則,你都知道嗎?

#時間複雜度分析基本原則
1.只關注迴圈次數最多的程式碼
2.加法法則:總複雜度等於量級最大的那段程式碼的複雜度
3.乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積

3.2.1.只關注迴圈次數最多的程式碼

案例程式碼:

1 public int sum(int n){
2    int sum = 0;// 第二行程式碼執行,需要1 個unit_time
3    for(int i = 1; i <= n; i++){// 第三行程式碼執行,需要n 個unit_time
4        sum += i;// 第四行程式碼執行,需要n 個unit_time
5    }   
6    return sum;// 第五行、第六行程式碼暫時忽略,不影響
7 }

複雜度分析:

1.這裡的原則:只關注迴圈次數最多的程式碼

2.第二行程式碼執行,需要1 個unit_time

3.第三行程式碼執行,需要n 個unit_time

4.第四行程式碼執行,需要n 個unit_time

5.第五行、第六行程式碼暫時忽略,不影響

 

6.通過以上分析,第三行、第四行程式碼迴圈執行次數最多:n。因此時間複雜度為:O(n)

3.2.2.加法法則

簡述:

加法法則:總複雜度等於量級最大的那段程式碼的複雜度

案例程式碼:

public int sum(int n){
    
    // 第一段程式碼
    int sum_1 = 0;
    for(int i=1; i< 100; i++){
        sum_1 += i;
    }
    
    // 第二段程式碼
    int sum_2 = 0;
    for(int j = 1; j <= n; j++){
        sum_2 += j;
    }
    
    // 第三段程式碼
    int sum_3 = 0;
    for(int k = 1; k <= n;k++){
        for(int h = 1; h <= n; h++){
            sum_3 += k * h
        }
    }
    
    return sum_1 + sum_2 + sum_3;
    
}

複雜度分析:

1.在sum方法中有兩段程式碼

2.第一段程式碼,複雜度是:O(100)

3.第二段程式碼,複雜度是:O(n)

4.第三段程式碼,複雜度是:O(n^2)

5.總複雜度是:O(100) + O(n) +O(n^2)

6.根據加法法則,最終複雜度是:O(n^2)

3.2.3.乘法法則

簡述:

乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積

案例程式碼:

public int sum(int n){
     int sum_1 = 0;
    for(int i=1; i< n; i++){// 第一層迴圈
        sum_1 += multi(i) ;// multi方法中,有第二層迴圈
    }
}

public int multi(int n){
    int multi_1 = 0;
    for(int i = 1; i<= n; i++){// 第二層迴圈
        multi_1 *= i;
    }
    
    return multi_1;
}

複雜度分析:

1.在sum方法中,有第一層迴圈:for(int i=1; i< n; i++){

2.在sum方法中,呼叫multi方法

3.在multi方法中,有第二層迴圈:for(int i = 1; i<= n; i++){

4.根據乘法法則,總時間複雜度等於,第一層迴圈,乘以第二層迴圈

5.因此總時間複雜度是:O(n*n) = O(n^2)

4.討論分享

#考考你答案:
1.你知道大O表示法,公式是如何來的嗎?
 1.1.參考【3.1.大O表示法公式推導】
 
2.你知道時間複雜度分析的常用原則嗎?
 2.1.只關注迴圈次數最多的程式碼
 2.2.加法法則:總複雜度等於量級最大的那段程式碼的複雜度
 2.3.乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積
 
3.你知道常見覆雜度的度量級嗎?
 3.1.常數階:O(1)
 2.2.對數階:O(logn)
 2.3.線性階:O(n)
 2.4.線性對數階:O(nlogn)
 2.5.平方階:O(n^2)
 2.6.立方階:O(n^3)

&n