Move docs/* to docs/zh/*
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 62 KiB |
@ -1,565 +0,0 @@
|
||||
# 双向队列
|
||||
|
||||
在队列中,我们仅能在头部删除或在尾部添加元素。如下图所示,「双向队列 double-ended queue」提供了更高的灵活性,允许在头部和尾部执行元素的添加或删除操作。
|
||||
|
||||

|
||||
|
||||
## 双向队列常用操作
|
||||
|
||||
双向队列的常用操作如下表所示,具体的方法名称需要根据所使用的编程语言来确定。
|
||||
|
||||
<p align="center"> 表 <id> 双向队列操作效率 </p>
|
||||
|
||||
| 方法名 | 描述 | 时间复杂度 |
|
||||
| ----------- | -------------- | ---------- |
|
||||
| pushFirst() | 将元素添加至队首 | $O(1)$ |
|
||||
| pushLast() | 将元素添加至队尾 | $O(1)$ |
|
||||
| popFirst() | 删除队首元素 | $O(1)$ |
|
||||
| popLast() | 删除队尾元素 | $O(1)$ |
|
||||
| peekFirst() | 访问队首元素 | $O(1)$ |
|
||||
| peekLast() | 访问队尾元素 | $O(1)$ |
|
||||
|
||||
同样地,我们可以直接使用编程语言中已实现的双向队列类。
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title="deque.py"
|
||||
# 初始化双向队列
|
||||
deque: deque[int] = collections.deque()
|
||||
|
||||
# 元素入队
|
||||
deque.append(2) # 添加至队尾
|
||||
deque.append(5)
|
||||
deque.append(4)
|
||||
deque.appendleft(3) # 添加至队首
|
||||
deque.appendleft(1)
|
||||
|
||||
# 访问元素
|
||||
front: int = deque[0] # 队首元素
|
||||
rear: int = deque[-1] # 队尾元素
|
||||
|
||||
# 元素出队
|
||||
pop_front: int = deque.popleft() # 队首元素出队
|
||||
pop_rear: int = deque.pop() # 队尾元素出队
|
||||
|
||||
# 获取双向队列的长度
|
||||
size: int = len(deque)
|
||||
|
||||
# 判断双向队列是否为空
|
||||
is_empty: bool = len(deque) == 0
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title="deque.cpp"
|
||||
/* 初始化双向队列 */
|
||||
deque<int> deque;
|
||||
|
||||
/* 元素入队 */
|
||||
deque.push_back(2); // 添加至队尾
|
||||
deque.push_back(5);
|
||||
deque.push_back(4);
|
||||
deque.push_front(3); // 添加至队首
|
||||
deque.push_front(1);
|
||||
|
||||
/* 访问元素 */
|
||||
int front = deque.front(); // 队首元素
|
||||
int back = deque.back(); // 队尾元素
|
||||
|
||||
/* 元素出队 */
|
||||
deque.pop_front(); // 队首元素出队
|
||||
deque.pop_back(); // 队尾元素出队
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
int size = deque.size();
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
bool empty = deque.empty();
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="deque.java"
|
||||
/* 初始化双向队列 */
|
||||
Deque<Integer> deque = new LinkedList<>();
|
||||
|
||||
/* 元素入队 */
|
||||
deque.offerLast(2); // 添加至队尾
|
||||
deque.offerLast(5);
|
||||
deque.offerLast(4);
|
||||
deque.offerFirst(3); // 添加至队首
|
||||
deque.offerFirst(1);
|
||||
|
||||
/* 访问元素 */
|
||||
int peekFirst = deque.peekFirst(); // 队首元素
|
||||
int peekLast = deque.peekLast(); // 队尾元素
|
||||
|
||||
/* 元素出队 */
|
||||
int popFirst = deque.pollFirst(); // 队首元素出队
|
||||
int popLast = deque.pollLast(); // 队尾元素出队
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
int size = deque.size();
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
boolean isEmpty = deque.isEmpty();
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title="deque.cs"
|
||||
/* 初始化双向队列 */
|
||||
// 在 C# 中,将链表 LinkedList 看作双向队列来使用
|
||||
LinkedList<int> deque = new LinkedList<int>();
|
||||
|
||||
/* 元素入队 */
|
||||
deque.AddLast(2); // 添加至队尾
|
||||
deque.AddLast(5);
|
||||
deque.AddLast(4);
|
||||
deque.AddFirst(3); // 添加至队首
|
||||
deque.AddFirst(1);
|
||||
|
||||
/* 访问元素 */
|
||||
int peekFirst = deque.First.Value; // 队首元素
|
||||
int peekLast = deque.Last.Value; // 队尾元素
|
||||
|
||||
/* 元素出队 */
|
||||
deque.RemoveFirst(); // 队首元素出队
|
||||
deque.RemoveLast(); // 队尾元素出队
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
int size = deque.Count;
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
bool isEmpty = deque.Count == 0;
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title="deque_test.go"
|
||||
/* 初始化双向队列 */
|
||||
// 在 Go 中,将 list 作为双向队列使用
|
||||
deque := list.New()
|
||||
|
||||
/* 元素入队 */
|
||||
deque.PushBack(2) // 添加至队尾
|
||||
deque.PushBack(5)
|
||||
deque.PushBack(4)
|
||||
deque.PushFront(3) // 添加至队首
|
||||
deque.PushFront(1)
|
||||
|
||||
/* 访问元素 */
|
||||
front := deque.Front() // 队首元素
|
||||
rear := deque.Back() // 队尾元素
|
||||
|
||||
/* 元素出队 */
|
||||
deque.Remove(front) // 队首元素出队
|
||||
deque.Remove(rear) // 队尾元素出队
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
size := deque.Len()
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
isEmpty := deque.Len() == 0
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="deque.swift"
|
||||
/* 初始化双向队列 */
|
||||
// Swift 没有内置的双向队列类,可以把 Array 当作双向队列来使用
|
||||
var deque: [Int] = []
|
||||
|
||||
/* 元素入队 */
|
||||
deque.append(2) // 添加至队尾
|
||||
deque.append(5)
|
||||
deque.append(4)
|
||||
deque.insert(3, at: 0) // 添加至队首
|
||||
deque.insert(1, at: 0)
|
||||
|
||||
/* 访问元素 */
|
||||
let peekFirst = deque.first! // 队首元素
|
||||
let peekLast = deque.last! // 队尾元素
|
||||
|
||||
/* 元素出队 */
|
||||
// 使用 Array 模拟时 popFirst 的复杂度为 O(n)
|
||||
let popFirst = deque.removeFirst() // 队首元素出队
|
||||
let popLast = deque.removeLast() // 队尾元素出队
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
let size = deque.count
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
let isEmpty = deque.isEmpty
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```javascript title="deque.js"
|
||||
/* 初始化双向队列 */
|
||||
// JavaScript 没有内置的双端队列,只能把 Array 当作双端队列来使用
|
||||
const deque = [];
|
||||
|
||||
/* 元素入队 */
|
||||
deque.push(2);
|
||||
deque.push(5);
|
||||
deque.push(4);
|
||||
// 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n)
|
||||
deque.unshift(3);
|
||||
deque.unshift(1);
|
||||
console.log("双向队列 deque = ", deque);
|
||||
|
||||
/* 访问元素 */
|
||||
const peekFirst = deque[0];
|
||||
console.log("队首元素 peekFirst = " + peekFirst);
|
||||
const peekLast = deque[deque.length - 1];
|
||||
console.log("队尾元素 peekLast = " + peekLast);
|
||||
|
||||
/* 元素出队 */
|
||||
// 请注意,由于是数组,shift() 方法的时间复杂度为 O(n)
|
||||
const popFront = deque.shift();
|
||||
console.log("队首出队元素 popFront = " + popFront + ",队首出队后 deque = " + deque);
|
||||
const popBack = deque.pop();
|
||||
console.log("队尾出队元素 popBack = " + popBack + ",队尾出队后 deque = " + deque);
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
const size = deque.length;
|
||||
console.log("双向队列长度 size = " + size);
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
const isEmpty = size === 0;
|
||||
console.log("双向队列是否为空 = " + isEmpty);
|
||||
```
|
||||
|
||||
=== "TS"
|
||||
|
||||
```typescript title="deque.ts"
|
||||
/* 初始化双向队列 */
|
||||
// TypeScript 没有内置的双端队列,只能把 Array 当作双端队列来使用
|
||||
const deque: number[] = [];
|
||||
|
||||
/* 元素入队 */
|
||||
deque.push(2);
|
||||
deque.push(5);
|
||||
deque.push(4);
|
||||
// 请注意,由于是数组,unshift() 方法的时间复杂度为 O(n)
|
||||
deque.unshift(3);
|
||||
deque.unshift(1);
|
||||
console.log("双向队列 deque = ", deque);
|
||||
|
||||
/* 访问元素 */
|
||||
const peekFirst: number = deque[0];
|
||||
console.log("队首元素 peekFirst = " + peekFirst);
|
||||
const peekLast: number = deque[deque.length - 1];
|
||||
console.log("队尾元素 peekLast = " + peekLast);
|
||||
|
||||
/* 元素出队 */
|
||||
// 请注意,由于是数组,shift() 方法的时间复杂度为 O(n)
|
||||
const popFront: number = deque.shift() as number;
|
||||
console.log("队首出队元素 popFront = " + popFront + ",队首出队后 deque = " + deque);
|
||||
const popBack: number = deque.pop() as number;
|
||||
console.log("队尾出队元素 popBack = " + popBack + ",队尾出队后 deque = " + deque);
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
const size: number = deque.length;
|
||||
console.log("双向队列长度 size = " + size);
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
const isEmpty: boolean = size === 0;
|
||||
console.log("双向队列是否为空 = " + isEmpty);
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title="deque.dart"
|
||||
/* 初始化双向队列 */
|
||||
// 在 Dart 中,Queue 被定义为双向队列
|
||||
Queue<int> deque = Queue<int>();
|
||||
|
||||
/* 元素入队 */
|
||||
deque.addLast(2); // 添加至队尾
|
||||
deque.addLast(5);
|
||||
deque.addLast(4);
|
||||
deque.addFirst(3); // 添加至队首
|
||||
deque.addFirst(1);
|
||||
|
||||
/* 访问元素 */
|
||||
int peekFirst = deque.first; // 队首元素
|
||||
int peekLast = deque.last; // 队尾元素
|
||||
|
||||
/* 元素出队 */
|
||||
int popFirst = deque.removeFirst(); // 队首元素出队
|
||||
int popLast = deque.removeLast(); // 队尾元素出队
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
int size = deque.length;
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
bool isEmpty = deque.isEmpty;W
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="deque.rs"
|
||||
/* 初始化双向队列 */
|
||||
let mut deque: VecDeque<u32> = VecDeque::new();
|
||||
|
||||
/* 元素入队 */
|
||||
deque.push_back(2); // 添加至队尾
|
||||
deque.push_back(5);
|
||||
deque.push_back(4);
|
||||
deque.push_front(3); // 添加至队首
|
||||
deque.push_front(1);
|
||||
|
||||
/* 访问元素 */
|
||||
if let Some(front) = deque.front() { // 队首元素
|
||||
}
|
||||
if let Some(rear) = deque.back() { // 队尾元素
|
||||
}
|
||||
|
||||
/* 元素出队 */
|
||||
if let Some(pop_front) = deque.pop_front() { // 队首元素出队
|
||||
}
|
||||
if let Some(pop_rear) = deque.pop_back() { // 队尾元素出队
|
||||
}
|
||||
|
||||
/* 获取双向队列的长度 */
|
||||
let size = deque.len();
|
||||
|
||||
/* 判断双向队列是否为空 */
|
||||
let is_empty = deque.is_empty();
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="deque.c"
|
||||
// C 未提供内置双向队列
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title="deque.zig"
|
||||
|
||||
```
|
||||
|
||||
## 双向队列实现 *
|
||||
|
||||
双向队列的实现与队列类似,可以选择链表或数组作为底层数据结构。
|
||||
|
||||
### 基于双向链表的实现
|
||||
|
||||
回顾上一节内容,我们使用普通单向链表来实现队列,因为它可以方便地删除头节点(对应出队操作)和在尾节点后添加新节点(对应入队操作)。
|
||||
|
||||
对于双向队列而言,头部和尾部都可以执行入队和出队操作。换句话说,双向队列需要实现另一个对称方向的操作。为此,我们采用“双向链表”作为双向队列的底层数据结构。
|
||||
|
||||
如下图所示,我们将双向链表的头节点和尾节点视为双向队列的队首和队尾,同时实现在两端添加和删除节点的功能。
|
||||
|
||||
=== "LinkedListDeque"
|
||||

