iT邦幫忙

2023 iThome 鐵人賽

DAY 1
0

序言 —— 從不完美中尋找完美

大家好呀!我是暗魂駭客,接下來參賽的30天請各位多多指教!希望我能順利完賽。

我會選擇參賽的理由主要是有兩個:
其一是想鍛鍊自己的技術寫作能力,這是我第一次參加技術文章撰寫的競賽,而且平時我不太做筆記,所以這對我來說是一個巨大的挑戰;其二是我對演算法有點半生不熟,除了學得不夠多之外,還有很多基礎知識的缺口和盲點,因此希望能透過這次競賽補足它們。

接下來30天的文章,我會從個人學習遇過的困難點出發,以 C++ 為主要實作語言,將許多我來回學了好幾次才搞懂的演算法和資料結構分享給大家。也因此我會常常引用許多參考資料來輔助說明,並且因為我本身對於演算法競賽和解題很感興趣,所以會時常引入 Atcoder、Codeforces、CSES、Leetcode 等等平台的題目作為範例。也因此本系列文章比較適合熟悉 C++ 基本語法並學過最基本演算法觀念的人

C++ 語法學習資源如果不排斥英文且想往程式競賽努力的話可以參考 Usaco Guide ,把 General + Bronze 看完應該差不多、如果偏好中文資源的話選擇也不少,偏好競賽取向的話可以看 從零到一:那些演算法競賽會用到的基礎語法 、 想要比較純粹的語法教學可以看 語言技術:C++ Gossip

語法上主要是希望讀者會以下幾個知識點即可:資料型態與變數的基本操作、基本運算、流程控制(判斷式、迴圈)、陣列、結構(struct)、運算子重載、指標和參照、函式、基本的遞迴寫法、STL(vector、pair、set、map、queue、stack、deque、bitset)

若在閱讀本系列文章或在自己實作的過程中碰到語法上的問題,若是完全沒頭緒的東西,建議查一查相關關鍵字後可以去 語言技術:C++ Gossip 翻翻看有沒有對應的段落;若是要查某個函式的功能或某個 STL 下的成員函式這類的問題,建議可以到 cppreferencecplusplus.com 找找。

比如我想找找 string 有沒有能提取子字串的成員函式?在 cppreference 你可以在右上角的搜尋欄直接查子字串的英文關鍵字 "substring",然後跳出來的第一個剛好就是了;或是在 cplusplus.com 中,可以在搜尋欄查 string 直接抵達它的 class 頁面,然後在下方的 member function中找自己要的函式。

之後進了目標頁面後推薦可以先看一遍 Example 從範例和裡面的註解中理解該語法的功能,再去看一下 Parameters 確認一下理解是否正確,最後如果有的話還會去看一下 Complexity 是否符合需求,Complexity 的部分會在下一章更仔細地跟大家聊,這邊先賣個關子。

在這次的鐵人賽中,我將主要依賴網路資源和 ChatGPT 作為參考。我會努力確保文章的嚴謹性和內容的充實度,這部分不容妥協。但由於我也還在學習中,難免會有錯誤或疏漏,希望大家能夠多多包容,也希望可以大家多給我一些建議,在下會仔細的拜讀並好好修正的。

還有,如果是被我參賽簡介吸引進來的人,嗯...我會盡量讓文章能包含我簡介中所說到的所有主題的...吧?(我盡力)

last but not least,因為本系列的文章並非什麼嚴謹的演算法資料結構教學,筆者在這個領域也並非專家,因此我會盡量以輕鬆、講一堆爛梗(X)的前提下跟大家介紹演算法,因為我認為能在過程中感受到快樂的學習才是好的學習過程,且反正我也沒有什麼形象上的包袱,所以接下來的敘述風格可能相當跳 TONE,再請各位多多包涵囉~

那幹話就到這邊為止,以下開始就是正文!

演算法與資料結構的必要性

在真正開始之前,還得先稍微講一下演算法和資料結構的必要性才行。

可以注意到,我談的是「必要性」而非重要性、用途、它們是什麼這類的問題,這是因為其他的問題在學習的過程中就會逐漸被揭示,不過那都建立在你肯學下去,在此之前你需要一個讓你堅持學習,「非學不可」的理由,而我此段想談的,則是我本身願意學習演算法的「必要原因」。

為了方便起見,我接下來會將演算法與資料結構簡稱為 DSA ( data structures and algorithms )

首先開宗明義地說:相比於物件導向(OOP)和函數導向(FP)等程式設計典範透過組織程式寫法以設計可讀性與維護性良好的程式碼;DSA 更針對於解決問題本身以及方法的效率,可以說是一個是針對人一個是針對問題。因此如果你認為學程式 OOP、FP 是身為軟體工程師必須持續鍛鍊的技能的話,那你也絕對不能錯過 DSA。你還不同意嗎?沒關係,我接下來會花很長的一大段說服你XD

