• Tidak ada hasil yang ditemukan

Chapter 0 競賽簡介(Competition in Informatics)

N/A
N/A
Protected

Academic year: 2023

Membagikan "Chapter 0 競賽簡介(Competition in Informatics)"

Copied!
24
0
0

Teks penuh

(1)

 

Chapter 0 競賽簡介(Competition in Informatics)

●Section 1 作戰計畫流程圖(Plan for Competition)

  註:日期戰場數據依照每年實際情況而有所更動。

(2)

 

●Section 2 學習資源介紹(Introduction to Resourse)

§2-1 線上評測系統(Online Judge)

* Uva Onlinejudge(Uva):【推薦】 

http://uva.onlinjudge.org/

 

* Usa Compute Olympiad(Usaco):【推薦】 

http://ace.delos.com/ 

* Temporary INFOR Online Judge(TIOJ):【推薦】 

http://tmtacm.no‐ip.org/JudgeOnline

 

* PKU Online Judge(PKUOJ): 

http://acm.pku.edu.cn/JudgeOnline/

 

* Z‐Trening: 

http://www.z‐trening.com/ 

* Velocious Informatics JudgeOnline System(Vijos): 

http://www.vijos.cn/ 

* Timus Online Judge: 

http://acm.timus.ru/ 

* Młodzieżowa Akademia Informatyczna: 

http://main.edu.pl/ 

* Topcoder: 

http://www.topcoder.com/ 

* Croatian Open Competition in Informatics: 

http://www.hsin.hr/coci/ 

§2-2 書籍(Book)

★《Introduction to algorithm 3/e》:Thomas H. C. , Charles E. L. , Ronald L. R. , Clifford S.

★《Fundamentals of Data Structures in C(C++) 2/e》:Horowitz, Sahni, Mehta

★《算法藝術與信息學競賽》:劉汝佳、黃亮,清華大學出版社

其實要推薦的書有很多,但最經典最重要最常用的也就這三本書,如果對其他書有興趣,則可以另 外再找講師詢問。

§2-3 網路資源(Internet Resourse)

◎ 建中培訓網站:

http://www.ck.tp.edu.tw/~peng/ 

阿就我們培訓網站嘛 XDD。

◎ math120908 講師個人網站:

http://math120908.infor.org/ 

其實這些網址在我空間上都有,而講義應該也會放在這裡。

◎ Cppreference:

http://www.cppreference.com/ 

此網站說了一些基本的 C/C++函式介紹,查詢函式時非常好用。

◎ DJWS 的網路日誌:

http://djws.wordpress.com/ 

此網站有許多關於演算法的介紹。

◎ hil 教授的上課投影片:

http://www.csie.ntu.edu.tw/~hil/teach.html 

此為台大演算法教授 hil 老師上課所使用的投影片。

◎ BBS 上的 sa072686 個人版:

telnet://sony.twbbs.org/

的 sa072686 個人版 此有 sa072686 同學寫 Uva 一千多題的解題記錄。

◎ Google、Wikipedia……。 

~( ̄▽ ̄)~(_△_)~( ̄▽ ̄)~(_△_)~( ̄▽ ̄)~ 

(3)

Chapter 1 基礎演算法(Basic Algorithm)

●Section 1 複雜度分析(Complexity)

相信大家在解數學問題的時候,都常常會想用一些高級而快速的方法,減少計算式子,

不只是能求快,也比較不容易計算錯誤。

是的,身為一個好的演算法,就必須要滿足「快」「狠」「準」這些條件。唔!簡單的說,

就是要讓程式很快跑出答案,然後答案就是正確的啦 XD。

不過聽說現在電腦一秒鐘可以執行好幾億個指令!?那要怎麼比較演算法的效率是高還 低呢?畢竟在每台電腦跑的速度都不一樣,而且輸入的數量多寡是不固定的。嘿嘿,這時候,

我們就要引進一種,叫做時間複雜度(Time Complexity)的東西。

既然不能吃,那要怎麼用呢?直接舉個例子來說吧,就拿排序n個數的演算法來說好了,

現在有兩種演算法,第一種需要使用2n2個指令才能把這n個數依大小順序排好,第二種則

需要50 logn 2n個指令。又我們假設今天有一台電腦每秒可以跑108個指令。以下是時間對照

表:

n 102 103 104 105

第一種排序法(2n2) 0.2(ms) 20(ms) 2(s) 200(s) 第二種排序法(50 logn 2n) 0.33(ms) 4.98(ms) 0.066(s) 0.830(s)

我們可以發現到,在n很小的時候,兩種執行速度都快的不得了,甚至第一種還快於第 二種,但是,當n越來越大的時候,因為第一種演算法的成長速度比第二種快,所以,速度 快的優勢就被追過去了,然後就會變成很慢很慢……,由下圖可以看出兩者的成長趨勢。

所以了,我們要估計某個演算法的時間複雜度的時候,都是利用成長率最快的項來估計 的,因此,我們通常也會忽略常數,畢竟在當n很大,時間函數成長很快的時候,常數就顯 得微不足道了。舉個例子:如時間函數T n( ) 100 n314n7,影響這個函數成長的領導項是

n3,這個時候,我們會說他的時間複雜度是O T n( ( ))O n( )3 ,其中的O這個符號(唸作 Big-O)

是我們用來代表影響時間函數成長的最重要因子,一般來說,多項式時間的演算法,這項都 會是他的最高次項。

§1-1 漸進記號(Asymptotic Notation)

有了簡單的函數增長概念,我們就來嚴格定義一下以下幾個漸進記號。

(4)

# Θ(theta):對於一個給定的函數 g(n), 用Θ(g(n))來表示函數集合:

1 2 0 0 1 2

( ( )) { ( ) :g n f n c c n, , R, n n 0 c g n( ) f n( ) c g n( )}

          

# O(Big O)

0 0

( ( )) { ( ) : , , 0 ( ) ( )}

O g nf nc nR   n n   f ncg n

# Ω(Big Omega)

0 0

( ( )) { ( ) :g n f n c n, R, n n 0 cg n( ) f n( )}

         

而我們在說 T(n)=Θ(g(n))其實意義是 T(n)Θ(g(n)),但是那個屬於符號時在是太難打 了…,阿不是是為了方便,所以就記錄成等於符號了(茶)。

嗯其實這個符號的意義對我們來說不是很重要的,只是最好知道一下,以免誤用,且對 於往後真正學習演算法時有個「哦!這我以前看過耶!」的感覺就 OK 了。

反而需要注意的是,因為他漸進意義下所忽略的常數,是有可能非常巨大的,一旦遇到 限制卡很緊時,常數因子的影響力就會表現出來,所以能將常數盡量減小也是該學習的一點。

§1-2 複雜度種類(Kind of Complexity)