|
||||
|
||||
=== "pushLast()"
|
||||

|
||||
|
||||
=== "pushFirst()"
|
||||

|
||||
|
||||
=== "popLast()"
|
||||

|
||||
|
||||
=== "popFirst()"
|
||||

|
||||
|
||||
实现代码如下所示。
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title="linkedlist_deque.py"
|
||||
[class]{ListNode}-[func]{}
|
||||
|
||||
[class]{LinkedListDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title="linkedlist_deque.cpp"
|
||||
[class]{DoublyListNode}-[func]{}
|
||||
|
||||
[class]{LinkedListDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="linkedlist_deque.java"
|
||||
[class]{ListNode}-[func]{}
|
||||
|
||||
[class]{LinkedListDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title="linkedlist_deque.cs"
|
||||
[class]{ListNode}-[func]{}
|
||||
|
||||
[class]{LinkedListDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title="linkedlist_deque.go"
|
||||
[class]{linkedListDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="linkedlist_deque.swift"
|
||||
[class]{ListNode}-[func]{}
|
||||
|
||||
[class]{LinkedListDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```javascript title="linkedlist_deque.js"
|
||||
[class]{ListNode}-[func]{}
|
||||
|
||||
[class]{LinkedListDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "TS"
|
||||
|
||||
```typescript title="linkedlist_deque.ts"
|
||||
[class]{ListNode}-[func]{}
|
||||
|
||||
[class]{LinkedListDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title="linkedlist_deque.dart"
|
||||
[class]{ListNode}-[func]{}
|
||||
|
||||
[class]{LinkedListDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="linkedlist_deque.rs"
|
||||
[class]{ListNode}-[func]{}
|
||||
|
||||
[class]{LinkedListDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="linkedlist_deque.c"
|
||||
[class]{doublyListNode}-[func]{}
|
||||
|
||||
[class]{linkedListDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title="linkedlist_deque.zig"
|
||||
[class]{ListNode}-[func]{}
|
||||
|
||||
[class]{LinkedListDeque}-[func]{}
|
||||
```
|
||||
|
||||
### 基于数组的实现
|
||||
|
||||
如下图所示,与基于数组实现队列类似,我们也可以使用环形数组来实现双向队列。
|
||||
|
||||
=== "ArrayDeque"
|
||||

|
||||
|
||||
=== "pushLast()"
|
||||

|
||||
|
||||
=== "pushFirst()"
|
||||

|
||||
|
||||
=== "popLast()"
|
||||

|
||||
|
||||
=== "popFirst()"
|
||||

|
||||
|
||||
在队列的实现基础上,仅需增加“队首入队”和“队尾出队”的方法。
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title="array_deque.py"
|
||||
[class]{ArrayDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title="array_deque.cpp"
|
||||
[class]{ArrayDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="array_deque.java"
|
||||
[class]{ArrayDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title="array_deque.cs"
|
||||
[class]{ArrayDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title="array_deque.go"
|
||||
[class]{arrayDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="array_deque.swift"
|
||||
[class]{ArrayDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```javascript title="array_deque.js"
|
||||
[class]{ArrayDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "TS"
|
||||
|
||||
```typescript title="array_deque.ts"
|
||||
[class]{ArrayDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title="array_deque.dart"
|
||||
[class]{ArrayDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="array_deque.rs"
|
||||
[class]{ArrayDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="array_deque.c"
|
||||
[class]{arrayDeque}-[func]{}
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title="array_deque.zig"
|
||||
[class]{ArrayDeque}-[func]{}
|
||||
```
|
||||
|
||||
## 双向队列应用
|
||||
|
||||
双向队列兼具栈与队列的逻辑,**因此它可以实现这两者的所有应用场景,同时提供更高的自由度**。
|
||||
|
||||
我们知道,软件的“撤销”功能通常使用栈来实现:系统将每次更改操作 `push` 到栈中,然后通过 `pop` 实现撤销。然而,考虑到系统资源的限制,软件通常会限制撤销的步数(例如仅允许保存 $50$ 步)。当栈的长度超过 $50$ 时,软件需要在栈底(即队首)执行删除操作。**但栈无法实现该功能,此时就需要使用双向队列来替代栈**。请注意,“撤销”的核心逻辑仍然遵循栈的先入后出原则,只是双向队列能够更加灵活地实现一些额外逻辑。
|
@ -1,13 +0,0 @@
|
||||
# 栈与队列
|
||||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
{ width="600" }
|
||||
|
||||
</div>
|
||||
|
||||
!!! abstract
|
||||
|
||||
栈如同叠猫猫,而队列就像猫猫排队。
|
||||
|
||||
两者分别代表着先入后出和先入先出的逻辑关系。
|
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 55 KiB |
@ -1,505 +0,0 @@
|
||||
# 队列
|
||||
|
||||
「队列 queue」是一种遵循先入先出规则的线性数据结构。顾名思义,队列模拟了排队现象,即新来的人不断加入队列的尾部,而位于队列头部的人逐个离开。
|
||||
|
||||
如下图所示,我们将队列的头部称为“队首”,尾部称为“队尾”,将把元素加入队尾的操作称为“入队”,删除队首元素的操作称为“出队”。
|
||||
|
||||

|
||||
|
||||
## 队列常用操作
|
||||
|
||||
队列的常见操作如下表所示。需要注意的是,不同编程语言的方法名称可能会有所不同。我们在此采用与栈相同的方法命名。
|
||||
|
||||
<p align="center"> 表 <id> 队列操作效率 </p>
|
||||
|
||||
| 方法名 | 描述 | 时间复杂度 |
|
||||
| --------- | -------------------------- | -------- |
|
||||
| push() | 元素入队,即将元素添加至队尾 | $O(1)$ |
|
||||
| pop() | 队首元素出队 | $O(1)$ |
|
||||
| peek() | 访问队首元素 | $O(1)$ |
|
||||
|
||||
我们可以直接使用编程语言中现成的队列类。
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title="queue.py"
|
||||
# 初始化队列
|
||||
# 在 Python 中,我们一般将双向队列类 deque 看作队列使用
|
||||
# 虽然 queue.Queue() 是纯正的队列类,但不太好用,因此不建议
|
||||
que: deque[int] = collections.deque()
|
||||
|
||||
# 元素入队
|
||||
que.append(1)
|
||||
que.append(3)
|
||||
que.append(2)
|
||||
que.append(5)
|
||||
que.append(4)
|
||||
|
||||
# 访问队首元素
|
||||
front: int = que[0];
|
||||
|
||||
# 元素出队
|
||||
pop: int = que.popleft()
|
||||
|
||||
# 获取队列的长度
|
||||
size: int = len(que)
|
||||
|
||||
# 判断队列是否为空
|
||||
is_empty: bool = len(que) == 0
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title="queue.cpp"
|
||||
/* 初始化队列 */
|
||||
queue<int> queue;
|
||||
|
||||
/* 元素入队 */
|
||||
queue.push(1);
|
||||
queue.push(3);
|
||||
queue.push(2);
|
||||
queue.push(5);
|
||||
queue.push(4);
|
||||
|
||||
/* 访问队首元素 */
|
||||
int front = queue.front();
|
||||
|
||||
/* 元素出队 */
|
||||
queue.pop();
|
||||
|
||||
/* 获取队列的长度 */
|
||||
int size = queue.size();
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
bool empty = queue.empty();
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="queue.java"
|
||||
/* 初始化队列 */
|
||||
Queue<Integer> queue = new LinkedList<>();
|
||||
|
||||
/* 元素入队 */
|
||||
queue.offer(1);
|
||||
queue.offer(3);
|
||||
queue.offer(2);
|
||||
queue.offer(5);
|
||||
queue.offer(4);
|
||||
|
||||
/* 访问队首元素 */
|
||||
int peek = queue.peek();
|
||||
|
||||
/* 元素出队 */
|
||||
int pop = queue.poll();
|
||||
|
||||
/* 获取队列的长度 */
|
||||
int size = queue.size();
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
boolean isEmpty = queue.isEmpty();
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title="queue.cs"
|
||||
/* 初始化队列 */
|
||||
Queue<int> queue = new();
|
||||
|
||||
/* 元素入队 */
|
||||
queue.Enqueue(1);
|
||||
queue.Enqueue(3);
|
||||
queue.Enqueue(2);
|
||||
queue.Enqueue(5);
|
||||
queue.Enqueue(4);
|
||||
|
||||
/* 访问队首元素 */
|
||||
int peek = queue.Peek();
|
||||
|
||||
/* 元素出队 */
|
||||
int pop = queue.Dequeue();
|
||||
|
||||
/* 获取队列的长度 */
|
||||
int size = queue.Count;
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
bool isEmpty = queue.Count == 0;
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title="queue_test.go"
|
||||
/* 初始化队列 */
|
||||
// 在 Go 中,将 list 作为队列来使用
|
||||
queue := list.New()
|
||||
|
||||
/* 元素入队 */
|
||||
queue.PushBack(1)
|
||||
queue.PushBack(3)
|
||||
queue.PushBack(2)
|
||||
queue.PushBack(5)
|
||||
queue.PushBack(4)
|
||||
|
||||
/* 访问队首元素 */
|
||||
peek := queue.Front()
|
||||
|
||||
/* 元素出队 */
|
||||
pop := queue.Front()
|
||||
queue.Remove(pop)
|
||||
|
||||
/* 获取队列的长度 */
|
||||
size := queue.Len()
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
isEmpty := queue.Len() == 0
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="queue.swift"
|
||||
/* 初始化队列 */
|
||||
// Swift 没有内置的队列类,可以把 Array 当作队列来使用
|
||||
var queue: [Int] = []
|
||||
|
||||
/* 元素入队 */
|
||||
queue.append(1)
|
||||
queue.append(3)
|
||||
queue.append(2)
|
||||
queue.append(5)
|
||||
queue.append(4)
|
||||
|
||||
/* 访问队首元素 */
|
||||
let peek = queue.first!
|
||||
|
||||
/* 元素出队 */
|
||||
// 由于是数组,因此 removeFirst 的复杂度为 O(n)
|
||||
let pool = queue.removeFirst()
|
||||
|
||||
/* 获取队列的长度 */
|
||||
let size = queue.count
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
let isEmpty = queue.isEmpty
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```javascript title="queue.js"
|
||||
/* 初始化队列 */
|
||||
// JavaScript 没有内置的队列,可以把 Array 当作队列来使用
|
||||
const queue = [];
|
||||
|
||||
/* 元素入队 */
|
||||
queue.push(1);
|
||||
queue.push(3);
|
||||
queue.push(2);
|
||||
queue.push(5);
|
||||
queue.push(4);
|
||||
|
||||
/* 访问队首元素 */
|
||||
const peek = queue[0];
|
||||
|
||||
/* 元素出队 */
|
||||
// 底层是数组,因此 shift() 方法的时间复杂度为 O(n)
|
||||
const pop = queue.shift();
|
||||
|
||||
/* 获取队列的长度 */
|
||||
const size = queue.length;
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
const empty = queue.length === 0;
|
||||
```
|
||||
|
||||
=== "TS"
|
||||
|
||||
```typescript title="queue.ts"
|
||||
/* 初始化队列 */
|
||||
// TypeScript 没有内置的队列,可以把 Array 当作队列来使用
|
||||
const queue: number[] = [];
|
||||
|
||||
/* 元素入队 */
|
||||
queue.push(1);
|
||||
queue.push(3);
|
||||
queue.push(2);
|
||||
queue.push(5);
|
||||
queue.push(4);
|
||||
|
||||
/* 访问队首元素 */
|
||||
const peek = queue[0];
|
||||
|
||||
/* 元素出队 */
|
||||
// 底层是数组,因此 shift() 方法的时间复杂度为 O(n)
|
||||
const pop = queue.shift();
|
||||
|
||||
/* 获取队列的长度 */
|
||||
const size = queue.length;
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
const empty = queue.length === 0;
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title="queue.dart"
|
||||
/* 初始化队列 */
|
||||
// 在 Dart 中,队列类 Qeque 是双向队列,也可作为队列使用
|
||||
Queue<int> queue = Queue();
|
||||
|
||||
/* 元素入队 */
|
||||
queue.add(1);
|
||||
queue.add(3);
|
||||
queue.add(2);
|
||||
queue.add(5);
|
||||
queue.add(4);
|
||||
|
||||
/* 访问队首元素 */
|
||||
int peek = queue.first;
|
||||
|
||||
/* 元素出队 */
|
||||
int pop = queue.removeFirst();
|
||||
|
||||
/* 获取队列的长度 */
|
||||
int size = queue.length;
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
bool isEmpty = queue.isEmpty;
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="queue.rs"
|
||||
/* 初始化双向队列 */
|
||||
// 在 Rust 中使用双向队列作为普通队列来使用
|
||||
let mut deque: VecDeque<u32> = VecDeque::new();
|
||||
|
||||
/* 元素入队 */
|
||||
deque.push_back(1);
|
||||
deque.push_back(3);
|
||||
deque.push_back(2);
|
||||
deque.push_back(5);
|
||||
deque.push_back(4);
|
||||
|
||||
/* 访问队首元素 */
|
||||
if let Some(front) = deque.front() {
|
||||
}
|
||||
|
||||
/* 元素出队 */
|
||||
if let Some(pop) = deque.pop_front() {
|
||||
}
|
||||
|
||||
/* 获取队列的长度 */
|
||||
let size = deque.len();
|
||||
|
||||
/* 判断队列是否为空 */
|
||||
let is_empty = deque.is_empty();
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="queue.c"
|
||||
// C 未提供内置队列
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title="queue.zig"
|
||||
|
||||
```
|
||||
|
||||
## 队列实现
|
||||
|
||||
为了实现队列,我们需要一种数据结构,可以在一端添加元素,并在另一端删除元素。因此,链表和数组都可以用来实现队列。
|
||||
|
||||
### 基于链表的实现
|
||||
|
||||
如下图所示,我们可以将链表的“头节点”和“尾节点”分别视为“队首”和“队尾”,规定队尾仅可添加节点,队首仅可删除节点。
|
||||
|
||||
=== "LinkedListQueue"
|
||||

|
||||
|
||||
=== "push()"
|
||||

|
||||
|
||||
=== "pop()"
|
||||

|
||||
|
||||
以下是用链表实现队列的代码。
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title="linkedlist_queue.py"
|
||||
[class]{LinkedListQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title="linkedlist_queue.cpp"
|
||||
[class]{LinkedListQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="linkedlist_queue.java"
|
||||
[class]{LinkedListQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title="linkedlist_queue.cs"
|
||||
[class]{LinkedListQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title="linkedlist_queue.go"
|
||||
[class]{linkedListQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="linkedlist_queue.swift"
|
||||
[class]{LinkedListQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```javascript title="linkedlist_queue.js"
|
||||
[class]{LinkedListQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "TS"
|
||||
|
||||
```typescript title="linkedlist_queue.ts"
|
||||
[class]{LinkedListQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title="linkedlist_queue.dart"
|
||||
[class]{LinkedListQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="linkedlist_queue.rs"
|
||||
[class]{LinkedListQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="linkedlist_queue.c"
|
||||
[class]{linkedListQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title="linkedlist_queue.zig"
|
||||
[class]{LinkedListQueue}-[func]{}
|
||||
```
|
||||
|
||||
### 基于数组的实现
|
||||
|
||||
由于数组删除首元素的时间复杂度为 $O(n)$ ,这会导致出队操作效率较低。然而,我们可以采用以下巧妙方法来避免这个问题。
|
||||
|
||||
我们可以使用一个变量 `front` 指向队首元素的索引,并维护一个变量 `size` 用于记录队列长度。定义 `rear = front + size` ,这个公式计算出的 `rear` 指向队尾元素之后的下一个位置。
|
||||
|
||||
基于此设计,**数组中包含元素的有效区间为 `[front, rear - 1]`**,各种操作的实现方法如下图所示。
|
||||
|
||||
- 入队操作:将输入元素赋值给 `rear` 索引处,并将 `size` 增加 1 。
|
||||
- 出队操作:只需将 `front` 增加 1 ,并将 `size` 减少 1 。
|
||||
|
||||
可以看到,入队和出队操作都只需进行一次操作,时间复杂度均为 $O(1)$ 。
|
||||
|
||||
=== "ArrayQueue"
|
||||

|
||||
|
||||
=== "push()"
|
||||

|
||||
|
||||
=== "pop()"
|
||||

|
||||
|
||||
你可能会发现一个问题:在不断进行入队和出队的过程中,`front` 和 `rear` 都在向右移动,**当它们到达数组尾部时就无法继续移动了**。为解决此问题,我们可以将数组视为首尾相接的“环形数组”。
|
||||
|
||||
对于环形数组,我们需要让 `front` 或 `rear` 在越过数组尾部时,直接回到数组头部继续遍历。这种周期性规律可以通过“取余操作”来实现,代码如下所示。
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title="array_queue.py"
|
||||
[class]{ArrayQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title="array_queue.cpp"
|
||||
[class]{ArrayQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="array_queue.java"
|
||||
[class]{ArrayQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title="array_queue.cs"
|
||||
[class]{ArrayQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title="array_queue.go"
|
||||
[class]{arrayQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="array_queue.swift"
|
||||
[class]{ArrayQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```javascript title="array_queue.js"
|
||||
[class]{ArrayQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "TS"
|
||||
|
||||
```typescript title="array_queue.ts"
|
||||
[class]{ArrayQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title="array_queue.dart"
|
||||
[class]{ArrayQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="array_queue.rs"
|
||||
[class]{ArrayQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="array_queue.c"
|
||||
[class]{arrayQueue}-[func]{}
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title="array_queue.zig"
|
||||
[class]{ArrayQueue}-[func]{}
|
||||
```
|
||||
|
||||
以上实现的队列仍然具有局限性,即其长度不可变。然而,这个问题不难解决,我们可以将数组替换为动态数组,从而引入扩容机制。有兴趣的同学可以尝试自行实现。
|
||||
|
||||
两种实现的对比结论与栈一致,在此不再赘述。
|
||||
|
||||
## 队列典型应用
|
||||
|
||||
- **淘宝订单**。购物者下单后,订单将加入队列中,系统随后会根据顺序依次处理队列中的订单。在双十一期间,短时间内会产生海量订单,高并发成为工程师们需要重点攻克的问题。
|
||||
- **各类待办事项**。任何需要实现“先来后到”功能的场景,例如打印机的任务队列、餐厅的出餐队列等。队列在这些场景中可以有效地维护处理顺序。
|
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 54 KiB |
@ -1,515 +0,0 @@
|
||||
# 栈
|
||||
|
||||
「栈 stack」是一种遵循先入后出的逻辑的线性数据结构。
|
||||
|
||||
我们可以将栈类比为桌面上的一摞盘子,如果需要拿出底部的盘子,则需要先将上面的盘子依次取出。我们将盘子替换为各种类型的元素(如整数、字符、对象等),就得到了栈数据结构。
|
||||
|
||||
如下图所示,我们把堆叠元素的顶部称为“栈顶”,底部称为“栈底”。将把元素添加到栈顶的操作叫做“入栈”,删除栈顶元素的操作叫做“出栈”。
|
||||
|
||||

|
||||
|
||||
## 栈常用操作
|
||||
|
||||
栈的常用操作如下表所示,具体的方法名需要根据所使用的编程语言来确定。在此,我们以常见的 `push()`、`pop()`、`peek()` 命名为例。
|
||||
|
||||
<p align="center"> 表 <id> 栈的操作效率 </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[stack.len() - 1];
|
||||
|
||||
/* 元素出栈 */
|
||||
let pop = stack.pop().unwrap();
|
||||
|
||||
/* 获取栈的长度 */
|
||||
let size = stack.len();
|
||||
|
||||
/* 判断是否为空 */
|
||||
let is_empty = stack.is_empty();
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="stack.c"
|
||||
// C 未提供内置栈
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title="stack.zig"
|
||||
|
||||
```
|
||||
|
||||
## 栈的实现
|
||||
|
||||
为了深入了解栈的运行机制,我们来尝试自己实现一个栈类。
|
||||
|
||||
栈遵循先入后出的原则,因此我们只能在栈顶添加或删除元素。然而,数组和链表都可以在任意位置添加和删除元素,**因此栈可以被视为一种受限制的数组或链表**。换句话说,我们可以“屏蔽”数组或链表的部分无关操作,使其对外表现的逻辑符合栈的特性。
|
||||
|
||||
### 基于链表的实现
|
||||
|
||||
使用链表来实现栈时,我们可以将链表的头节点视为栈顶,尾节点视为栈底。
|
||||
|
||||
如下图所示,对于入栈操作,我们只需将元素插入链表头部,这种节点插入方法被称为“头插法”。而对于出栈操作,只需将头节点从链表中删除即可。
|
||||
|
||||
=== "LinkedListStack"
|
||||

|
||||
|
||||
=== "push()"
|
||||

|
||||
|
||||
=== "pop()"
|
||||

|
||||
|
||||
以下是基于链表实现栈的示例代码。
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title="linkedlist_stack.py"
|
||||
[class]{LinkedListStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title="linkedlist_stack.cpp"
|
||||
[class]{LinkedListStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="linkedlist_stack.java"
|
||||
[class]{LinkedListStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title="linkedlist_stack.cs"
|
||||
[class]{LinkedListStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title="linkedlist_stack.go"
|
||||
[class]{linkedListStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="linkedlist_stack.swift"
|
||||
[class]{LinkedListStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```javascript title="linkedlist_stack.js"
|
||||
[class]{LinkedListStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "TS"
|
||||
|
||||
```typescript title="linkedlist_stack.ts"
|
||||
[class]{LinkedListStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title="linkedlist_stack.dart"
|
||||
[class]{LinkedListStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="linkedlist_stack.rs"
|
||||
[class]{LinkedListStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="linkedlist_stack.c"
|
||||
[class]{linkedListStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title="linkedlist_stack.zig"
|
||||
[class]{LinkedListStack}-[func]{}
|
||||
```
|
||||
|
||||
### 基于数组的实现
|
||||
|
||||
使用数组实现栈时,我们可以将数组的尾部作为栈顶。如下图所示,入栈与出栈操作分别对应在数组尾部添加元素与删除元素,时间复杂度都为 $O(1)$ 。
|
||||
|
||||
=== "ArrayStack"
|
||||

|
||||
|
||||
=== "push()"
|
||||

|
||||
|
||||
=== "pop()"
|
||||

|
||||
|
||||
由于入栈的元素可能会源源不断地增加,因此我们可以使用动态数组,这样就无须自行处理数组扩容问题。以下为示例代码。
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title="array_stack.py"
|
||||
[class]{ArrayStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title="array_stack.cpp"
|
||||
[class]{ArrayStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="array_stack.java"
|
||||
[class]{ArrayStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title="array_stack.cs"
|
||||
[class]{ArrayStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title="array_stack.go"
|
||||
[class]{arrayStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="array_stack.swift"
|
||||
[class]{ArrayStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```javascript title="array_stack.js"
|
||||
[class]{ArrayStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "TS"
|
||||
|
||||
```typescript title="array_stack.ts"
|
||||
[class]{ArrayStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title="array_stack.dart"
|
||||
[class]{ArrayStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="array_stack.rs"
|
||||
[class]{ArrayStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="array_stack.c"
|
||||
[class]{arrayStack}-[func]{}
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title="array_stack.zig"
|
||||
[class]{ArrayStack}-[func]{}
|
||||
```
|
||||
|
||||
## 两种实现对比
|
||||
|
||||
**支持操作**
|
||||
|
||||
两种实现都支持栈定义中的各项操作。数组实现额外支持随机访问,但这已超出了栈的定义范畴,因此一般不会用到。
|
||||
|
||||
**时间效率**
|
||||
|
||||
在基于数组的实现中,入栈和出栈操作都是在预先分配好的连续内存中进行,具有很好的缓存本地性,因此效率较高。然而,如果入栈时超出数组容量,会触发扩容机制,导致该次入栈操作的时间复杂度变为 $O(n)$ 。
|
||||
|
||||
在链表实现中,链表的扩容非常灵活,不存在上述数组扩容时效率降低的问题。但是,入栈操作需要初始化节点对象并修改指针,因此效率相对较低。不过,如果入栈元素本身就是节点对象,那么可以省去初始化步骤,从而提高效率。
|
||||
|
||||
综上所述,当入栈与出栈操作的元素是基本数据类型时,例如 `int` 或 `double` ,我们可以得出以下结论。
|
||||
|
||||
- 基于数组实现的栈在触发扩容时效率会降低,但由于扩容是低频操作,因此平均效率更高。
|
||||
- 基于链表实现的栈可以提供更加稳定的效率表现。
|
||||
|
||||
**空间效率**
|
||||
|
||||
在初始化列表时,系统会为列表分配“初始容量”,该容量可能超过实际需求。并且,扩容机制通常是按照特定倍率(例如 2 倍)进行扩容,扩容后的容量也可能超出实际需求。因此,**基于数组实现的栈可能造成一定的空间浪费**。
|
||||
|
||||
然而,由于链表节点需要额外存储指针,**因此链表节点占用的空间相对较大**。
|
||||
|
||||
综上,我们不能简单地确定哪种实现更加节省内存,需要针对具体情况进行分析。
|
||||
|
||||
## 栈典型应用
|
||||
|
||||
- **浏览器中的后退与前进、软件中的撤销与反撤销**。每当我们打开新的网页,浏览器就会将上一个网页执行入栈,这样我们就可以通过后退操作回到上一页面。后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么需要两个栈来配合实现。
|
||||
- **程序内存管理**。每次调用函数时,系统都会在栈顶添加一个栈帧,用于记录函数的上下文信息。在递归函数中,向下递推阶段会不断执行入栈操作,而向上回溯阶段则会执行出栈操作。
|
@ -1,31 +0,0 @@
|
||||
# 小结
|
||||
|
||||
### 重点回顾
|
||||
|
||||
- 栈是一种遵循先入后出原则的数据结构,可通过数组或链表来实现。
|
||||
- 从时间效率角度看,栈的数组实现具有较高的平均效率,但在扩容过程中,单次入栈操作的时间复杂度会劣化至 $O(n)$ 。相比之下,基于链表实现的栈具有更为稳定的效率表现。
|
||||
- 在空间效率方面,栈的数组实现可能导致一定程度的空间浪费。但需要注意的是,链表节点所占用的内存空间比数组元素更大。
|
||||
- 队列是一种遵循先入先出原则的数据结构,同样可以通过数组或链表来实现。在时间效率和空间效率的对比上,队列的结论与前述栈的结论相似。
|
||||
- 双向队列是一种具有更高自由度的队列,它允许在两端进行元素的添加和删除操作。
|
||||
|
||||
### Q & A
|
||||
|
||||
!!! question "浏览器的前进后退是否是双向链表实现?"
|
||||
|
||||
浏览器的前进后退功能本质上是“栈”的体现。当用户访问一个新页面时,该页面会被添加到栈顶;当用户点击后退按钮时,该页面会从栈顶弹出。使用双向队列可以方便实现一些额外操作,这个在双向队列章节有提到。
|
||||
|
||||
!!! question "在出栈后,是否需要释放出栈节点的内存?"
|
||||
|
||||
如果后续仍需要使用弹出节点,则不需要释放内存。若之后不需要用到,`Java` 和 `Python` 等语言拥有自动垃圾回收机制,因此不需要手动释放内存;在 `C` 和 `C++` 中需要手动释放内存。
|
||||
|
||||
!!! question "双向队列像是两个栈拼接在了一起,它的用途是什么?"
|
||||
|
||||
双向队列就像是栈和队列的组合,或者是两个栈拼在了一起。它表现的是栈 + 队列的逻辑,因此可以实现栈与队列的所有应用,并且更加灵活。
|
||||
|
||||
!!! question "撤销(undo)和反撤销(redo)具体是如何实现的?"
|
||||
|
||||
使用两个堆栈,栈 `A` 用于撤销,栈 `B` 用于反撤销。
|
||||
|
||||
1. 每当用户执行一个操作,将这个操作压入栈 `A` ,并清空栈 `B` 。
|
||||
2. 当用户执行“撤销”时,从栈 `A` 中弹出最近的操作,并将其压入栈 `B` 。
|
||||
3. 当用户执行“反撤销”时,从栈 `B` 中弹出最近的操作,并将其压入栈 `A` 。
|