此筆記用于指導(dǎo)初學(xué)者閱讀。原文在此!,出于便于交流的考慮,內(nèi)容重放在此。由于作業(yè)部落支持LaTex公式,所以,更加清晰。更新也以作業(yè)部落的鏈接為主。《算法導(dǎo)論》到底有沒有用?有啥用?
寒假了,必須各自回家。女朋友對小明說,我會(huì)想你的,怎么辦?小明說,沒事,這個(gè)你拿著,想我就狠狠地看看:一本《算法導(dǎo)論》。女朋友接著問,哪你想我怎么辦?小明嘿嘿一笑說,沒事,我還有一本!《算法導(dǎo)論》......
第一部分 基礎(chǔ)
第一章 算法
1、什么是算法?(An algorithm is thus a suquence of computational steps that transform the input into the output.)
大家還需要去找答案,注意要進(jìn)一步歸納總結(jié)。
術(shù)語:
computational problem(計(jì)算性問題)
decisional problem(判定性問題)
計(jì)算領(lǐng)域三大問題:什么是計(jì)算機(jī)?什么問題可計(jì)算(可解)?解某個(gè)問題需要多長時(shí)間、消耗多少資源?
2、算法能解決什么問題?
書本給出了很多實(shí)例,說明算法能解決很多很多問題,似乎要顯示說算法無所不能。其實(shí)我們必須關(guān)心,什么問題算法不能解決。
擴(kuò)展:停機(jī)問題(什么是停機(jī)問題,有什么重大意義?)
3、什么是數(shù)據(jù)結(jié)構(gòu)?
鏈表、隊(duì)列、棧、堆,二叉樹等等,需要慢慢填空......
4、難解問題
概念: P、NP、NPC,不理解可跳過,記住P是多項(xiàng)式時(shí)間內(nèi)可解的問題集合即可。
思考: 難題為什么是難?有什么已知的難題?
換一個(gè)角度思考: 什么是容易解的問題?
術(shù)語: reduction(規(guī)約),這不容易的。主要用于對NPC的描述。
5、算法的效率
用實(shí)際的運(yùn)算給出直觀的感受,讀者必須去感受。
比較:log(N),sqrt(N),N,N^2,2^N ,N!
6、算法以及其他技術(shù)的討論
討論內(nèi)容,有很多很多“?”,不是難點(diǎn),但需要思考,對初學(xué)者,這種思考往往是提高水平的正確路徑。
7、擴(kuò)展思考與檢索
為什么說算法是計(jì)算機(jī)科學(xué)的核心內(nèi)容?算法的研究起源在哪里?什么時(shí)候開始人們認(rèn)為:算法奠定了計(jì)算機(jī)科學(xué)的基礎(chǔ)?
第二章 算法起步
2.1、插入排序(以實(shí)例帶動(dòng)講解)
術(shù)語:Loop invariants(循環(huán)不變量?)
這是CLRS分析算法的一種重要技巧,要認(rèn)真理解為何引入這個(gè)概念,以后如何運(yùn)用這個(gè)概念。
偽代碼的表示法,簡單工作!
2.2、算法分析模型
2.2.1 重點(diǎn):RAM
注意:本書講解RAM以犧牲準(zhǔn)確性為代價(jià)獲得更通俗易懂的表達(dá),原始的RAM模型可沒這么簡單。但是我喜歡這種講解。
擴(kuò)展思考:除了RAM模型還有什么分析算法的模型?
Additional Comments about RAM: 由于不少同學(xué)紛紛表示對RAM的不理解,我多說幾句。首先聲明,不理解RAM模型并不會(huì)嚴(yán)重影響后續(xù)閱讀,所以你可以非常放心目前的“不理解”。但我依然建議大家看看多兩遍課本這并不冗長的兩頁說明講解。以下是一些要點(diǎn):
- RAM模型是用于分析算法的一種計(jì)算模型;并非真實(shí)的計(jì)算機(jī)。
- 一種計(jì)算模型通常會(huì)包括:指令、內(nèi)存、數(shù)據(jù)類型、指令調(diào)度等內(nèi)容。然而,書本并不講解這些,反而是強(qiáng)調(diào),不要濫用RAM模型。言外之意是,這些我就不多說了,只要不按常規(guī)來,不亂出牌,基本就ok...... (體會(huì),2.2 第三段的這種含義)。說白了,這里給出RAM模型但是并沒有具體解釋RAM模型!
- 數(shù)據(jù)類型,只強(qiáng)調(diào)一點(diǎn),大小為n的整數(shù)可以表達(dá)為c lg n比特。言外之意還是,不要亂來即可。(2.2 第四段)
- 指令、內(nèi)存層次結(jié)構(gòu)、單cpu、指令的串行執(zhí)行等等要求都非常常規(guī)。(2.2 第五、六段)
- 指出分析是困難的,但難點(diǎn)是在數(shù)學(xué):組合數(shù)學(xué)、概率論、代數(shù)等,跟RAM沒多大關(guān)系。(2.2 第七段)
- 最后展望未來,我們還有很多可以選擇的計(jì)算模型。
總而言之,準(zhǔn)確定義計(jì)算模型是復(fù)雜的,在保證計(jì)算模型不被濫用的情況下,我們可以犧牲準(zhǔn)確性而得到相應(yīng)的簡潔性與清晰性。對于初入門的CSers,剛學(xué)完C語言的同學(xué),剛好你們不懂什么內(nèi)存層次、并發(fā)計(jì)算等(即所謂“濫用”之處),所以,把RAM下的程序理解為一般的C語言程序則剛剛好。畢竟你們想濫用模型也往往不知道如何濫用(這樣說真的不好嗎?)。
2.2.2 插入排序的效率分析
這里只是一個(gè)實(shí)例。
擴(kuò)展思考:冒泡排序的效率如何分析?是什么?與插入排序相比,哪個(gè)算法更好?或者,各有什么優(yōu)勢劣勢?
2.2.3 Worst-case and average-case analysis(難點(diǎn)!)
為何要區(qū)分兩種不同的效率分析?如何理解這兩個(gè)概念?我們更需要的是那種效率?
例子:對整數(shù)x和y,求x^y需要多少時(shí)間?也許我們沒有準(zhǔn)確答案,大概我們知道計(jì)算x的y次方要比計(jì)算x+y要耗更多的時(shí)間。然而,如果x = 2,算2^y只需要進(jìn)行y次左移,并不比x + y慢。也就是說,在特定的情況下我們也許會(huì)有非常高效的算法,然而在一般情況下,算法需要執(zhí)行更多的時(shí)間。因此我們需要平均復(fù)雜性來衡量這樣的一般情況。有時(shí)候我們對特定情形下的最短時(shí)間并不感興趣,因?yàn)槲覀儽容^保守,此時(shí)我們會(huì)問,最多需要多少時(shí)間(上界)來完成這項(xiàng)工作。此時(shí),我們考慮最懷情況下的復(fù)雜性。
2.3、算法設(shè)計(jì)
重點(diǎn):分治法
實(shí)例:歸并排序,相信大家可以愉快地閱讀下去,請注意Loop invariants的使用。
提示:遞歸樹是非常好的工具,用起來!
2.4、習(xí)題(提示:該做題了!)
2.5、編程題
此題源自于某位同學(xué)的提問,課本建議“將插入排序與歸并排序結(jié)合使用,當(dāng)一組數(shù)的個(gè)數(shù)足夠小時(shí),使用插入排序進(jìn)行處理,再進(jìn)行合并”。從而提高排序的效率。問題是如何判斷數(shù)組個(gè)數(shù)是多少才足夠小呢?我的建議是,編程測試。
1、設(shè)計(jì)實(shí)現(xiàn)插入排序與歸并排序結(jié)合使用的排序程序;
2、設(shè)置一個(gè)變量控制何時(shí)進(jìn)行插入排序;
3、對1000k的隨機(jī)數(shù)組進(jìn)行排序,測試顯示在不同的控制下排序的效率。
第三章 函數(shù)的增長
3.1、Asymptotic notation(漸進(jìn)表示法):Big O,Omiga,Theta
術(shù)語:上界、下界、緊致界
簡單而言:最多做多少步?最少做多少步?最多做那么多步且最少也需要這么多步!
難點(diǎn)1:相關(guān)計(jì)算,多做題!
難點(diǎn)2:惱人的常數(shù)c
同學(xué)提問,常數(shù)c到底是多少?怎么理解BigO等等表達(dá)中需要乘一個(gè)常數(shù)?
回答:要理解這個(gè)惱人的c,首先要明白漸進(jìn)式表達(dá)是一種松散的界,c可以理解為松散度系數(shù)。其次我們要理解這個(gè)c往往不影響我們的結(jié)論,盡管我們不知道它的真實(shí)大小。例子:考慮快速排序與歸并排序,都是O(n lg n)的復(fù)雜度。那么我們可以說它們分別低于c_1 n lg n
(記為f)和c_2 n lg n(記為g),其中c_1和c_2是兩個(gè)不同的常數(shù),我們并不知道具體數(shù)值。現(xiàn)在我們想比較這兩種排序的大小,因?yàn)槲覀兒軗?dān)心不同的常數(shù)項(xiàng)會(huì)對我們造成影響,f/g = c_1/c_2,得到一個(gè)常數(shù)!盡管我們還是不知道f/g的大小,但是我們知道這兩個(gè)數(shù)值相比只差一個(gè)常數(shù),當(dāng)n增加得很大的時(shí)候,常數(shù)不變,往往就顯得無關(guān)緊要了。值得注意的是,這里只是說,通常情況下c不影響,并不是說常數(shù)永遠(yuǎn)不對算法復(fù)雜性有影響。
3.2、標(biāo)準(zhǔn)表達(dá)及常用函數(shù)
提示:有很多比較難的函數(shù),不一定要死記(能記住也是鼓勵(lì)的!),先知道,用到了再查。或者在以后以專題的形式重點(diǎn)攻克。比如:Stirling公式。
Side Comments:曾經(jīng)與某同學(xué)討論算法的學(xué)習(xí),他說自己學(xué)得很慢,碰到每一個(gè)難題都想去解決(不解決就不舒服斯基),不解決就沒辦法看其他內(nèi)容。我不很同意這種做法,不懂應(yīng)該成為常態(tài)!要先建立一種全局觀,并有整體的思路,允許存在以后解決的技術(shù)和理論難點(diǎn)。拿這里來說,兩頁書的公式包含了很多的數(shù)學(xué)內(nèi)容,就單單是一個(gè)Stirling公式,我就沒有把握需要多少時(shí)間去把握它。如果糾結(jié)一個(gè)Stirling公式而不去看以下的內(nèi)容,有意義嗎?當(dāng)然,相信這位同學(xué)也不是在糾結(jié)Stirling公式,只是舉一個(gè)例子。
提示:除了理解和做題還有什么辦法?
3.3、測試
以下測試是在CLRS的作者Cormen的主頁上找到的,建議做一做:
第四章 Recurrences(遞歸式)
本章講解三種計(jì)算漸進(jìn)復(fù)雜性界的方法:替換法、遞歸樹法以及Master Theorem(主定理??)
4.1 最大子數(shù)組問題
暫略~
4.2 矩陣乘法
暫略~
4.3、替換法:猜一個(gè)結(jié)果、證明結(jié)論(歸納法證明)
技巧1:猜測結(jié)果減去一個(gè)低階子項(xiàng)使得證明得證;注意邊界條件,可令n 大于某個(gè) n_0項(xiàng),使得邊界條件成立。
技巧2:換元法,例如,令 m = lg n 。適用于處理遞歸式中有sqr(n)的情形。
4.4、遞歸樹法
本質(zhì)上是通過構(gòu)造遞歸樹,得到一個(gè)正確的猜測。還是要通過替換法進(jìn)行結(jié)論證明!
4.5、主定理:菜譜式定理,證明可以先跳過。
目標(biāo):給定 T(n) = aT(n/b) + f(n),a,b > 1,f(n)為正函數(shù)。求遞歸式的界。
直觀上(忽略某些細(xì)節(jié)而言),是通過比較n^{(log_b a)} 與 f(n)的大小進(jìn)行判斷:如果前者大,則是\Theta(n^{(log_b a)});如果是后者大,答案則是\Theta(f(n));如果一樣大,則要乘上一個(gè)對數(shù)函數(shù)子項(xiàng)得到\Theta(n^{(log_b a)} lg n) = \Theta(f(n) lg n)。
思考:為何是比較n^{(log_b a)} 與 f(n)的大小呢?
提示:n^{(log_b a)} 是不是等于 a^{(log_b n)} ? a^{(log_b n)}在遞歸式中代表什么含義?
Comment1:這種方法并不是萬能的,有很大的局限。其次,在運(yùn)用中不能忽略某些細(xì)節(jié),比如,大小的比較,在這里要求的是多項(xiàng)式大或者多項(xiàng)式小。
Comment2:第一次閱讀不要求證明并不能說明這里的證明就非常難。絕非如此,這個(gè)證明沒有使用到復(fù)雜的技巧,只要把握住“遞歸樹”的思路,理解并不困難。不畏難的同學(xué),在進(jìn)度跟得上的前提下,可先行閱讀。特別是在假設(shè)n=2^b下的證明,不難理解。
Comment3:有意思的是,在應(yīng)用替換法、遞歸樹法、主定理方法做題的時(shí)候,同學(xué)們都會(huì)很自覺地根據(jù)自己的喜好進(jìn)行選用。實(shí)際上,他們應(yīng)該是對這三種方法有自己的評判和選擇(能說是偏見嗎?),但是又不自覺地歸納總結(jié)出來。比如,有同學(xué)認(rèn)為替換法很隨意,也難以猜對,所以不使用。又比如:有同學(xué)認(rèn)為Master Theorem只是套公式,是死記硬背,沒什么意思,也不選用。然后,認(rèn)為遞歸樹法比較靠譜。其實(shí),我認(rèn)為這樣的理解都頗為片面。
首先,我提倡大家猜!要知道能建立一種直觀是非常重要的,不要忘記,“猜”就是一種能力,在科研起步的早期,使用“猜”的方法去培養(yǎng)直觀可又大收獲。其次,使用遞歸樹法不要忘記證明!遞歸樹只不過是幫助猜測的的一種方法,結(jié)論必須得到證明。第三,有菜譜為何不用?沒錯(cuò),是死記硬背,不要忘記,“記憶力”就是智商的一種!而且,這套方法是前人經(jīng)過整理證明出來的有效方法,可直接使用,因?yàn)閾?dān)心自己不理解而不去使用算不算“因噎廢食”?
因此,在此我提倡,做題的時(shí)候三種方法都使用起來。首先猜,猜對猜不對都要去猜一下。其次,畫遞歸樹,能畫不能畫都去嘗試一下。如果遞歸樹或者替換法能得到答案,最后用Master Theorem驗(yàn)證一下自己的答案。
當(dāng)然,這并非萬能方法。也許我們可以不斷積累技巧,其中,“換元法”我還是建議大家多多練習(xí)。
2016年3月注記:最近,15圖靈班對“主定理“的證明進(jìn)行了討論,也是本學(xué)期的第一次討論。效果不錯(cuò),討論熱烈。討論過程表示,同學(xué)們基本上理解了該定理,也掌握了其中基本的證明技巧。相信,繼續(xù)下去,這個(gè)討論班會(huì)有較大的收獲。
4.6、做習(xí)題檢驗(yàn)這三種方法。本章強(qiáng)調(diào)練習(xí)!
第五章 概率分析與隨機(jī)算法
建議在沒有概率論基礎(chǔ)時(shí)忽略。當(dāng)然,這里要求的概率基礎(chǔ)也比較少。建議掌握一些相關(guān)概念,比如,什么是概率分析,什么是隨機(jī)算法。
期望大家走到這里的時(shí)候不要超過一周時(shí)間!
補(bǔ)錄:
在實(shí)際的學(xué)習(xí)過程中,我還是堅(jiān)持要求學(xué)生們進(jìn)行了概率分析的學(xué)習(xí),困難是他們沒有上過概率課。因此,簡單地補(bǔ)了幾個(gè)概念:示性隨機(jī)變量、期望值、期望的線性性。結(jié)果,此章的內(nèi)容可以順利完成。
這說明:在需要的時(shí)候?qū)δ承┲R點(diǎn)進(jìn)行補(bǔ)充并非不可能完成的任務(wù)。反過來證明了那些在此卻步的同學(xué)并非能力不足,而是信心不足。
第二部分 排序與序性統(tǒng)計(jì)
這一部分出了講排序等算法之外,實(shí)際上也講了一種簡單數(shù)據(jù)結(jié)構(gòu)--數(shù)組和堆。
第六章 堆排序
6.1 堆
堆可以視為一種完全二叉樹,也可視為數(shù)組。理解堆的關(guān)鍵不在堆本身,我寧愿大家先理解完全二叉樹。估計(jì),作者此處假設(shè)讀者已經(jīng)掌握了一定的樹的知識。如果你在第一次閱讀碰到困難,不妨停下來看看樹的概念,并畫一棵樹,然后體會(huì)、理解、證明幾個(gè)關(guān)于樹的結(jié)論。
樹的概念:根、節(jié)點(diǎn)、葉子、左兒子、右兒子、父節(jié)點(diǎn)、樹的高度
關(guān)于樹的結(jié)論:n個(gè)元素的完全二叉樹的高度是floor(lg n);高度為h的完全二叉樹的元素個(gè)數(shù)最多(最少)是多少?n個(gè)元素的完全二叉樹,葉子的數(shù)目最多是多少?這些結(jié)論通常可以自己總結(jié),然后用歸納法進(jìn)行證明。
6.2、堆的操作與堆排序
有了樹的概念之后,堆的操作與堆排序都是容易理解的。
6.5 優(yōu)先隊(duì)列
第七章 快速排序
這一章可說的東西暫時(shí)不多。相信大部分同學(xué)已經(jīng)知道并學(xué)習(xí)過這個(gè)算法。與之前學(xué)習(xí)稍有區(qū)別的是,這里是講兩個(gè)版本的快排算法:確定性算法與隨機(jī)化算法。后者與前者只有微妙的區(qū)別,就是在選擇主元的時(shí)候是均勻隨機(jī)地選擇。
對隨機(jī)快排的思考:為什么需要這樣做?何時(shí)需要這樣做?這樣做得到什么好處?
這一章的難點(diǎn)在QSort的效率分析。由于是概率分析,在沒有概率基礎(chǔ)的情況下,暫時(shí)跳過。
排序算法的兩種重要屬性:in-place 和 stable。
QSort中的Partition方法比較,這篇Blog的回答真是太專業(yè)了。為什么Hoare的Partition算法更有優(yōu)勢反而不寫在正文,留做思考題呢?
經(jīng)驗(yàn)教訓(xùn):簡單的概率知識作為CLRS的先驗(yàn)知識還是非常有必要。實(shí)際上,所需要的概率知識也不是很多,應(yīng)該在第一學(xué)期就適當(dāng)?shù)刈屚瑢W(xué)們掌握。比如,使用Pass等人的Discrete Mathematics的Lecture作為基礎(chǔ)就不錯(cuò)。
第八章 線性時(shí)間排序算法
1、排序算法的下界
利用判定樹模型證明所有使用元素對比的排序算法的復(fù)雜度下界是\Omega(n*log ~\ n)。基本思路:判定樹是一棵二叉樹,中間節(jié)點(diǎn)是元素之間的對比,葉子是排好序后數(shù)組下標(biāo)的全排列(有點(diǎn)繞口)。如果有n個(gè)元素,則數(shù)組下標(biāo)的全排列有n!種可能,即判定樹的葉子個(gè)數(shù)(記為l)至少有n!個(gè)。二叉樹的樹高為h,它有多少葉子?把以上推理記為:n! <= l <= 2^h 。簡單計(jì)算得出結(jié)論。
難點(diǎn)在于判定樹模型,為何可以把HeapSort、QSort、MergeSort等等使用對比的排序算法的行為抽象為判斷樹?我暫時(shí)也沒有非常合理簡單的講解可以提供,需要大家自己體會(huì)。其實(shí)我看書本也沒能解釋得很清楚。總而言之,結(jié)論非常重要。
擴(kuò)展思考、練習(xí): 如何比較HeapSort、QSort、MergeSort的優(yōu)劣?它們都有相同的復(fù)雜性,而且已經(jīng)達(dá)到最優(yōu),說明它們的性能相同?最好用實(shí)踐來檢驗(yàn),任何已知結(jié)論都不可靠,需要驗(yàn)證。
2、計(jì)數(shù)排序
這是一種具有線性復(fù)雜性的排序算法,當(dāng)然,有優(yōu)勢必然要付出一定的代價(jià)。
思考:這里的代價(jià)是什么呢?
基本思路:所有待排序的元素(或用于排序的關(guān)鍵字)都是某個(gè)范圍之內(nèi)的整數(shù)值。首先,輸入待排序的數(shù)組A,統(tǒng)計(jì)其中某個(gè)數(shù)值出現(xiàn)的次數(shù),存于C數(shù)組;從而得出小于或等于某個(gè)數(shù)值的數(shù)值的個(gè)數(shù),還是存于C數(shù)組;最后,根據(jù)C數(shù)組的統(tǒng)計(jì),將A數(shù)組中的元素放入數(shù)組B合適的位置中,B即為輸出。這三個(gè)步驟都是線性時(shí)間的復(fù)雜度,加起來依然是線性復(fù)雜度。
3、基數(shù)排序
基本思路:首先,把待排序元素的鍵值分成d個(gè)數(shù)位,每個(gè)digit有k個(gè)可能值。然后使用具有stability屬性(比如,使用計(jì)數(shù)排序)的排序算法,分別針對d個(gè)數(shù)位進(jìn)行排序。復(fù)雜性:Theta(d*(n+k))。基數(shù)排序的快速依賴于它調(diào)用的計(jì)數(shù)排序。
思考:線性時(shí)間的基數(shù)排序是不是擊敗了其他所有的排序算法?為何?
4、桶排序
基本思路:對n個(gè)元素進(jìn)行排序,均勻隨機(jī)地將這n個(gè)元素丟入n個(gè)桶之中。每個(gè)桶都有一個(gè)編號n_i,編號越小的桶里面的元素就越小。使用插入排序?qū)ν爸性剡M(jìn)行排序。按桶編號順序輸出桶中元素。
復(fù)雜性分析:里面用到了隨機(jī)變量、隨機(jī)變量的期望等概率論中內(nèi)容。拋開這些不談,能不能從直觀上看到某些東西呢?把n個(gè)元素丟入桶中需要的時(shí)間是Theta(n),然后對n個(gè)桶中的元素排序。每一個(gè)桶假設(shè)有n_i個(gè)元素,那么就需要O(n_i^2)的時(shí)間(插入排序!)。拍腦瓜的時(shí)候來了,均勻隨機(jī)地在n個(gè)桶中放n個(gè)元素,平均(所謂期望無非就是平均)每個(gè)桶里面有多少個(gè)元素?如果你算對了,書上的那一堆嚇人的公式大概都是浮云了~
提示:桶排序使用了特定的數(shù)據(jù)結(jié)構(gòu),可在后續(xù)數(shù)據(jù)結(jié)構(gòu)的內(nèi)容學(xué)完之后再對桶排序進(jìn)行編程練習(xí)。
第九章 中值及序位統(tǒng)計(jì)
本章最難懂不是其算法,而是其標(biāo)題(吐槽!)。中文版直接將Statistics翻譯為統(tǒng)計(jì)學(xué)(“中位數(shù)和順序統(tǒng)計(jì)學(xué)”)讓人摸不著頭腦。實(shí)際上,本章只是考慮對n個(gè)元素求其最小值、最大值和中間值;一般化地,求n個(gè)元素的第i小的那個(gè)元素,稱為Selection算法。
1、最大值與最小值
簡單思路: 按順序從第一個(gè)元素開始,逐個(gè)元素對比,并保存下當(dāng)前最大(最小)的元素位置。Theta(n)的復(fù)雜性。
疑問:這樣做是不是最好的做法(常用詞:optimal)呢?答案,yes!繼續(xù)提問:why?
稍微難一點(diǎn)的問題:如何同時(shí)求最大值與最小值。
2、平均復(fù)雜性為線性時(shí)間的Selection算法
思路:使用QSort算法對數(shù)組進(jìn)行劃分的思路,是一種分治法,使用了遞歸。
提示:放心跳過大量的概率復(fù)雜性分析。
3、最壞情況為線性時(shí)間的Selection算法
思路:
復(fù)雜性分析:
第二部分結(jié)束。個(gè)人認(rèn)為,第二部分的內(nèi)容比第一部分稍容易,除了概率分析(剛好大家也可以跳過)之外,沒有什么理論難點(diǎn)。所以,期望大家走到這里只是第二周學(xué)習(xí)的結(jié)束。無論你認(rèn)為是快還是慢,都可以反思一下,為什么?也可以進(jìn)行討論。
第三部分 數(shù)據(jù)結(jié)構(gòu)
相信CLRS的作者會(huì)假定自己的學(xué)生(或者讀者)懂一點(diǎn)點(diǎn)數(shù)據(jù)結(jié)構(gòu),但是要求掌握的并不多(也許這種要求之低可以低到以至于可以忽略)。因此,在引入數(shù)據(jù)結(jié)構(gòu)的時(shí)候,作者強(qiáng)調(diào)了“動(dòng)態(tài)集合”的操作:檢索、插入、刪除、最小值、最大值、前驅(qū)成員、后繼成員等,完全是高度的抽象。字典是本章主要考察的實(shí)現(xiàn)對象。
反思:在學(xué)習(xí)CLRS之前是否需要集合論及簡單的數(shù)據(jù)結(jié)構(gòu)知識?盡管不知道大家如何想,實(shí)際上,如果CLRS是第一學(xué)期之后學(xué)習(xí),要滿足這兩種前驅(qū)知識的需求也并不難達(dá)到。
注意:本章強(qiáng)調(diào)實(shí)踐,使用C++及OO編程的技術(shù)做完本章習(xí)題,基本就覆蓋了普通Data Structures with C++ 的內(nèi)容。我在對比了William Ford的《Data Structures with C++》的目錄之后得出此結(jié)論。如果我的學(xué)生可以完成這些內(nèi)容的學(xué)習(xí),就必須問一句:數(shù)據(jù)結(jié)構(gòu)還必須那樣教嗎?
第十章 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)
1、棧與隊(duì)列
應(yīng)該沒有什么難度,大家可以自學(xué)并實(shí)現(xiàn)相關(guān)算法。自覺利用OO編程,設(shè)計(jì)相關(guān)的Class定義。
2、鏈接表
唯一的難點(diǎn)在于:指針!所以,還是認(rèn)為第一學(xué)期的課程必須包括指針編程的訓(xùn)練。
3、實(shí)現(xiàn)指針與對象
這一節(jié)講解如何在不提供指針與對象的程序語言中實(shí)現(xiàn)指針與對象。很奇怪的目的,是嗎?因?yàn)槲覀儸F(xiàn)在大部分的語言都已經(jīng)實(shí)現(xiàn)了指針與對象。所以,看完這一章感覺不奇怪的話,那么任務(wù)就完成了。不要忘記,C/C++是使用其他程序語言實(shí)現(xiàn)的程序語言。我們往往對C語言中的指針運(yùn)算感到困惑,比如指針的++、--運(yùn)算,指針的指針,指針的指針的指針等等,掃除這些困惑的一種方法就是了解這種變量的實(shí)現(xiàn)方法!(注,問正確的問題,是你走出正確理解的重要一步。)
4、表達(dá)有根樹
這一節(jié)有兩個(gè)重要內(nèi)容:二叉樹與帶根樹的實(shí)現(xiàn)方法。需要講解的內(nèi)容不多,重點(diǎn)在于通過指針結(jié)構(gòu)實(shí)現(xiàn)樹及其各種遍歷、查詢。有根樹與二叉樹的區(qū)別僅僅在于前者的節(jié)點(diǎn)會(huì)有任意多個(gè)后代節(jié)點(diǎn),每一個(gè)節(jié)點(diǎn)都有一個(gè)指針指向自己的父親節(jié)點(diǎn),也有一個(gè)指針指向自己的右邊兄弟節(jié)點(diǎn)。編程!
第11章 Hash表
Hash表是實(shí)現(xiàn)字典的有效數(shù)據(jù)結(jié)構(gòu)。Hash表也許會(huì)被翻譯為”哈希表“或者“散列表”,只是這種翻譯也許并不需要。直觀而言,字典,即通過關(guān)鍵詞檢索相對應(yīng)的數(shù)據(jù)項(xiàng)。
1、直接尋址
使用關(guān)鍵詞(key)作為存儲(chǔ)的檢索號(對應(yīng)一個(gè)存儲(chǔ)位置:slot)。高效簡單,前提是key的范圍足夠小。
2、Hash表
與直接尋址相比,這里使用Hash函數(shù)將Key映射到一個(gè)存儲(chǔ)位置K。使用Hash函數(shù)的原因是Key的范圍很大,但是真正使用到的Key只是一小部分。比如,中國有13億人口,要存儲(chǔ)所有人的個(gè)人信息。如果以姓名為Key,這也許是一個(gè)無窮大的范圍,我們沒必要為每一個(gè)可能存在的姓名準(zhǔn)備一個(gè)存儲(chǔ)位置。
因此,這里的關(guān)鍵是設(shè)計(jì)合理的Hash函數(shù)。我們要求Hash函數(shù)可高效實(shí)現(xiàn),并且盡可能避免碰撞(Collision)。所謂碰撞,即存在兩個(gè)不同的key,k1和k2,使得h(k1) == h(k2)。首先,我們看Hash函數(shù)h的定義,一定要明確,碰撞存在!為什么?
當(dāng)碰撞發(fā)生之后,如何處理存在的沖突呢? Chaining!
3、Hash函數(shù)
解決上一節(jié)遺留問題,如何設(shè)計(jì)“好”的Hash函數(shù)。
a. 什么是好的Hash函數(shù)?
b. 使用除法
c. 使用乘法
*** Universal Hashing 第一次閱讀跳過,然而開學(xué)后的學(xué)習(xí)中Universal Hashing依然是可以進(jìn)行的內(nèi)容。目前看效果一般,主要是更深入的學(xué)習(xí)與研究沒辦法開展。另外就是此內(nèi)容在表述上不同的教材有一定的區(qū)別,也帶來一定的困難。不過,依然令人滿意。以后可以以專題的形式再次學(xué)習(xí)。
4.開放尋址
不使用列表,在數(shù)組的基礎(chǔ)上直接實(shí)現(xiàn)碰撞發(fā)生的處理。算法簡單,分析有點(diǎn)困難,還是要卡在隨機(jī)變量及期望!放過定理證明先。
*5、完全Hash (暫時(shí)跳過)
第12章 二叉搜索樹
1、 什么是二叉搜索樹 ?
二叉搜索樹是滿足以下特性的二叉樹:x是樹的節(jié)點(diǎn),x的左子樹的所有非空節(jié)點(diǎn)的key都比x的key要小,x的右子樹的所有非空節(jié)點(diǎn)的key都比x的key要大。
三種遍歷方式:前序、中序與后序。簡單而言,從根出發(fā),有三種選擇:先訪問根節(jié)點(diǎn)再訪問子樹;先訪問左子樹,訪問根節(jié)點(diǎn),再訪問右子樹;先訪問子樹,最后訪問根節(jié)點(diǎn)。
2、二叉搜索樹的查詢、遍歷
容易!理解思路:樹是一種遞歸結(jié)構(gòu),即樹的子孫節(jié)點(diǎn)都是一棵樹。因此,首先通過遞歸的思路去理解是最合適的。比如:簡單的中序遍歷來說,輸入:一棵樹的樹根。遍歷:遞歸遍歷樹根的左子樹,訪問樹根節(jié)點(diǎn),遞歸遍歷樹根的右子樹。
3、二叉搜索樹的插入與刪除
插入算法容易。刪除算法稍微復(fù)雜。這里要注意,第三版在此處有非常大的改進(jìn)。我看的是第二版,情況分析是清晰的,但是偽代碼經(jīng)過了整合優(yōu)化,可讀性較差。第三版的Delete算法描述清晰BigO(n)倍。所以,無論多么著名的書,都可以質(zhì)疑。
一個(gè)簡單的證明題,用于幫助理解刪除操作過程:刪除z節(jié)點(diǎn),當(dāng)其左子樹與又子樹都不為Nil,找其successor y節(jié)點(diǎn)(Case 3) 。請證明,y的左子樹必為nil。
若干課外資料:
a、一份純C的BST教程,作者聲稱非常不喜歡理論:-D。
b、一份Python的BST教程,適當(dāng)?shù)臅r(shí)候可以學(xué)習(xí)一下Python。
*4、二叉搜索樹的隨機(jī)生成
給定一系列元素,以隨機(jī)的順序?qū)⑺鼈儾迦攵鏄渲小D康模玫揭豢谩拜^矮”的樹。重點(diǎn)在概率分析。
第13章 紅黑樹
本章的主要任務(wù)還是與樹的高度作斗爭,要得到矮的樹,關(guān)鍵是要樹平衡發(fā)展。紅黑樹是一種特殊的二叉搜索樹,每一個(gè)節(jié)點(diǎn)增加一個(gè)顏色屬性(紅節(jié)點(diǎn)或者黑節(jié)點(diǎn)),目的是建立平衡二叉樹,這是重點(diǎn)也是難點(diǎn)。
1、什么是紅黑樹
紅黑樹有五點(diǎn)屬性需要把握:每個(gè)節(jié)點(diǎn)不是紅就是黑;根節(jié)點(diǎn)是黑節(jié)點(diǎn);所有的葉子都是黑;每一個(gè)紅節(jié)點(diǎn)的孩子都是黑節(jié)點(diǎn);每一節(jié)點(diǎn)走到葉子的不同路徑包含相同個(gè)數(shù)的黑節(jié)點(diǎn)(保證黑高相同,也就保證了樹高的平均)。
2、樹的旋轉(zhuǎn)
一種插入、刪除的輔助操作,用于保證紅黑樹的屬性。容易~
3、插入、刪除
與二叉樹的插入刪除主要有一點(diǎn)不同,即插入、刪除會(huì)導(dǎo)致紅黑樹屬性變化,因此必須修補(bǔ),使得原有屬性得以保持。這里應(yīng)該算是一個(gè)難點(diǎn),也許是除了數(shù)學(xué)概率分析之外,數(shù)據(jù)結(jié)構(gòu)當(dāng)中的第一個(gè)難點(diǎn)。要克服這里的困難應(yīng)該如何做?建議在閱讀了基本內(nèi)容(先忽略大部分的算法)之后,多用筆紙畫圖,對不同實(shí)例進(jìn)行分析,比如,在何種情況下插入節(jié)點(diǎn)會(huì)(或者不會(huì))導(dǎo)致屬性改變?首先要通過這種簡單的運(yùn)算得到直觀,再詳細(xì)閱讀課本印證自己的觀點(diǎn)。簡而言之,作圖將會(huì)給你帶來理解上的幫助。
當(dāng)困難到來的時(shí)候、考驗(yàn)到來的時(shí)候,離進(jìn)步就不遠(yuǎn)了。你只需要深呼吸、沉一口氣,把它攻克下來! 當(dāng)然也需要講一點(diǎn)學(xué)習(xí)技巧。1、手頭有筆紙,把5種RBT的屬性記下來作參考。2、根據(jù)不同的實(shí)例不斷畫圖。3、跟著作者的思路,doubly-black node是一個(gè)關(guān)鍵。4、對RBT不要著急編程,不要讓程序成為你理解的絆腳石。先理解,讓程序成為你加深理解的工具。
問題:為何在RBTree中使用T.nil取代原來BSTree中的NIL?
回答:首先要注意到T.nil是object,具備各種attributes;不同的是,NIL只是一個(gè)標(biāo)記。其次,T.nil的作用很大,書本用了一個(gè)高度概況的字眼來描述:處理RBTree處理中的邊界條件。實(shí)際上,在詳細(xì)分析算法的時(shí)候,對RBTree進(jìn)行處理的幾種操作包括對葉子的操作,比如,旋轉(zhuǎn)。這個(gè)時(shí)候葉子就不能僅僅是一個(gè)標(biāo)記,而是一個(gè)完整的對象。這種情況很多,你如果沒有發(fā)現(xiàn),說明你對算法的分析沒有覆蓋算法執(zhí)行的許多情形。為什么要統(tǒng)一用一個(gè)T.nil?答案就簡單了,節(jié)省空間。這是一個(gè)好問題。如果你沒有理解到這個(gè)問題的價(jià)值,說明你沒有真正理解T.nil的作用與意義。
- 其實(shí)代碼并沒有怎么重要,但是,如果有一位同學(xué)寫了代碼,還是參考一下吧: 16級某同學(xué)的代碼 。
第14章 數(shù)據(jù)結(jié)構(gòu)的擴(kuò)展
本節(jié)內(nèi)容試圖在“教科書式”數(shù)據(jù)結(jié)構(gòu)與“工程實(shí)踐中”的數(shù)據(jù)結(jié)構(gòu)之間搭建橋梁,通過實(shí)例展示在工程實(shí)踐中如果擴(kuò)展教科書數(shù)據(jù)結(jié)構(gòu)來完成特定的功能、滿足相關(guān)的要求。大部分的情況下,我們只需要“擴(kuò)展”教科書數(shù)據(jù)結(jié)構(gòu),而無需重新定義全新的數(shù)據(jù)結(jié)構(gòu)。那些張口就說“學(xué)校授課內(nèi)容很重要,但是......”“老師授課只注重理論,缺乏實(shí)踐......”的人請閉嘴吧,至少你要明白什么是“工程實(shí)踐”再說話,明白教科書、老師怎么理解“工程實(shí)踐”再瞎說吧。
這一部分的內(nèi)容主要還是關(guān)于紅黑樹。
1、動(dòng)態(tài)序性統(tǒng)計(jì)
紅黑樹的一種應(yīng)用,在節(jié)點(diǎn)中增加某些信息,用于講O(n)復(fù)雜度的序性統(tǒng)計(jì)下降到O(lg n)。可作為第二節(jié)的引入。
2、如何擴(kuò)展數(shù)據(jù)結(jié)構(gòu)
四個(gè)步驟,一個(gè)定理。
3、區(qū)間樹
這一節(jié)是對第2節(jié)所提出方法的具體實(shí)例。
本部分難度不大,但考慮到本部分需要大量的編程,10天時(shí)間完成吧!搞完了就安心過春節(jié)了。
第四部分 高級算法設(shè)計(jì)與分析
高級,到底高級在哪里?Advanced,我寧愿理解為“更進(jìn)一步”的。在此之前的算法設(shè)計(jì)與分析只有分治法,到了這一章我們并不是拋棄分治法另起爐灶,而是,進(jìn)一步,進(jìn)一步......,一步一步地使得算法具有更好的效率。如果這么想,整個(gè)思路就很清晰了:動(dòng)態(tài)規(guī)劃這一章就是講當(dāng)大問題分解成子問題,如果重復(fù)的子問題不斷出現(xiàn)該如何解決。貪心算法更進(jìn)一步,如果子問題的分割在特定問題的時(shí)候具有某種可利用的屬性,那么我們可以如何優(yōu)化算法。因此,不要被“高級”嚇倒,也不要被“高級”誤導(dǎo):這里的內(nèi)容不存在更高級別的抽象,而且思路與之前的內(nèi)容聯(lián)系緊密,并非邏輯關(guān)聯(lián)性不強(qiáng)的飛躍。因此,第一個(gè)問題必須改成:Where do you advance from?
第15章 動(dòng)態(tài)規(guī)劃
動(dòng)態(tài)規(guī)劃只是一個(gè)名詞,完全無法體現(xiàn)其應(yīng)有的內(nèi)涵。
這是16級某同學(xué)的筆記。
第16章 貪心算法
第17章 平攤分析法
平攤分析的目的?
平攤分析的三種方法:累加平均、記賬法、勢能分析法。
理解的難度不大。最大的收獲在講解習(xí)題、看視頻(競爭分析)。
如何靈活使用?
以上內(nèi)容,從閱讀的角度,一個(gè)星期也許夠了,但是從做題的角度看,我無法估計(jì)!建議盡可能快地完成本部分閱讀。然后進(jìn)入一個(gè)反饋循環(huán),再閱讀再討論,加強(qiáng)習(xí)題。
第五部分 高級數(shù)據(jù)結(jié)構(gòu)
第18章、B樹
平衡的搜索樹,主要用于磁盤檢索。每個(gè)節(jié)點(diǎn)有多個(gè)鍵值,有多個(gè)孩子節(jié)點(diǎn)。我認(rèn)為需要提醒的兩點(diǎn):插入只在葉子進(jìn)行;刪除可在任意節(jié)點(diǎn)進(jìn)行。葉子的定義與BST樹有重大區(qū)別。
第19章、Fibonacci堆
1、之前有Binary Heap的內(nèi)容,為何要提出Fibonacci Heap?有何優(yōu)勢?有何劣勢?具體操作怎么做?復(fù)雜性是多少?解決這些基本問題。
2、這種Heap與Fibonacci數(shù)列有什么關(guān)系?這是難點(diǎn)。
第20章、van Emde Boas樹
發(fā)明者Peter van Emde Boas,荷蘭科學(xué)家。簡記為vEB樹。
第21章、用于不相交集合的數(shù)據(jù)結(jié)構(gòu)
高級數(shù)據(jù)結(jié)構(gòu)之所有“高級”的原因是什么? “進(jìn)一步”,到底是如何進(jìn)一步的?思考!
第六部分 圖算法
第七部分 高級專題
Appendix A. 本文助記符(統(tǒng)一借助LaTex的記號)
1、"^" 代表“帽子”,N^2表示N的平方。
2、“_"代表下標(biāo),比如n_2表示n帶下標(biāo)2。
3、"\in" 表示集合的包含關(guān)系,比如,A \in B表示A集合是B集合的子集。
4、BigO,Theta,Omiga,一個(gè)英國朋友,兩個(gè)希臘朋友:-D
5、floor、ceiling,分別代表下取整與上取整的那兩個(gè)符號。
Appendix B. 相關(guān)網(wǎng)絡(luò)資源
0、CLRS的主頁
1、《算法導(dǎo)論》網(wǎng)易公開課
2、作者之一Cormen的主頁
3、作者之一Rivest的主頁(大名鼎鼎的圖靈獎(jiǎng)得主啊!)
4、Khan Academy‘s algorithms tutorials
5、進(jìn)階閱讀:Readings in Algorithms,Stanford的一門課程,里面有比較前沿的papers列表,還有我希望大家可以學(xué)習(xí)的FFT。留給我自己看看。
6、Algorithm Unlocked,CLRS作者之一的Cormen的新作,據(jù)說比CLRS更淺顯易懂。我看過目錄和部分章節(jié),易懂也許是的,但是內(nèi)容就少了很多,而且論述的順序與風(fēng)格充滿了作者的個(gè)人趣味。按作者的本意來說,Algorithm Unlocked只是一盤開胃菜,可以作為學(xué)習(xí)CLRS的輔助閱讀。我看確實(shí)如此!然而我看不出有任何理由推薦我的學(xué)生去看這本書,因?yàn)閷Υ蟛糠值娜硕赃@真是很大一盤餐前小點(diǎn),你很容易就找出很多很多理由拒絕進(jìn)行這樣的學(xué)習(xí):哇啊是英語;哇啊真是太理論;哇啊我又不做科學(xué)家.....如果你真的下決心去吃它,還不如立即開始CLRS。如果你不下決心,去淺嘗則止,收獲也不會(huì)很多,那還不如不看。(2015年2月5日補(bǔ)記.)
7、Algorithms,Papadimitriou等人2006年出版的一本算法書,我經(jīng)常翻看,還不錯(cuò),篇幅小,簡潔。有中文注釋版名為《算法概論》,推薦購買。這是一篇網(wǎng)絡(luò)評論。
8、Algorithm Design,Jon Kleinberg 等人2005年的著作,非常著名。不適合入門,有些高級且較新的內(nèi)容。這里有Lecture Slides.(當(dāng)年大一就抱著它啃的哪位同學(xué)估計(jì)已經(jīng)放棄算法學(xué)習(xí)了吧......)
9、http://codeforces.com/,競賽網(wǎng)站?據(jù)說有很多好的代碼。
10、MIT 6.006: Introduction to Algorithms. 2011