摘要:除特別標(biāo)注外,文章非原創(chuàng)插圖全部來(lái)自課程相關(guān)資源。劇透預(yù)警內(nèi)容包含大作業(yè)的關(guān)鍵問(wèn)題解法分析。為的返回值此方案下,判斷只需要對(duì)應(yīng),判斷使用結(jié)果準(zhǔn)確,判斷檢測(cè)的對(duì)應(yīng)是否為。更新此方法已確定違反的。
Princeton的算法課是目前為止我上過(guò)的最酣暢淋漓的一門課,得師如此夫復(fù)何求,在自己的記憶徹底模糊前,愿對(duì)這其中一些印象深刻的點(diǎn)做一次完整的整理和回顧,以表敬意。
注:
這是一篇更關(guān)注個(gè)人努力與完成任務(wù)項(xiàng)目過(guò)程相關(guān)的文章,內(nèi)容集中于課程背后值得提到的部分,不會(huì)介紹課程基本信息及學(xué)習(xí)時(shí)必讀的設(shè)定要求等部分,敬請(qǐng)諒解。
在學(xué)習(xí)一門課程的時(shí)候考慮為什么這么教是個(gè)人習(xí)慣,我會(huì)嘗試給出一些解讀,為什么這門課這么屌(awesome)。
優(yōu)化無(wú)止境,越學(xué)習(xí)才能越深刻地感受自己的無(wú)知,即使是作業(yè)內(nèi)已提到的額外內(nèi)容我也并沒(méi)有一一探究完整,這里只是謙卑地盡力記錄自己的努力,并無(wú)意與誰(shuí)比較,如有新的進(jìn)展還會(huì)回來(lái)更新。
除特別標(biāo)注外,文章非原創(chuàng)插圖全部來(lái)自課程相關(guān)資源。
每一小節(jié)的作業(yè)題目鏈接到specification,“√”鏈接到checklist,“〇”鏈接到code下載。
這里提供一個(gè)全文完整的資源樹。
劇透預(yù)警:
內(nèi)容包含大作業(yè)的關(guān)鍵問(wèn)題解法分析。
作為第一部分開學(xué)第一課,作業(yè)Percolation可謂精妙:
既沒(méi)有復(fù)雜的語(yǔ)法使用(僅數(shù)組操作),又著實(shí)比在基礎(chǔ)語(yǔ)言層面上升了一個(gè)檔次;
漂亮的visualizer動(dòng)畫效果激勵(lì)著初學(xué)者完成任務(wù);
強(qiáng)大的autograder功能初次展現(xiàn),評(píng)價(jià)算法的主要管道一目了然,每一微秒每一字節(jié)都很重要;
針對(duì)學(xué)習(xí)迅速的同學(xué)還隱含了一個(gè)很大的挑戰(zhàn):在僅使用一個(gè)WeightedQuickUnionUF對(duì)象的前提下,解決backwash問(wèn)題。
下面主要聊聊backwash:
問(wèn)題的核心源自virtual top/bottom,一個(gè)強(qiáng)行被導(dǎo)師在課程視頻中提到的優(yōu)化方法,于是天真的同學(xué)們(我)就去開心地實(shí)現(xiàn)這個(gè)看似無(wú)辜而又性能楚楚動(dòng)人的方法,卻毫不了解導(dǎo)師在下一節(jié)中馬上提出的backwash問(wèn)題是何物,還覺(jué)得這種低級(jí)錯(cuò)誤怎么可能會(huì)發(fā)生:
java-algs4 PercolationVisualizer percolation/input10.txt
……
(我發(fā)誓這就是我看到的東西……)
把痛碎的心小心拼回,認(rèn)真思索一番后確定問(wèn)題根源出在virtual bottom,一個(gè)顯而易見(jiàn)的解決方案便浮現(xiàn)在眼前:使用兩個(gè)UF,一個(gè)使用vb一個(gè)不用,判斷percolate用前者,判斷full用后者,解決!
Raw Score 100.00 / 100.00
然而checklist的一句話又引起了我的注意:
However, many students consider this to be the most challenging and creative part of the assignment (especially if you limit yourself to one union-find object).
加上autograder特別溫馨的提醒“bonus failed”,我不得不重新開始審視這個(gè)問(wèn)題。
后來(lái)的事實(shí)證明,直到課程結(jié)束沒(méi)有一個(gè)問(wèn)題再讓我如此頭疼。經(jīng)過(guò)了一段可謂長(zhǎng)征式的思考,加之在forum的討論中得到了一點(diǎn)靈感(多加利用Find()),最終形成并實(shí)現(xiàn)了解決方案,方案核心如下(corner case另行處理):
將原方案中表示open狀態(tài)的boolean數(shù)組改為byte數(shù)組,設(shè)定規(guī)則如下:初始化的默認(rèn)值0代表blocked site,賦1代表open site,賦2代表與尾行相連的open site;
每open一個(gè)site,如果位于尾行則賦2,否則賦1;
分別對(duì)每個(gè)鄰接site檢測(cè):如任何一方的root site對(duì)應(yīng)byte值為2,將雙方Union后的root site設(shè)為2。(root為Find()的返回值)
此方案下,判斷open只需要對(duì)應(yīng)byte>0,判斷full使用UF結(jié)果準(zhǔn)確,判斷percolates檢測(cè)virtual top的root site對(duì)應(yīng)byte是否為2。
Raw Score 101.25 / 100.00
Test 2 (bonus): Check that total memory <= 11 N^2 + 128 N + 1024 bytes
==> passed
對(duì)這個(gè)作業(yè)來(lái)說(shuō),上面這三行的成績(jī)凝聚了太多。
II. Resizing Array & Linked Lists與Randomized Queues and Deques √ 〇第二周課程正式展開,但在作業(yè)深度上相對(duì)稍有下降,根據(jù)指定的性能和API要求,選擇適當(dāng)?shù)膶?shí)現(xiàn)方式(動(dòng)態(tài)數(shù)組、單、雙向鏈表),實(shí)現(xiàn)Randomized Queue和Deque,主要訓(xùn)練基本的算法分析和編程嚴(yán)謹(jǐn)性(完善處理corner-case),但并無(wú)特別的難點(diǎn)。有兩個(gè)小技巧可以提出:鏈表可使用sentinel node(s)使代碼更簡(jiǎn)潔(checklist已提),寫client時(shí)可先shuffle再插入(bonus)。
2018更新:最近同學(xué)在做這道題時(shí)發(fā)現(xiàn)autograder已明確指出不可以使用數(shù)組,這就使得這個(gè)bonus變得有趣多了:
仔細(xì)想一下,既然API已經(jīng)把我們非常嚴(yán)格地限制在只能遍歷一遍輸入的情況下,而我們?nèi)匀幌M鸕Q不超過(guò)k個(gè)元素,那么在正常讀入k個(gè)元素后面對(duì)下一個(gè)元素我們只有兩個(gè)選擇,dequeue一個(gè)舊元素然后enqueue新元素,或直接忽略這個(gè)新元素;所以問(wèn)題的關(guān)鍵就變成了一個(gè)概率問(wèn)題,兩個(gè)選擇應(yīng)當(dāng)如何權(quán)衡,才能保證每個(gè)元素最終留在RQ中的概率相等呢?
相對(duì)應(yīng)地,autograder也有完整的假設(shè)檢驗(yàn)(t-test)來(lái)實(shí)際檢測(cè),小伙子你真的做對(duì)了嗎?
不必多說(shuō),擼起袖子上吧,撿起高中的排列組合知識(shí),仔細(xì)分析一下,你算出的忽略當(dāng)前新元素的概率應(yīng)該是這樣:
$$mathrm{P}_{discard}=frac{mathrm{C}_{n-1}^k}{mathrm{C}_n^k}=frac{n-k}{n}$$
plug it in and feel the magic! 不得不說(shuō)這個(gè)作業(yè)的設(shè)計(jì)之精巧,看似非常不起眼的一個(gè)小bonus,竟然也幾乎是蘊(yùn)含著Monte Carlo方法的一些基礎(chǔ)啟發(fā),真是妙啊。
過(guò)程中一并學(xué)習(xí)了Java的Generics,Iterable,Iterator概念,在大學(xué)課程部分已學(xué)習(xí)掌握得比較熟練,可不談,the result speaks:
Raw Score 100.19 / 100.00
Test 3 (bonus): Check that maximum size of any or Deque or RandomizedQueue object created is <= k
==> passed
結(jié)合兩次作業(yè)特性,可看出一些值得一提的點(diǎn):
作業(yè)任務(wù)要求與說(shuō)明往往面面俱到細(xì)致入微,在給定公有API框架及其對(duì)應(yīng)時(shí)間空間復(fù)雜度的基礎(chǔ)上,結(jié)合課程視頻知識(shí)內(nèi)容,這種“受指導(dǎo)”的編程過(guò)程變得十分清晰,尤其是其中API的設(shè)計(jì),風(fēng)格簡(jiǎn)潔高效到自成一家,我認(rèn)可自己的代碼風(fēng)格已很簡(jiǎn)潔,而algs4.jar讓我第一次看到了更高;
給定充足的測(cè)試材料,包括各類corner-case或相對(duì)有趣的輸入數(shù)據(jù)(如sedgewick60.txt),及合適情況下生動(dòng)的可視化工具(如PercolationVisualizer.java),都使得學(xué)生可以全力,高效,有動(dòng)力地,將精力集中在核心算法本身上。
III. Sorting與Pattern Recognition - Collinear Points √ 〇本周的課程介紹了兩大經(jīng)典排序:Mergesort和Quicksort,自然作業(yè)也與sorting緊密相關(guān)。Collinear Points(找出所有4個(gè)或以上共線的點(diǎn)構(gòu)成的點(diǎn)集)是第一個(gè)在運(yùn)行時(shí)見(jiàn)證“好的算法”與“暴力算法”直觀差別的作業(yè),這樣的對(duì)比能給學(xué)生帶來(lái)深刻的影響:忙了好久,為了什么?
上圖為暴力算法(~N^4)求解100個(gè)數(shù)據(jù)點(diǎn)(input100.txt),下圖為基于排序的算法(~N^2logN)求解1423個(gè)數(shù)據(jù)點(diǎn)(rs1423.txt)
測(cè)試數(shù)據(jù)還有很多。圖中示例對(duì)快速算法給定數(shù)據(jù)量龐大了不止10倍,運(yùn)行時(shí)間卻與不到1/10數(shù)據(jù)量下的暴力算法接近;對(duì)上圖數(shù)據(jù),快速算法基本看不到找線的動(dòng)畫過(guò)程就完成了;對(duì)下圖數(shù)據(jù),暴力算法在可以忍受的時(shí)間里基本找不到幾條線。
這樣的運(yùn)行結(jié)果可以給學(xué)生一種非常好的對(duì)自己努力的掌控感,正是這樣一個(gè)個(gè)美妙的瞬間使學(xué)生能以最好的狀態(tài)與飽滿的好奇心在算法之路上繼續(xù)走下去。
作業(yè)說(shuō)明中對(duì)核心算法的描述非常清晰,應(yīng)當(dāng)特別小心的技術(shù)點(diǎn)(浮點(diǎn)誤差、正負(fù)零等等)也都在checklist中予以強(qiáng)調(diào),因而實(shí)現(xiàn)難度不算很大,其中使用了Java的Comparable與Comparator,排序過(guò)程調(diào)用Arrays.sort(),詳細(xì)思考問(wèn)題理清關(guān)系后,實(shí)現(xiàn)非常自然,前提是編程基本功必須扎實(shí)。
值得提到的點(diǎn):
checklist鼓勵(lì)初學(xué)者開始編寫快速算法時(shí)先不要擔(dān)心5個(gè)或以上的點(diǎn)共線的情況,而實(shí)際上對(duì)基本功扎實(shí)的同學(xué),從開始便考慮這個(gè)問(wèn)題更為合適;
checklist提到compare()和FastCollinearPoints類可以完全避免浮點(diǎn)數(shù)用整形運(yùn)算實(shí)現(xiàn),我想到的唯一符合要求(Point.java注釋中規(guī)定不得依賴toString())的方法是,對(duì)Point類的compareTo()方法返回值增加意義(不單純返回正負(fù)1),以獲取兩點(diǎn)的坐標(biāo)之差(因題目給定坐標(biāo)取值范圍0-32767,可在返回值低兩位字節(jié)存儲(chǔ)x坐標(biāo)差, 高兩位字節(jié)存儲(chǔ)y坐標(biāo)差),再利用坐標(biāo)差判斷斜率是否相等。雖依然完全符合題目要求,但已有一點(diǎn)奇技淫巧之嫌,并且不一定能夠通過(guò)autograder(評(píng)分時(shí)會(huì)替換Point類去測(cè)試FastCollinearPoints),不多討論。(更新:此方法已確定違反FastCollinearPoints的API。)
最終實(shí)現(xiàn)版本拿到了100分,未發(fā)現(xiàn)關(guān)于bonus的討論。
IV. Priority Queue與8 Puzzle √ 〇剛剛講解完使用Binary Heap實(shí)現(xiàn)的Priority Queue,便迎來(lái)了這周的8 Puzzle——使用PQ實(shí)現(xiàn)A*解決NP難問(wèn)題,所以——重點(diǎn)在優(yōu)化咯。
(Image Source Here)
左邊是目標(biāo)狀態(tài),右邊是起始狀態(tài);對(duì),就是那個(gè)——小時(shí)候功能手機(jī)上的蒙娜麗莎。
隨著學(xué)習(xí)的深入,這次作業(yè)的復(fù)雜度有了一個(gè)明顯的上升,但這門課最美妙的地方之一也就在這里:面臨的問(wèn)題越來(lái)越復(fù)雜,而導(dǎo)師給出的指導(dǎo)思路依然保持最大程度的簡(jiǎn)潔優(yōu)雅,每一步驟的設(shè)定都直指問(wèn)題核心,并在實(shí)現(xiàn)功能基礎(chǔ)上提供非常豐富的優(yōu)化選擇,以及每個(gè)優(yōu)化會(huì)帶來(lái)的影響分析。
"It"s a funny feeling being took under the wings of a dragon. It"s warmer than you think." —— Gangs of New York
整個(gè)問(wèn)題相對(duì)比較復(fù)雜,但經(jīng)過(guò)給定API的劃分和給定的限制后,問(wèn)題變成了實(shí)現(xiàn)一個(gè)個(gè)目標(biāo)明確的小方法而已,其中最復(fù)雜的不過(guò)是A*的實(shí)現(xiàn),而周到合理的封裝和PQ的使用也使得這個(gè)過(guò)程無(wú)比自然,在此之前我從未意識(shí)到我可以將A*在如此短小清晰的代碼中實(shí)現(xiàn):(源碼如此,僅省略了聲明過(guò)程)
while (!pq.min().board.isGoal()) { cur = pq.delMin(); for (Board nb : cur.board.neighbors()) { if (cur.prev != null && nb.equals(cur.prev.board)) continue; pq.insert(new Node(nb, cur.moves+1, cur)); } }
PQ使用了Manhattan Priority作為Comparator,Node為封裝Board的單鏈表節(jié)點(diǎn),結(jié)束后獲取最短路徑只需取PQ中最小Node,利用prev指針遍歷。
解決8 Puzzle這一NP難問(wèn)題的核心代碼便完成了。
當(dāng)然,在其余部分還是有不少值得提到的點(diǎn):
用char[]存儲(chǔ)一個(gè)Board的狀態(tài)比用int[][]好太多,但使用前需要詳細(xì)測(cè)試將char做數(shù)字使用與直接用int的區(qū)別;
Board的equals()方法只需比較char[]構(gòu)成的String即可,早期因圖省事直接比較了toString()的返回值(one-liner),造成了很大的性能損失,最后尋找問(wèn)題來(lái)源也費(fèi)了不少功夫(教訓(xùn):看似再簡(jiǎn)單的部分也要在腦袋里多轉(zhuǎn)一轉(zhuǎn)再下手,devil in the details,…,you name it);
Solvability: 這篇文章描述的解決方案應(yīng)為checklist所述方案,但需要脆弱地依賴toString()反推Board內(nèi)容,在本期課程的API設(shè)定中已被明確禁止,要求使用兩個(gè)同步A*的方案解決(最好使用一個(gè)PQ),但未來(lái)session可能會(huì)在兩方案對(duì)應(yīng)API設(shè)定間切換,所以我都實(shí)現(xiàn)了一遍,上面出現(xiàn)的A*代碼來(lái)自文章所述方案;
實(shí)現(xiàn)Priority的cache時(shí)需要稍多做思考,直覺(jué)想到的第一方案是儲(chǔ)存在Node中與A*過(guò)程配合,而這將使A*代碼迅速腫脹,且沒(méi)有很好地利用更多規(guī)律:如checklist所指出,對(duì)相鄰Board每次重新計(jì)算Priority其實(shí)也有很多重復(fù)工作,我的做法是將cache過(guò)程作為Board類一個(gè)私有構(gòu)造函數(shù),構(gòu)造相鄰Priority只需做對(duì)應(yīng)增量操作即可,以最簡(jiǎn)潔的手段達(dá)到了目的。
我的最終測(cè)試文件及結(jié)果在這里,運(yùn)行于i5-2450 (8G),結(jié)尾幾個(gè)復(fù)雜的4x4開始因內(nèi)存不足報(bào)錯(cuò),論壇查到大概需要5-6G超出了我的空閑內(nèi)存,即使要測(cè)也會(huì)用到虛擬內(nèi)存,因而結(jié)果參考意義不大。
對(duì)于這個(gè)作業(yè),努力的最終匯報(bào)便是,看著終端中puzzle一個(gè)個(gè)被正確解決沾沾自喜,然后到相關(guān)的拓展材料中去體驗(yàn)無(wú)知,發(fā)現(xiàn)更大的世界。
這一周的Balanced Search Trees課程令我印象深刻。在進(jìn)入作業(yè)討論前,我希望先聊聊這次的課程內(nèi)容:
在前一周的課程中就已引入了二叉樹的概念,一個(gè)無(wú)比經(jīng)典的數(shù)據(jù)結(jié)構(gòu),這我在大學(xué)課程中便已了解到,只是認(rèn)識(shí)非常淺薄,并從未,也從未認(rèn)為我能夠,動(dòng)手實(shí)現(xiàn)過(guò)。
可如果說(shuō)在上一周的學(xué)習(xí)中彌補(bǔ)了我的這個(gè)問(wèn)題,對(duì)二叉樹有了更深刻的理解,那么這一周的課程徹底顛覆了曾經(jīng)心中幼稚的見(jiàn)解,所謂質(zhì)變,我應(yīng)該是在這里體會(huì)到的:
從小學(xué)四年級(jí)開始在少年宮學(xué)習(xí)BASIC到初中的PASCAL,以致雖然后來(lái)停止學(xué)習(xí),但因?yàn)榻佑|時(shí)間遠(yuǎn)早于同齡人而在此方面經(jīng)歷了各種優(yōu)勢(shì),但從小在我心中“程序”,以及它背后所代表的機(jī)械邏輯始終被認(rèn)為是“就那么回事兒”的。我天真地認(rèn)為自己早已掌握“程序”界的終極真理,無(wú)非就是嚴(yán)絲合縫一步不差地執(zhí)行、再執(zhí)行,只要“機(jī)械”性地認(rèn)真下去,便沒(méi)有什么解決不了的,以至于從小就在性格中造就了一面“徹頭徹尾的唯物+實(shí)用主義者”,不相信任何世間美好。
有這句天真的話在前,接著慢慢成長(zhǎng),讀書,行路,閱人,歷事,自然會(huì)學(xué)到原來(lái)這個(gè)世界真的不是那樣,開始理解愛(ài)與人性,開始理解這個(gè)世界的美妙。加上比較幸運(yùn),因而“唯心+浪漫主義者”的一面在我的性格里也發(fā)展得很好。但大學(xué)開始繼續(xù)學(xué)習(xí)計(jì)算機(jī),多少,對(duì)“程序”這一徹頭徹尾“唯物+實(shí)用”精神的產(chǎn)物,還是抱著一些看法的。
結(jié)束這一周的課程學(xué)習(xí)后,我改變了這個(gè)看法。
課程開始先講解了2-3 search trees,一種引入了新規(guī)則樹型結(jié)構(gòu)。這是一個(gè)“理解容易,實(shí)現(xiàn)復(fù)雜”的Symbol table方案,它可以對(duì)任何輸入數(shù)據(jù)保證樹形結(jié)構(gòu)的平衡以達(dá)到保證各項(xiàng)操作logN的復(fù)雜度,而規(guī)則卻足夠簡(jiǎn)單到可以在課堂中很快描述清楚,可以算是課程中第一個(gè)令我驚艷的算法。
這時(shí)我想:果然在一步步深入后算法變得高深莫測(cè)了起來(lái)呢,這個(gè)2-3 tree,理解起來(lái)容易,要實(shí)現(xiàn)起來(lái)可真是困難吶。已經(jīng)不是二叉樹那么簡(jiǎn)單的東西了,以后是不是只會(huì)更難啊,看來(lái)我遇到了一個(gè)屏障,這是不是我能走到的最遠(yuǎn)了啊。(開始有一點(diǎn)點(diǎn)擔(dān)心)
直到視頻結(jié)尾出現(xiàn)關(guān)于implementation的討論:
Bottom Line: Could do it, but there"s a better way.
緊接著下一節(jié)就是我們的主角:左傾紅黑二叉樹(LLRB)。
它只做到了一件事:將2-3 tree帶回了二叉樹世界,卻僅對(duì)樸素的二叉樹做極其微小的改動(dòng)——每個(gè)節(jié)點(diǎn)增加1 bit,用以表示“顏色”,加之無(wú)比簡(jiǎn)潔的引導(dǎo),便在現(xiàn)實(shí)世界中實(shí)現(xiàn)了原先只是構(gòu)想的2-3 tree幾乎全部的優(yōu)點(diǎn)。
紅黑樹本身就是70年代Sedgewick教授參與提出的,而LLRB是由他一手提出的極其簡(jiǎn)潔的紅黑樹實(shí)現(xiàn)版本,尤其是它的insertion,在課堂上作為重點(diǎn)講解,僅在原樸素二叉樹實(shí)現(xiàn)代碼基礎(chǔ)上,增加了3個(gè)小工具函數(shù)(左旋、右旋、翻轉(zhuǎn))和遞歸插入過(guò)程中的4行代碼(如圖),便完成了所有工作。
在徹底理解LLRB后,我想我被征服了。程序不再僅僅是徹頭徹尾“唯物+實(shí)用”精神的產(chǎn)物,而是我們理解世界的工具,也見(jiàn)證著我們改變世界過(guò)程,它與其他一切事物一樣,可以充滿真正的美。
我想可以這樣評(píng)價(jià)紅黑樹背后的思想:
它就像人類從蒙昧?xí)r代開啟智慧的一道光,將原來(lái)松散無(wú)序的組織變得優(yōu)雅而高效,并且還尊重了所有舊時(shí)代的傳統(tǒng);
也正是因?yàn)樽鹬貍鹘y(tǒng),才使得新的秩序得以穩(wěn)定存在;
新的秩序完整了傳統(tǒng),成為了傳統(tǒng)當(dāng)中,最為亮麗的部分。
這樣的成果也不是憑空出現(xiàn)的:
勇于跳出傳統(tǒng)的框框,用自由的視角審度;(2-3 tree已不是二叉樹)
回到現(xiàn)實(shí)在一點(diǎn)一滴中細(xì)膩體驗(yàn);(發(fā)現(xiàn)除繁瑣的直接實(shí)現(xiàn)外其他實(shí)現(xiàn)方式的可能性)
不妄自菲薄出身,用尊重的態(tài)度行動(dòng)。(選擇回到經(jīng)典二叉樹模式,試圖在舊傳統(tǒng)上建立新秩序)
知行合一,至此可謂正途。
同為插入255個(gè)隨機(jī)節(jié)點(diǎn),左圖為樸素二叉樹,右圖為左傾紅黑二叉樹。
作為第一部分的最后一個(gè)作業(yè),kd-tree其實(shí)難度沒(méi)有它看起來(lái)那么大。課程部分已很完整地討論過(guò)很多種經(jīng)典的樹形結(jié)構(gòu)(分析+實(shí)現(xiàn)),到這個(gè)階段學(xué)生應(yīng)已對(duì)樹形結(jié)構(gòu)非常熟悉了,實(shí)現(xiàn)kd-tree已是水到渠成。
同樣的,編程嚴(yán)謹(jǐn)性在這個(gè)作業(yè)中變得更加重要——情況相比前幾周又要復(fù)雜一些,從零編寫一個(gè)特定(且不簡(jiǎn)單的)規(guī)則下運(yùn)行的樹形結(jié)構(gòu),主要功能是高效地插入與查找;
這次的作業(yè)同樣包含了一個(gè)暴力算法的版本(使用默認(rèn)的紅黑樹結(jié)構(gòu)),除了在執(zhí)行效率上可以做比較外,還提供了一個(gè)重要的軟件工程思路:面臨一個(gè)較為復(fù)雜,而優(yōu)化空間很大的問(wèn)題時(shí),先實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的暴力算法版本檢測(cè)結(jié)果(往往可以非常簡(jiǎn)便地實(shí)現(xiàn)),再考慮進(jìn)階的算法與數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)性能優(yōu)化,并在實(shí)現(xiàn)期間,暴力算法可以作為檢驗(yàn)算法正確性最直接的參考。
實(shí)現(xiàn)過(guò)程中值得提到的點(diǎn):
節(jié)點(diǎn)的奇偶性是一個(gè)不小的麻煩,編寫需要十分謹(jǐn)慎,我的做法是在Node類中添加了一個(gè)boolean用以表示奇偶,相信一定有更好的方法(不存儲(chǔ)RectHV),還會(huì)回來(lái)探索;
Nearest Neighbor的查找過(guò)程需要思考清楚,剪枝的界限十分清晰,尤其是剪裁其中一個(gè)子樹的條件:如果本子樹中找到的最近點(diǎn)與給定點(diǎn)距離 已小于 給定點(diǎn)到另一子樹矩形區(qū)域的距離(點(diǎn)到點(diǎn)距離比點(diǎn)到矩形距離還近時(shí)),才能跳過(guò)另一子樹的遍歷過(guò)程。
左圖為Nearest Neighbor,右圖為Range Search。(根據(jù)鼠標(biāo)狀態(tài)信息)
圖中紅色部分為暴力算法運(yùn)行結(jié)果,藍(lán)色部分為kdtree運(yùn)行結(jié)果;在增加數(shù)據(jù)量后多帶帶測(cè)試,暴力解法效率明顯下降。
在一個(gè)學(xué)期的辛苦努力后,帶著滿滿的收獲與日益成熟的頭腦,第二部分正式開始了。
“相信經(jīng)過(guò)了第一部分的學(xué)習(xí)同學(xué)們都會(huì)有很大的提高,短暫休息后也恢復(fù)了飽滿的學(xué)習(xí)熱情,那么我有理由相信你們準(zhǔn)備好了接受點(diǎn)真正的考驗(yàn)?!?
—— 視頻中永遠(yuǎn)不會(huì)提到的導(dǎo)師內(nèi)心獨(dú)白(歷經(jīng)折磨后的腦補(bǔ))
第二部分在課程與作業(yè)兩個(gè)方向的難度都比第一部分提升了一個(gè)檔次。好在不巧被導(dǎo)師言中,我的確有非常好的提升感和新一輪的熱情。
所以,放馬過(guò)來(lái)吧。
第一周便直奔主題,開啟了數(shù)據(jù)結(jié)構(gòu)領(lǐng)域的又一大領(lǐng)域:圖。
課程中出現(xiàn)了一個(gè)很有趣的分類值得提到,是形容問(wèn)題難度的:
任何程序員都能夠解決。
典型的勤奮的算法課學(xué)生能夠解決。
雇傭一個(gè)領(lǐng)域?qū)<摇?/p>
非常難。
沒(méi)有人知道難度。
已證明不可能解決。
并舉出了一些符合條件例子來(lái)說(shuō)明這些分類,當(dāng)然可以料到的,有很多難度2的例子。
這針雞血打得,嘖嘖嘖,不留痕跡。
很好。諸位紅著眼的同學(xué),準(zhǔn)備變身吧。
第一周的作業(yè)是WordNet,少了第一部分作業(yè)多圖多動(dòng)畫的喧鬧,不再為搏人眼球而吵吵嚷嚷,卻在安靜的terminal和腦中的抽象之海中展開了一場(chǎng)貨真價(jià)實(shí)的戰(zhàn)斗。
因文字方面中西文化背景截然不同,導(dǎo)致WordNet的概念不太方便簡(jiǎn)單地描述清楚,但作業(yè)說(shuō)明中已解釋得比較詳細(xì),這里只放一張示意圖:
整個(gè)作業(yè)可以分為三大部分:選擇合適的結(jié)構(gòu)存儲(chǔ)結(jié)構(gòu)復(fù)雜的詞典及其網(wǎng)絡(luò)關(guān)系(基本功),實(shí)現(xiàn)正確高效的廣度優(yōu)先遍歷計(jì)算SAP(難點(diǎn)),和在此基礎(chǔ)上一層一層更細(xì)致的優(yōu)化(進(jìn)階);
本次也是checklist優(yōu)化部分唯一標(biāo)注optional的作業(yè),因?yàn)榈拇_實(shí)現(xiàn)起來(lái)有難度;分布如上所注。
詞典(synsets)為ID與單詞的多對(duì)多關(guān)系(一詞多號(hào),一號(hào)多詞,釋義僅供參考無(wú)需讀?。?,上下位關(guān)系(hypernyms)是以ID為單位的樹形結(jié)構(gòu)(圖中的箭頭關(guān)系),可以明顯看出hypernyms使用有向圖表示,synsets存儲(chǔ)方式取決于訪問(wèn)需求,因程序中正反都會(huì)使用(以詞求號(hào),以號(hào)求詞,求詞存在),我選擇了空間換時(shí)間:
private ArrayList> synset; // 以號(hào)求詞(獲取SAP中的ancestor對(duì)應(yīng)的單詞) private HashMap > ids; // 以詞求號(hào)(獲取索引單詞的ID以計(jì)算SAP) private HashSet nouns; // 求詞存在(API需求功能) private SAP sap; // 有向圖
文件讀取過(guò)程稍繁瑣,編程習(xí)慣要良好,保持謹(jǐn)慎不會(huì)錯(cuò)。
接著是SAP的實(shí)現(xiàn),這里周旋的余地很大,一定要認(rèn)真思考問(wèn)題本質(zhì),并多考慮一些特殊情況檢測(cè)自己的想法,再開始下手去做,并如checklist所強(qiáng)調(diào),不要輕易嘗試直接實(shí)現(xiàn)優(yōu)化版本,因?yàn)榧?xì)節(jié)繁瑣不易考慮周全,而可能帶來(lái)的潛在問(wèn)題非常多,每實(shí)現(xiàn)一部分都要做周全的測(cè)試確保無(wú)誤才行。(盡管最終成功的優(yōu)化,也可能僅僅是幾行的改變而已)
這里要提到的點(diǎn)(細(xì)節(jié)):
在checklist提到的三個(gè)優(yōu)化選擇中,我選擇了相對(duì)保守,但工作良好的優(yōu)化方式:每次的搜索初始化使用HashMap構(gòu)造函數(shù);正確實(shí)現(xiàn)了兩端同步運(yùn)行的BFS,并提供了提前結(jié)束的出口;只緩存了單個(gè)ID(單個(gè)節(jié)點(diǎn)與單個(gè)節(jié)點(diǎn))的查詢結(jié)果;
對(duì)單ID緩存的存儲(chǔ)方式,我的symbol table使用了一種很直接的無(wú)重合又簡(jiǎn)單的hash計(jì)策:給定查詢ID為v與w,總ID數(shù)為V,則查詢結(jié)果的編號(hào)記為:
(V-1)+(V-2)+(V-3)+...+v+(w-v),化簡(jiǎn)后為(V-1)v-(vv+v)/2+w。
提前結(jié)束BFS的出口開始時(shí)沒(méi)有考慮清楚,就暫時(shí)沒(méi)有添加,提交的版本拿到了103.12;后來(lái)仔細(xì)思考,發(fā)現(xiàn)了這部分余地,僅增加了一行代碼,分?jǐn)?shù)便提高了不少;
SAP同時(shí)支持DAG與DCG(有環(huán)圖),仔細(xì)分析如下這個(gè)特殊情況可能有助思考:(給定節(jié)點(diǎn)1與8,求其SAP)
(Made with Graphviz)
BFS如由節(jié)點(diǎn)1先開始,則輪到第4次迭代時(shí),節(jié)點(diǎn)1遍歷,發(fā)現(xiàn)共同祖先節(jié)點(diǎn)5,路徑距離為4+3=7,但此時(shí)不能停止搜索;實(shí)際SAP的共同祖先為1,路徑距離為0+4=4,是在第4次迭代的節(jié)點(diǎn)8遍歷部分被發(fā)現(xiàn)的。
在這門課程的通用說(shuō)明中便已提示過(guò),不要把a(bǔ)utograder當(dāng)作debug的工具,以省去自己發(fā)現(xiàn)問(wèn)題的過(guò)程;我必須保持誠(chéng)實(shí):至少,對(duì)于我這樣編程時(shí)基本“實(shí)用”精神至上的性格來(lái)講,因?yàn)閍utograder在各個(gè)方面都真的很完善,很多時(shí)候這是一條很難遵守的規(guī)定,所以我也體會(huì)到了這樣做的壞處:對(duì)于溫室中的作業(yè)來(lái)說(shuō)有便利的autograder幫助簡(jiǎn)直美好,但real world problem永遠(yuǎn)是殘酷的;在這個(gè)作業(yè)中,我想我失去了一些很寶貴的機(jī)會(huì),利用充足的測(cè)試材料親自去到那些數(shù)據(jù)的最深處游蕩,去體會(huì)探索算法的優(yōu)劣,這個(gè)過(guò)程簡(jiǎn)直是最迷人的部分(如上示例);意識(shí)到這些后,有一種作弊的愧疚感;每個(gè)人都應(yīng)當(dāng)正視它們,然后作出自己的選擇。
最終成績(jī)?yōu)?06.25,具體Bonus點(diǎn)可以看這個(gè)文件。
守得云開見(jiàn)月明。
本周作業(yè)算是一個(gè)“輕松有趣”的短暫休息;輕松到checklist都不知道該寫點(diǎn)什么好了……
在第一部分結(jié)束后休息的階段,因?yàn)槭值胗浲瓿蛇@門課作業(yè)的快感,于是到booksite轉(zhuǎn)悠了好久,又完成了一些額外的項(xiàng)目;
(完整項(xiàng)目下載:N-Body,Barnes-Hut (很美),Atomic Nature of Matter)
而緊接著在booksite推薦的Nifty Assignment中便見(jiàn)到了這個(gè)作業(yè),感覺(jué)很是驚艷,但還沒(méi)有動(dòng)手做,沒(méi)想到這么快就又見(jiàn)到它了。
(取自源SIGGRAPH2007介紹視頻,2008推出改進(jìn)版)
作業(yè)中值得提到的點(diǎn)其實(shí)不多,大部分功能的實(shí)現(xiàn)都非常直接,中間加入了一點(diǎn)圖像處理的入門知識(shí),同樣也非常好理解,重點(diǎn)是最短路徑的BFS:
對(duì)于固定值邊界的處理比較繁瑣,需要仔細(xì)處理;
對(duì)很多圖像處理問(wèn)題,雖處理對(duì)象大體上還符合“圖”的定義,但因?yàn)楹芏鄨D像固有的因素,會(huì)將問(wèn)題簡(jiǎn)化得多,不必再使用重量級(jí)的Digraph類,就事論事地解決問(wèn)題即可;
要找到能量最小的seam需要對(duì)整個(gè)圖像計(jì)算路徑距離,我的做法是維護(hù)distTo(最近距離)和edgeTo(最近距離對(duì)應(yīng)“父節(jié)點(diǎn)”)兩個(gè)數(shù)組,遍歷全圖后在最后一行(列)找到distTo的最小值即為所求seam的尾元素,通過(guò)追蹤edgeTo即可得到所求seam。
energy的復(fù)用是一個(gè)小難點(diǎn),需要認(rèn)真考慮每移除一列或一行seam后,哪些情況下的點(diǎn)應(yīng)該被重新計(jì)算,參考作業(yè)說(shuō)明的vertical-seam.png、horizontal-seam.png或下圖去分析,可能會(huì)有幫助:
我想到的轉(zhuǎn)置的目的是為了在縱橫兩個(gè)方向都能利用System.arraycopy()的高效,開始時(shí)我沒(méi)有做這項(xiàng)優(yōu)化,是手寫了removeHorizontalSeam()的這個(gè)過(guò)程,因已達(dá)到了滿分要求便沒(méi)有再優(yōu)化。
VIII. MaxFlow與Baseball Elimination √ 〇MaxFlow將是本課程接觸的最復(fù)雜的圖論問(wèn)題了。別這樣,我才剛剛興奮起來(lái)……
同樣地,作業(yè)Baseball Elimination是一個(gè)非常巧妙的Maxflow應(yīng)用,甚至都與“流量”沒(méi)有任何關(guān)系:
根據(jù)實(shí)時(shí)賽季數(shù)據(jù),通過(guò)建立流量模型,判斷當(dāng)前局勢(shì)是否已出現(xiàn)被“數(shù)學(xué)上淘汰”的隊(duì)伍(已無(wú)法再獲得冠軍);
雖然算法上十分有趣,但個(gè)人并不喜歡這道題背后某個(gè)方向的思維:對(duì)辛苦奮戰(zhàn)的運(yùn)動(dòng)員來(lái)說(shuō),每場(chǎng)比賽都是戰(zhàn)斗,每個(gè)細(xì)節(jié)的表現(xiàn)才定義了自己,不是由一群snobbish的“科學(xué)家”說(shuō)你被“數(shù)學(xué)上淘汰”了,你就真的毫無(wú)希望了;
當(dāng)然同時(shí)要明白這只是“黑暗版”的理解,對(duì)于專業(yè)選手來(lái)講任何信息都有其價(jià)值,在賽前以最清楚的數(shù)據(jù)明確自己的處境,以更好地調(diào)整戰(zhàn)略、激勵(lì)人心,再參與比賽,才算是真正在現(xiàn)實(shí)世界用出了好算法的最大價(jià)值。
Again, all in all,要nerdy地?zé)釔?ài),但不要做nerd。
同如修行的和尚,功德要回向眾生,而禪喜,留給自己就好。
本次作業(yè)在細(xì)節(jié)上有一些非常簡(jiǎn)單的小技巧可以幫助簡(jiǎn)化實(shí)現(xiàn)(簡(jiǎn)潔性也是checklist提示的,參考代碼不到200行,我的版本為150行左右),但可能不是第一次思考時(shí)就能想到:
在讀取文件數(shù)據(jù)時(shí)就可額外計(jì)算(保存)一些后期需要的數(shù)據(jù):目前榜首隊(duì)伍與獲勝場(chǎng)數(shù)(用于簡(jiǎn)單淘汰),和以雙方隊(duì)伍為單位的比賽場(chǎng)次(用于建立FlowNetwork的頂點(diǎn)數(shù));
"Do not worry about over-optimizing your program because the data sets that arise in real applications are tiny." 對(duì)我的思路,這意味著無(wú)需考慮FlowNetwork的復(fù)用問(wèn)題——一個(gè)早期造成很多痛苦的優(yōu)化嘗試;
FordFulkerson類的使用實(shí)際上使問(wèn)題僅剩下了建模部分,且在說(shuō)明中已解釋得非常詳細(xì),故實(shí)現(xiàn)十分直接。
IX. Tries與Boggle √ 〇然而Boggle卻是第二部分課程作業(yè)中,最有挑戰(zhàn)性的一個(gè)。(因?yàn)槲覜](méi)有拿到Bonus T_T)
“The goal on this assignment is raw speed—for example, it"s fine to use 10× more memory if the program is 10× faster. ”
(“明確地”暗示使用Trie)
從動(dòng)手開始做本次作業(yè),到基本感到?jīng)]有優(yōu)化余地,我的代碼經(jīng)歷了大概4-5個(gè)大版本的改變,小的修正與優(yōu)化更是不計(jì)其數(shù),即使是這樣一個(gè)固定情形下的問(wèn)題,也令我好好體會(huì)了一次,程序迭代更新、不斷修正與優(yōu)化的過(guò)程。
本次任務(wù)相當(dāng)于編寫圖示游戲的AI部分——BoggleSolver,而要拿到滿分以至Bonus,需要一秒內(nèi)解決成千上萬(wàn)個(gè)Boggle Board。
下面是我經(jīng)歷的迭代過(guò)程的簡(jiǎn)短介紹:(只選出了幾個(gè)典型版本,括號(hào)內(nèi)為autograder針對(duì)getAllValidWords()的時(shí)間測(cè)試,值為5秒內(nèi)調(diào)用次數(shù)的reference / student ratio,越小越快;滿分要求小于2,Bonus要求小于0.5;源材料沒(méi)有保留完整,數(shù)據(jù)可能稍有出入)
一版(~2.5):簡(jiǎn)單更改庫(kù)中現(xiàn)成的TST(三叉搜索樹)類(增加一個(gè)單行的PrefixQuery小函數(shù)),將Board預(yù)處理為char[W*H],使用非遞歸方式的DFS(主要維護(hù)兩個(gè)stack)遍歷Board實(shí)現(xiàn);
二版(~1.6):在前版基礎(chǔ)上,改TST為手寫實(shí)現(xiàn)的26-way Trie類,Board預(yù)處理為Bag
三版(~0.8):在前版基礎(chǔ)上,改非遞歸方式DFS為遞歸,將Trie類內(nèi)容直接寫入BoggleSolver,在DFS過(guò)程中直接傳遞Node指針而非調(diào)用PrefixQuery函數(shù);
終版(0.55):在前版基礎(chǔ)上,全面整理了各步驟細(xì)節(jié),cache中間變量,使用Bag而非HashSet存儲(chǔ)查詢結(jié)果,及(參考論壇討論后)其他細(xì)節(jié)上的技巧性優(yōu)化。
很遺憾最終依然沒(méi)有拿到Bonus,但每一份性能都已是得來(lái)不易。
Test 2: timing getAllValidWords() for 5.0 seconds using dictionary-yawl.txt
(must be <= 2x reference solution)reference solution calls per second: 6175.83
student solution calls per second: 11200.52
reference / student ratio: 0.55
學(xué)習(xí)經(jīng)驗(yàn):
開始時(shí)其實(shí)比較抗拒自己實(shí)現(xiàn)一些工具類,如TST或Trie,除了必要的功能外很不愿意改動(dòng)它們;但當(dāng)完成前期版本,開始尋求性能優(yōu)化,實(shí)實(shí)在在地了解了這些工具類運(yùn)作機(jī)理后,才發(fā)現(xiàn)最大的障礙其實(shí)來(lái)自這些工具類“為通用性而做出的性能上的犧牲”,于是義無(wú)反顧地自己手動(dòng)實(shí)現(xiàn)了滿足“最低需求”的版本,但因細(xì)節(jié)盡在自己的掌握,使得在DFS中傳遞Node指針這樣最直接有效的實(shí)現(xiàn)方式變得可能,自然,也得到了對(duì)應(yīng)的回報(bào);
checklist也不是圣經(jīng),有獨(dú)立思考問(wèn)題的意識(shí)才可能發(fā)現(xiàn)更大的世界:在實(shí)現(xiàn)非遞歸版DFS時(shí)明顯感到比較吃力,同時(shí)需要追蹤維護(hù)很多變量,而DFS的邏輯本身也更適合遞歸;非遞歸版DFS全當(dāng)練手,而能夠轉(zhuǎn)回遞歸實(shí)現(xiàn)版本則是大膽?yīng)毩⑺伎嫉慕Y(jié)果;
有同學(xué)使用多線程達(dá)到了非??斓某煽?jī)(0.24),但在算法課程作業(yè)這個(gè)意義上,多線程其實(shí)尚處于“灰色區(qū)域”,有作弊的嫌疑——況且也沒(méi)什么技術(shù)含量,無(wú)須在意。
X. Data Compression與Burrows-Wheeler √ 〇如果要用一個(gè)詞來(lái)形容這次的作業(yè),我想這個(gè)新學(xué)的fancy word會(huì)再合適不過(guò),“Nifty”。
課程講到這周基本接近尾聲,正則表達(dá)式?jīng)]有作為重點(diǎn)分析太多,而數(shù)據(jù)壓縮,則成為了這最后一次作業(yè)的主題;它看似無(wú)趣,卻在作業(yè)細(xì)致入微的步驟上隱藏著許多非常精致的點(diǎn),其間如抽絲撥繭般細(xì)膩的過(guò)程,堪稱快感連連;而其中一個(gè)Circular Suffix Array(CSA)排序的實(shí)現(xiàn),可以涵蓋從第一部分起討論過(guò)的所有排序算法:題目本身并沒(méi)有任何限制;學(xué)生需要認(rèn)真比較每一種排序方式的優(yōu)劣,并選擇出其中一種或幾種方法的組合,重新制造出一個(gè)最適合當(dāng)前情況下的高效解決方案;在這個(gè)意義上,它相比前面的作業(yè),與real world problem更接近了一步。
數(shù)據(jù)壓縮的過(guò)程本身就是比較抽象的,而這次的作業(yè)說(shuō)明與checklist更是文字翻倍而全無(wú)附圖,有同學(xué)開始時(shí)看不懂題目也是正常,慢慢來(lái)啦。【下文所有中括號(hào)為題目簡(jiǎn)短說(shuō)明,推薦閱讀詳細(xì)說(shuō)明】
i | Original Suffixes | Sorted Suffixes | index[i] |
---|---|---|---|
0 | A B R A C A D A B R A ! | ! A B R A C A D A B R A | 11 |
1 | B R A C A D A B R A ! A | A ! A B R A C A D A B R | 10 |
2 | R A C A D A B R A ! A B | A B R A ! A B R A C A D | 7 |
*3 | A C A D A B R A ! A B R | A B R A C A D A B R A ! | *0 |
4 | C A D A B R A ! A B R A | A C A D A B R A ! A B R | 3 |
5 | A D A B R A ! A B R A C | A D A B R A ! A B R A C | 5 |
6 | D A B R A ! A B R A C A | B R A ! A B R A C A D A | 8 |
7 | A B R A ! A B R A C A D | B R A C A D A B R A ! A | 1 |
8 | B R A ! A B R A C A D A | C A D A B R A ! A B R A | 4 |
9 | R A ! A B R A C A D A B | D A B R A ! A B R A C A | 6 |
10 | A ! A B R A C A D A B R | R A ! A B R A C A D A B | 9 |
11 | ! A B R A C A D A B R A | R A C A D A B R A ! A B | 2 |
【encode部分:對(duì)源字符串的CSA(左)排序,返回排好序的CSA(右)的最后一列(ARD!RCAAAABB),及源字符串所在位置(3)?!?br>認(rèn)真閱讀作業(yè)材料即可大致了解,Burrows-Wheeler壓縮算法的三部分:
Huffman壓縮已實(shí)現(xiàn)不必關(guān)心;
Move-to-front編碼最為簡(jiǎn)單可直接實(shí)現(xiàn);
Burrows-Wheeler decoder被稱為“the trickest part”,但緊接著便已提示到key-indexed counting與10行的核心代碼長(zhǎng)度,其實(shí)已算是提示了很多;
而其encoding需要使用Circular Suffix Array,所以最大的課題其實(shí)是,如何以最高效率實(shí)現(xiàn)Circular Suffix Array排序。
下面就先來(lái)看CSA的排序問(wèn)題:
因由源字符串逐個(gè)循環(huán)偏移構(gòu)成,CSA是一種具備很多特殊性質(zhì)的數(shù)組,而對(duì)這樣的數(shù)組排序照搬通用的排序算法一定有很大的優(yōu)化空間沒(méi)有利用,因此手工打造一個(gè)高效實(shí)用“特別化”的排序算法,就變成了眼前最實(shí)際的問(wèn)題。
有上一周迭代更新的經(jīng)驗(yàn)在前,這次幾乎是立即開始了手工打造的過(guò)程,而可考慮的范圍又幾乎沒(méi)有限制,以下是我的幾個(gè)正確版本:(分?jǐn)?shù)為CSA構(gòu)造函數(shù)運(yùn)行時(shí)間的student / reference ratio(越小越快),均取數(shù)據(jù)長(zhǎng)度為409600的測(cè)試成績(jī)作為樣本;兩個(gè)分?jǐn)?shù)僅輸入數(shù)據(jù)不同,第一個(gè)為隨機(jī)ASCII,第二個(gè)為典型英文內(nèi)容(dickens.txt);滿分要求均為3以下)
一版(2.85,5.16):統(tǒng)一使用Mergesort;
二版(4.07,6.69):統(tǒng)一使用Comparator暴力比較(或封裝為Comparable類,效率接近);
三版(0.45,1.34):長(zhǎng)度15以下切換到insertion sort,以上使用MSD radix sort;
四版(1.29,1.87):長(zhǎng)度15以下切換到insertion sort,以上使用3 way radix quick sort;
五版(3.04,3.83):改編自booksite所附源碼,較為低效的ManberMyers實(shí)現(xiàn)(使用MSD排好首位字符,接著使用簡(jiǎn)單quicksort);
這幾個(gè)是在權(quán)衡利弊后選擇實(shí)現(xiàn)(如沒(méi)有選擇LSD),并已保證正確性的版本(其中不少的實(shí)現(xiàn)使用了algs4.jar源碼),可以看出目前相對(duì)最高效的方案是MSD+insertion;
因?qū)φn堂中提到的ManberMyers算法很感興趣,所以后期做了不少嘗試,但還是沒(méi)有完全原創(chuàng)地完成一個(gè)正確版本,加上還有很多好的選擇,這里還會(huì)繼續(xù)努力;
課程API對(duì)Java的static塊沒(méi)有要求,所以可以在合適處(如MoveToFront類)利用。
最后是本作業(yè)最“Nifty”的部分,Burrows-Wheeler decoder:
i | Sorted Suffixes | next |
---|---|---|
0 | ! ? ? ? ? ? ? ? ? ? ? A | 3 |
1 | A ? ? ? ? ? ? ? ? ? ? R | 0 |
2 | A ? ? ? ? ? ? ? ? ? ? D | 6 |
*3 | A ? ? ? ? ? ? ? ? ? ? ! | 7 |
4 | A ? ? ? ? ? ? ? ? ? ? R | 8 |
5 | A ? ? ? ? ? ? ? ? ? ? C | 9 |
6 | B ? ? ? ? ? ? ? ? ? ? A | 10 |
7 | B ? ? ? ? ? ? ? ? ? ? A | 11 |
8 | C ? ? ? ? ? ? ? ? ? ? A | 5 |
9 | D ? ? ? ? ? ? ? ? ? ? A | 2 |
10 | R ? ? ? ? ? ? ? ? ? ? B | 1 |
11 | R ? ? ? ? ? ? ? ? ? ? B | 4 |
【decode部分(層層相扣,建議讀原文):已知CSA最后一列(ARD!...),與源字符串所在行(3);對(duì)最后一列排序可得第一列(!AAA...);而next數(shù)組使用方式如下:因已知源字符串(排序前第0行)在排序后位置為3,而next[3] = 7,即排序后第7行(B...A)為排序前的第1行(第0行的下一行),可知源字符串第1位為"B"(根據(jù)第3行,第0位為‘A’),依此類推即可得到源字符串;next數(shù)組的構(gòu)造為通過(guò)比較首尾字符出現(xiàn)位置得到:如第i行末位為A,而首位第一個(gè)未標(biāo)記過(guò)的A出現(xiàn)在第j行,則next[i] = j,標(biāo)記第j行?!?br>開始時(shí)我只看了作業(yè)說(shuō)明,所以冒失地寫好了一個(gè)“暴力decode”還渾然不自知,直到參考checklist后:“WTF”
事實(shí)證明那10行核心代碼基本與下圖所示相同:
問(wèn)題的關(guān)鍵在于next數(shù)組的構(gòu)造,可總結(jié)為如下幾點(diǎn):
key-indexed counting核心機(jī)理是index的累加,可以保證排序結(jié)果的穩(wěn)定(stable);
根據(jù)作業(yè)說(shuō)明的分析,next數(shù)組構(gòu)建的過(guò)程,確定重復(fù)字符位置的核心機(jī)理也是“穩(wěn)定性”(針對(duì)多次出現(xiàn)的同一字符,首尾列的相對(duì)先后順序一致);
對(duì)CSA可以不顯式創(chuàng)建字符串?dāng)?shù)組而僅保留源字符串,只添加一個(gè)簡(jiǎn)單的根據(jù)offset和index循環(huán)獲取對(duì)應(yīng)字符的工具函數(shù)即可,這為直接使用key-indexed counting創(chuàng)造了非常好的環(huán)境。
于是在對(duì)源碼極簡(jiǎn)單的改變后,完成了截然不同的任務(wù):(spoiler alert)
for (int i = 0; i < N; i++) count[t[i]+1]++; for (int i = 0; i < 256; i++) count[i+1] += count[i]; // The trickiest part for (int i = 0; i < N; i++) next[count[t[i]]++] = i;
其中N為字符串長(zhǎng)度,count為計(jì)數(shù)數(shù)組(默認(rèn)ASCII編碼包含256個(gè)字符),next為目標(biāo)數(shù)組,t為已知尾列字符串的對(duì)應(yīng)char[];(無(wú)需顯式排序找出首列,第二步count的累加已達(dá)到目的)
6行完成了next數(shù)組的構(gòu)建,加上接下來(lái)還原源字符串的過(guò)程,達(dá)到10行。
仔細(xì)品讀其中內(nèi)容至理解透徹,相信這妙不可言的感覺(jué)一定已深深印在閣下腦海。
歡迎來(lái)到算法世界。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/65984.html
摘要:最近著手學(xué)習(xí)的這本書,開始做習(xí)題時(shí)發(fā)現(xiàn)配套網(wǎng)站上對(duì)應(yīng)的習(xí)題答案并不完全,后發(fā)現(xiàn)以及有些人的博客上有部分答案,不過(guò)一般只做了第一章節(jié)的題目,大概是題目太多了的原因,在此自己整理自己所做的一份答案,希望有同行的人一起交流,分享。 最近著手學(xué)習(xí)Robert Sedgewick的Algorithms這本書,開始做習(xí)題時(shí)發(fā)現(xiàn)配套網(wǎng)站上對(duì)應(yīng)的習(xí)題答案并不完全,google后發(fā)現(xiàn)github以及有些...
摘要:實(shí)際上這個(gè)情形中存在冪定律實(shí)際上絕大多數(shù)的計(jì)算機(jī)算法的運(yùn)行時(shí)間滿足冪定律?;谘芯康弥瓌t上我們能夠獲得算法,程序或者操作的性能的精確數(shù)學(xué)模型。 前言 上一篇:并查集下一篇:棧和隊(duì)列 在算法性能上我們常常面臨的挑戰(zhàn)是我們的程序能否求解實(shí)際中的大型輸入:--為什么程序運(yùn)行的慢?--為什么程序耗盡了內(nèi)存? 沒(méi)有理解算法的性能特征會(huì)導(dǎo)致客戶端的性能很差,為了避免這種情況的出線,需要具備算法...
摘要:作者微信號(hào)微信公眾號(hào)簡(jiǎn)書地址最近機(jī)器學(xué)習(xí)工程師已經(jīng)成為了一個(gè)非常熱門的崗位,很多的工程師都想轉(zhuǎn)行到這個(gè)崗位。本文根據(jù)上面的課程,列了一個(gè)從新手到專業(yè)工程師的學(xué)習(xí)計(jì)劃,提供給大家學(xué)習(xí)。 作者:chen_h微信號(hào) & QQ:862251340微信公眾號(hào):coderpai簡(jiǎn)書地址:http://www.jianshu.com/p/32b2... 最近機(jī)器學(xué)習(xí)工程師已經(jīng)成為了一個(gè)非常熱門的崗...
摘要:是你學(xué)習(xí)從入門到專家必備的學(xué)習(xí)路線和優(yōu)質(zhì)學(xué)習(xí)資源。的數(shù)學(xué)基礎(chǔ)最主要是高等數(shù)學(xué)線性代數(shù)概率論與數(shù)理統(tǒng)計(jì)三門課程,這三門課程是本科必修的。其作為機(jī)器學(xué)習(xí)的入門和進(jìn)階資料非常適合。書籍介紹深度學(xué)習(xí)通常又被稱為花書,深度學(xué)習(xí)領(lǐng)域最經(jīng)典的暢銷書。 showImg(https://segmentfault.com/img/remote/1460000019011569); 【導(dǎo)讀】本文由知名開源平...
閱讀 2849·2021-08-20 09:37
閱讀 1606·2019-08-30 12:47
閱讀 1089·2019-08-29 13:27
閱讀 1684·2019-08-28 18:02
閱讀 749·2019-08-23 18:15
閱讀 3084·2019-08-23 16:51
閱讀 931·2019-08-23 14:13
閱讀 2124·2019-08-23 13:05