最佳複雜度、最差複雜度、平均複雜度。通常我們最關心的就是最差複雜度了,因為對 於競賽來說,如果有筆測資非常之機車+邪惡,那你即使最佳、平均複雜度很低也沒用,還 是會有 TLE 的機會的,所以我們一直希望的就是能夠將最差複雜度降低,當然假如在時空允 許,或者情況很平均分散的話,偶爾也是會犧牲一點最差複雜度來換取平均與最佳複雜度的,

常見的情況就有 qsort, kth element, random construct BST……,這些將會在之後章節作介 紹>.^。

而我們除了估計時間複雜度以外,空間複雜度也是需要注意的。空間複雜度的估計與時 間複雜度差不多,只是是計算這個演算法所需花費的空間大小估計而已。

而又往往為了降低其中一個複雜度另一個就會相對提高。所以啦,在時空(?)間取得其中 平衡就顯得格外的重要了。

§1-3 遞迴複雜度(Recursive Complexity)

因為關於遞迴問題的估計複雜度比較複雜,所以我們往往會把問題變成一顆遞迴樹來思 考。例如式子 T(n)=3(n/4)+cn2就可以化成下列遞迴樹:

log 34

2

2 2 2 2 2 2 2 2 2

4

( ) ( ) ( )

4 4 4

log ( ) ( ) ( ) ( ) ( ) ( ) ( ) ( ) ( )

16 16 16 16 16 16 16 16 16

(1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1)

n

cn c

n n n

c c c

n n n n n n n n n n

c c c c c c c c c

T T T T T T T T T T T T

 











           

 4

2

2

2 2

log 3

2

3 16 ( 3)

16

( )

: ( ) n

cn cn

n Total O n

 

       

因而 T(n)=O(n2)。而有鑑於這類遞迴問題複雜度分析比較困難,所以就簡單說明一下著 名的主定理:

(5)

<<主定理(Master Theorem)>>

考慮這樣的遞迴式:T(n)=aT(n/b)+f(n)。

則對於常數 a1, b>1,我們有以下情況:

情況 1: 如果存在常數ε>1,使得 f n( )O n( logba),則 T(n)=Θ(nlogba) 情況 2: 若 f n( ) (nlogba),則 T(n)=Θ(nlogbalgn)

情況 3: 如果對某常數ε>0,有 f n( )O n( logba+)且對常數 c<1 與所有足夠大的 n 有 af(n/b)

c*f(n),則 T(n)=Θ(f(n))

以上定理其實可以看看就好,其大概想法就是利用遞迴形成的樹狀圖來分析。至於詳細證明請自 行參閱《I2A》或相關書籍。

§1-4 小結(Conclusion)

就競賽來說,分析複雜度的意義其實並不是說是最重要的,你常常有可能有高複雜度但 卻可以 AC,也或許有低複雜度卻一直 TLE。但是正確的簡單估計複雜度卻是必要的,不只是 因為筆試有機會考(= =”),也是幫助你決策是否要把題目 co 下去的因素之一,如果明顯 有高複雜度的話那你 co 下去不但拿不到分還浪費時間,又或許你估計錯誤的話你又擔心複雜 度太高而不敢衝一發,所以了學習正確估計複雜度是有實質意義的。

●Section 2 高精度運算(High Degree of Accuracy)

大部分程式語言中的資料型別,其所能儲存的值都有一定的範圍,那若要做的運算超出 這個範圍時該怎麼辦?或所需求精準度很高時該怎麼做?等咻碰嗎?!當然不可能啦,所以 這時候…嘿嘿~就必須自己寫一種資料結構了。

§2-1 大數的資料結構實踐(Data Structure Implementation)

我們分別使用一個陣列來儲存各個位數還有一個變數來紀錄大數。

例如:21474836472147483647 以上用大數儲存就如下

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

7 4 6 3 8 4 7 4 1 2 7 4 6 3 8 4 7 4 1 2

length = 20

※註:因為在 C 的語法中,陣列是由0開始存取,所以實際位數是到length-1。但是以下偽代 碼,陣列都是由1開始到length,這點要注意一下。

§2-2 大數加法(Addition)

一般來說,我們都是利用直式加減乘除法來做運算,所以說以下的運算方法,皆是使用 直式運算方式來思考。

BIGNUM_ADD (bignum a,bignum b) 1 create c as a bignum

2 c.length ← max( a.length , b.length ) 3 for i ← 1 to c.length

4 do c[i] ← a[i] + b[i]

5 for i ← 1 to c.length

6 do c[i+1] ← c[i+1] + c[i] / 10 7 c[i] ← c[i] mod 10

(6)

8 if c[c.length] ≠ 0

9 then c.lengthc.length + 1 10 return c

§2-3 大數減法(Subtraction)

基本上有兩種想法,一種是最基本的借位補位直式減法,也就是平常最常用的方法。

再來另一種,就是利用補數的概念,來算減法。

也就是先求被除數的補數,再利用加法將兩數相加,最後在減掉多出位即可。

例如:

2147483647 – 123456789

= 2147483647 + (10000000000 – 0123456789) –1000000000

= 2147483647 + 9876543211 – 1000000000

= 12024026858 –1000000000

= 2024026858

其中 9876543211 為 0123456789的10補數。而至於實際寫法,就請大家自己練習了。

§2-4 大數乘法(Multiplication)

與直式乘法相同,將兩邊的每一位相乘即可。其中要注意的是 a 的第 i 位乘以 b 的第 j 位,會對應到c的第i + j位,所以我們有以下的寫法。

BIGNUM_MULTIPLY (bignum a,bignum b) 1 create c as a bignum

2 c.length a.length + b.length 3 for i ← 1 to a.length

4 do for j ← 1 to b.length

5 do c[i + j] ← c[i + j] + a[i] * b[j]

6 for i ← 1 to c.length

7 do c[i+1] c[i+1] + c[i] / 10 8 c[i] ← c[i] mod 10

9 if c[c.length] ≠ 0

10 then c.lengthc.length + 1 11 return c

§2-5 大數除法(Division)

既然乘法是連加,那除法可以用連減的方式來解決囉?當然是可以的,只是當商數非常 大的時候,你的連減就會變的非常的慢,而且,我剛剛乘法也不是用連加的不是嗎 XD。

所以我們還是用老方法,直式除法,就像我們平常熟悉的,一位一位去減下來,所以有以下 的代碼。

BIGNUM_DIVIDE (bignum a,bignum b) 1 create c as a bignum

2 b move right for a.lengthb.length digits  b * 10a length b length. - .

3 for ia.lengthb.length downto 0 4 do if a > b

5 then c[i] ← c[i] + 1 6 aab 7 else if ab

(7)

8 then b move left for 1 digit  b / 10 9 c.lengtha.lengthb.length

10 if c[c.length] = 0

11 then c.length c.length - 1 12 return c

別看上面寫的短短的,自己寫過就會知道有多不好寫了(笑)。

§2-6 大數開方(Square root)

大數開方有幾種作法,直式開方,十分逼近,二分逼近。

一般來說,寫直式開方會很麻煩,而且也不見得比較快,所以建議還是寫二分逼近法。

