1. 程式人生 > >算法系列教程01

算法系列教程01

大家好,好久沒有更新文章了,對不起大家。今天開始,我要寫另一個系列教程:演算法。

開篇語

為什麼要寫算法系列教程呢?因為最近剛看完《Algorithms, 4th Edition》這本經典演算法書(電子書,中英版網上都有下載),有了些新收穫,覺得那些零散的知識點和經驗有必要也值得花時間好好整理一下。另外,我自己的演算法知識應該還沒有達到去學好“機器學習”這門學科的程度,在寫教程的同時我也會不斷學習,我想和大家一起用演算法這把鑰匙開啟“機器學習”這扇大門,去探索演算法是如何讓機器“學習”的。

為什麼要學習演算法呢?我個人覺得最終目的還是為了提高自己的職場競爭力。演算法的本質就是解決問題,所以學習演算法本質上是在提高解決問題的能力,這是職場最重要的能力之一。另外,如果你想在面試中表現得更加出色,也要對演算法有一定程度的掌握。

寫教程是很耗費精力的,而且學習演算法是很枯燥的,但如果大家都能參與進來一起交流討論,那麼整個過程就是不斷進行頭腦風暴、集思廣益的過程,這會是我繼續寫作的動力,這也會使得枯燥的演算法學習之路變得有趣,並讓每個人收穫滿滿。

為了更方便大家利用碎片化的時間學習,更好的利用空閒時間在手機上閱讀,這個系列的每一篇文章我都會精心排版,而且每一篇文章閱讀時間都不會太長,會盡量控制在 6 分種以內(不包括思考的時間)。

這個系列會包含三部分內容。第一部分是演算法基礎,由於演算法和資料結構是密切相關的,所以也會講到一些資料結構的知識,但不會講太細。第二部分以常見的演算法案例解析為主,包括解析一些演算法面試題。第三部分是演算法在機器學習中的應用,由於我自己也沒有學習過機器學習,所以這部分是探索式和交流式的,我會把我學到的用我理解寫出來,希望大家能參與進來一起交流。

這個系列的文章程式碼選擇用 JavaScript 語言來編寫,程式語言都是相通的,我相信你看了 JavaScript 寫的演算法實現可以很快用你熟悉的語言寫出來。但第三部分的機器學習相關內容,可能選用 Python,現在還不確定。

希望這個算法系列教程能幫助大家在今後的程式設計道路上得心應手。

開篇演算法題:FizzBuzz

好了,我們今天以一個演算法問題 FizzBuzz 來開篇吧。FizzBuzz 是一個非常典型的用來面試的演算法題。下面是 FizzBuzz 的自然語言描述:

編寫一個程式,列印 1 到 100 中的數字,但能被 3 整除的變為“Fizz”,能被 5 整除的變為“Buzz”,既能被 3 整除又能被 5 整除的變為“FizzBuzz”。

一看是不是很簡單?確實很簡單。看完題,隨手就能寫出下面的程式碼:

for (var i = 1; i < 101; i++) {  if (i % 3 == 0) console.log('Fizz')  else if (i % 5 == 0) console.log('Buzz')  else if (i % 3 == 0 && i % 5 == 0) console.log('FizzBuzz')  else console.log(i)}

和需求吻合,好像沒什麼問題。等等,仔細檢查,這段程式碼問題大了,FizzBuzz永遠沒有輸出的機會,例如遇到 i=15 還是會輸出Fizz。這是初級程式設計師很容易犯的一個錯誤,拿到需求後直接就是一通寫,對需求的理解比較生硬,不容易考慮到上下文的關聯關係。糾正後的寫法是將第 4 行的 if 條件放到最前面:

for (var i = 1; i < 101; i++) {  if (i % 3 == 0 && i % 5 == 0) console.log('FizzBuzz')  else if (i % 3 == 0) console.log('Fizz')  else if (i % 5 == 0) console.log('Buzz')  else console.log(i)}

這樣寫是對的,但如果是面試,這個答案只能算是及格。如何能做到優秀呢?我總結寫演算法可以分為三個步驟:

第一步是實現,即按照需求把演算法寫對了,讓程式能正確的執行;第二步是優化,即降低複雜度,讓程式運算更高效。第三步是美化,即讓程式碼看起來美觀簡潔,這一步很多時候會靈活運用到程式語言本身的語法糖,但簡潔有時候會犧牲程式碼的易讀性,這需要在實際場景中稍作權衡。

做到這三步,我覺得應對面試中的演算法題就可以拿優秀了。

前面我們已經完成了寫 FizzBuzz 演算法的第一步,我們再來做第二步:優化。既能被 3 整除又能被 5 整除,我們很自然會想到 3 和 5 的最小公倍數:15,能被 15 整除的數一定也是既能被 3 整除又能被 5 整除的。也就是說上面的第二行程式碼可以只對 i 判斷一次(而不是兩次),下面是優化後的程式碼:

for (var i = 1; i < 101; i++) {  if (i % 15 == 0) console.log('FizzBuzz')  else if (i % 3 == 0) console.log('Fizz')  else if (i % 5 == 0) console.log('Buzz')  else console.log(i)}

我們再來看第三步:美化。上面我們寫這個演算法用了 6 行程式碼(包括大括號),如果要減少程式碼量,能減少到多少行呢?這裡給到 2 行的實現方案:

for (let i = 0; i < 100; )  console.log((++i % 3 ? '' : 'Fizz') + (i % 5 ? '' : 'Buzz') || i)

這個實現來自 Reddit 。當然也可以寫成一行,但寫成兩行閱讀體驗更好。這段程式碼很好理解,我就不解讀了,有疑問可以在留言區留言提問。

最後,對於 FizzBuzz 演算法問題,你有其它的實現方案嗎?歡迎在留言區討論。