# 認識演算法 `演算法(Algorithm)`是在有限的步驟之內,提供明確的法則,求出問題正確答案的程序。它可以是一種方法、法則或者程序,讓資料可按照預先設計的方式處理。 ## 演算法設計原則 1. 輸入:演算法可以包含**零個或一個以上**的輸入。 2. 輸出:演算法**至少**須產生一個輸出。 3. 明確性:電腦程式是根據指令一步一步地執行,沒有臨機應變能力,因此演算法的每一個步驟,都必須清楚、明確地交代。 4. 有效性:演算法的每一步驟都須是一可行的運算,也須能在有限的時間內完成。 5. 有限性:演算法須在有限的次數內解決問題,而結束工作。 ## 演算法設計策略 常見的演算法策略有數種,在此介紹其中五種。 ### 分治法(Divide-and-Conquer) `分治法(Divide-and-Conquer)`,是將大問題拆解成小問題,分頭解決得到答案,最後再彙總變成大問題的解答。 [`遞迴(recursion)`](https://hackmd.io/@howkii-studio/python_modulization/#recursion)是實作這個策略的重要方法。 #### 隨堂練習 1. 階乘的計算 階乘的基本定義如下 - n == 0, n! = 1 - n > 0, n! = n x (n-1)!, n正整數 請設計程式計算 **4!** 為何? 參考程式碼如下: ```python= def factorial(i): if i == 0: return 1 else: return( i * factorial(i-1)) print(factorial(4)) ``` 2. 費伯納西數列 `費伯納西數列(Fibonacci Polynomial)`的基本定義如下: - n=1, F~n~=0 - n=2, F~n~=1 - n>2, F~n~=F~n-1~+F~n-2~ > n為正整數 請撰寫程式計算F(11)為何? 參考程式碼如下: ```python= def fib(n): if n==1: return 0 elif n==2: return 1 else: return(fib(n-1)+fib(n-2)) print(fib(11)) ``` ### 動態規劃法 `動態規劃(Dynamic Programming)`,類似Divide and Conquer,一個問題的答案來相依於子問題,常用來解決最佳解的問題。 和Divide and Conquer不同的地方在於,動態規劃多使用了memoization的機制,將處理過的子問題答案記錄下來,避免重複計算,因此在子問題重疊的時候應該使用動態規劃,可以解決重覆計算並保留遞迴思考的優點。 #### 隨堂練習 使用動態規劃法,來解決費伯納西數列問題。 `費伯納西數列(Fibonacci Polynomial)`的基本定義如下: - n=1, F~n~=0 - n=2, F~n~=1 - n>2, F~n~=F~n-1~+F~n-2~ > n為正整數 請撰寫程式計算F(11)為何? **參考程式碼如下:** 使用一個`List`,把解過的問題答案記錄起來。 ```python= FibArray = [None]*1000 #結果暫存區 def fibonacci(n): result = FibArray[n] if result == None: if n==1: return 0 elif n==2: return 1 else: result = fibonacci(n-1)+fibonacci(n-2) FibArray[n] = result return result print(fibonacci(5)) ``` ### 暴力演算法(Brute force) 又稱`列舉法(Enumeration)`,和分治法完全不同的概念,就是將問題的可能的走向都全部列出來,列舉出所有可能的答案。 當資料量不大的時候,反而是最直接解決問題的策略。 #### 範例 如果有一個密碼系統,是只能輸入四位數字,那麼直接從0000依序嘗試到9999,是最直接的方法。 ```python= for i in range(10000): print(str(i).zfill(4)) ``` 不過千萬別用在一般手機,品牌廠商都設置了相對應的機制,例如嘗試幾次錯誤就會鎖起來不再讓使用者輸入密碼。 ### 回溯法(Backtracking) `回溯法(Backtracking)`是列舉法的優化策略之一。 主要用於搜尋,運用遞迴的方式進行,當發現搜尋過程中,不滿足求解條件時,就回溯返回,嘗試別的路徑,避免無效搜索。 最常見的應用範例是「老鼠走迷宮」 假設有一個迷宮盒子,老鼠想試圖從入口走到出口,但必須依循三個原則: a. 一次只能走一格 b. 遇到牆無法走時,退回一步找其他道路 c. 走過的路不會走第二次 根據以上原則,依序舉列出所有可能的路線。 經典問題: - N皇后問題 - 騎士走棋盤 ### 貪心法則(Greedy Method) `貪心法則(Greedy Method)`的原理:在對問題求解時,判斷當下最好的選擇 過程: - 把求解的問題分成若干個子問題 - 對每一子問題求解,得到子問題的區域性**最優解** - 把子問題的解區域性最優解合成原來解問題的一個解 #### 隨堂練習 換鈔的最佳策略:請設計程式,列出使用最少數量的貨幣去進行換零錢。 > 想法:**本題最佳的換鈔策略,是從大面額的貨幣開始換** 步驟如下: 1. 從最大面額貨幣開始,判斷找錢金額是否大於該面額 2. 如果大於該面額,則進行除法,確認要提供的該面額的張數 3. 取找錢金額相除該面額的餘數,則可以得到尚未找的錢,繼續進行換鈔 參考程式碼: ```python= def change(origin): money = [1000, 500, 100, 50, 10, 5, 1] #最佳選擇:最大面額開始嘗試 for i in range(0, len(money),1): if origin >= money[i] : print(f"{money[i]}*{int(origin / money[i])}") origin = origin % money[i] change(965) ``` ## 演算法成效衡量 * 時間複雜度:執行次數與執行時間 * 空間複雜度:使用的記憶體空間 ## 總結 ###### tags: `資料結構與演算法`