而什麼是二分逼近法?就是對數值去做 Binary Search。而十分逼近則是一位一位去逼近,其 實這兩者速度都差不多,但就容易寫的程度來說,二分逼近會比較輕鬆。二分逼近就是利用 所謂 BinarySearch 的技巧,我們將在遞迴部分作介紹。

§2-7 大數優化(Bignumber optimization)

事實上,大數運算還可以運用一些技巧來進行優化,想一想?宣告的陣列本身的空間,

只記一位會不會浪費掉?因此很明顯的,我們可以利用「壓位數」的技巧來使運行速度增快,

而壓位數的意思就是代表陣列裡一格不只存一位的意思,但是壓位數須要注意的是不要不小 心 Overflow 了,特別是乘法部分。而且壓位數的除法會變得複雜一點點,這就留給你們自 己想了。

※建議:本人不建議直接使用 long long 存取(記憶體使用大並且也比較慢),而較建議用 int 存 7,8 位,

只要運算過程記得形態轉換就 OK 了。

§2-8 小結(Conclusion)

以上介紹的就是基本的大數運算,不過上面只討論正數問題,當遇到負數怎麼辦呢,有 小數點又怎麼辦呢?嘿嘿!這時候就必須考驗你的智慧了!!

剛開始的時候大數常常會被直接當成一種考題,但是到後來會變成一種出題者心機的手 段,所以當看到一個題目時,先估計數值範圍是必要的!但是也不要以為,假如輸出答案是 在 int 或 long long 內就不用寫大數,在運算過程中 Overflow 或 Downflow 是常有的事,

所以大數的適用與否,必須經過適當的估計,當用則用,以免造成無法挽回的後果!!

●Section3 排序問題(Time Complexity)

排序演算法,可以說是學習演算法中,獨立出來的一門學問,自古以來都有許多學者專 精於這塊區域。也往往都是學習演算法第一個碰到的問題,以下就對於排序問題作介紹。

所謂的排序問題,顧名思義,就是將一串雜亂無章的數字,將他由小排到大或由大排到 小。在排序問題中,我們有一連串不同的演算法,有快的有慢的,有容易寫的有難寫的,嘿 嘿,這也就證明了這是多麼古老而多人研究的問題了。

我們下列排序方式,都以輸出遞增序列a1 ≤ a2 ≤ a3 ≤ … ≤ an為主。

§3-1 相關名詞定義(Definition for proper nouns)

a. 穩定性質(Stability)

stable 性質的意思是說,對於一個原本在數列中的兩個數 i<j 如果滿足 ai=aj,那麼在排 序以後的序列也會滿足 ai在 aj前方。這性質好像看起來沒什麼用,但如果要排序的陣列存有

(8)

其他資料時,這種穩定的性質就會顯露他的重要性了。而基本上有 stable sort 的排序有 Bubble Sort, Insertion Sort, Merger Sort……,但其實 instable sort 只要簡單改一下就可以 做到 stable 了。【想想看如何將 instable sort 改成 stable?】

§3-2 各種常見排序演算法(Sorting Algorithm)

a. 氣泡排序法(Bubble Sort)

氣泡排序法可以說是書上最常見的排序法。每次都走過一次整個陣列,把相鄰兩個中較 大的放右邊,較小的放到左邊,如此重複n次就能完成排序的動作。【想想看為什麼?】

<時間複雜度為O(n2)

BUBBLE_SORT (A)

1 for i ← 1 to length[A]

2 do for j ← 1 to length[A] – i 3 do if A[j] > A[j+1]

4 then exchange A[j]A[j+1]

b. 選擇排序法(Selection Sort)

選擇排序法同樣是每次走過整個陣列,不同的是它是在走的過程中紀錄最小的,最後在 將他放到後面去,同樣是要重複 n 次。

<時間複雜度為O(n2)

SELECTION_SORT(A)

1 for i ← 1 to length[A]

2 do ti

3 for ji + 1 to length[A]

4 do if A[j] < A[t]

5 then tj 6 exchange A[t] ↔ A[i]

c. 插入排序法(Insertion Sort)

插入排序法是假設前i-1個已經排序完成,在插入第 i 個數的時候,就只需要一個一個比 較,找到適合的地方放進去,最後輸入完,也就剛好排序完了。

<時間複雜度為O(n2)

INSERTION-SORT(A)

1 for i ← 2 to length[A]

2 do ji – 1

3 while j > 0 and A[j + 1] < A[j]

4 do exchange A[j + 1] ↔ A[j]

5 jj – 1

以上三個為一般常見,為較慢的O(n2)排序法,接下來就要介紹比較快速,也是較常用的 排序演算法。

d. 合併排序法(Merge sort)

合併排序法,用到我們之後所要講的 D&C 來解決排序問題(詳細可見後一節)。這個方 法首先是先將資料一分為二,然後分別排序這兩組資料。最後在合併起來。而此方法最重要 的就是,如何將兩堆已排序好的部份在O(n)時間內合併。

【先想想看,如何將兩個已排序數列在O(n)時間內合併?】

(9)

<時間複雜度為O(nlgn)。>

MERGE-SORT(A, p, r) 1 if p < r

2 then q

(pq)/2

3 MERGE-SORT(A, p, q) 4 MERGE-SORT(A, q + 1, r) 5 MERGE(A, p, q, r)

MERGE (A, p, q, r) 1 n1p q + 1 2 n2r q

3 create arrays L[1..n1+1] and R[1..n2+1]

4 for i ← 1 to n1

5 do L[i] A[p+i-1]

6 for j ← 1 to n2

7 do R[j] ← A[q+j]

8 L[n1+1] ← ∞ 9 R[n2+1] ← ∞ 10 i ← 1

11 j ← 1

12 for kp to r 13 do if L[i] ≤ R[j]

14 then A[k] ← L[i]

15 ii + 1 16 else A[k] ← R[j]

17 jj + 1 e. 快速排序法(Quicksort)

快速排序法同樣也是利用 D&C 的技術來解決問題。

他是先任取一個數當作標記,把比他小的數放他前面,比較大的數排他後面。再將這兩 堆去做排序,就這樣一直遞迴下去,最後就完成排序動作了。

<時間複雜度在一般情況為O(nlgn),在最糟狀況可達O(n2)>

【想想看,最糟情況是什麼時候?】

【想想看,有沒有好方法選擇標記數,使得平均速度快些?(較不會遇到最糟情況)】

QUICKSORT(A, p, r) 1 if p < r

2 then q ← PARTITION(A, p, r) 3 QUICKSORT (A, p, q) 4 QUICKSORT (A, q + 1, r) PARTITION (A, p, r)

1 x A[r]

2 ip – 1

3 for jp to r – 1 4 do if A[j] ≤ x 5 then ii + 1

(10)

6 exchange A[i] ↔ A[j]

7 exchange A[i + 1] ↔ A[r]

8 return i + 1

f. 其他排序法(Other Sorting Algorithm)

以上的排序法是經由比較交換來完成的,我們把它叫做比較排序法(Comparison sort),這種排序法已經被證明其時間複雜度的下限為(nlgn)。

