複雜度

複雜度

複雜度分析

  1. 數據結構和算法的目標:快、省,即執行效率和資源消耗。
  2. 「過後統計法」具備很大侷限性,提早預估效率很重要。
  3. 複雜度分析是學習算法的精髓和分析算法的利器。

時間複雜度

  1. 假設每行代碼執行時間意義,全部代碼的執行時間 T(n) 和每行代碼的執行次數 n 成正比。
T(n) = O (f(n))
  1. 大O時間複雜度表示代碼執行效率隨數據規模增加的變化趨勢,也叫漸進時間複雜度
  2. 當n很大時,低階、常量、係數並不左右增加趨勢,能夠省略,只須要記錄最大量級。
  1. 只關注循環次數最多的一段代碼.
  2. 加法法則:總複雜度等於量級最大的那一段代碼。
  3. 乘法法則:嵌套代碼的複雜度等於內外代碼複雜度之積。
  1. 常見覆雜度量級
    1. 非多項式量級:\(O(2^n),O(n!)\),稱爲NP算法,效率較低。
    2. 多項式量級:\(O(1),O(logn),O(n),O(nlogn),O(n^k)\)

\(O(1)\)算法

int i = 1 ;
int j = 2 ;
int sum = i + j ;

常量級時間複雜度的表示方法,即使有 3 行,也是 O(1) ,並不是 O(3) 。通常狀況下,只要不存在循環、遞歸,複雜度都爲 O(1) ,與代碼量無關。數組

\(O(logn)、O(nlogn)\)數據結構

int i = 1 ;
while ( i <= n )
{
    i = i * 2 ;
}

這段代碼的複雜度爲 \(O(log_2n)\),不一樣底數的對數能夠互相轉換,係數能夠省略,因此統一表示爲 \(O(logn)\)
\(O(nlogn)\) 表示把 \(O(logn\)) 的代碼循環執行n遍。數據結構和算法

\(O(n+m)、O(n*m)\)學習

T1(n) + T2(m) = O (f(n) + g(m))
T1(n) * T2(m) = O (f(n) * g(m))

當有多個數據規模,表示複雜度時不能省略。spa


空間複雜度

  1. 大 O 空間複雜度表示代碼存儲空間隨數據規模增加的變化趨勢,也叫漸空間間複雜度
void print(int n) 
{
    int i = 0;
    int[] a = new int[n];
    for (i; i <n; ++i) 
    {
        a[i] = i * i;
    }
}
  1. int[] a = new int[n]申請了大小爲 n 的 int 類型數組,忽略常量階的空間申請,因此上述代碼空間複雜度爲 O(n)。
  2. 常見的空間複雜度有\(O(1),O(n),O(n^2)\);

最好、最壞狀況時間複雜度

public int find(int[] array,int n,int x)
{
    int p = -1 ;
    for(int i = 0 ; i < n ; ++i)
    {
        if(array[i] == x)
        {
            p = i ;
            break ;
        }
    }
    return p ;
}
/**
    上述代碼的做用是返回數組中 x 出現的位置。
    最好:第一個就是要找-> O(1)
    最壞:遍歷整個數組沒有找到該元素-> O(n)
**/
  1. 最好狀況時間複雜度

最理想狀況下,執行這段代碼的時間複雜度。code

  1. 最壞狀況時間複雜度

最糟糕狀況下,執行這段代碼的時間複雜度。遞歸

平均時間複雜度

分析上述代碼,在長度爲n的數組中查詢x的位置,有 n+1 種狀況,分別爲數組的 0 到 n-1 位置上和不在數組中,把每種狀況下,須要遍歷的元素個數累加起來,除以 n+1 ,獲得須要遍歷元素個數的平均值。資源

\[ \frac{1+2+3+...+n+n}{n+1}\quad=\quad\frac{n(n+3)}{2(n+1)} \]

忽略常量、係數、低階,簡化後時間複雜度爲O(n);結果雖然正確,可是這樣計算沒有考慮每種狀況發生的機率,正確計算過程以下:
\[ 1*\frac{1}{2n}+2*\frac{1}{2n}+...+n*\frac{1}{2n}+n*\frac{1}{2}=\frac{3n+1}{4} \]
此結果是加權平均值,平均時間複雜度即加權平均時間複雜度,簡化後得 O(n)。

均攤時間複雜度

int[] array = new int[n];
int count = 0;
void insert(int val) 
{
  if (count == array.length) 
  {
    int sum = 0;
    for (int i = 0; i < array.length; ++i) 
    {
      sum = sum + array[i];
    }
    array[0] = sum;
    count = 1;
  }
  array[count] = val;
  ++count;
}

這段代碼實現了一個往數組中插入數據的功能。當數組滿了以後,即 count == array.length 時,用 for 循環遍歷數組求和,將求和以後的 sum 值放到數組的第一個位置,而後從第二個 位置開始插入數據。若是數組一開始就有空閒空間,則直接將數據插入數組

分析以上代碼,得出:

  1. 最好狀況時間複雜度:有空閒位置,直接插入下標爲 count 的位置,複雜度爲 O(1)
  2. 最壞狀況時間複雜度:遍歷數組,複雜度爲 O(n)
  3. 加權平均時間複雜度:有空閒位置 n 種狀況,不空閒 1 種,機率都爲\(\frac{1}{n+1}\),加權平均時間複雜度爲 O(1)

\[ 1*\frac{1}{n+1}+1*\frac{1}{n+1}+...+n*\frac{1}{n+1}=>O(1) \]

  1. 均攤時間複雜度是用攤還分析法得出的:

    例如上述代碼中,每一次 O(n) 的插入操做,都會緊跟 n-1 個 O(1) 的插入操做,把耗時多的操做均攤到接下來 O(1) 的操做上,那麼這一組操做的均攤時間複雜度就是 O(1)

    對一個數據結構進行一組連續操做中,大部分狀況下時間複雜度都很低,只有個別狀況下時間複雜度比較高,並且這些操做之間存在先後連貫的時序關係,這個時候,咱們能夠將這一組操做放在一塊兒分析,看是否能將較高時間複雜度那次操做的耗時,平攤到其餘那些時間複雜度比較低的操做上。

    在可以應用均攤時間複雜度分析的場合,通常均攤時間複雜度就等於最好狀況時間複雜度

相關文章
相關標籤/搜索