This commit is contained in:
krahets
2024-04-06 03:02:20 +08:00
parent 0a9daa8b9f
commit 8d37c215c8
148 changed files with 70398 additions and 408 deletions
@@ -0,0 +1,66 @@
---
comments: true
---
# 1.1   演算法無處不在
當我們聽到“演算法”這個詞時,很自然地會想到數學。然而實際上,許多演算法並不涉及複雜數學,而是更多地依賴基本邏輯,這些邏輯在我們的日常生活中處處可見。
在正式探討演算法之前,有一個有趣的事實值得分享:**你已經在不知不覺中學會了許多演算法,並習慣將它們應用到日常生活中了**。下面我將舉幾個具體的例子來證實這一點。
**例一:查字典**。在字典裡,每個漢字都對應一個拼音,而字典是按照拼音字母順序排列的。假設我們需要查詢一個拼音首字母為 $r$ 的字,通常會按照圖 1-1 所示的方式實現。
1. 翻開字典約一半的頁數,檢視該頁的首字母是什麼,假設首字母為 $m$ 。
2. 由於在拼音字母表中 $r$ 位於 $m$ 之後,所以排除字典前半部分,查詢範圍縮小到後半部分。
3. 不斷重複步驟 `1.` 和 步驟 `2.` ,直至找到拼音首字母為 $r$ 的頁碼為止。
=== "<1>"
![查字典步驟](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png){ class="animation-figure" }
=== "<2>"
![binary_search_dictionary_step2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png){ class="animation-figure" }
=== "<3>"
![binary_search_dictionary_step3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png){ class="animation-figure" }
=== "<4>"
![binary_search_dictionary_step4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png){ class="animation-figure" }
=== "<5>"
![binary_search_dictionary_step5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png){ class="animation-figure" }
<p align="center"> 圖 1-1 &nbsp; 查字典步驟 </p>
查字典這個小學生必備技能,實際上就是著名的“二分搜尋”演算法。從資料結構的角度,我們可以把字典視為一個已排序的“陣列”;從演算法的角度,我們可以將上述查字典的一系列操作看作“二分搜尋”。
**例二:整理撲克**。我們在打牌時,每局都需要整理手中的撲克牌,使其從小到大排列,實現流程如圖 1-2 所示。
1. 將撲克牌劃分為“有序”和“無序”兩部分,並假設初始狀態下最左 1 張撲克牌已經有序。
2. 在無序部分抽出一張撲克牌,插入至有序部分的正確位置;完成後最左 2 張撲克已經有序。
3. 不斷迴圈步驟 `2.` ,每一輪將一張撲克牌從無序部分插入至有序部分,直至所有撲克牌都有序。
![撲克排序步驟](algorithms_are_everywhere.assets/playing_cards_sorting.png){ class="animation-figure" }
<p align="center"> 圖 1-2 &nbsp; 撲克排序步驟 </p>
上述整理撲克牌的方法本質上是“插入排序”演算法,它在處理小型資料集時非常高效。許多程式語言的排序庫函式中都有插入排序的身影。
**例三:貨幣找零**。假設我們在超市購買了 $69$ 元的商品,給了收銀員 $100$ 元,則收銀員需要找我們 $31$ 元。他會很自然地完成如圖 1-3 所示的思考。
1. 可選項是比 $31$ 元面值更小的貨幣,包括 $1$ 元、$5$ 元、$10$ 元、$20$ 元。
2. 從可選項中拿出最大的 $20$ 元,剩餘 $31 - 20 = 11$ 元。
3. 從剩餘可選項中拿出最大的 $10$ 元,剩餘 $11 - 10 = 1$ 元。
4. 從剩餘可選項中拿出最大的 $1$ 元,剩餘 $1 - 1 = 0$ 元。
5. 完成找零,方案為 $20 + 10 + 1 = 31$ 元。
![貨幣找零過程](algorithms_are_everywhere.assets/greedy_change.png){ class="animation-figure" }
<p align="center"> 圖 1-3 &nbsp; 貨幣找零過程 </p>
在以上步驟中,我們每一步都採取當前看來最好的選擇(儘可能用大面額的貨幣),最終得到了可行的找零方案。從資料結構與演算法的角度看,這種方法本質上是“貪婪”演算法。
小到烹飪一道菜,大到星際航行,幾乎所有問題的解決都離不開演算法。計算機的出現使得我們能夠透過程式設計將資料結構儲存在記憶體中,同時編寫程式碼呼叫 CPU 和 GPU 執行演算法。這樣一來,我們就能把生活中的問題轉移到計算機上,以更高效的方式解決各種複雜問題。
!!! tip
如果你對資料結構、演算法、陣列和二分搜尋等概念仍感到一知半解,請繼續往下閱讀,本書將引導你邁入資料結構與演算法的知識殿堂。
@@ -0,0 +1,20 @@
---
comments: true
icon: material/calculator-variant-outline
---
# 第 1 章 &nbsp; 初識演算法
![初識演算法](../assets/covers/chapter_introduction.jpg){ class="cover-image" }
!!! abstract
一位少女翩翩起舞,與資料交織在一起,裙襬上飄揚著演算法的旋律。
她邀請你共舞,請緊跟她的步伐,踏入充滿邏輯與美感的演算法世界。
## Chapter Contents
- [1.1 &nbsp; 演算法無處不在](https://www.hello-algo.com/en/chapter_introduction/algorithms_are_everywhere/)
- [1.2 &nbsp; 演算法是什麼](https://www.hello-algo.com/en/chapter_introduction/what_is_dsa/)
- [1.3 &nbsp; 小結](https://www.hello-algo.com/en/chapter_introduction/summary/)
@@ -0,0 +1,13 @@
---
comments: true
---
# 1.3 &nbsp; 小結
- 演算法在日常生活中無處不在,並不是遙不可及的高深知識。實際上,我們已經在不知不覺中學會了許多演算法,用以解決生活中的大小問題。
- 查字典的原理與二分搜尋演算法相一致。二分搜尋演算法體現了分而治之的重要演算法思想。
- 整理撲克的過程與插入排序演算法非常類似。插入排序演算法適合排序小型資料集。
- 貨幣找零的步驟本質上是貪婪演算法,每一步都採取當前看來最好的選擇。
- 演算法是在有限時間內解決特定問題的一組指令或操作步驟,而資料結構是計算機中組織和儲存資料的方式。
- 資料結構與演算法緊密相連。資料結構是演算法的基石,而演算法是資料結構發揮作用的舞臺。
- 我們可以將資料結構與演算法類比為拼裝積木,積木代表資料,積木的形狀和連線方式等代表資料結構,拼裝積木的步驟則對應演算法。
@@ -0,0 +1,65 @@
---
comments: true
---
# 1.2 &nbsp; 演算法是什麼
## 1.2.1 &nbsp; 演算法定義
<u>演算法(algorithm</u>是在有限時間內解決特定問題的一組指令或操作步驟,它具有以下特性。
- 問題是明確的,包含清晰的輸入和輸出定義。
- 具有可行性,能夠在有限步驟、時間和記憶體空間下完成。
- 各步驟都有確定的含義,在相同的輸入和執行條件下,輸出始終相同。
## 1.2.2 &nbsp; 資料結構定義
<u>資料結構(data structure</u>是計算機中組織和儲存資料的方式,具有以下設計目標。
- 空間佔用儘量少,以節省計算機記憶體。
- 資料操作儘可能快速,涵蓋資料訪問、新增、刪除、更新等。
- 提供簡潔的資料表示和邏輯資訊,以便演算法高效執行。
**資料結構設計是一個充滿權衡的過程**。如果想在某方面取得提升,往往需要在另一方面作出妥協。下面舉兩個例子。
- 鏈結串列相較於陣列,在資料新增和刪除操作上更加便捷,但犧牲了資料訪問速度。
- 圖相較於鏈結串列,提供了更豐富的邏輯資訊,但需要佔用更大的記憶體空間。
## 1.2.3 &nbsp; 資料結構與演算法的關係
如圖 1-4 所示,資料結構與演算法高度相關、緊密結合,具體表現在以下三個方面。
- 資料結構是演算法的基石。資料結構為演算法提供了結構化儲存的資料,以及操作資料的方法。
- 演算法是資料結構發揮作用的舞臺。資料結構本身僅儲存資料資訊,結合演算法才能解決特定問題。
- 演算法通常可以基於不同的資料結構實現,但執行效率可能相差很大,選擇合適的資料結構是關鍵。
![資料結構與演算法的關係](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png){ class="animation-figure" }
<p align="center"> 圖 1-4 &nbsp; 資料結構與演算法的關係 </p>
資料結構與演算法猶如圖 1-5 所示的拼裝積木。一套積木,除了包含許多零件之外,還附有詳細的組裝說明書。我們按照說明書一步步操作,就能組裝出精美的積木模型。
![拼裝積木](what_is_dsa.assets/assembling_blocks.png){ class="animation-figure" }
<p align="center"> 圖 1-5 &nbsp; 拼裝積木 </p>
兩者的詳細對應關係如表 1-1 所示。
<p align="center"> 表 1-1 &nbsp; 將資料結構與演算法類比為拼裝積木 </p>
<div class="center-table" markdown>
| 資料結構與演算法 | 拼裝積木 |
| -------------- | ---------------------------------------- |
| 輸入資料 | 未拼裝的積木 |
| 資料結構 | 積木組織形式,包括形狀、大小、連線方式等 |
| 演算法 | 把積木拼成目標形態的一系列操作步驟 |
| 輸出資料 | 積木模型 |
</div>
值得說明的是,資料結構與演算法是獨立於程式語言的。正因如此,本書得以提供基於多種程式語言的實現。
!!! tip "約定俗成的簡稱"
在實際討論時,我們通常會將“資料結構與演算法”簡稱為“演算法”。比如眾所周知的 LeetCode 演算法題目,實際上同時考查資料結構和演算法兩方面的知識。