但是對於某些特殊的資料,我們有一些方法可以減少到O(nlgn)以下的複雜度,以下介紹常見 幾種。

(f.1)計數排序法(Counting Sort)

計算每個數字出現過幾次,然在由小到大的填入原本的陣列。

<時間複雜度為O(n+m)> 其中m為輸入數值範圍。

COUNTING-SORT(A, k)

1 initialize C[1…m] = 0 m is the upper bound of C 2 for j ← 1 to length[A]

3 do C[A[j]] ← C[A[j]]+1 4 t ← 1

5 for i ← 0 to m 6 do while C[i] > 0 7 do A[t] ← i 8 C[i] ← C[i] – 1 9 tt + 1

這是一個很快的算法。可是缺點非常耗記憶體,且數值範圍大的時候沒辦法使用,假如 有負數也必須要做修正,當然這也只適用於整數排序而已。也因此常常是用在出現於將小數 據範圍的正整數做排序(m=O(n))。

(f.2)基數排序法(Radix Sort)

先照低位數排序,再照次低位,再第三位…這樣下去,這跟一般想法不太一樣,他排序 順序是低到高,平常想到都是高到低,所以對每位數排序時,就必須要有某些性質。【想一想?

哪些性質?】

<時間複雜度為O((n+B)logBm)>其中B為進位制,m為數值範圍

基本上這些特殊的線性時間排序法(Linear Time Sorting)一般來說是不太會用到的,往往 只有當遇到時間卡很緊等等的時候才會真正拿來寫。不過這些想法基本上必須要了解一下,

或許以後在處理問題時,也可以利用一些類似的想法來處理,例如說 Edge List 的製作(詳 見往後章節)就可以用到 Counting Sort 的想法,而 Suffix Array 的倍增算法中也會頻繁用 到這兩種排序法。

§3-3 內建的排序函數(Sorting Library Function)

(1) qsort

void qsort( void *buf, size_t num, size_t size, int (*compare)(const void *, const void *) );

C 內建的 Quick Sort,需要注意的是在 compare 函數裡要將 void 轉成所需的資料型態。

(2) sort

void sort( iterator start, iterator end );

(11)

void sort( iterator start, iterator end, StrictWeakOrdering cmp );

STL 內建,使用的是 intro sort,最差與平均情況都是 O(n lg n),intro sort 其實就是多 種排序演算法的混合,根據不同算法在不同大小時候的表現選擇算法,使得它的速度比 qsort() 快。

§3-3 排序的應用(Sorting Application)

排序是基本而重要的,在各種問題中,常常預處理都跟排序有關係,雖然實際比賽的時 後大部分都不用自己 co 排序,只要用內建函式就好了,不過,是希望大家除了使用內建函式,

更重要的是要知道其真正想法,因為有些題目會必須利用到排序的想法,例如說下面這兩題:

問題一《逆序數對》(TIOJ1080,逆序數對)

對一個數列S來說,若S的第isi與第jsj符合si > sj,並且i < j的話,那麼我們說(i, j) 是一個逆序數對。請問給定數列S,請問總共有多少個逆序數對呢?

問題二《第 k 大的數》(TIOJ1364,蛋糕內的信物)

對一個數列S來說,要如何在O(n)的時間內找出第k大的數呢?

像這種時候就沒辦法使用依賴內建函式了,所以說,練習自己寫O(nlgn)的算法就變的很重要 了。

相關題目:TIOJ 1205, 1208, 1287

●Section 4 幾個常用的數學演算法(General Math Algorithm)

§4-1 最大公因數(Greatest Common Divisor)

最大公因數,是在數論相關問題中,常常會用到的一環,當遇到關於數學的程式題時,

常常會用到最大公因數,那麼,怎麼樣快速而有效的求出最大公因數呢?

有一個方法是,對於每個小於a,b的數,去試試看能否整除兩者,其中最大的便是最大 公因數了,但是,這樣時間是O(min(a,b)),還是不夠快。

那另一種方法是說,將兩者因數分解,在去比對公因數,不過這樣子不但 code 起來麻 煩,也不會快多少。

事實上,這個問題可以回溯到古希臘時期……,Euclid 發明了一種方法,叫做 Euclid 輾 轉相除法,他利用公因數的性質,快速的求出了最大公因數d ( , ) ( , -a bb b ma) 其中m, 所以其實又可以寫成d ( , ) ( , mod )a bb b a ,你看出遞迴關係了嗎?試試看吧。【可以試著 去思考什麼樣的數字會形成最差狀況?而其複雜度又為多少?】

GCD 應用:

利用 Euclid 輾轉相除法,我們在求解例如 ax+by=d 的時候,可以很容易的求出滿足條 件的 a 與 b。而這個的應用對於解決數論相關問題有重要的影響,當要求模的逆時,就可以 使用這種做法了。【想想看,怎麼求?事實上只要在函數中紀錄一些東西,就可以輕鬆算出來 了。】

§4-2 質數(Prime Number)

相同的,常常在遇到數學相關問題時,都會用到質數,那如何快速求出質數,就變成非 常重要了。因為質數分布是非常離散而無規律的,似乎沒有什麼快速的捷徑法,那麼要如何 加快求質數的效率呢?利用質數的定義,我們可以知道,要驗證一個數是否為質數,可以對

(12)

於2到 p

 所有的整數,如果能除盡他,就代表他不是質數【想想為什麼?】,看起來這個 方法還不錯,不過,假如你是要驗證一個數是否為質數,這樣還 OK,但是一但你要建立質 數表,這種方法就行不通了。

因此,我們又回到古希臘時期,發現,原來兩千多年前就有人想出如何建立一個質數表 了,這叫做 Eratosthenes 篩法,事實上他只是把我們剛剛的想法反過來而已。簡單的說,他 是把每個質數i當成篩子,把i的倍數全部過濾掉,當你對於小於 n的質數都篩完後,同 時代表你找出所有小於等於n的質數了。

ERATOSTHENES(n)

1 make array p[1...n] with value = 1 initialize 2 m[1] ← 0

3 for i 2 to sqrt(n)

4 do if p[i] = 1 i is a prime number 5 then for j i × i to n step i

6 do p[j] ← 0

【思考一下為什麼第二層迴圈起點為 i*i?】

因數分解

要怎麼質因數分解呢,當然就是對於小於他的質數,去除除看,過程中紀錄一些資訊就 完成了。質數還有許多應用,特別是之後在 Hash 表的時候可以拿來用,等到往後章節再做 介紹。

●Section 5 遞迴與分而治之(Recursion & Divide and Conquer)

「以此要領,重複實施,即得答案。」-pangfeng

分而治之,顧名思義,就是把問題分成幾個小問題,然後再一個一個去解決的一種方法。

為什麼要用遞迴呢?通常來說,對於問題給定輸入值很小的時候,我們會很容易的解決他,

