# 麻省理工學院公開課-算法導論_第一講-課程簡介及演算法分析 ###### tags: `MIT` `Algorithms` `網易` [課程連結](http://open.163.com/newview/movi/free?pid=M6UTT5U0I&mid=M6V2T1JGF) ## Analysis of Algorithms 演算法分析是關於電腦程式效能與資源利用的理論研究,特別觀注在效能身上,我們要學習如何可以讓事情處理的更快。雖然有不少事情比效能還要重要,但效能已經決定"行不行"。舉例來說,目前很多系統需求為real-time,如果程式的執行速度不夠快,那就直接意謂著做不到。演算法要做的就是將不可能化為可能。 課程中提到,為什麼演算法是基礎?好的演算法決定執行的速度,我們願意拿多少速度來交換其它比執行速度更快議題,像是安全性,或者為什麼用java而不是用更快的c? ## Problem: sorting 排序是一個演算法中非常古老的議題 給定一個輸入序列$a_1, a_2, \cdots, a_n$,我們希望得到一個輸出$a_1', \cdots, a_n'$,其中$a_1' < a_2' < \cdots < a_n'$,由小到大排列。 ## Insertion sort  課程中會以pseudocode的方式來呈現演算法的過程,這不是一個正式的程式碼,是一個概念。 1. 外圈loop的條件為$j \leftarrow 2 \space \text{to} \space n$ 2. 內圈loop的條件為$i \leftarrow j - 1$ 3. 內圈loop會遞減到0為止 4. 已經排序過的就視為invariant 每次的排序目的就是增加已經排序過的數值的長度,也就是簡報中的sorted處。  整個大致過程是: * 我們找一個$j$,然後將$j-1$這個所在的index(課程中定義為key)的value往$j$複製,一直到找到適合這個key-value($j$)的位置,然後插入(因此稱為insertion sort) * 接續執行$j+1$ 從課堂的範例可以很清楚看到,排序是從第2個元素開始跟第1個元素比較。  running time取決於很多因素: * input本身,舉例來說,input本身已經是有序的,那自然insertion sort就不需要處理過多事情,至少內圈的部份都不用處理了,而最糟的情況就是整個是逆序的,那全部都要重來 * input size,愈大,那處理時間自然愈長 我們會希望知道整個演算法的執行時間的upper bound,這代表著對使用者的一種承諾,這演算法最多三秒完成,跟這演算法最少三秒,但你不知道最多是不是三年完成,這是兩個完全不一樣的概念。  因此對於分析演算法的執行時間,我們通常只觀注於Worst-case, 符號約定: * T(n):給定任意的input size-n,其"最大"執行時間,這意味著只會有一個時間,拿掉"最大"那就不是這個意思了 另外還有Average-case,這但牽扯到期望值,那就跟機率有關,需要做點假設,假設整個input size是uniform distribution,不過基本上不會談到。 最後,還有Best-case,但這是假的(bogus),no good,因為你可以作弊,弄些東西讓它看起執行的很快,但實際上大多數情況並不是這樣的。  那insertion sort的worst-case為何?這取決幾個因素: * 執行演算法的電腦 * 如果我們想看的是相對速度,那就是在同一台電腦上執行兩個演算法 * 如果我們想看的是絕對速度,那就是在不同電腦上執行同一個演算法 但這談不完,太多因素了,所以我們會從演算法的大方向來看,也就是asymptotic analysis(漸近分析),我們忽略掉機器相關的因素,而且不是直接的去計算實際的執行時間,而是演算法執行時間的增長情況,也就是當n無限大的時候,其T(n)為何。  這邊說明幾個漸近符號: 1. $\Theta$,觀念很簡單,就是去掉低階項,然後忽略掉前面的常數 * 舉例來說,$3n^3 + 90n^2 - 5n + 6046 = \Theta(n^3)$ 這意謂著,如果$n$趨近於無限,那$T(n^2)$一定會比$T(n^3)$還要快,即使$T(n^2)$是在較慢的電腦,而$T(n^3)$是在較快的電腦上執行,因此,漸近符號的表示一次滿足了上面我們所提的絕對、相對速度,記得,我們觀注的是隨著input size增長而增長的執行時間。  上圖我們可以看到$\Theta(n^2)$與$\Theta(n^3)$執行時間的趨勢線,不管前面的趨勢如何,它們兩個就是會在$n_0$之後,$\Theta(n^2)$永遠有著比$\Theta(n^3)$更小的開銷。 但以工程技術的角度來看,有時候$n_0$大到電腦無法計算。這也是為什麼有些時候我們會對執行速度較慢的演算法有興趣,因為即使它們以漸近分析來看是比較慢的,但在合理的input size內,它們的執行速度還是快的。  剛才我們說過,insertion sort的worst-case就是逆排,也就是由大到小的排序。 假設,每一個元素的操作所花費的時間都是常數時間,而因為我們做的是漸近分析,因此這常數是多少並不是那麼重要。 $T(n) = \sum_{j=2}^n \Theta(j)$,後面的$\Theta(j)$是來自外層迴圈,當$j=2$的時候,內圈的`do`也會做2次的操作,因為我們是考慮worst-case,當$j=3$,那`do`就做3次的操作,因此內層`do`的操作就是$\Theta(j)$,而$T(n) = \sum_{j=2}^n \Theta(j) = \Theta(n^2)$,這是一個等差級數的計算,$1+2+3+4+...+n = \dfrac{n(n + 1)}{2} = \dfrac{n^2}{2} + \dfrac{n}{2}$。 在$n$很小的情況下,insertion sort還算是快,但是當$n$非常大的時候就不是這麼一回事了。  Merge Sort,另一種排序演算法 Merge Sort A[1 ... n] * 判斷是否n = 1,那就沒有排序的必要 * 當n > 1,則從$\lceil n/2 \rceil$切半遞迴排序,變成$A[1 ... \lceil n/2 \rceil]$與$A[\lceil n/2 \rceil ... n]$ * 合併兩個各自排序好的資料  圖上可以看到兩個各別排序好的資料,而最小值一定在兩個陣列的第一個索引值其中一個,比較誰小之後拿到最終輸出的陣列中,然後刪掉,下一步一樣的,最小值依然在兩個陣列的第一個索引值,一樣的比較誰小,拿到最終輸出的陣列中。 我們做的永遠都是比較兩個陣列的第一個索引值,沒有其它的事,對於$n$個輸入的時間複雜度為$\Theta(n)$,這亦稱為linear time(線性時間)  1. 第一次的判斷只是確定長度,這是一個常數時間,因此為$\Theta(1)$ 2. 我們將長度切為兩段,執行兩個工作,因此為$2T$,而每次處理的工作是原來的一半,因此為$2T(n/2)$ 3. 合併兩個排序好的資料,這剛已說,為$\Theta(n)$  $$ T(n) = \begin{cases} \Theta(1), & \text{if $n=1$} \\ 2T(n/2) + \Theta(n), & \text{if $n > 1$} \end{cases} $$ 解這時間複雜度的方法為Recursion tree(遞迴樹),$T(n) = 2T(n/2) + cn$,如上圖所示,tree root為cn,而左右兩邊各為$T(n/2)$,整體相加起來依然是$2T(n/2) + cn$。其中$c$可以視為當$n=1$或分解、合併步驟中處理每個element所需的時間。  就這樣一直的下去,最終的節點就是$\Theta(1)$,這意味著樹的高度會接近$\log n$,而最終的節點會有$n$,因為我們整個展開它。 可以發現到,每一層的執行時間都是$cn$,一直到最後$\Theta(n)$(不一定是$cn$,但不影響結果),整個執行時間為$(cn)\log n + \Theta(n)$,因為高度就是$\log n$,意思就是我們有$\log n$個$cn$。而$(cn)\log n$比$\Theta(n)$高階,忽略$\Theta(n)$,因此為$\Theta(n \log n)$。 很明顯,$\Theta(n \log n)$一定比$\Theta(n^2)$還要來的快,大約在$n > 30$之後,這就會非常明顯。
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.