首先我會從較「理論」的角度談起

理論角度的必要性

我初學程式設計是從 Java 學起,掌握大部分常用語法和基本物件導向觀念後的我開始探索自己能做些什麼,開始試著自己寫一些小專題來玩。但我發現有些程式的撰寫的「邏輯」很難想,像是解數獨、走迷宮、解魔術方塊等這類小專題。而且有一次我硬著頭皮寫出來的程式不僅超~級長,而且還莫名其妙跑很久,我檢查自己的程式,試圖倚靠直覺優化掉一點迴圈次數、遞迴遞迴、變數使用,結果不是出錯就是沒什麼明顯效果。最後上網查作法,卻發現別人的程式相當精簡,我卻看不出它到底為什麼比我快?

這時我才開始思考:該怎麼做才能寫出可以精確滿足需求,資源消耗又在可接受範圍內的程式?

提出這樣的問題是很關鍵的,因為人類在歷史的某一刻也遇過類似的問題...

你以為我要提艾倫‧圖靈在二戰期間設計了「圖靈機」模型以此來描述什麼是「可計算的」的劃時代發表或是破解德軍「恩尼格瑪密碼機」的歷史嗎?這太老套了啦~網路很多,自己去看XD

我比較想聊的是古希臘某個多才多藝,名叫亞里斯多德的人和其所開創的物理學。那時他的物理學都是以定性分析為主流,也就是不涉及數字或數量,依照觀察和主觀想法來分析事物,說白一點就是用直覺來看事情。亞里斯多德憑藉本身的好奇心與求知慾,也發現了許多自然界的物理現象並給予解釋,他的理論也在之後很長一段時間被人們所信賴普拿疼

但某時某刻,科學家(還是該叫哲♂學家?)發現亞里斯多德的理論存在諸多不足,因為亞里斯多德是出自主觀的觀察,所以那些理論不一定任何情況都是如此、甚至根本就不存在那種現象,只不過是靠直覺從某些自然現象想像的原理而已。

人們因此發現他們需要更精準客觀地分析和證明才能使物理學更進一步,所以科學家們嘗試透過實驗和引入數學分析來研究自然現象。最經典的案例莫過於艾薩克·牛頓,他利用了微積分這一數學工具,還以此提出三條基本運動定律,這些定律用數學來描述自然現象,並且能夠通過實驗來驗證。由此而後,物理學的進展進入到了新的階段,並逐漸發展我們所熟知的樣貌。

沒事講這些幹嘛!還記得我前面關於自己寫專案遇到困難的部分嗎?我最後的問題是「該怎麼做才能寫出可以精確滿足需求,資源消耗又在可接受範圍內的程式?」,會遇到這問題是因為我自己寫的程式莫名其妙跑很久,即使倚靠直覺上的優化也難以解決,最後看了做法也難以看出究竟哪裡不夠好,這部分是不是跟亞里斯多德的理論所面臨的問題很類似(難以分析|證明)?因此程式也需要引入數學來輔助解決問題!

而引入數學分析的程式這種概念早就被討論過了(跟前面篇幅約一句話的圖靈有關呦)!仔細從最底層來思考電腦能做的事情,就是計算!所以程式的本質上其實就是計算,而洽好有一門學問在探討如何設計一個可以透過一連串計算解決問題的解法並討論不同問題不同解法之間好壞該如何分類,而這門學問就是 DSA!

如同亞里斯多德透過定性分析來解釋自然界的現象,雖然一開始被接受,卻隨著科學的發展被淘汰了。這一點在程式設計中也是一樣的,初學者一開始或許會依賴直覺來寫程式,但隨著問題難度的提高,最終會發現單純倚靠直覺是不好的。這正是為什麼我們需要 DSA!

DSA 不是一門技術,而是一種理論、一種精神、一種態度

因此對於想學寫程式的人來說,DSA 就是讓原本倚賴原始直覺的程式能有所依據,讓你能更精準描述和分析你要解決的問題以及你解決問題的方法。舉例來說就好像想成為棒球選手,就需要分析自身的比賽表現與身體狀況制定科學的訓練計劃,而非盲目的狂練球或操體能。因此對於寫程式的人 DSA 是不可或缺的。

實用角度的必要性

當然,即使如此也會有人這麼想:
「常常聽軟體工程師說工作時9成以上的時候根本用不著 DSA 啊!不都是套套函式庫就解決的事情嗎?(-へ-)」、
「DSA 不是只有面試時LeetCode刷題用得上嗎?反正不過是教科書上的知識,要用到再惡補一下就好¯\(ツ)/¯」、
「那種刷題式的鍛鍊根本是毒瘤,就跟國高中時代的填鴨教育一樣,炫技般的解題技巧根本鍛鍊不到程式能力,只會讓未來的同事靠北你(-_-)」

