演算法概論
1 演算法
1.1 什麼是演算法
演算法簡單的來說就是完成一個問題所需要的具體步驟和方法,比如以下就 是一個演算法的例子。
LCFA (Loli Classification Fast Algorithm) 輸入: 一隻不超過 15 歲的 Loli
輸出: 一個字串 (String) 代表 Loli 的類別 Start
Input Loli L 試著拿棒棒糖餵 L
詢問 L 她的名字 幫 L 綁雙馬尾
Output無口蘿莉 Output妹妹蘿莉 Output馬尾蘿莉 Output傲嬌蘿莉
End
L 默默的接受了 L回答
「我...我 才不 喜 歡 吃棒棒糖呢」
L沒回答 L 和你同姓 挺適合 L的 L罵你變態
但當然,大部分的演算法都是為了數學的計算上而發明的,最有名的例子大概 是希臘人歐幾里得在公元前 300 發明用來計算最大公因數的輾轉相除法。
到了 20 世紀電腦的發明,讓計算更加快速,計算的資料量增加也讓演算法 之間效率的差距更加明顯,如何設計好的演算法便成了重要的課題。
1.2 好的演算法
對於同樣的題目,可能會有許多種不同的演算法可以解決它,但我們要怎麼 判斷一個演算法較另一種演算法好呢?
最優先考慮的當然是正確性,最好的話當然是希望演算法可以保證對於任何 合法的輸入都能給出正確的輸出 (這也是大部分題目所要求的)。再不然你的演
再來就是效率的問題了,效率分為時間 (操作數) 和空間 (記憶體) 的使用 量,最簡單的方法就是將演算法在電腦上實作然後實際測量演算法所花費的時 間,然後再加以比較,但是問題來了,對於一個輸入資料可能演算法 A 較 B 快,但對於另一個輸入資料可能反而演算法 B 較 A 快。而且電腦對於不同類 型的操作速度也可能不一樣。比如以下兩個演算法
Algorithm 1 MakeLoliHappy1
1: procedure MakeLoliHappy1(N)
2: for i = 1 to N do
3: Push_Loli()
4: end for
5: end procedure
Algorithm 2 MakeLoliHappy2
1: procedure MakeLoliHappy2(N)
2: for i = 1 to N do
3: for j = 1 to N do
4: Play_With_Loli()
5: end for
6: end for
7: end procedure
如果問說哪一個演算法快,我們並不能回答,因為我們不知道 Push_Loli() 花 的時間有 Play_With_Loli() 哪個比較長,假設 Push_Loli() 一次要花 t1 的時 間,Play_With_Loli() 一次要花 t2 的時間,可以知道第 1 種演算法大概要花 t1N 的時間,第 2 種演算法大概要花 t2N2 的時間,如果 t1 = 100t2,那我們的 答案或許是「當 N < 100 時,第 2 種演算法比較快;而當 N > 100 時,第 1 種演算法比較快。」這樣模玲兩可的答案。但有一點可以知道,就是隨者 N 越 來越大,第 2 種演算法所花的時間絕對會遠超過第 1 種演算法!這是因為第 1 種演算法花的時間和 N 是成「線性關係」,而第 2 種演算法花的時間和 N 是 成「平方關係」。從這個例子我們可以知道演算法的快慢除了操作的常數時間 以外,更重要的是他和輸入的規模成何種「關係」,我們稱這種「關係」作時間 複雜度。
1.3 時間複雜度
我們通常用 Big-O 記號來表示一個演算法的上限時間複雜度,Big-O 的嚴格 定義如下
f(n) = O(g(n)) 若 ∃n0,∃c > 0,∀n > n0,|f(n)| ≤c|g(n)|
也就是說存在一個固定的常數 c,對於夠大的 n,f(n) 都不大於 c 倍的 g(n)。 因此比如剛剛的第 1 種演算法,它花的時間和 N 是成「線性關係」,我們就 記做 O(N),而第 2 種就記做 O(N2)。當然時間複雜度的種類也不只這些,還 有 O(1), O(N3), O(NlgN), O(2N) 甚至 O(N22N) 等等奇怪的時間複雜度。以 下假設電腦 1 秒可以做 109 的操作
N = 10 N = 1000 N = 106 N = 108 O(N) < 0.1sec < 0.1sec < 0.1sec 0.1sec O(NlgN) < 0.1sec < 0.1sec < 0.1sec 2.6sec
O(N√
N) < 0.1sec < 0.1sec 1sec 16.7min O(N2) < 0.1sec < 0.1sec 16.7min 1day3hr O(N3) < 0.1sec 1sec 31year 3×107 years O(2N) < 0.1sec 3×10284 years BOOM BOOOOOOM
從這個表可以知道,即使演算法的時間複雜度僅差一個次方,在 N 直很大的情 況下,時間花費也會有很大的差異。下表列出了每種複雜度在何種 N 值以下大 概可以在題目限時 1 秒的情況下執行完成,可做參考。
O(lgN) - O(√
N) 1016 O(N) 107∼108 O(N lgN) 106
O(N√
N) 10000∼80000 O(N2) 3000∼10000 O(N3) 500∼800 O(N4) 90 O(2N) 27 O(N22N) 20
1.4 排序演算法
這裡拿排序的演算法做分析時間複雜度的例子
首先我們定義「排序」這種問題:給 N 個資料,及他們之間的比較方式,
請將他們由小到大排好。
最簡單的想法我們可以枚舉所有 N! 排列,之後再用 O(N) 的時間檢查這種 排列是不是對的,但這樣時間複雜度 O(N ·N!),並不能滿意。事實上排序的演 算法非常的多,比如我們日常生活中常用的插入排序法。
Algorithm 3 Insertion Sort
1: procedure Insertion Sort(N, A[])
2: for i ←1 to N −1 do
3: t ←A[i]
4: for j ←i−1 down to 0 do
5: if A[j]> t then
6: A[j+ 1] ← A[j]
7: else
8: Break
9: end if
10: end for
11: A[j + 1]← k
12: end for
13: end procedure
或是經典的泡沫排序法
Algorithm 4 Bubble Sort
1: procedure Bubble Sort(N, A[])
2: for i ←N −1 down to 0 do
3: for j ←0 to i do
4: if A[j+ 1] > A[j] then
5: Swap(A[j + 1],A[j])
6: end if
7: end for
分析可以知道這些演算法都是 O(N2) 的。能不能更快?答案是肯定的,這裡簡 單介紹 Merge Sort 和 Quick Sort。
Merge Sort 的原理很簡單,對於長度是 N 的序列,我們可以將它切成長
度 相 等 的 兩 段 分 別 遞 迴 下 去 做 排 序,現 在 的 問 題 變 成 給 兩 段 長 度 為 N2 的 已排序序列,我們要如何將它合併為一條已排序序列呢?如果我們可以在 O(N) 的時間內做到,那我們排序長度為 N 的序列所花的時間便可以寫為 T(N) = 2T (N2)+O(N),而解這個遞迴式可以得到 T(N) =O(N lgN)。 Algorithm 5 Merge Sort
1: procedure Merge_Sort(A[], L, R)
2: if L < R then
3: M ← ⌊L+R2 ⌋
4: Merge_Sort(A, L, M)
5: Merge_Sort(A, M + 1, R)
6: Merge(A, L, M, R)
7: end if
8: end procedure
Quick Sort 想法和 Merge Sort 有點像,對於長度為 N 的序列,我們選一個 基準,比基準小的我們把它丟到一邊,比基準大的丟另一邊,這只要花 O(N) 的時間,之後兩段遞迴去做,最好的情況就是兩段的長度一樣,那我們便會 得到相同的遞迴式 T(N) = 2T (N2)+ O(N) 而知道他的最佳時間複雜度亦為 O(N lgN)。雖然這是最佳的情況,不過分析可以知道它的平均時間複雜度也是 O(N lgN) 的。
Algorithm 6 Quick Sort
1: procedure Quick_Sort(A[], L, R)
2: if L < R then
3: M ← Partition(A, L, R)
4: Quick_Sort(A, L, M −1)
5: Quick_Sort(A, M + 1, R)
6: end if
7: end procedure
2 資料結構
2.1 什麼是資料結構
在一個演算法中,有時候除了數值的運算之外,我們可能還會對大量的資 料進行查詢或是修改,資料結構主要是在研究如何把原始的「資料」,組織、
安排、存放到電腦中。好的資料結構對於節省儲存空間與處理速度將有很大幫 助,甚至可能會影響演算法的時間複雜度。假設我們有以下的資料要維護。
Name Sion Eustia Kudryavka Nikaidou Shinku · · ·
Height 148cm 150cm 145cm 141cm · · ·
Tags Scientist,Legaloli Miserable Half-blooded Scientist,Red-eyed · · ·
... ... ... ... ... . ..
而我們的詢問便可能是「詢問名字為 S 的屬性」或是「詢問身高最高的人的 名字」等等,而修改可能是「將名字為 S 的身高改變」或是「增加一筆新的資 料」等等。
那我們要怎麼安排資料結構呢?最簡單的想法就是都不處理,直接將資料存 進一個陣列裡,這樣的好處是因為資料都不需要經過任何處理,所以對於增加 一筆資料或是刪除一筆資料我們都可以在 O(1) 的時間完成。
但是對於一些詢問,這種資料結構的表現可能就不太理想,比如我們要查詢
「最高的」一筆資料,我們就必須把陣列從頭到尾掃過一遍才能知道,假設有 N 筆資料,那我們就必須花 O(N) 的時間。又或是我們要詢問是否存在身高為 L 的一筆資料,那一樣我們必須花 O(N) 的時間才能知道。
有沒有辦法改進呢?可以。比如我們用先前提到的排序法將資料按照身高由 小到大排序,我們稱這樣的資料結構為 Sorted-Array。這樣當我們要查詢「最 高的」一筆資料,我們只需要查詢陣列中最後的一筆資料,而可以在 O(1) 的 時間回答。至於詢問是否存在身高為 L 的一筆資料,我們則可以在 lgN 的時 間辦到,這裡要介紹二分搜尋法的概念。
2.2 二分搜尋法
給定有 N 個資料的以排序陣列 A,我們要快速的找到第一個不小於 X 的 資料。其實二分搜尋法的概念就和猜數字遊戲很像,假設現在可能的範圍是 [L, R],令 M = L+R2 ,若 A[M] ≥ X,則我們可以縮小範圍成 [L, M],反之若 A[M] < X 我們便可縮小範圍為 [M + 1, R],這樣我們可能的範圍每次都縮小 一半,當範圍內只有一個數時我們便找到了,因此可以在 lgN 的時間完成。
2.3 不同資料結構的比較
這樣看來 Sorted-Array 似乎在查詢都比單純的 Array 還快,但當然也是有缺
點的,比如當我們要增加或是修改一筆資料,注意到在修改後我們還要保持它
仍然是 Sorted-Array!因此我們必須在 O(N) 的時間才能完成。
建構 查最大值 查 X 是否存在 增加/刪除一筆資料
Array O(N) O(N) O(N) O(1)cm
Sorted-Array O(NlgN) O(1) O(lgN) O(N)cm
從表中可以知道,如果我們要加速某些操作,我們可能就必須犧牲在某些操作 的速度,或者是我們可能需要負擔額外的空間,所以對於不同的問題,我們必 須使用不同的資料結構讓效率最好,因此熟悉各種不同的資料結構是重要的,
我們也將在後面介紹更多不同的資料結構。
3 Exercise
1. 證明輾轉相除法的時間複雜度為 O(N),其中 N 為數字中較大的那個。
2. 證明 lgN! = O(N lgN) 和 N lgN = O(lgN!)。 3. (以下皆假設存在常數 c,對於所有 x < c, T(x) = 1)
求滿足以下等式的時間複雜度,T(N) = T(23) +O(N)。 4. 求滿足以下等式的時間複雜度,T(N) = 2T(12) +O(N)。 5. 求滿足以下等式的時間複雜度,T(N) = T(√
N) + O(1)。
6. 求滿足以下等式的時間複雜度,T(N) = T(N −1) +T(N −2) +O(1)。 7. 請說服任何一隻傲嬌 Loli:基於比較的排序法的時間複雜度的下限為
O(N lgN)。
8. 請給出一個 (期望)O(N) 的演算法,可以在長度為 N 未排序的陣列中找出 第 K 大的數。[TIOJ 1080]
9. 請給出一個 O(N lgN) 的演算法,對於一個長度為 N 的陣列 A,計算出 所有滿足 A[i] > A[j] 且 i < j 的數對 (i, j) 的個數。[TIOJ 1167]
10. 有 N 隻 Loli,第 i 隻 Loli 分別在 si 的時間來到你家玩並在 ti 的時間離 開,請問你最幸福的時候,即最多有幾隻 Loli 同時在你家。[TIOJ 1410]
11. 根據 K-Loli-loLi law,存在一個常數 K,如果同時有 K 隻第一次來你家 的 Loli 在你家玩,她們就會再帶一隻新的 Loli 到你家,你希望最後有 N 隻以上的 Loli 在你家玩,請問你一開始要帶幾隻新的 Loli 到你家玩呢?
12. 有 N 隻 Loli 排成一列吵著要跟你玩,你剛好只有 K 小時的空閒時間,
因此你決定將此 Loli 列隊分成 K 段,然後依序跟每一段的 Loli 群玩一 小時。但這些 Loli 都有一個正整數 ti 代表第 i 隻 Loli 的腹黑度,而一群 Loli 的腹黑度即是個別腹黑度的總和。如果一次跟一群太腹黑的 Loli 群玩 你可能會被耍得團團轉。怎麼分這 K 段讓最腹黑的一群的腹黑度最小呢?
[TIOJ 1432]
Mission List
主線任務
日期 作戰名稱 戰場 作戰說明
9 月底 建中資訊校內賽 建中 選約 12 名建中代表校隊
11 月 北市賽 某高中 建中代表校隊參加,約前 10 名參加 全國賽
12 月 全國賽 某高中 前 10 直接進入 TOI 一階
3 月 建中校內補選 建中 人數視建中校隊多少人已取得露營考 資格決定
3 月 TOI 入營考 師大 全國約 100 人取 20 人進入一階 4 月 TOI 一階 師大 取 12 人進入二階
5 月 TOI 二階 師大 選 4 人成為 IOI 國手
7 月 6−13
IOI2013 澳洲 為國爭光!!
副本任務
日期 作戰名稱 戰場 作戰說明
11 月 台北市軟體競賽 松山工農 校內推薦 10 名參加,要先通過第一 階段的筆試才會進到第二階段的上機
測試 11−12
月
NPSC 台大 三人組隊報名,分初賽和決賽,同校 至多 3 對進決賽,進決賽就有很好的 隨身碟,第一名還有筆電!(一人一 台) 二、三名也都有很不錯的獎品。