淺談時間複雜度

shimingxin1007發表於2024-07-16

時間複雜度

定義

衡量一個演算法的快慢,一定要考慮資料規模的大小。所謂資料規模,一般指輸入的數字個數、輸入中給出的圖的點數與邊數等等。一般來說,資料規模越大,演算法的用時就越長。而在演算法競賽中,我們衡量一個演算法的效率時,最重要的不是看它在某個資料規模下的用時,而是看它的用時隨資料規模而增長的趨勢,即 時間複雜度

​ ——OI Wiki

在演算法中,使用時間複雜度來衡量程式執行時間的多少。每條語句執行的次數稱為該語句的頻度,整段程式碼的總執行次數則稱為整段程式碼的頻度。在演算法競賽中,由於輸入可以在給定的資料範圍內任意給定,我們為保證演算法能夠透過某個資料範圍內的任何資料,一般考慮最壞時間複雜度。

下面,我將用更通俗易懂的語言,解釋時間複雜度。

分析

在推導的時候,我們應該採用無限大的思想來簡化大 \(O\) 表示式,具體如下:

1.用常數 \(1\) 代替執行時間中的所有加法的常數。無論常數是多少都使用 \(1\) 替換。因為執行函式和問題規模 \(n\) 的大小無關,它是執行時間恆定的,像時間複雜度為\(O(1)\) 的又被稱作常數階。如:執行 \(100\)\(1 \to n\) 的迴圈,這份程式碼的時間複雜度為 \(O(n)\) 而並非 \(O(100n)\)

2.如果最高階項存在且係數不為 \(1\),則去除掉與這個項相乘的係數,例如 \(O(2n^2)\) 係數為 \(2\),直接簡化為 \(O(n^2)\)

3.如果表示式有多項含有無限大變數的式子,只保留一個擁有指數最高的變數的式子。如 \(O(2n^2+2n)\) 應簡化為 \(O(n^2)\)

經過上面三個步驟推到出來的結果就是演算法對應的大 \(O\) 階。

注意,在遇到類似於 \(O(2n+m)\) 的時間複雜度時,由於 \(n\)\(m\) 之間沒有聯絡,所以這份程式碼的時間複雜度應是 \(O(n+m)\)。在遇到類似於 \(O(n^2+nq)\) 的時間複雜度時,由於我們無法確定 \(n^2\)\(nq\) 的大小(\(n\)\(q\) 的大小不確定),因此這份程式碼的時間複雜度依然是 \(O(n^2+nq)\)

舉例:

  • \(O(12)\to O(1)\)

  • \(O(2n+3)\to O(n)\)

  • \(O(3n^2+2n+1)\to O(n^2)\)

  • \(O(5\log_2 n+20)\to O(\log n)\)

  • \(O(2n+3n\log_2 n+19)\to O(n\log n)\)

  • \(O(6n^3+2n^2+3n+4)\to O(n^3)\)

  • \(O(2^n+n^4)\to O(2^n)\)

Another

哪些數是常數?

我們曾提到,“用常數 \(1\) 代替執行時間中的所有加法的常數”,那麼,什麼是常數呢?

我們一個例子看起。

const int n=114514;
for (int i=1;i<=n;i++){
    cout<<"hello world\n";
}

程式碼中,\(MAXN\) 雖然是一個變數,但是它的值是固定的,所以它不被看做輸入資料規模,所以程式碼的時間複雜度是 \(O(1)\) 而不是 \(O(n)\)

進行時間複雜度計算時,哪些變數被視作輸入規模是很重要的,而所有和輸入規模無關的量都被視作常量,計算複雜度時可當作 \(1\) 來處理。

相關文章