以上那些說法我是在某些論壇和社群中的人看到的,雖然它們只是極少數人的想法,但我認為是有思考的價值的!因此接下來要從實際一點的角度探討學習 DSA 的必要性。

的確實際開發基本上用不到 DSA,套函式庫就好、的確 DSA 通常只在升學時APCS等程式檢定和面試刷LeetCode等時候才比較會被關注、也的確「完全盲目的刷題」是有害的。不過反過來看可以發現,這些言論也承認了 DSA 在面試、升學等場景是有用的,只是 DSA 在其他時候派不太上用場而已,而且練習方式不對還會造成反效果。我接下來將逐點發表我個人的看法:

1. DSA 在面試、升學等場景是有用,但是之後要用到再學就好

關於這點,首先比較值得討論的是為什麼它會被作為評價能力的指標?

畢竟我沒當過教授或面試官,所以不是很清楚原因,但我認為和 DSA 能讓人更精準描述和分析你要解決的問題以及你解決問題的方法的特質有關,因為這樣的你能量化你程式採用的方法地效率,有理有據地報告為什麼你的程式比較快?或是該怎麼做才能更快?而不是貼出一張單薄的執行速度比較圖表;當然也可能是因為 DSA 很講究思考邏輯的敏捷和正確性,因此用它作為指標比較容易篩出聰明人吧?

再來,得討論一下關於「要用到再學就好」到底對不對?

其實好像真的是這樣ww,不過我認為這是建立在你對 DSA 已有一定程度以上的熟悉才能做到「要用到再學」,因為 DSA 其實和數學很像,因此你可以稍微想像一下要你在沒學過相關知識的情況下學會並應用快速傅立葉轉換(FFT),你能「要用到再學」嗎?還是會變成「要用到再抄」?差一個字差很多!因為搞不好你會因為搞錯演算法或資料結構某些使用上的限制,而導致測試時乍看之下沒問題,實際運行卻發生各種怪怪的事情,這種問題超難 debug 的!

2. DSA 在實際開發中大部分時候派不上用場,套函式庫就好

老實說雖然我覺得問這問題有點像以前的高中生在問「我學三角函數幹嘛?出門買菜又不會用到三角函數」,不過我覺得還是可以從兩個角度來想:

  1. 所謂物以稀為貴,當你想和別人拉開差距、得到更好的待遇你就必須凸顯你的特別之處。在大多數情況下,雖然不需要使用 DSA,但當出現必須用到 DSA 的情境時,這正是你能展示專業優勢和拉開與他人差距的時機!

  2. 假設"需要用到"的那一刻一直都沒有出現,那你學 DSA 能幹嘛呢?因為 DSA 不是一門"技術"而是一種"理論" 技豈是如此不便之物。透過學習 DSA 的過程,你不知不覺會被其"精神"所影響,從而塑造出一套分析、解決問題的計算思維,這樣的思維會讓你能夠更透徹的認識需求,即使你沒有實際用到 DSA,但你看待問題的角度會有所不同,這樣一來你一樣可以像上面第一點所說的那樣「拉開差距」了

3. 填鴨式的刷題、炫技般的解題技巧只是在害自己程式碼的品質降低

老實說我覺得這某種程度上是對的,但這問題完全可以避免掉的!

以高中數學舉例,很多人在準備高中數學考試時是怎麼準備的?把所有公式和它們的變化背起來然後變身無情的刷題機器?這樣對考試的確很有用!但這樣考試過後就忘光光下次遇到類似主題只能重讀的例子也很常見,更慘的是若上大學後還以這種方式學習,會直接被大學數學打爛...那這要怎麼避免呢?老實說我數學不好所以只能引述我之前在數學社團看到的說法「要去理解定義,不要看過就算了、自己去推導一遍公式和證明、解題多思考,不要背做法」

在 DSA 的學習也是一樣的。僅為了面試或升學而狂刷考古題、或是死記硬背各種解法而不理解其背後的原理、將某些只在解題適用的超精簡程式寫法不分場合拿來使用。這些做法可能會帶來短期的效益讓你 pass 面試或檢定,但從長期來看,這樣的學習方式是非常有害的,像是因為不清楚原理和各種技巧的使用時機,結果把一些不適用目前狀況的技巧硬代入自己的程式中,這樣應該很難不被同事靠背XD。

小結

畢竟是第一篇文章,可能多少有點囉嗦,但我應該有讓各位理解學習 DSA 的必要性了..嗎?

也因為我並非專業人士,所以可能存在不少的錯誤之處,非常歡迎任何的建議!

那接下來終於可以真正開始來探討演算法與資料結構本身了!接下來我將從分析解決問題方法的效率的方法開始介紹,請各位敬請期待~


下一篇
Day 2 你對速度一無所知
系列文
CS補完計畫—演算法與資料結構的第三次衝擊30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言