而當我們能利用這些容易解的小小子問題,來將較大母問題解決時,我們便可以選擇使用遞 迴演算法。通常寫出遞迴的 code 都不會太長,但是,遞迴常常會出現重複子問題、遞迴過 深等種種的問題,最嚴重的,便是他的複雜度,往往都是指數級成長,這時候,如何改善遞 迴式,或適當的優化,就變得頗重要了。

以下就舉幾個例子,來讓你們了解遞迴與分而治之的用途。

§5-1 簡單的例子(Easy Example)

a. 階乘(Factorial)

明顯的我們求N!可以化作(N 1)! ( N 1) N!而當N = 0時0! = 1,故我們可以寫出以 下遞迴式。 ( ) 1 , 0

( 1) , 1

f n n

n f n n

  ,等寫出了式子,接下來要 code 就簡單多了。

如這個例子就可以寫成:

FACTORIAL(n) 1 if n = 0

2 then return 1

3 return n × FACTORIAL(n - 1)

(13)

b. 費氏數列(Fibonacci Sequence)

相信大家都知道費氏數列表示可以表示成F n( 2)F n(  1) F n( ),特別的有

(0) (1) 1

FF  。

寫出遞迴式後,寫出 code 就是輕而易舉的事了。

FIBONACCI_NUMBER(n) 1 if n ≤ 1

2 then return 1

3 return FIBONACCI_NUMBER(n - 1) + FIBONACCI_NUMBER(n - 2)

§5-2 遞迴的時間複雜度(Time Complexity of Recursion)

參考《Section1 -3 主定理》的介紹。

§5-3 問題討論(Problem Discussion)

相信大家對遞迴有一定的了解了,但是,以上兩題都比較偏向數學,接下來的想法與實 踐,就是比較困難一點點了,所以請大家踴躍提出想法與熱烈討論。

問題一《河內塔》(TIOJ 1355, 河內之塔-蘿莉塔)

現在有三根柱子,一開始在第一根柱子上有n個圓盤,由大到小疊好,請印出所有搬動圓 盤步驟,把所有圓盤從1號柱搬到3號柱子,且移動過程中大圓盤不得疊在小圓盤上。

問題二《冪運算》(Uva 374, Big Mod)

給定B, P, M,試以O(lg P)的複雜度算出BP mod M

問題三《Binary Tree Reconstruction》

給定一棵二元數的 pre-order 以及 in-order,求 post-order。

前序(pre-order): 根節點左子樹右子樹(ABCDE)

中序(in-order): 左子樹根節點右子樹(BADCE)

後序(post-order): 左子樹右子樹根節點(BDECA)

問題四《Joseph Problem》

n個人編號1至n圍成一圈,現在由第一個人開始數,每數k個人就把第k個人殺掉,並 由下一個人繼續開始數。試問最後會活下來的是編號多少的人?

問題五《八后問題》(Uva 750, 8 Queen Chess Problems)

在西洋棋的棋盤中你可以放置8個皇后而且彼此都不衝突(就是都不能吃到對方)。給你某 一個皇后的位置,請你寫一個程式來輸出所有這樣可能的安排。

問題六《Savage Garden》(Uva 10230, Savage Garden)

在一個長為2n的正方形盤面上,挖去了一個空格。試用「L 形」拼圖(總共 3 格所構成的)

把整個盤面拼滿。

問題七《三色多邊形》

有一個N邊形,且每個頂點上都有紅綠藍三種顏色其中一種,且兩兩相鄰頂點顏色互不相 同,三種顏色一定都會出現!現在要你畫出許多對角線將此多邊形分割,分割成N - 2個三 頂點恰為三種顏色的三角形,而任兩條對角線只可能相交在頂點上,或者不相交。

問題八《丟失的數》(TIOJ 1358, 丟失的數)

現在有n-1個相異數,範圍是0 ~ n-1。請問0 ~ n-1中沒有出現在這n-1個數的是哪一個數 字?每次可詢問第i個數二進位表示法的右邊數來第j位(Bi,j),請使用盡量少次的詢問。

A

B C

D E

(14)

問題九《最近點距對》(TIOJ 1500, Clean up on aisle 3)

給你平面上 n 個點(n ≦ 106),問你說所有點對中,距離最近的兩個為何?

§5-4 二分搜尋(Binary Search)

在一個已序的數列中,找出一個給定的數的位置。

基本的想法就是利用數列「已經排序好」的這個性質,每次都將想要找的數和正中央的 那個數比較,並依照比較結果可以知道要找的數是在左半還是在右半,這樣一直重複下去,

可以在 O(lgn)的時間內找到要找的數。

BINARY_SEARCH(A, x, y, key) 1 while x + 1 < y

2 do m ← ( x + y ) / 2 3 if key < A[m]

4 then y m 5 else xm 6 if A[x] = key 7 then return x

8 else return NOT_FOUND

【想一想】:當有相同的元素時,題目要求要找出 (1)最前面的一個 (2)最後面的一個,要怎 麼樣處理呢?

但是如果你要的東西沒排序好怎麼辦呢?很明顯的,這時候排序的算法就派上用場了。

因此二分搜往往都會已排序來當預處理的程序。

二分搜尋看起來頗簡單呀?我幹嘛要特別拿出來講呢?那是因為二分搜尋實在是一種很 美妙的東西,能夠把複雜度 O(d)降到 O(lgd)。適當的使用你就會發現人生更美好。

二分搜出現的情形有很多種類型,以下就來看看簡單的幾題:

問題一《Dark Rank》(TIOJ 1360, Dark Rank)

給你一個 m×n 的表格,我們想找出每橫排中最大的數在哪,而我們已經知道這個表格有 個性質,就是每橫排最大值會在前一排最大值的右邊。每次可以詢問任一格的值,請用盡 量少的次數來找出每橫排的最大值位置。

問題二《尋找神秘的胖子》(TIOJ 1525, 尋找神秘的胖子)

給某個圓中的一個點,但此圓的資訊不告訴你,不過你每次可以尋問在平面上某個點是否 在圓內,現在希望你再利用盡量少的次數,詢問出此圓的半徑。

基本上二分搜的應用很廣,往往會結合往後所學東西(如 Greedy, DP, Simulation……),往 往都是假設答案值後,再去驗證這個答案是否有辦法滿足條件,最後再對這個答案值做 Binary Search。但實際應用必須等學到往後東西才有辦法說明,因此在此就先不提及。

●Section 6 離散化(Discretization)

離散化在解決很多問題時是很常用的技巧,當你要處理的問題資料量很小,但是資料分 步很分散的時候,就可以使用到離散化的操作。其基本思想就是在眾多可能的情況中「只考 慮我需要用的值」。

下面舉個簡單的例子來說明:如果現在在一條長長的走廊上有10個垃圾,但是從第一個 到最後一個垃圾的距離有一公里,現在我想用掃把將垃圾掃掉,是不是要從第一個開始慢慢

(15)

