跳轉至

2.5   小結

1.   重點回顧

演算法效率評估

  • 時間效率和空間效率是衡量演算法優劣的兩個主要評價指標。
  • 我們可以透過實際測試來評估演算法效率,但難以消除測試環境的影響,且會耗費大量計算資源。
  • 複雜度分析可以消除實際測試的弊端,分析結果適用於所有執行平臺,並且能夠揭示演算法在不同資料規模下的效率。

時間複雜度

  • 時間複雜度用於衡量演算法執行時間隨資料量增長的趨勢,可以有效評估演算法效率,但在某些情況下可能失效,如在輸入的資料量較小或時間複雜度相同時,無法精確對比演算法效率的優劣。
  • 最差時間複雜度使用大 \(O\) 符號表示,對應函式漸近上界,反映當 \(n\) 趨向正無窮時,操作數量 \(T(n)\) 的增長級別。
  • 推算時間複雜度分為兩步,首先統計操作數量,然後判斷漸近上界。
  • 常見時間複雜度從低到高排列有 \(O(1)\)\(O(\log n)\)\(O(n)\)\(O(n \log n)\)\(O(n^2)\)\(O(2^n)\)\(O(n!)\) 等。
  • 某些演算法的時間複雜度非固定,而是與輸入資料的分佈有關。時間複雜度分為最差、最佳、平均時間複雜度,最佳時間複雜度幾乎不用,因為輸入資料一般需要滿足嚴格條件才能達到最佳情況。
  • 平均時間複雜度反映演算法在隨機資料輸入下的執行效率,最接近實際應用中的演算法效能。計算平均時間複雜度需要統計輸入資料分佈以及綜合後的數學期望。

空間複雜度

  • 空間複雜度的作用類似於時間複雜度,用於衡量演算法佔用記憶體空間隨資料量增長的趨勢。
  • 演算法執行過程中的相關記憶體空間可分為輸入空間、暫存空間、輸出空間。通常情況下,輸入空間不納入空間複雜度計算。暫存空間可分為暫存資料、堆疊幀空間和指令空間,其中堆疊幀空間通常僅在遞迴函式中影響空間複雜度。
  • 我們通常只關注最差空間複雜度,即統計演算法在最差輸入資料和最差執行時刻下的空間複雜度。
  • 常見空間複雜度從低到高排列有 \(O(1)\)\(O(\log n)\)\(O(n)\)\(O(n^2)\)\(O(2^n)\) 等。

2.   Q & A

Q:尾遞迴的空間複雜度是 \(O(1)\) 嗎?

理論上,尾遞迴函式的空間複雜度可以最佳化至 \(O(1)\) 。不過絕大多數程式語言(例如 Java、Python、C++、Go、C# 等)不支持自動最佳化尾遞迴,因此通常認為空間複雜度是 \(O(n)\)

Q:函式和方法這兩個術語的區別是什麼?

函式(function)可以被獨立執行,所有參數都以顯式傳遞。方法(method)與一個物件關聯,被隱式傳遞給呼叫它的物件,能夠對類別的例項中包含的資料進行操作。

下面以幾種常見的程式語言為例來說明。

  • C 語言是程序式程式設計語言,沒有物件導向的概念,所以只有函式。但我們可以透過建立結構體(struct)來模擬物件導向程式設計,與結構體相關聯的函式就相當於其他程式語言中的方法。
  • Java 和 C# 是物件導向的程式語言,程式碼塊(方法)通常作為某個類別的一部分。靜態方法的行為類似於函式,因為它被繫結在類別上,不能訪問特定的例項變數。
  • C++ 和 Python 既支持程序式程式設計(函式),也支持物件導向程式設計(方法)。

Q:圖解“常見的空間複雜度型別”反映的是否是佔用空間的絕對大小?

不是,該圖展示的是空間複雜度,其反映的是增長趨勢,而不是佔用空間的絕對大小。

假設取 \(n = 8\) ,你可能會發現每條曲線的值與函式對應不上。這是因為每條曲線都包含一個常數項,用於將取值範圍壓縮到一個視覺舒適的範圍內。

在實際中,因為我們通常不知道每個方法的“常數項”複雜度是多少,所以一般無法僅憑複雜度來選擇 \(n = 8\) 之下的最優解法。但對於 \(n = 8^5\) 就很好選了,這時增長趨勢已經佔主導了。