###### tags: `Notes` `Big-Oh` `algorithm` # 時間複雜度 ## [速查表](https://kknews.cc/zh-tw/tech/l3y4qng.html) ## [初學者學演算法|談什麼是演算法和時間複雜度](https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E8%AB%87%E4%BB%80%E9%BA%BC%E6%98%AF%E6%BC%94%E7%AE%97%E6%B3%95%E5%92%8C%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6-b1f6908e4b80) ### 演算法 演算法的簡單定義式:輸入 + 演算法 = 輸出 ![](https://miro.medium.com/max/1000/1*85eZc1aesbrPA3Rj55dVHw.jpeg) ### 時間複雜度 大 O 符號是用來描述一個演算法在輸入 n 個東西時,總執行時間與 n 的關係。 ![](https://miro.medium.com/max/700/1*LiDirYGz4qCHDflA_j0zsA.jpeg) ![](https://miro.medium.com/max/700/1*MYETv-_QFW2hMBAWbvnKAw.jpeg) ## [初學者學演算法|從時間複雜度認識常見演算法](https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E6%8E%92%E5%BA%8F%E6%B3%95%E5%85%A5%E9%96%80-%E9%81%B8%E6%93%87%E6%8E%92%E5%BA%8F%E8%88%87%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E6%B3%95-23d4bc7085ff) ### 常見的六種時間複雜度與演算法 |Big Oh|演算法| |-|:-:| |O(1)|陣列讀取| |O(n)|簡易搜尋| |O(log n)|二分搜尋| |O(n²)|選擇排序法<Br>插入排序法| |O(n logn)|合併排序法| |O(2^n)|費波那契數列| ### O(1):陣列讀取 時間複雜度為 O(1) 的演算法,代表著不管你輸入多少個東西,程式都會在同一個時間跑完。 在程式設計中,最簡單的例子就是讀取一個陣列中特定索引值的元素。 ![](https://miro.medium.com/max/2000/1*x6F0ENO7N8GsqHfxf-80OA.jpeg) ```python= Pokemons = ["卡丘","胖丁","尼龜","比獸","呆獸","種子","小剛"] print(Pokemons[n]) >> "卡丘" ``` ### O(n):簡易搜尋 時間複雜度為 O(n) 的演算法,代表著執行步驟會跟著輸入 n 等比例的增加。 例如當 n = 8,程式就會在 8 個步驟完成。最簡單的例子,就是所謂的簡易搜尋。 ```python= Pokemons = ["卡丘","胖丁","尼龜","比獸","呆獸","種子","小剛"] for Pokemon in Pokemons: if Pokemon == "呆獸": print("找到呆獸!") break else: print("這個櫃子裡不是呆獸") ``` ### O(log n):二分搜尋法 a.k.a 終極密碼搜尋法 ![](https://miro.medium.com/max/2000/1*gxrCcIKY4JNouQkM4JlQww.jpeg) ## [初學者學演算法|排序法入門:選擇排序與插入排序法](https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E6%8E%92%E5%BA%8F%E6%B3%95%E5%85%A5%E9%96%80-%E9%81%B8%E6%93%87%E6%8E%92%E5%BA%8F%E8%88%87%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E6%B3%95-23d4bc7085ff) ### O(n²):選擇排序法 (Selection Sort) 時間複雜度為 O(n²) 的演算法,代表著執行步驟會跟著輸入 n 成次方比例的增加。最基礎的排序法之一:選擇排序法(Selection Sort) 是 O(n²) 複雜度的代表。 - **找最小值** :point_right: ==n== 從「未排序好的數字」中找到最小值 - **丟到左邊** :point_right:(n+(n-1)+(n-2)+…+1) = ==n * (n+1) / 2== 把最小值丟到「未排序好的數字」的最左邊,把它標示成已排序好 假設有一個 [41, 33, 17, 80, 61, 5, 55] 的陣列,我們用圖的例子來一步一步理解選擇排序是如何進行,在下面的圖中,我們把尚未排序好的數字用紅色標示,這輪找到的最小值以橘色標示,排序好的以藍色標示。 ![](https://miro.medium.com/max/1400/1*MUEvL8qTjbRtz22PlTSXPA.jpeg) - **結合找到最小值與丟到最左邊** 把上面的兩個結果加起來,(n * (n+1) / 2) + n = n * (n+3) / 2。回想一下我們在這篇提到的,時間複雜度只取最高次方項並省略所有係數,因此雖然選擇排序的步驟數是 n * (n+3) / 2,其時間複雜度我們還是會記為 O(n²)。 ### O(n²):插入排序法 (Insertion Sort) 同樣擁有 O(n²) 時間複雜度,插入排序法 Insertion Sort 則是另外一個非常常見的排序法。簡單來說,插入排序法就是你玩撲克牌時用到的排序 - 讀一個數字 從「未排序過的數字」中讀取一個數 - 插入合適位置 把這個讀取的數往前插入一個位置 現在,我們重新使用 [41, 33, 17, 80, 61, 5, 55] 的陣列,在下面的圖中,我們把尚未排序過的數字用紅色標示,這輪要插入的值以橘色標示,排序過的以藍色標示。 ![](https://miro.medium.com/max/1400/1*b4zbvCTViTymTzSH9qtDUw.jpeg) - **讀一個數字與結合插入合適位置** 我們在「插入合適位置」需要 1+2+3+…+n 個步驟、在「讀一個數字」需要總共 n 個步驟,計算後 就會得到和選擇排序法相同的 n * (n+3) / 2 個步驟,其時間複雜度我們記為 O(n²)。 ### O(n logn):合併排序 (Merge Sort) 時間複雜度為 O(n log n) 的演算法,代表著執行時間會隨著以二為底的 log n 再乘上 n 成長。最常見的例子是合併排序法 (Merge Sort) 與快速排序法 (Quick Sort),而本篇文章將以新手比較好掌握的合併排序法為例。 ![](https://miro.medium.com/max/2000/1*lbHMJmGtjb_qe943kE_bmg.jpeg) - 每回合的合併要一個一個比對,需要花:O(n) - 每回合都縮減一半,所以總共需要回合數:O(log n) - 所以總共是 O(n log n) ## [初學者學演算法|從費氏數列認識何謂遞迴](https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E5%BE%9E%E8%B2%BB%E6%B0%8F%E6%95%B8%E5%88%97%E8%AA%8D%E8%AD%98%E4%BD%95%E8%AC%82%E9%81%9E%E8%BF%B4-dea15d2808a3) ### O(2^n):費波那契數列 (Fibonacci numbers) 時間複雜度為 O(2^n) 的演算法,代表著執行步驟會是 2 的 n 次方。實務上來說,這樣的執行效率非常的慢,例如當輸入為 100 時,執行步驟就會暴增到 30 位數,等於即使每秒都能執行 1 百億個步驟,也需要個天荒地老 1 千億年才能完成。因此,這樣的時間複雜度是大部分工程師在設計演算法時想要避免的。 而這樣的時間複雜度,最常見的例子是以遞迴計算費波那契數列 (Fibonacci numbers)。 ![](https://miro.medium.com/max/1400/1*GYlkF7G60gbe1B951GulYw.jpeg) ```python 計算費波那契數列第 10 項的程式碼如下: def fibo(n): if n == 0: return 0; elif n == 1: return 1; return fibo(n-1) + fibo(n-2) ​ print(fibo(10)) ``` - 每往下一層,步驟數變 2 倍 - 總共有 n 層 - 時間複雜度:O(2^n) ### 同場加映:如何加速計算費氏數列的演算法 在上面我們提到過,時間複雜度 O(2^n),在實務上非常的慢。因此,我們就要來試著改善上面的演算法,看看能不能讓他加速一點。 我們可以「用空間換取時間」,把每一個算過的值都用一個大表依序記下來。這樣當我們想要知道 F(3) 是多少時,我不用重新計算 F(2)+F(1),只要去大表上找到 F(3) 的答案就好。讓我們以計算 F(10) 為例: ```python table = [0]*3 table[0] = 0 table[1] = 1 ​ for i in range(2,11): table[2] = table[1] + table[0] table[0] = table[1] table[1] = table[2] ​ print(table[2]) ``` 算每一項的同時我們需要去讀取前兩項的值,並把它加起來,總共需要三個步驟。而我們總共需要算 n-1 次,所以總共的步驟數是 3(n-1) 次,只看最高次方項並拔掉係數後,我們就可以得到加速後的時間複雜度:O(n) ## Bubble Sort、Bucket Sort ### O(n²):氣泡排序法 (Bubble Sort) 氣泡排序法(Bubble Sort)是最容易理解和實作的一種排序演算法,也翻譯作冒泡排序法。由於它很容易學習,所以也是許多演算法課程中第一個學習的排序演算法。 - 逐步比較第 n 個位置和第 n+1 個位置元素的大小 ![](https://pic.pimg.tw/emn178/1352552991-3385741824.png) 總共需要進行 n 個回合,每個回合進行 n-i 次。故時間複雜度為O(n²) ### O(n²):桶排序法 (Bucket Sort) 桶排序法會將資料先分類丟進不同的桶子中, 接著再將所有桶子內的數列以==插入排序==等適合**少量**資料的演算法進行排序之後, 再依照桶子的順序把桶子中的元素串接在一起,如此一來就能完成所有排序。 ![](https://4.bp.blogspot.com/-a_kcbOcjY8Q/VLgatONWASI/AAAAAAAAAJk/lwVXIqvvsN8/s1600/sort.png) - 最差情況: 當所有的元素都在同一個桶中,時間複雜度為桶內的排序演算法速度 O(n²) - 最優情況: 元素完全分散在 m 個不同的桶子中,完全不需進行排序。O(n + m) ## 附件 ### 資料結構 ![](https://i1.kknews.cc/SIG=3ucccfk/ctp-vzntr/p992sr28n9sn4q25o1o7489r27s64514.jpg) ### 排序 ![](https://i1.kknews.cc/SIG=2vj3eid/ctp-vzntr/110q26q1455748n1npq3p7oon77336po.jpg) ## 圖形 ![](https://i1.kknews.cc/SIG=2mh198c/ctp-vzntr/33667q68173q405rno4p3q178q3776s0.jpg) ## Heap ![](https://i1.kknews.cc/SIG=3js1sf6/ctp-vzntr/n52sq12341324rq49814p9p1o25oq21s.jpg)