hello-algo/zh-hant/docs/chapter_stack_and_queue/stack.md
Yudong Jin b2f0d4603d
Many bug fixes and improvements (#1270)
* Add Ruby and Kotlin icons
Add the avatar of @curtishd

* Update README

* Synchronize zh-hant and zh versions.

* Translate the pythontutor blocks to traditional Chinese

* Fix en/mkdocs.yml

* Update the landing page of the en version.

* Fix the Dockerfile

* Refine the en landingpage

* Fix en landing page

* Reset the README.md
2024-04-11 20:18:19 +08:00

436 lines
14 KiB
Markdown
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 堆疊
<u>堆疊stack</u>是一種遵循先入後出邏輯的線性資料結構。
我們可以將堆疊類比為桌面上的一疊盤子,如果想取出底部的盤子,則需要先將上面的盤子依次移走。我們將盤子替換為各種型別的元素(如整數、字元、物件等),就得到了堆疊這種資料結構。
如下圖所示,我們把堆積疊元素的頂部稱為“堆疊頂”,底部稱為“堆疊底”。將把元素新增到堆疊頂的操作叫作“入堆疊”,刪除堆疊頂元素的操作叫作“出堆疊”。
![堆疊的先入後出規則](stack.assets/stack_operations.png)
## 堆疊的常用操作
堆疊的常用操作如下表所示,具體的方法名需要根據所使用的程式語言來確定。在此,我們以常見的 `push()`、`pop()`、`peek()` 命名為例。
<p align="center"><id> &nbsp; 堆疊的操作效率 </p>
| 方法 | 描述 | 時間複雜度 |
| -------- | ---------------------- | ---------- |
| `push()` | 元素入堆疊(新增至堆疊頂) | $O(1)$ |
| `pop()` | 堆疊頂元素出堆疊 | $O(1)$ |
| `peek()` | 訪問堆疊頂元素 | $O(1)$ |
通常情況下,我們可以直接使用程式語言內建的堆疊類別。然而,某些語言可能沒有專門提供堆疊類別,這時我們可以將該語言的“陣列”或“鏈結串列”當作堆疊來使用,並在程式邏輯上忽略與堆疊無關的操作。
=== "Python"
```python title="stack.py"
# 初始化堆疊
# Python 沒有內建的堆疊類別,可以把 list 當作堆疊來使用
stack: list[int] = []
# 元素入堆疊
stack.append(1)
stack.append(3)
stack.append(2)
stack.append(5)
stack.append(4)
# 訪問堆疊頂元素
peek: int = stack[-1]
# 元素出堆疊
pop: int = stack.pop()
# 獲取堆疊的長度
size: int = len(stack)
# 判斷是否為空
is_empty: bool = len(stack) == 0
```
=== "C++"
```cpp title="stack.cpp"
/* 初始化堆疊 */
stack<int> stack;
/* 元素入堆疊 */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* 訪問堆疊頂元素 */
int top = stack.top();
/* 元素出堆疊 */
stack.pop(); // 無返回值
/* 獲取堆疊的長度 */
int size = stack.size();
/* 判斷是否為空 */
bool empty = stack.empty();
```
=== "Java"
```java title="stack.java"
/* 初始化堆疊 */
Stack<Integer> stack = new Stack<>();
/* 元素入堆疊 */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* 訪問堆疊頂元素 */
int peek = stack.peek();
/* 元素出堆疊 */
int pop = stack.pop();
/* 獲取堆疊的長度 */
int size = stack.size();
/* 判斷是否為空 */
boolean isEmpty = stack.isEmpty();
```
=== "C#"
```csharp title="stack.cs"
/* 初始化堆疊 */
Stack<int> stack = new();
/* 元素入堆疊 */
stack.Push(1);
stack.Push(3);
stack.Push(2);
stack.Push(5);
stack.Push(4);
/* 訪問堆疊頂元素 */
int peek = stack.Peek();
/* 元素出堆疊 */
int pop = stack.Pop();
/* 獲取堆疊的長度 */
int size = stack.Count;
/* 判斷是否為空 */
bool isEmpty = stack.Count == 0;
```
=== "Go"
```go title="stack_test.go"
/* 初始化堆疊 */
// 在 Go 中,推薦將 Slice 當作堆疊來使用
var stack []int
/* 元素入堆疊 */
stack = append(stack, 1)
stack = append(stack, 3)
stack = append(stack, 2)
stack = append(stack, 5)
stack = append(stack, 4)
/* 訪問堆疊頂元素 */
peek := stack[len(stack)-1]
/* 元素出堆疊 */
pop := stack[len(stack)-1]
stack = stack[:len(stack)-1]
/* 獲取堆疊的長度 */
size := len(stack)
/* 判斷是否為空 */
isEmpty := len(stack) == 0
```
=== "Swift"
```swift title="stack.swift"
/* 初始化堆疊 */
// Swift 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用
var stack: [Int] = []
/* 元素入堆疊 */
stack.append(1)
stack.append(3)
stack.append(2)
stack.append(5)
stack.append(4)
/* 訪問堆疊頂元素 */
let peek = stack.last!
/* 元素出堆疊 */
let pop = stack.removeLast()
/* 獲取堆疊的長度 */
let size = stack.count
/* 判斷是否為空 */
let isEmpty = stack.isEmpty
```
=== "JS"
```javascript title="stack.js"
/* 初始化堆疊 */
// JavaScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用
const stack = [];
/* 元素入堆疊 */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* 訪問堆疊頂元素 */
const peek = stack[stack.length-1];
/* 元素出堆疊 */
const pop = stack.pop();
/* 獲取堆疊的長度 */
const size = stack.length;
/* 判斷是否為空 */
const is_empty = stack.length === 0;
```
=== "TS"
```typescript title="stack.ts"
/* 初始化堆疊 */
// TypeScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用
const stack: number[] = [];
/* 元素入堆疊 */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* 訪問堆疊頂元素 */
const peek = stack[stack.length - 1];
/* 元素出堆疊 */
const pop = stack.pop();
/* 獲取堆疊的長度 */
const size = stack.length;
/* 判斷是否為空 */
const is_empty = stack.length === 0;
```
=== "Dart"
```dart title="stack.dart"
/* 初始化堆疊 */
// Dart 沒有內建的堆疊類別,可以把 List 當作堆疊來使用
List<int> stack = [];
/* 元素入堆疊 */
stack.add(1);
stack.add(3);
stack.add(2);
stack.add(5);
stack.add(4);
/* 訪問堆疊頂元素 */
int peek = stack.last;
/* 元素出堆疊 */
int pop = stack.removeLast();
/* 獲取堆疊的長度 */
int size = stack.length;
/* 判斷是否為空 */
bool isEmpty = stack.isEmpty;
```
=== "Rust"
```rust title="stack.rs"
/* 初始化堆疊 */
// 把 Vec 當作堆疊來使用
let mut stack: Vec<i32> = Vec::new();
/* 元素入堆疊 */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* 訪問堆疊頂元素 */
let top = stack.last().unwrap();
/* 元素出堆疊 */
let pop = stack.pop().unwrap();
/* 獲取堆疊的長度 */
let size = stack.len();
/* 判斷是否為空 */
let is_empty = stack.is_empty();
```
=== "C"
```c title="stack.c"
// C 未提供內建堆疊
```
=== "Kotlin"
```kotlin title="stack.kt"
/* 初始化堆疊 */
val stack = Stack<Int>()
/* 元素入堆疊 */
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
/* 訪問堆疊頂元素 */
val peek = stack.peek()
/* 元素出堆疊 */
val pop = stack.pop()
/* 獲取堆疊的長度 */
val size = stack.size
/* 判斷是否為空 */
val isEmpty = stack.isEmpty()
```
=== "Ruby"
```ruby title="stack.rb"
# 初始化堆疊
# Ruby 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用
stack = []
# 元素入堆疊
stack << 1
stack << 3
stack << 2
stack << 5
stack << 4
# 訪問堆疊頂元素
peek = stack.last
# 元素出堆疊
pop = stack.pop
# 獲取堆疊的長度
size = stack.length
# 判斷是否為空
is_empty = stack.empty?
```
=== "Zig"
```zig title="stack.zig"
```
??? pythontutor "視覺化執行"
https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A0%86%E7%96%8A%0A%20%20%20%20%23%20Python%20%E6%B2%92%E6%9C%89%E5%85%A7%E5%BB%BA%E7%9A%84%E5%A0%86%E7%96%8A%E9%A1%9E%E5%88%A5%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E7%95%B6%E4%BD%9C%E5%A0%86%E7%96%8A%E4%BE%86%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%E7%96%8A%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%20stack%20%3D%22%2C%20stack%29%0A%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E9%A0%82%E5%85%83%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E7%96%8A%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%85%83%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E5%A0%86%E7%96%8A%E5%BE%8C%20stack%20%3D%22%2C%20stack%29%0A%0A%20%20%20%20%23%20%E7%8D%B2%E5%8F%96%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E7%9A%84%E9%95%B7%E5%BA%A6%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%B7%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%A0%86%E7%96%8A%E6%98%AF%E5%90%A6%E7%82%BA%E7%A9%BA%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
## 堆疊的實現
為了深入瞭解堆疊的執行機制我們來嘗試自己實現一個堆疊類別
堆疊遵循先入後出的原則因此我們只能在堆疊頂新增或刪除元素然而陣列和鏈結串列都可以在任意位置新增和刪除元素**因此堆疊可以視為一種受限制的陣列或鏈結串列**。換句話說我們可以遮蔽陣列或鏈結串列的部分無關操作使其對外表現的邏輯符合堆疊的特性
### 基於鏈結串列的實現
使用鏈結串列實現堆疊時我們可以將鏈結串列的頭節點視為堆疊頂尾節點視為堆疊底
如下圖所示對於入堆疊操作我們只需將元素插入鏈結串列頭部這種節點插入方法被稱為頭插法”。而對於出堆疊操作只需將頭節點從鏈結串列中刪除即可
=== "LinkedListStack"
![基於鏈結串列實現堆疊的入堆疊出堆疊操作](stack.assets/linkedlist_stack_step1.png)
=== "push()"
![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png)
=== "pop()"
![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png)
以下是基於鏈結串列實現堆疊的示例程式碼
```src
[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{}
```
### 基於陣列的實現
使用陣列實現堆疊時我們可以將陣列的尾部作為堆疊頂如下圖所示入堆疊與出堆疊操作分別對應在陣列尾部新增元素與刪除元素時間複雜度都為 $O(1)$
=== "ArrayStack"
![基於陣列實現堆疊的入堆疊出堆疊操作](stack.assets/array_stack_step1.png)
=== "push()"
![array_stack_push](stack.assets/array_stack_step2_push.png)
=== "pop()"
![array_stack_pop](stack.assets/array_stack_step3_pop.png)
由於入堆疊的元素可能會源源不斷地增加因此我們可以使用動態陣列這樣就無須自行處理陣列擴容問題以下為示例程式碼
```src
[file]{array_stack}-[class]{array_stack}-[func]{}
```
## 兩種實現對比
**支持操作**
兩種實現都支持堆疊定義中的各項操作陣列實現額外支持隨機訪問但這已超出了堆疊的定義範疇因此一般不會用到
**時間效率**
在基於陣列的實現中入堆疊和出堆疊操作都在預先分配好的連續記憶體中進行具有很好的快取本地性因此效率較高然而如果入堆疊時超出陣列容量會觸發擴容機制導致該次入堆疊操作的時間複雜度變為 $O(n)$
在基於鏈結串列的實現中鏈結串列的擴容非常靈活不存在上述陣列擴容時效率降低的問題但是入堆疊操作需要初始化節點物件並修改指標因此效率相對較低不過如果入堆疊元素本身就是節點物件那麼可以省去初始化步驟從而提高效率
綜上所述當入堆疊與出堆疊操作的元素是基本資料型別時例如 `int` `double` 我們可以得出以下結論
- 基於陣列實現的堆疊在觸發擴容時效率會降低但由於擴容是低頻操作因此平均效率更高
- 基於鏈結串列實現的堆疊可以提供更加穩定的效率表現
**空間效率**
在初始化串列時系統會為串列分配初始容量”,該容量可能超出實際需求並且擴容機制通常是按照特定倍率例如 2 進行擴容的擴容後的容量也可能超出實際需求因此**基於陣列實現的堆疊可能造成一定的空間浪費**。
然而由於鏈結串列節點需要額外儲存指標**因此鏈結串列節點佔用的空間相對較大**。
綜上我們不能簡單地確定哪種實現更加節省記憶體需要針對具體情況進行分析
## 堆疊的典型應用
- **瀏覽器中的後退與前進軟體中的撤銷與反撤銷**。每當我們開啟新的網頁瀏覽器就會對上一個網頁執行入堆疊這樣我們就可以通過後退操作回到上一個網頁後退操作實際上是在執行出堆疊如果要同時支持後退和前進那麼需要兩個堆疊來配合實現
- **程式記憶體管理**。每次呼叫函式時系統都會在堆疊頂新增一個堆疊幀用於記錄函式的上下文資訊在遞迴函式中向下遞推階段會不斷執行入堆疊操作而向上回溯階段則會不斷執行出堆疊操作