掃到最後一個呢?(意思是整條走廊都被掃到),很明顯的,廢話當然不用呀!我所要做的就 是走到第一個垃圾的地方掃起來,再走到第二個地方掃起來…,最後走到最後一個垃圾,並 掃起就好了。

由上面這個例子來看,我所需要考慮的就只是「我所要掃的垃圾的位置」,而不是整條走 廊。這就是一個很好的例子,相信大家對離散化也有些概念了。

而離散化實踐很簡單,通常我們會將整個資料先利用O(nlgn)時間排序,並給排序後的值 標上記號,以後處理資料則照標號就好了。但要注意的是,因為是使用預處理(pre-process

的技術,因此對於動態存取的問題則沒辦法使用離散化來處理。

而其要處理的問題往往都與計算幾何有關(當然也並不全然),總之下面幾個問題讓大家 思考一下:

問題一《Skyscrapers》

一條直線上並排著 n 棟建築物,每棟建築物有他的高度 hi。因為最近淹大水,所以如果建 築物高度不夠高則會被水淹沒。現在給你 d 天淹水的高度,問你說每天會有多少連續段建 築物不會被水淹沒?(n, d <= 106

問題二《Shaping Regions》(Usaco3-1, Shaping Regions)

現在有一張白紙,並且要將不同大小的色紙貼在白紙上(n<=1000),假如兩塊矩形面積 有重疊,那後放的一定在上面,現在給你每個色紙大小與放的順序,問你說最後每個顏色 覆蓋多少面積?

(16)
(17)

Chapter 2 基礎資料結構(Basic Data Structure)

●Section 0 什麼是資料結構?(What is Data Structure?)

資料結構,也就是一種特別的資料儲存方式,就像 int, long long, float, double……等 等,只不過算是自己自訂型態的一種結構,而這種結構,能夠幫助我們加快執行的速度。當 然,有些資料結構的意義不在於他的實做,甚至只是他的想法。所以,其實資料結構並不一 定是自己定義的一種型態,也可能是使用既有的資料型態幫助實踐,以下我們就來介紹幾種 常見的資料型態。

●Section 1 陣列(Array)

陣列,是大部分程式語言都內建的一種資料結構,也是最基本的,所以其功用在此不再 多言。重要的是,array 是各種資料結構應用的基礎,如果不會寫 array……,嘿嘿,那就等 著咻碰吧 XDDD(茶)。

●Section 2 串列(Linked-list)

Linked list,主要想法是指一群散佈在各個地方的 node,藉由某些方式連結,而通常是 一串藉由「指標」連結在一起的一種自訂資料結構。一般來說有分雙向與單向,雙向串列 (doubly linked list)如下圖:

tail[L]

key next prev

/

2 3 8

/ 5

head[L]

Linked list 是個有彈性的動態結構,而其主要與 array 的操作不同是:Insert 和 Delete 操作,我們只需要 O(1)時間,但是這並不包含 Search 的時間,要 Search 第 k 個元素,就必 須用掉 O(n)的時間。

§2-1 串列的實踐(Implementation)

串列的實踐,最簡單有兩種方法:

(1) 指標直接實踐:明顯的就是定義個 struct,用指標來連結下個 node,每增加或刪除一個 節點就 new 或 delete 一個空間,這樣也是最省空間的方式。

(2) 使用陣列模擬指標:預先開好個結構陣列,其中有兩個變數,分別是是存取 next 與 pre 的索引值(index),例如下圖:

L 4

8 3 5 2

7

3 4 3

1 7 /

/

8 7 6 5 4 3 2 1

prev key next

※建議:個人是比較推薦第二種,因為配置與刪除記憶體空間速度較慢,且較容易發生不預 期的錯誤。

(18)

§2-2 哨兵(Sentinels)

當用指標來實踐 linked list 時,頭或尾插入或刪除元素,會因為 NULL 的狀況需要考慮,

所以造成許多麻煩,這時我們可以使用一個叫做哨兵的東西,也就是多創個新的節點,來代 表 NULL,如下圖:

2 3 8

5 nil[L]

Linked list 應用是非常廣的,建構一棵 tree,一個 graph,都是常用的手法,或者是在 某些模擬(simulation)題中,也是很實用的。基本上你要將 linked list 看成跟 array 一樣 的基本結構,這兩個資料結構幾乎是所有資料結構的基礎,常常實踐都必須靠這兩者。

事實上,Linked list 一開始寫的時候,並不會那麼容易上手,因為指標的想法並不是說 那麼容易,不過當你多練習幾次之後,就可以漸漸上手。

●Section 3 堆疊(Stack)

堆疊,顧名思義,就是一種將各個元素堆在一起的一種結構。想像你把一堆書疊在一起,

假如你想拿一本書,你就必須從上面開始拿;假如你要放一本書,你不會插在中間,一定是 往上疊。是的!堆疊就是像一疊書的資料結構,並且支援兩種操作,丟入(PUSH)、丟出

(POP),如下圖:

Stack 結構的實作其實只需要用到 array 就夠了,因為只有一個出口,你只要記錄 top 的位置就夠了。

對於這種先進後出,後進先出的機制,我們稱作FILO(First-In-Last-Out)。你會想說這 麼簡單的結構,有啥鳥用?事實上對於解決許多問題,都會用到 stack 的想法,例如說,平 時在 recursion 的時候,系統就會建立一個 function stack,以存取函式資料內容,所以了,

當你遞迴過深,記憶體將無法負荷!

以下幾道問題,就是利用 stack 的想法來解題。

問題一《火車調動》(Uva 514)

堆疊市的火車站長得如右圖一般。火車車廂由 A 處依1, 2, 3, …, n的順序進站。如今給定一個1到n的重排,試問是否能在 station 處經過一些調度,使火車依照這樣的重排順序出站。

問題二《合法括弧》

有若干種括弧,如(), [], {}, <>。給定一串括弧,試問其是否為合法?(能不交錯地配對)

例如({<()[]()>[]})是合法的,([]}和<>>和[[]都是不合法的。

註:長度為 n 由’(‘和’)’構成的合法括弧字串──卡特蘭數 Catalan Number。

top

top

PUSH

POP

(19)

問題三《運算式》(Uva ACM 727,Equation)

將一個中置運算式 in-order改成後置運算式 post-order。(運算元最多只有一位數)

中置運算式:其實就像我們一般寫的式子,可以有括弧,如(3+2)5、((1+2)(3+4)+5)(6+7) 後置運算式:運算子放在運算元後,沒有括弧,如32+5、12+34+5+67+。

問題四《排隊視線問題》(TIOJ 1176,Cows)

給定一串數列有n個數,試在O(n)時間求出每個數在它之後第一個比它小的數是多少?

例如對數列2, 0, 7, 8, 3, 6, 4, 5, 4, 1,求出的結果是0, -, 3, 3, 1, 4, 1, 4, 1, -。

問題五《最大矩形 part1》(TIOJ 1370,超大鏡框設置)

現在有一堆不同高度的建築物排列且連在一起(如右圖),現在你想 要用相機把這群建築物拍下來,而且因為天空太髒了,所以不想拍 到天空,假設相片的長寬可以任意伸縮,試問最大可以拍出多大面 積的照片?

挑戰一《最大矩形 part2》(USACO Section 6.1, A Rectangular Barn)

在一個零一矩陣中(長度不大於1000),找一塊最大的長方形,裡面全部都是 0。

試問這樣的長方形最大的面積為多少?

挑戰二《最大矩形 part3》(TIOJ1283,超大畫框設置)

現在有個這樣的框架:只任何一個水平線截出的框架區段是連續 的,並且由上往下該區段只會往右移動的框架(如左圖)。

現在你想找到一個最大面積的矩形位置放置你最喜愛的一幅畫。

挑戰三《Cartesian Tree 笛卡耳樹》(TIOJ 1549 胖胖國小的疊羅漢)

給你一個陣列 A[1…n],你希望建立一棵樹滿足以下性質:

這棵樹的 root 是 A[1...n]中最小的值。

而 root 的左子樹是 A[1...root-1]所形成的 Cartesian Tree。

而 root 的右子樹是 A[root+1...n]所形成的 Cartesian Tree。

(20)

●Section 4 佇列(Queue)

§4-1 佇列(Queue)

佇列(Queue),就是一種像排隊的機制,先來的人先買票入場,後來的人後買票入場,

也就是 FIFO(First-In-First-Out)。因為 queue 列有兩端,所以我們分別用 front 跟 rear 來 記錄 queue 前端與後端。

基本操作有兩個:

push(enqueue):在 queue 前端插入一個新元素。

pop(dequeue):將 queue 後端元素 pop 掉。

就如右圖所示,push 就是將 front++,pop 則是 rear++。

實踐則是可以使用陣列或串列,並使用兩個指標指向 front 跟 rear,差別在於每次 pop 時陣 列無法釋放記憶體,而串列則可以。但是這樣不是很浪費記憶體嗎?因此我們可以用環狀的 結構叫環狀佇列(Circular Queue),來作實踐,顯而易見的,如果要實踐環狀佇列,只需要 使用 mod 操作便可達成。然而因為空間大小是固定,所以假如一開始大小還是不夠大,即 使是環狀最後還是會爆炸的。

queue 的應用方面,大部分是在 graph 上,之後教到的 BFS,就會使用到 queue 這樣 的資料結構。另外像電腦鍵盤跟印表機也都是使用 queue 來記錄作業優先順序的。

§4-2 雙向佇列(Double-ended-queue,Deque)

主要結構與 queue 相同,唯一不同者就是 deque 的兩邊都可以 push 跟 pop。對於往 後遇到的一些能利用題目單調性來減少時間的算法,有很重要的幫助。

例如說現在有一段區間,假設你想至左而右詢問不同的區間的最大值為何時,如何使用 O(n)將所有問題回答呢?(假設第 i 次詢問的區間是[Li, Ri],那其中至左而右的意思就是對 所有 i<j,滿足 Li<=Lj且 Ri<=Rj,如下圖)

在 deque 中要維護的性質有兩個:

A. 對於原區間中的兩個數 ai、aj且 i<j,如果兩數都出現在 deque 中,那 ai也會在 aj左方,

簡單的說就是原區中的順序到了 deque 中也不會改變。

B. 在 deque 中的値必定是「非嚴格遞減」的。也就是(dequerear ≧ dequerear+1 ≧ ··· ≧ dequefront-1≧ dequefront)。

有了這樣的約定,我們可以知道 deque 中最左端的値(dequerear)必是此區間最大值。

而為了維護整個 deque 的性質,我們可以做以下兩個操作:

(1)詢問的區間右界右移:

像 stack 在維護由下而上遞減性質一樣,假設新加入的値是 ai。 如果 dequefront>ai,則直接 push_front(ai );

如果 dequefront ≦ a,則先 pop_front(ai i ),直到 dequefront>ai,再 push_front(ai )。

可以這樣做是因為對於所有右界在新元素左方的詢問已經問完了,所以 pop 掉數字小的

(21)

元素情況一定不會比較差。

(2)詢問的區間左界右移:

這就比較簡單了,如果最左方的元素剛好對應到要刪掉的元素,那就可以 pop_back 了。

§4-3 優先級佇列(Priority Queue)

雖然名為 queue,但是這個只是借用 queue 的名字,事實上跟 queue 相差甚遠,它並 不是 FIFO,而是每次 pop 元素,一定會是佇列中「優先級最小」的元素,至於實踐上,通 常是用 heap 來實踐,這將在之後課程會介紹。

stack &queue 相關題目:

ACM 127, 271, 239, 442, 511, 514, 727

TIOJ 1012, 1063, 1237, 1283, 1320, 1566, 1574

●Section 5 樹狀結構(Tree)

§4-1 何謂樹?(What is Tree?)

樹是一種很特殊的圖(Graph),基本上就是一棵上下顛倒的樹,相信大家應該看過樹狀 圖之類的東西吧?而在資訊中常用的樹大概就是長那樣,如下圖:

§4-2 定義(Definition)

樹原本定義為一個連通但其中不含任何的圈的圖,但是卻有以下性質,且這些性質與G 是一棵樹其實是等價的:

(1) G是一個樹。

(2) G為連通且沒有圈。

(3) G為連通且| E | = | V | - 1。

(4) G中任兩點恰好有唯一一條路徑連接。

(5) G為連通,但是去掉任何一條邊後都會變成不連通。

(6) G中沒有圈,但是加上任何一條邊後都會變成有圈。

這裡我們說的樹一般指的是「有根樹」(Rooted Tree)。有根樹就是將其中一點指定為根節點

Root),而其他點的深度定義就是他和根節點的距離。

對於以上定義不太清楚沒關係,圖的詳細介紹將會留至《Chapter5-圖論》再做介紹。

§4-3 相關名詞定義(Definition for proper nouns)

父節點(parent node)、子節點(child node):如果a和b有邊相連,且b的深度比a的 深度多一,則我們稱a是b的母節點,b是a的子節點。一個節點可能有很多子節點,但只會 有一個母節點。

(22)

兄弟節點(sibling):有相同母節點的兩個節點。

度數(degree):一個節點的子節點數目。

葉子(leaf):沒有子節點的節點。

高度(height):我們稱一棵樹中節點的最大深度為該樹的高度。

k元樹(k-ary tree):如果一個樹每個節點的度數都不大於k,則稱此樹為一個k元樹。

子樹(subtree):以某一個節點為根節點,並取所有他以下的節點和邊形成的樹。

森林(forest):就是很多很多棵的樹(其實就是沒有圈的圖)

§4-3 樹的表示法(Representation of Tree)

要如何來儲存一棵樹呢?我們先考慮最簡單的情況:二元樹,這時可以用兩個指標分別 儲存左子節點和右子節點的位置。

Struct Node{

DataType data;

Struct Node* ParentNode;

Struct Node* LeftChild;

Struct Node* RightChild;

};

或是假如你知道了這棵樹是完全二元樹(complete binary tree),或是它的深度不深,沒 有很歪斜,那麼可以較簡單的用陣列來儲存一棵樹。令A[1]為根節點,對於一個節點A[n],

它的子節點是A[2n]和A[2n+1],母節點是A[n/2]。

用同樣的方法,假如我們知道了一個樹是k元樹,也可以用一個指標陣列來指向它的所 有子節點。

Struct Node{

DataType data;

Struct Node* ParentNode;

(23)

Struct Node* ChildNode[k];

};

假如這棵樹沒有任何的限制的話,有一個叫做Left-child-right-sibling的方法可以來記錄 它。就是對於每個節點,記錄它最左邊的子節點,和他右邊相鄰的兄弟節點。雖然轉換會較 為麻煩,但這個是紀錄任意樹的比較合理的方法,尤其是它需要的記憶體用量會比上面那種 k元樹紀錄法還少的多。

Struct Node{

DataType data;

Struct Node* ParentNode;

Struct Node* LeftChild;

Struct Node* RightSibling;

};

§4-4 二元樹的走訪(Traversal of Tree)

對於一個二元樹,我們有三種最常用的方法可以走過這棵樹所有的節點。

1. 前序表示法(pre-order)根節點 -> 左子樹 -> 右子樹 2. 中序表示法(in-order)左子樹 -> 根節點 -> 右子樹 3. 後序表示法(post-order)左子樹 -> 右子樹 -> 根節點

這三種方法都可以用遞迴來實現,而有趣的是,只要知道任意兩種表示法,就可以確定這 棵樹進而得到第三種表示法,這在遞回那節時已提過,因此就不多做說明。

§4-5 二元搜尋樹(Binary Search Tree - BST)

二元搜尋樹是一種二元樹,並且滿足對於任何一個節點,它的左子樹的節點的值都比它 小,它的右子樹的節點的值都比它大。而對二元搜尋樹,我們可以進行幾種操作。

(1) 尋找元素(Find):

和二元搜尋的概念一模一樣,從root開始,現在的數值比要找的數值大就往左走,如果比 較小就往右走,相等的話就找到了。

(2) 插入元素(Insert):

和尋找元素的時候一樣,從root開始找,直到走到一個葉子為止,然後把它加在該葉子的 子節點。

(3) 刪除元素(Delete):

刪除元素時比較麻煩,如果要刪除的節點是x,則有三種情況:

* 如果x是leaf,就直接刪除就好。

* 如果x只有一個子節點,則把該子節點連到x的母節點,並且刪除x。

* 如果x有兩個子節點,我們選取左子樹最右邊的一個元素y替代x,並且刪除y。

如果二元搜尋樹的高度是h,則上述三個操作的時間複雜度都是O(h)。

我們知道,如果一個二元樹是完全的,那麼它的高度為O(lgn),所以上述演算法的複雜度 就是很快的O(lgn)。但是這樣建立出來的二元搜尋樹往往是不完全,甚至於很不平衡的,在 最糟糕的情況下,全部元素排成一個直線時,h就變成O(n)了。

為了預防這種情形,我們可以在建立時將增加點的順序用成隨機的,可以證明隨機後的 BST的深度會是O(lgn)的,但是假如你的輸入不是一次給完的話(也就是要求要是在線算

(24)

法),那麼這招便不可行了。

而其實還有各種的平衡樹(balanced tree),例如AVL-tree、Red-Black tree、Treap、Splay 等等,但是因為這些的方法都很複雜,所以在這裡就不介紹了。

其實樹還有很多的應用,像後面會講到的Heap就也是一種二元樹,Disjoint Set是一種森 林等等。所以樹是相當相當基本且重要的一種資料結構呢!

問題ㄧ《約瑟問題》(TIOJ1383,約瑟問題)

有 n 個人圍成一圈,從第一個人開始數,每個人的椅子都被裝上"強制脫出裝置",現在給 你 n 個數,代表每一次要數幾個人之後彈出,請問你人被彈出的順序?

相關題目:TIOJ 1106, 1108, 1213, 1214

Referensi

Dokumen terkait

甲國與乙國為世界上惟二的國家,平時彼此並沒有進行貿易,後來兩國政府在人民的壓力 下,試著開放某產品的國際貿易,結果很快地兩國就因為貿易逆差而有紛爭,下表為兩國 貿易前的相關資料。請問:關於此紛爭的敘述,下列何者說法最符合兩國的情況? 甲國 價格 10 20 30 40 需求量 50 40 30 20 供給量 30 40 50 60 乙國 價格 20 30 40

1 崑山科技大學 「110 年高中職師生空間設計競賽與教學精進研習」 線上研習 活動簡章 一、活動目的: 崑山科技大學空間設計系第一屆「崑山盃全國高中職學生創意空間設計競賽」,獲 得學校師生熱烈響應,共有來自全國12所高中職學校參加,參與競賽之高中職生作品 達百餘件,在歷經兩階段評選後,共有16組學生作品入圍決賽,競爭達7萬6仟元的

課程教學活動設計 (主題課程架構表中各教學單元均應規劃於此表格中) 為避免網路傳輸以致檔案遺失,建請參賽團隊請先填寫此表後,再逕自登入報名網站複製此表資料。 勿分批上傳,以免檔案覆蓋。所有欄位皆必填 單元名稱 請依據教案自行命名 設計理念 運用游於藝主題以解決真實問題為目標:簡述發現了什麼的問題,如何結合展覽

SPAO 賣場裡的商品⼤大多兩兩周更更新⼀一次, 有些商品甚⾄至每天更更新 為滿⾜足消費者喜新厭舊的⼝口味,除了了速度,還需 要有⾜足夠的數量量 SPAO 不論男/女裝,都有⻄西裝、內衣、 Basic 和⽜牛仔四⼤大系列列;光是 T-shirt 就有 60 個系列列,⼀一 年年約推出超過 300 款 T-shirt... 推廣⾏行行銷策略略 互動促進買氣

進入中學生網站後台管理端,刪除有下 列問題的檔案: 甲、無法開啟或有亂碼之作品。 乙、投稿作品之封面格式不符規定有 插圖、或未自成一頁、或內容不齊 全。 丙、作者資料有誤超過三人或不同年 級參賽。 丁、投稿類別、年級錯誤者。 戊、格式不符四大架構壹、前言。貳、 正文。參、結論。肆、引註資料。 己、一人投稿二篇以上或相同作品投 稿不同類別。

122 明道文藝 405 青春筆耕│華文中學生文學交流 談,我卻驀地想通了。不管是舞蹈或成績亦或人生的過程皆如坐雲霄飛車, 忽起忽落,忽生忽死,有好就有壞,有快樂就有悲傷,有開始就有結束。誰 不陷溺於這兩端的苦樂參半中?我們的心如繩,遭受痛苦、挫敗時往往是自 己綁緊自己。失敗並不可怕,可怕的是挫折後一蹶不振,從此消沉度日。