演算法不是程式語言。演算法是用來解決特定問題的方法與過程。
一個定義良好的計算方式,會包含單一,或一組完整的輸入,並且產生出一個值,或一組值作為輸出,如果所有輸出的值都是正確答案,則說明解決了問題。
假設今天要解決一個問題是:「如何把芋頭和牛奶,做成芋頭牛奶」
可以說這3個步驟就是把芋頭和牛奶做成芋頭牛奶的一種演算法
在電腦科學中有一條公式:
我們透過設計一連串的指令、動作,讓電腦去執行,以便協助我們解決一些特定問題
今天說穿了,其實演算法就是一種解決問題的邏輯思維!
而在TAOCP整理了一個演算法需要具備五個特性:
所謂的電腦,可不只單純是主機、筆電,具上述計算能力的最小單位就是一個晶片,而晶片早就被廣泛應用在我們生活之中,像是遙控器,手機,電視,火警,保全。
所以只要具備計算能力硬體在的地方我們就需要演算法,也因此有演算法可以改變世界之說。
你可能不這麼認為,電腦不是很聰明嗎? 你看現在有自動駕駛,AI繪圖,聊天室AI…
但是沒騙你,電腦真的超級笨,電腦就像超級魁儡一樣,超級聽話,而且幾乎不出錯,更重要的是,運算速度超級快。
根據這三個特性,發明了一種方式叫做"Programming" 把現實世界要解決的問題變成數學,丟給電腦來幫我們做運算,所以聰明的並不是電腦,而是背後的人用聰明的方式,讓其他人誤以為電腦很強大,很聰明。
無論如何,我認為你都應該把自己的想法寫下來,一是如果題目複雜,你可能會想很多種想法,但如果都沒寫下來,會很難判斷可行性。
再者當你詢問其他人的時候,別人不知道你的程式碼的變數意義,也不知道你寫的邏輯,當你一一介紹你的變數意涵時,別人很有可能還是看不懂QQ,所以如果你直接拿出你的想法紙,根據題目意義來敘述,會簡單明瞭許多,這也就是你為什麼要把自己的想法寫下來。
那要如何寫下自己的想法呢?
嘗試把人腦變的單純,不要用人腦習以為常的觀點來思考事情,而是要模擬計算機(computer)處理事情的方法。依據這種思維模式,很自然的會這樣想:「不要一次看到事情的全貌,要把問題切割成一個一個具體的數理步驟,將解決步驟條列式的以數學和邏輯的語言寫出來」。
Bottom – UP程式設計策略的精隨就是:遇到不會解的問題時,就不要嘗試一次全部解決,先找出「解決部分問題」的方法,先做會做的部分,接著再用「相同的方法、相似的程式片段」,依序把剩下的問題用暴力式的方式寫出來。這時候的暴力程式雖然不夠優雅,但確實可以正確的解決問題。
之後,我們就可以引入變數和迴圈…等觀念,用變數取代重複的敘述中「不同的部分」,然後再用迴圈取代重複的敘述中「相同的部分」,來優化暴力程式。
來看一個經典的範例:在螢幕上顯示(印出)九九乘法表。假設我們現在根本不知從哪裡開始著手,沒關係,依照Bottom – UP程式策略的精神,我們先做會做的部分就好,例如寫2 * 2 = 4 ~ 2 * 9 = 18 …,這個總沒有問題吧。所以我們就先寫出:
2 * 2 = 4
2 * 3 = 6
2 * 4 = 8
… … …
2 * 9 = 18
3 * 2 = 6
3 * 3 = 9
3 * 4 = 12
… … …
3 * 9 = 27
注意到了嗎,我們可以把前面的乘數以一個變數取代(例如i),後面的乘數以另一個變數取代(例如 j),所以其實我們的演算法就是計算「i * j」,並讓 i 的值從2 ~ 9做變化,j 的值也是從2 ~ 9做變化。
我們以變數取代「不同的部分」,並且用迴圈取代「相同的部分」,將暴力程式做優化,結果如下:
UP – Bottom程式設計策略有兩個步驟:
工作變數、迴圈變數(計數器)、或者另一個常聽到的旗標變數(flag),其實也都是一般的變數而已,只是方便做出變數應用情境上的區別,便於我們理解與使用。
這裡我們舉一個「印出圖形」的例子,應用UP – Bottom程式設計策略的精隨。例如要在螢幕上顯示(印出)以下的圖形:
一開始我們不知道如何著手,於是依照UP – Bottom程式設計策略的精神,先想想看如何寫出解決問題的「最外層迴圈」敘述,也就是「印出一列訊息後換列,共印出五列」。這樣的迴圈蠻簡單的,如下:
外迴圈和虛擬碼都寫出來了,我們再來想想如何把中文的虛擬碼轉換成C語言。由於每一列印出的 * 號不固定,因此我們以一個整數變數x來代表每列 * 符號的個數,為了便於理解與使用,我們把x特稱為工作變數。
我們把最外層的迴圈變數i,和工作變數x的關係寫出來:
所以我們就可以把剛剛的程式碼寫的更清楚了
中間的部分很明顯就是用for迴圈來完成,所以程式碼結果為:
第三個訣竅就是,整合了前兩種,無論是Bottom – UP或是UP – Bottom兩種方法,只是思維方式的不同,最後還是可以得到一樣的答案,就算程式碼略有不同也無所謂。所以綜合起來應用,成為第三種訣竅,其步驟是:
不過訣竅終究只是訣竅,只是輔助的技巧而已,還是要搭配許多練習和實戰經驗,下苦功去實作過,還要不停吸收新知,不停的精進自己的技巧,才能真正學好演算法。
常常聽到有人說,這個效率不好! 另外一個效率比較好,那到底怎麼來比較一個演算法的效率呢?
這邊簡單說一些常見的方程,在極限中的行為
非常重要的函數關係!!!!
而當出現多個的時候,如果是加法,可以直接取最大的,乘法則不行
例如
~
~
有這些估計,就可以帶入題目,或者現實上的 size 進行估計。
如果一個題目的 n 值為 ,根據OJ上一秒大概跑
可以推論 的算法是不行的,而
然而描述一個函數有以下幾種方式 (但通常會直接用 big O 居多)
簡單一點的描述你可以看成
所以當我們描述兩個方程 f, g 在趨近極限的狀況
f = O(g) 你就可以大致看成成 f <= g ,以此類推
接下來來進行比較嚴謹的定義
O-notation(大O符號)描述了函數漸近行為的上限 (upper bound):它表示一個函數的增長速度不會超過某個特定的速率。這個速率是基於最高次項的。
數學上的定義,若 f = O(g),則存在一個常數 c > 0 使得當 N 夠大的時候,對於所有 n >= N,有 f(n) <= c(g(n))
舉例來說:
= O()
= O()
= O()
注意這裡是用 big-O ,所以以下情況也是
這樣是 =
為什麼二分會是 (logn)
假設區間大小是 n ,二分的時候每次都會讓區間大小縮小一半
第 1 次找:
第 2 次找:
第 k 次找:
當 等於 1 的時候停止搜尋
所以得到
簡單的可以直接看出來的 (讚!)
複雜遞迴: 可以使用 master theorem 來判斷
太複雜的??
雖然複雜度可以估計,但實際上輸入輸出的時間也是耗費很大,所以為了避免掉這個問題,會使用,但基本上 CPE 不需要用也可以。
(畢竟如果卡了輸入輸出,就看不出演算法實際效率了,例如圖論題目,需要大量 node 以及 edge 的時候,通常輸入挺龐大的)
常見選擇
、、、、
need to check q and n which is bigger