This commit is contained in:
krahets
2026-07-19 03:42:10 +08:00
parent cb5235bc3f
commit 2b8612bc41
238 changed files with 7477 additions and 3 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,106 @@
---
comments: true
hide: [edit]
---
<!-- 此檔案由 utils/exercises/publish_exercises.py 根據 exercises.yaml 自動生成,請勿直接修改。 -->
# 5.5 &nbsp; 練習
## 5.5.1 &nbsp; 知識鞏固
### 1. &nbsp; 堆疊和佇列會先取出誰
準備一個空堆疊 `S` 和一個空佇列 `Q`,分別對它們執行下面同一組操作:
步驟 1:加入 `A`
步驟 2:加入 `B`
步驟 3:移除一個元素並記錄;
步驟 4:加入 `C`
步驟 5:不斷移除並記錄,直到容器為空。
請分別寫出 `S``Q` 中元素被移除的順序,並用“先入後出”或“先入先出”解釋差異。
??? success "參考答案"
堆疊 `S` 的移除順序是 `B、C、A`:加入 `A、B` 後先彈出最近加入的 `B`,再加入 `C`
此後依次彈出 `C、A`,體現“先入後出”。
佇列 `Q` 的移除順序是 `A、B、C`:加入 `A、B` 後先移除最早加入的 `A`
再加入 `C`,此後依次移除 `B、C`,體現“先入先出”。
### 2. &nbsp; 佇列尾越過陣列末尾怎麼辦
用長度為 5 的環形陣列實現佇列,陣列索引為 `04`
當前 `front = 3``size = 2`,佇列中的 `A、B` 分別位於索引 3、4。
<!-- numbered-subquestions -->
1. 執行“`C` 入列”時,`C` 應放在哪個索引?入列後 `size` 是多少?
2. 接著執行一次出列,彈出哪個元素?新的 `front``size` 分別是多少?
3. 此時從佇列首到佇列尾的邏輯順序是什麼?出列時是否需要移動陣列中的其他元素?為什麼?
??? success "參考答案"
1. 新元素的位置為
`(front + size) % 5 = (3 + 2) % 5 = 0`
所以 `C` 放在索引 0。入列後 `size = 3`
2. 出列彈出當前佇列首 `A`。新的佇列首索引為
`(3 + 1) % 5 = 4`,因此 `front = 4``size = 2`
3. 有效元素的邏輯順序為 `B、C`,其中 `B` 位於索引 4`C` 位於索引 0。
出列時只需移動 `front` 並修改 `size`,環形陣列用取餘讓索引回到開頭,
因此無須把其他元素整體向前移動。
### 3. &nbsp; 雙向佇列的兩端操作
這裡規定:`push_first` 表示從佇列首加入,`push_last` 表示從佇列尾加入,
`pop_first` 表示從佇列首彈出,`pop_last` 表示從佇列尾彈出。
對一個空的雙向佇列 `deq` 依次執行:
1. `push_last(A)`
2. `push_last(B)`
3. `push_first(C)`
4. `pop_last()`
5. `push_last(D)`
6. `pop_first()`
<!-- numbered-subquestions -->
1. 兩次彈出的元素分別是什麼?
2. 全部操作完成後,從佇列首到佇列尾還剩哪些元素?
3. 檢查這 6 步操作:只允許從佇列尾加入、從佇列首刪除的佇列能否全部完成?如果不能,請指出無法完成的操作;再說明雙向佇列能否完成及其原因。
??? success "參考答案"
前三步後,雙向佇列從佇列首到佇列尾為 `[C, A, B]`
<!-- numbered-subquestions -->
1. `pop_last()` 彈出 `B`;加入 `D` 後佇列為 `[C, A, D]`
`pop_first()` 再彈出 `C`
2. 最後剩下 `[A, D]`
3. 只允許在佇列尾新增、在佇列首刪除的佇列不能完成全部操作:
第 3 步 `push_first(C)` 要求從佇列首加入,第 4 步 `pop_last()` 要求從佇列尾刪除,都超出了這種佇列的操作範圍。
雙向佇列的兩端都可以新增和刪除,因此能夠完成這 6 步操作。
## 5.5.2 &nbsp; 程式設計練習
### 1. &nbsp; 檢查括號序列
給定一個只包含 `()``[]``{}` 這三類括號的字串 `s`,請使用堆疊判斷它是否合法。
合法序列須同時滿足:每個右括號都必須與最近一個尚未配對的左括號型別匹配,
並且走訪結束後沒有未配對的左括號。返回布林值表示判斷結果。
??? tip "解題提示"
1. 可以建立“右括號到對應左括號”的對映
2. 遇到右括號時,先檢查堆疊是否為空,再檢查堆疊頂是否匹配
3. 走訪結束後,堆疊也必須為空
[LeetCode](https://leetcode.cn/problems/valid-parentheses/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [題目解析](https://leetcode.cn/problems/valid-parentheses/solutions/9185/valid-parentheses-fu-zhu-zhan-fa-by-jin407891080/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
@@ -0,0 +1,22 @@
---
comments: true
icon: material/stack-overflow
---
# 第 5 章 &nbsp; 堆疊與佇列
![堆疊與佇列](../assets/covers/chapter_stack_and_queue.jpg){ class="cover-image" }
!!! abstract
堆疊如同疊貓貓,而佇列就像貓貓排隊。
兩者分別代表先入後出和先入先出的邏輯關係。
## 本章內容
- [5.1 &nbsp; 堆疊](stack.md)
- [5.2 &nbsp; 佇列](queue.md)
- [5.3 &nbsp; 雙向佇列](deque.md)
- [5.4 &nbsp; 小結](summary.md)
- [5.5 &nbsp; 練習](exercises.md)
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
---
comments: true
---
# 5.4 &nbsp; 小結
### 1. &nbsp; 重點回顧
- 堆疊是一種遵循先入後出原則的資料結構,可透過陣列或鏈結串列來實現。
- 在時間效率方面,堆疊的陣列實現具有較高的平均效率,但在擴容過程中,單次入堆疊操作的時間複雜度會劣化至 $O(n)$ 。相比之下,堆疊的鏈結串列實現具有更為穩定的效率表現。
- 在空間效率方面,堆疊的陣列實現可能導致一定程度的空間浪費。但需要注意的是,鏈結串列節點所佔用的記憶體空間比陣列元素更大。
- 佇列是一種遵循先入先出原則的資料結構,同樣可以透過陣列或鏈結串列來實現。在時間效率和空間效率的對比上,佇列的結論與前述堆疊的結論相似。
- 雙向佇列是一種具有更高自由度的佇列,它允許在兩端進行元素的新增和刪除操作。
### 2. &nbsp; Q & A
**Q**:瀏覽器的前進後退是否是雙向鏈結串列實現?
瀏覽器的前進後退功能本質上是“堆疊”的體現。當用戶訪問一個新頁面時,該頁面會被新增到堆疊頂;當用戶點選後退按鈕時,該頁面會從堆疊頂彈出。使用雙向佇列可以方便地實現一些額外操作,這個在“雙向佇列”章節有提到。
**Q**:在出堆疊後,是否需要釋放出堆疊節點的記憶體?
如果後續仍需要使用彈出節點,則不需要釋放記憶體。若之後不需要用到,`Java``Python` 等語言擁有自動垃圾回收機制,因此不需要手動釋放記憶體;在 `C``C++` 中需要手動釋放記憶體。
**Q**:雙向佇列像是兩個堆疊拼接在了一起,它的用途是什麼?
雙向佇列就像是堆疊和佇列的組合或兩個堆疊拼在了一起。它表現的是堆疊 + 佇列的邏輯,因此可以實現堆疊與佇列的所有應用,並且更加靈活。
**Q**:撤銷(undo)和反撤銷(redo)具體是如何實現的?
使用兩個堆疊,堆疊 `A` 用於撤銷,堆疊 `B` 用於反撤銷。
1. 每當使用者執行一個操作,將這個操作壓入堆疊 `A` ,並清空堆疊 `B`
2. 當用戶執行“撤銷”時,從堆疊 `A` 中彈出最近的操作,並將其壓入堆疊 `B`
3. 當用戶執行“反撤銷”時,從堆疊 `B` 中彈出最近的操作,並將其壓入堆疊 `A`