Revisit the English version (#1835)

* Review the English version using Claude-4.5.

* Update mkdocs.yml

* Align the section titles.

* Bug fixes
This commit is contained in:
Yudong Jin
2025-12-30 17:54:01 +08:00
committed by GitHub
parent 091afd38b4
commit 45e1295241
106 changed files with 4195 additions and 3398 deletions

View File

@@ -1,320 +1,320 @@
# Double-ended queue
# Deque
In a queue, we can only delete elements from the head or add elements to the tail. As shown in the figure below, a <u>double-ended queue (deque)</u> offers more flexibility, allowing the addition or removal of elements at both the head and the tail.
In a queue, we can only remove elements from the front or add elements at the rear. As shown in the figure below, a <u>double-ended queue (deque)</u> provides greater flexibility, allowing the addition or removal of elements at both the front and rear.
![Operations in double-ended queue](deque.assets/deque_operations.png)
![Operations of deque](deque.assets/deque_operations.png)
## Common operations in double-ended queue
## Common Deque Operations
The common operations in a double-ended queue are listed below, and the names of specific methods depend on the programming language used.
The common operations on a deque are shown in the table below. The specific method names depend on the programming language used.
<p align="center"> Table <id> &nbsp; Efficiency of double-ended queue operations </p>
<p align="center"> Table <id> &nbsp; Efficiency of Deque Operations </p>
| Method Name | Description | Time Complexity |
| ------------- | -------------------------- | --------------- |
| `pushFirst()` | Add an element to the head | $O(1)$ |
| `pushLast()` | Add an element to the tail | $O(1)$ |
| `popFirst()` | Remove the first element | $O(1)$ |
| `popLast()` | Remove the last element | $O(1)$ |
| `peekFirst()` | Access the first element | $O(1)$ |
| `peekLast()` | Access the last element | $O(1)$ |
| Method | Description | Time Complexity |
| -------------- | ------------------------- | --------------- |
| `push_first()` | Add element to front | $O(1)$ |
| `push_last()` | Add element to rear | $O(1)$ |
| `pop_first()` | Remove front element | $O(1)$ |
| `pop_last()` | Remove rear element | $O(1)$ |
| `peek_first()` | Access front element | $O(1)$ |
| `peek_last()` | Access rear element | $O(1)$ |
Similarly, we can directly use the double-ended queue classes implemented in programming languages:
Similarly, we can directly use the deque classes already implemented in programming languages:
=== "Python"
```python title="deque.py"
from collections import deque
# Initialize the deque
# Initialize deque
deq: deque[int] = deque()
# Enqueue elements
deq.append(2) # Add to the tail
deq.append(2) # Add to rear
deq.append(5)
deq.append(4)
deq.appendleft(3) # Add to the head
deq.appendleft(3) # Add to front
deq.appendleft(1)
# Access elements
front: int = deq[0] # The first element
rear: int = deq[-1] # The last element
front: int = deq[0] # Front element
rear: int = deq[-1] # Rear element
# Dequeue elements
pop_front: int = deq.popleft() # The first element dequeued
pop_rear: int = deq.pop() # The last element dequeued
pop_front: int = deq.popleft() # Front element dequeue
pop_rear: int = deq.pop() # Rear element dequeue
# Get the length of the deque
# Get deque length
size: int = len(deq)
# Check if the deque is empty
# Check if deque is empty
is_empty: bool = len(deq) == 0
```
=== "C++"
```cpp title="deque.cpp"
/* Initialize the deque */
/* Initialize deque */
deque<int> deque;
/* Enqueue elements */
deque.push_back(2); // Add to the tail
deque.push_back(2); // Add to rear
deque.push_back(5);
deque.push_back(4);
deque.push_front(3); // Add to the head
deque.push_front(3); // Add to front
deque.push_front(1);
/* Access elements */
int front = deque.front(); // The first element
int back = deque.back(); // The last element
int front = deque.front(); // Front element
int back = deque.back(); // Rear element
/* Dequeue elements */
deque.pop_front(); // The first element dequeued
deque.pop_back(); // The last element dequeued
deque.pop_front(); // Front element dequeue
deque.pop_back(); // Rear element dequeue
/* Get the length of the deque */
/* Get deque length */
int size = deque.size();
/* Check if the deque is empty */
/* Check if deque is empty */
bool empty = deque.empty();
```
=== "Java"
```java title="deque.java"
/* Initialize the deque */
/* Initialize deque */
Deque<Integer> deque = new LinkedList<>();
/* Enqueue elements */
deque.offerLast(2); // Add to the tail
deque.offerLast(2); // Add to rear
deque.offerLast(5);
deque.offerLast(4);
deque.offerFirst(3); // Add to the head
deque.offerFirst(3); // Add to front
deque.offerFirst(1);
/* Access elements */
int peekFirst = deque.peekFirst(); // The first element
int peekLast = deque.peekLast(); // The last element
int peekFirst = deque.peekFirst(); // Front element
int peekLast = deque.peekLast(); // Rear element
/* Dequeue elements */
int popFirst = deque.pollFirst(); // The first element dequeued
int popLast = deque.pollLast(); // The last element dequeued
int popFirst = deque.pollFirst(); // Front element dequeue
int popLast = deque.pollLast(); // Rear element dequeue
/* Get the length of the deque */
/* Get deque length */
int size = deque.size();
/* Check if the deque is empty */
/* Check if deque is empty */
boolean isEmpty = deque.isEmpty();
```
=== "C#"
```csharp title="deque.cs"
/* Initialize the deque */
// In C#, LinkedList is used as a deque
/* Initialize deque */
// In C#, use LinkedList as a deque
LinkedList<int> deque = new();
/* Enqueue elements */
deque.AddLast(2); // Add to the tail
deque.AddLast(2); // Add to rear
deque.AddLast(5);
deque.AddLast(4);
deque.AddFirst(3); // Add to the head
deque.AddFirst(3); // Add to front
deque.AddFirst(1);
/* Access elements */
int peekFirst = deque.First.Value; // The first element
int peekLast = deque.Last.Value; // The last element
int peekFirst = deque.First.Value; // Front element
int peekLast = deque.Last.Value; // Rear element
/* Dequeue elements */
deque.RemoveFirst(); // The first element dequeued
deque.RemoveLast(); // The last element dequeued
deque.RemoveFirst(); // Front element dequeue
deque.RemoveLast(); // Rear element dequeue
/* Get the length of the deque */
/* Get deque length */
int size = deque.Count;
/* Check if the deque is empty */
/* Check if deque is empty */
bool isEmpty = deque.Count == 0;
```
=== "Go"
```go title="deque_test.go"
/* Initialize the deque */
/* Initialize deque */
// In Go, use list as a deque
deque := list.New()
/* Enqueue elements */
deque.PushBack(2) // Add to the tail
deque.PushBack(2) // Add to rear
deque.PushBack(5)
deque.PushBack(4)
deque.PushFront(3) // Add to the head
deque.PushFront(3) // Add to front
deque.PushFront(1)
/* Access elements */
front := deque.Front() // The first element
rear := deque.Back() // The last element
front := deque.Front() // Front element
rear := deque.Back() // Rear element
/* Dequeue elements */
deque.Remove(front) // The first element dequeued
deque.Remove(rear) // The last element dequeued
deque.Remove(front) // Front element dequeue
deque.Remove(rear) // Rear element dequeue
/* Get the length of the deque */
/* Get deque length */
size := deque.Len()
/* Check if the deque is empty */
/* Check if deque is empty */
isEmpty := deque.Len() == 0
```
=== "Swift"
```swift title="deque.swift"
/* Initialize the deque */
// Swift does not have a built-in deque class, so Array can be used as a deque
/* Initialize deque */
// Swift does not have a built-in deque class, can use Array as a deque
var deque: [Int] = []
/* Enqueue elements */
deque.append(2) // Add to the tail
deque.append(2) // Add to rear
deque.append(5)
deque.append(4)
deque.insert(3, at: 0) // Add to the head
deque.insert(3, at: 0) // Add to front
deque.insert(1, at: 0)
/* Access elements */
let peekFirst = deque.first! // The first element
let peekLast = deque.last! // The last element
let peekFirst = deque.first! // Front element
let peekLast = deque.last! // Rear element
/* Dequeue elements */
// Using Array, popFirst has a complexity of O(n)
let popFirst = deque.removeFirst() // The first element dequeued
let popLast = deque.removeLast() // The last element dequeued
// When using Array simulation, popFirst has O(n) complexity
let popFirst = deque.removeFirst() // Front element dequeue
let popLast = deque.removeLast() // Rear element dequeue
/* Get the length of the deque */
/* Get deque length */
let size = deque.count
/* Check if the deque is empty */
/* Check if deque is empty */
let isEmpty = deque.isEmpty
```
=== "JS"
```javascript title="deque.js"
/* Initialize the deque */
// JavaScript does not have a built-in deque, so Array is used as a deque
/* Initialize deque */
// JavaScript does not have a built-in deque, can only use Array as a deque
const deque = [];
/* Enqueue elements */
deque.push(2);
deque.push(5);
deque.push(4);
// Note that unshift() has a time complexity of O(n) as it's an array
// Please note that since it's an array, unshift() has O(n) time complexity
deque.unshift(3);
deque.unshift(1);
/* Access elements */
const peekFirst = deque[0]; // The first element
const peekLast = deque[deque.length - 1]; // The last element
const peekFirst = deque[0];
const peekLast = deque[deque.length - 1];
/* Dequeue elements */
// Note that shift() has a time complexity of O(n) as it's an array
const popFront = deque.shift(); // The first element dequeued
const popBack = deque.pop(); // The last element dequeued
// Please note that since it's an array, shift() has O(n) time complexity
const popFront = deque.shift();
const popBack = deque.pop();
/* Get the length of the deque */
/* Get deque length */
const size = deque.length;
/* Check if the deque is empty */
/* Check if deque is empty */
const isEmpty = size === 0;
```
=== "TS"
```typescript title="deque.ts"
/* Initialize the deque */
// TypeScript does not have a built-in deque, so Array is used as a deque
/* Initialize deque */
// TypeScript does not have a built-in deque, can only use Array as a deque
const deque: number[] = [];
/* Enqueue elements */
deque.push(2);
deque.push(5);
deque.push(4);
// Note that unshift() has a time complexity of O(n) as it's an array
// Please note that since it's an array, unshift() has O(n) time complexity
deque.unshift(3);
deque.unshift(1);
/* Access elements */
const peekFirst: number = deque[0]; // The first element
const peekLast: number = deque[deque.length - 1]; // The last element
const peekFirst: number = deque[0];
const peekLast: number = deque[deque.length - 1];
/* Dequeue elements */
// Note that shift() has a time complexity of O(n) as it's an array
const popFront: number = deque.shift() as number; // The first element dequeued
const popBack: number = deque.pop() as number; // The last element dequeued
// Please note that since it's an array, shift() has O(n) time complexity
const popFront: number = deque.shift() as number;
const popBack: number = deque.pop() as number;
/* Get the length of the deque */
/* Get deque length */
const size: number = deque.length;
/* Check if the deque is empty */
/* Check if deque is empty */
const isEmpty: boolean = size === 0;
```
=== "Dart"
```dart title="deque.dart"
/* Initialize the deque */
/* Initialize deque */
// In Dart, Queue is defined as a deque
Queue<int> deque = Queue<int>();
/* Enqueue elements */
deque.addLast(2); // Add to the tail
deque.addLast(2); // Add to rear
deque.addLast(5);
deque.addLast(4);
deque.addFirst(3); // Add to the head
deque.addFirst(3); // Add to front
deque.addFirst(1);
/* Access elements */
int peekFirst = deque.first; // The first element
int peekLast = deque.last; // The last element
int peekFirst = deque.first; // Front element
int peekLast = deque.last; // Rear element
/* Dequeue elements */
int popFirst = deque.removeFirst(); // The first element dequeued
int popLast = deque.removeLast(); // The last element dequeued
int popFirst = deque.removeFirst(); // Front element dequeue
int popLast = deque.removeLast(); // Rear element dequeue
/* Get the length of the deque */
/* Get deque length */
int size = deque.length;
/* Check if the deque is empty */
/* Check if deque is empty */
bool isEmpty = deque.isEmpty;
```
=== "Rust"
```rust title="deque.rs"
/* Initialize the deque */
/* Initialize deque */
let mut deque: VecDeque<u32> = VecDeque::new();
/* Enqueue elements */
deque.push_back(2); // Add to the tail
deque.push_back(2); // Add to rear
deque.push_back(5);
deque.push_back(4);
deque.push_front(3); // Add to the head
deque.push_front(3); // Add to front
deque.push_front(1);
/* Access elements */
if let Some(front) = deque.front() { // The first element
if let Some(front) = deque.front() { // Front element
}
if let Some(rear) = deque.back() { // The last element
if let Some(rear) = deque.back() { // Rear element
}
/* Dequeue elements */
if let Some(pop_front) = deque.pop_front() { // The first element dequeued
if let Some(pop_front) = deque.pop_front() { // Front element dequeue
}
if let Some(pop_rear) = deque.pop_back() { // The last element dequeued
if let Some(pop_rear) = deque.pop_back() { // Rear element dequeue
}
/* Get the length of the deque */
/* Get deque length */
let size = deque.len();
/* Check if the deque is empty */
/* Check if deque is empty */
let is_empty = deque.is_empty();
```
@@ -327,7 +327,60 @@ Similarly, we can directly use the double-ended queue classes implemented in pro
=== "Kotlin"
```kotlin title="deque.kt"
/* Initialize deque */
val deque = LinkedList<Int>()
/* Enqueue elements */
deque.offerLast(2) // Add to rear
deque.offerLast(5)
deque.offerLast(4)
deque.offerFirst(3) // Add to front
deque.offerFirst(1)
/* Access elements */
val peekFirst = deque.peekFirst() // Front element
val peekLast = deque.peekLast() // Rear element
/* Dequeue elements */
val popFirst = deque.pollFirst() // Front element dequeue
val popLast = deque.pollLast() // Rear element dequeue
/* Get deque length */
val size = deque.size
/* Check if deque is empty */
val isEmpty = deque.isEmpty()
```
=== "Ruby"
```ruby title="deque.rb"
# Initialize deque
# Ruby does not have a built-in deque, can only use Array as a deque
deque = []
# Enqueue elements
deque << 2
deque << 5
deque << 4
# Please note that since it's an array, Array#unshift has O(n) time complexity
deque.unshift(3)
deque.unshift(1)
# Access elements
peek_first = deque.first
peek_last = deque.last
# Dequeue elements
# Please note that since it's an array, Array#shift has O(n) time complexity
pop_front = deque.shift
pop_back = deque.pop
# Get deque length
size = deque.length
# Check if deque is empty
is_empty = size.zero?
```
=== "Zig"
@@ -336,70 +389,70 @@ Similarly, we can directly use the double-ended queue classes implemented in pro
```
??? pythontutor "Visualizing Code"
??? pythontutor "Visualize Execution"
https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%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%8F%8C%E5%90%91%E9%98%9F%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22,%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22,%20pop_rear%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
## Implementing a double-ended queue *
## Deque Implementation *
The implementation of a double-ended queue is similar to that of a regular queue, it can be based on either a linked list or an array as the underlying data structure.
The implementation of a deque is similar to that of a queue. You can choose either a linked list or an array as the underlying data structure.
### Implementation based on doubly linked list
### Doubly Linked List Implementation
Recall from the previous section that we used a regular singly linked list to implement a queue, as it conveniently allows for deleting from the head (corresponding to the dequeue operation) and adding new elements after the tail (corresponding to the enqueue operation).
Reviewing the previous section, we used a regular singly linked list to implement a queue because it conveniently allows deleting the head node (corresponding to dequeue) and adding new nodes after the tail node (corresponding to enqueue).
For a double-ended queue, both the head and the tail can perform enqueue and dequeue operations. In other words, a double-ended queue needs to implement operations in the opposite direction as well. For this, we use a "doubly linked list" as the underlying data structure of the double-ended queue.
For a deque, both the front and rear can perform enqueue and dequeue operations. In other words, a deque needs to implement operations in the opposite direction as well. For this reason, we use a "doubly linked list" as the underlying data structure for the deque.
As shown in the figure below, we treat the head and tail nodes of the doubly linked list as the front and rear of the double-ended queue, respectively, and implement the functionality to add and remove nodes at both ends.
As shown in the figure below, we treat the head and tail nodes of the doubly linked list as the front and rear of the deque, implementing functionality to add and remove nodes at both ends.
=== "LinkedListDeque"
![Implementing Double-Ended Queue with Doubly Linked List for Enqueue and Dequeue Operations](deque.assets/linkedlist_deque_step1.png)
![Enqueue and dequeue operations in linked list implementation of deque](deque.assets/linkedlist_deque_step1.png)
=== "pushLast()"
=== "push_last()"
![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png)
=== "pushFirst()"
=== "push_first()"
![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png)
=== "popLast()"
=== "pop_last()"
![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png)
=== "popFirst()"
=== "pop_first()"
![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png)
The implementation code is as follows:
The implementation code is shown below:
```src
[file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{}
```
### Implementation based on array
### Array Implementation
As shown in the figure below, similar to implementing a queue with an array, we can also use a circular array to implement a double-ended queue.
As shown in the figure below, similar to implementing a queue based on an array, we can also use a circular array to implement a deque.
=== "ArrayDeque"
![Implementing Double-Ended Queue with Array for Enqueue and Dequeue Operations](deque.assets/array_deque_step1.png)
![Enqueue and dequeue operations in array implementation of deque](deque.assets/array_deque_step1.png)
=== "pushLast()"
=== "push_last()"
![array_deque_push_last](deque.assets/array_deque_step2_push_last.png)
=== "pushFirst()"
=== "push_first()"
![array_deque_push_first](deque.assets/array_deque_step3_push_first.png)
=== "popLast()"
=== "pop_last()"
![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png)
=== "popFirst()"
=== "pop_first()"
![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png)
The implementation only needs to add methods for "front enqueue" and "rear dequeue":
Based on the queue implementation, we only need to add methods for "enqueue at front" and "dequeue from rear":
```src
[file]{array_deque}-[class]{array_deque}-[func]{}
```
## Applications of double-ended queue
## Deque Applications
The double-ended queue combines the logic of both stacks and queues, **thus, it can implement all their respective use cases while offering greater flexibility**.
A deque combines the logic of both stacks and queues. **Therefore, it can implement all application scenarios of both, while providing greater flexibility**.
We know that software's "undo" feature is typically implemented using a stack: the system `pushes` each change operation onto the stack and then `pops` to implement undoing. However, considering the limitations of system resources, software often restricts the number of undo steps (for example, only allowing the last 50 steps). When the stack length exceeds 50, the software needs to perform a deletion operation at the bottom of the stack (the front of the queue). **But a regular stack cannot perform this function, where a double-ended queue becomes necessary**. Note that the core logic of "undo" still follows the Last-In-First-Out principle of a stack, but a double-ended queue can more flexibly implement some additional logic.
We know that the "undo" function in software is typically implemented using a stack: the system pushes each change operation onto the stack and then implements undo through pop. However, considering system resource limitations, software usually limits the number of undo steps (for example, only allowing 50 steps to be saved). When the stack length exceeds 50, the software needs to perform a deletion operation at the bottom of the stack (front of the queue). **But a stack cannot implement this functionality, so a deque is needed to replace the stack**. Note that the core logic of "undo" still follows the LIFO principle of a stack; it's just that the deque can more flexibly implement some additional logic.

View File

@@ -1,9 +1,9 @@
# Stack and queue
# Stack and Queue
![Stack and queue](../assets/covers/chapter_stack_and_queue.jpg)
![Stack and Queue](../assets/covers/chapter_stack_and_queue.jpg)
!!! abstract
A stack is like cats placed on top of each other, while a queue is like cats lined up one by one.
They represent the logical relationships of Last-In-First-Out (LIFO) and First-In-First-Out (FIFO), respectively.
Stacks are like stacking cats, while queues are like cats lining up.
They represent LIFO (Last In First Out) and FIFO (First In First Out) logic, respectively.

View File

@@ -1,22 +1,22 @@
# Queue
A <u>queue</u> is a linear data structure that follows the First-In-First-Out (FIFO) rule. As the name suggests, a queue simulates the phenomenon of lining up, where newcomers join the queue at the rear, and the person at the front leaves the queue first.
A <u>queue</u> is a linear data structure that follows the First In First Out (FIFO) rule. As the name suggests, a queue simulates the phenomenon of lining up, where newcomers continuously join the end of the queue, while people at the front of the queue leave one by one.
As shown in the figure below, we call the front of the queue the "head" and the back the "tail." The operation of adding elements to the rear of the queue is termed "enqueue," and the operation of removing elements from the front is termed "dequeue."
As shown in the figure below, we call the front of the queue the "front" and the end the "rear." The operation of adding an element to the rear is called "enqueue," and the operation of removing the front element is called "dequeue."
![Queue's first-in-first-out rule](queue.assets/queue_operations.png)
![FIFO rule of queue](queue.assets/queue_operations.png)
## Common operations on queue
## Common Queue Operations
The common operations on a queue are shown in the table below. Note that method names may vary across different programming languages. Here, we use the same naming convention as that used for stacks.
The common operations on a queue are shown in the table below. Note that method names may vary across different programming languages. We adopt the same naming convention as for stacks here.
<p align="center"> Table <id> &nbsp; Efficiency of queue operations </p>
<p align="center"> Table <id> &nbsp; Efficiency of Queue Operations </p>
| Method Name | Description | Time Complexity |
| ----------- | -------------------------------------- | --------------- |
| `push()` | Enqueue an element, add it to the tail | $O(1)$ |
| `pop()` | Dequeue the head element | $O(1)$ |
| `peek()` | Access the head element | $O(1)$ |
| Method | Description | Time Complexity |
| -------- | ------------------------------------------ | --------------- |
| `push()` | Enqueue element, add element to rear | $O(1)$ |
| `pop()` | Dequeue front element | $O(1)$ |
| `peek()` | Access front element | $O(1)$ |
We can directly use the ready-made queue classes in programming languages:
@@ -25,9 +25,9 @@ We can directly use the ready-made queue classes in programming languages:
```python title="queue.py"
from collections import deque
# Initialize the queue
# Initialize queue
# In Python, we generally use the deque class as a queue
# Although queue.Queue() is a pure queue class, it's not very user-friendly, so it's not recommended
# Although queue.Queue() is a pure queue class, it is not very user-friendly, so it is not recommended
que: deque[int] = deque()
# Enqueue elements
@@ -37,23 +37,23 @@ We can directly use the ready-made queue classes in programming languages:
que.append(5)
que.append(4)
# Access the first element
# Access front element
front: int = que[0]
# Dequeue an element
# Dequeue element
pop: int = que.popleft()
# Get the length of the queue
# Get queue length
size: int = len(que)
# Check if the queue is empty
# Check if queue is empty
is_empty: bool = len(que) == 0
```
=== "C++"
```cpp title="queue.cpp"
/* Initialize the queue */
/* Initialize queue */
queue<int> queue;
/* Enqueue elements */
@@ -63,23 +63,23 @@ We can directly use the ready-made queue classes in programming languages:
queue.push(5);
queue.push(4);
/* Access the first element*/
/* Access front element */
int front = queue.front();
/* Dequeue an element */
/* Dequeue element */
queue.pop();
/* Get the length of the queue */
/* Get queue length */
int size = queue.size();
/* Check if the queue is empty */
/* Check if queue is empty */
bool empty = queue.empty();
```
=== "Java"
```java title="queue.java"
/* Initialize the queue */
/* Initialize queue */
Queue<Integer> queue = new LinkedList<>();
/* Enqueue elements */
@@ -89,23 +89,23 @@ We can directly use the ready-made queue classes in programming languages:
queue.offer(5);
queue.offer(4);
/* Access the first element */
/* Access front element */
int peek = queue.peek();
/* Dequeue an element */
/* Dequeue element */
int pop = queue.poll();
/* Get the length of the queue */
/* Get queue length */
int size = queue.size();
/* Check if the queue is empty */
/* Check if queue is empty */
boolean isEmpty = queue.isEmpty();
```
=== "C#"
```csharp title="queue.cs"
/* Initialize the queue */
/* Initialize queue */
Queue<int> queue = new();
/* Enqueue elements */
@@ -115,23 +115,23 @@ We can directly use the ready-made queue classes in programming languages:
queue.Enqueue(5);
queue.Enqueue(4);
/* Access the first element */
/* Access front element */
int peek = queue.Peek();
/* Dequeue an element */
/* Dequeue element */
int pop = queue.Dequeue();
/* Get the length of the queue */
/* Get queue length */
int size = queue.Count;
/* Check if the queue is empty */
/* Check if queue is empty */
bool isEmpty = queue.Count == 0;
```
=== "Go"
```go title="queue_test.go"
/* Initialize the queue */
/* Initialize queue */
// In Go, use list as a queue
queue := list.New()
@@ -142,25 +142,25 @@ We can directly use the ready-made queue classes in programming languages:
queue.PushBack(5)
queue.PushBack(4)
/* Access the first element */
/* Access front element */
peek := queue.Front()
/* Dequeue an element */
/* Dequeue element */
pop := queue.Front()
queue.Remove(pop)
/* Get the length of the queue */
/* Get queue length */
size := queue.Len()
/* Check if the queue is empty */
/* Check if queue is empty */
isEmpty := queue.Len() == 0
```
=== "Swift"
```swift title="queue.swift"
/* Initialize the queue */
// Swift does not have a built-in queue class, so Array can be used as a queue
/* Initialize queue */
// Swift does not have a built-in queue class, can use Array as a queue
var queue: [Int] = []
/* Enqueue elements */
@@ -170,25 +170,25 @@ We can directly use the ready-made queue classes in programming languages:
queue.append(5)
queue.append(4)
/* Access the first element */
/* Access front element */
let peek = queue.first!
/* Dequeue an element */
// Since it's an array, removeFirst has a complexity of O(n)
/* Dequeue element */
// Since it's an array, removeFirst has O(n) complexity
let pool = queue.removeFirst()
/* Get the length of the queue */
/* Get queue length */
let size = queue.count
/* Check if the queue is empty */
/* Check if queue is empty */
let isEmpty = queue.isEmpty
```
=== "JS"
```javascript title="queue.js"
/* Initialize the queue */
// JavaScript does not have a built-in queue, so Array can be used as a queue
/* Initialize queue */
// JavaScript does not have a built-in queue, can use Array as a queue
const queue = [];
/* Enqueue elements */
@@ -198,25 +198,25 @@ We can directly use the ready-made queue classes in programming languages:
queue.push(5);
queue.push(4);
/* Access the first element */
/* Access front element */
const peek = queue[0];
/* Dequeue an element */
// Since the underlying structure is an array, shift() method has a time complexity of O(n)
/* Dequeue element */
// The underlying structure is an array, so shift() has O(n) time complexity
const pop = queue.shift();
/* Get the length of the queue */
/* Get queue length */
const size = queue.length;
/* Check if the queue is empty */
/* Check if queue is empty */
const empty = queue.length === 0;
```
=== "TS"
```typescript title="queue.ts"
/* Initialize the queue */
// TypeScript does not have a built-in queue, so Array can be used as a queue
/* Initialize queue */
// TypeScript does not have a built-in queue, can use Array as a queue
const queue: number[] = [];
/* Enqueue elements */
@@ -226,25 +226,25 @@ We can directly use the ready-made queue classes in programming languages:
queue.push(5);
queue.push(4);
/* Access the first element */
/* Access front element */
const peek = queue[0];
/* Dequeue an element */
// Since the underlying structure is an array, shift() method has a time complexity of O(n)
/* Dequeue element */
// The underlying structure is an array, so shift() has O(n) time complexity
const pop = queue.shift();
/* Get the length of the queue */
/* Get queue length */
const size = queue.length;
/* Check if the queue is empty */
/* Check if queue is empty */
const empty = queue.length === 0;
```
=== "Dart"
```dart title="queue.dart"
/* Initialize the queue */
// In Dart, the Queue class is a double-ended queue but can be used as a queue
/* Initialize queue */
// In Dart, the Queue class is a deque and can also be used as a queue
Queue<int> queue = Queue();
/* Enqueue elements */
@@ -254,24 +254,24 @@ We can directly use the ready-made queue classes in programming languages:
queue.add(5);
queue.add(4);
/* Access the first element */
/* Access front element */
int peek = queue.first;
/* Dequeue an element */
/* Dequeue element */
int pop = queue.removeFirst();
/* Get the length of the queue */
/* Get queue length */
int size = queue.length;
/* Check if the queue is empty */
/* Check if queue is empty */
bool isEmpty = queue.isEmpty;
```
=== "Rust"
```rust title="queue.rs"
/* Initialize the double-ended queue */
// In Rust, use a double-ended queue as a regular queue
/* Initialize deque */
// In Rust, use deque as a regular queue
let mut deque: VecDeque<u32> = VecDeque::new();
/* Enqueue elements */
@@ -281,18 +281,18 @@ We can directly use the ready-made queue classes in programming languages:
deque.push_back(5);
deque.push_back(4);
/* Access the first element */
/* Access front element */
if let Some(front) = deque.front() {
}
/* Dequeue an element */
/* Dequeue element */
if let Some(pop) = deque.pop_front() {
}
/* Get the length of the queue */
/* Get queue length */
let size = deque.len();
/* Check if the queue is empty */
/* Check if queue is empty */
let is_empty = deque.is_empty();
```
@@ -305,7 +305,55 @@ We can directly use the ready-made queue classes in programming languages:
=== "Kotlin"
```kotlin title="queue.kt"
/* Initialize queue */
val queue = LinkedList<Int>()
/* Enqueue elements */
queue.offer(1)
queue.offer(3)
queue.offer(2)
queue.offer(5)
queue.offer(4)
/* Access front element */
val peek = queue.peek()
/* Dequeue element */
val pop = queue.poll()
/* Get queue length */
val size = queue.size
/* Check if queue is empty */
val isEmpty = queue.isEmpty()
```
=== "Ruby"
```ruby title="queue.rb"
# Initialize queue
# Ruby's built-in queue (Thread::Queue) does not have peek and traversal methods, can use Array as a queue
queue = []
# Enqueue elements
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
# Access front element
peek = queue.first
# Dequeue element
# Please note that since it's an array, Array#shift has O(n) time complexity
pop = queue.shift
# Get queue length
size = queue.length
# Check if queue is empty
is_empty = queue.empty?
```
=== "Zig"
@@ -314,20 +362,20 @@ We can directly use the ready-made queue classes in programming languages:
```
??? pythontutor "Code Visualization"
??? pythontutor "Visualize Execution"
https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%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%E9%98%9F%E5%88%97%0A%20%20%20%20%23%20%E5%9C%A8%20Python%20%E4%B8%AD%EF%BC%8C%E6%88%91%E4%BB%AC%E4%B8%80%E8%88%AC%E5%B0%86%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%B1%BB%20deque%20%E7%9C%8B%E4%BD%9C%E9%98%9F%E5%88%97%E4%BD%BF%E7%94%A8%0A%20%20%20%20%23%20%E8%99%BD%E7%84%B6%20queue.Queue%28%29%20%E6%98%AF%E7%BA%AF%E6%AD%A3%E7%9A%84%E9%98%9F%E5%88%97%E7%B1%BB%EF%BC%8C%E4%BD%86%E4%B8%8D%E5%A4%AA%E5%A5%BD%E7%94%A8%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
## Implementing a queue
## Queue Implementation
To implement a queue, we need a data structure that allows adding elements at one end and removing them at the other. Both linked lists and arrays meet this requirement.
To implement a queue, we need a data structure that allows adding elements at one end and removing elements at the other end. Both linked lists and arrays meet this requirement.
### Implementation based on a linked list
### Linked List Implementation
As shown in the figure below, we can consider the "head node" and "tail node" of a linked list as the "front" and "rear" of the queue, respectively. It is stipulated that nodes can only be added at the rear and removed at the front.
As shown in the figure below, we can treat the "head node" and "tail node" of a linked list as the "front" and "rear" of the queue, respectively, with the rule that nodes can only be added at the rear and removed from the front.
=== "LinkedListQueue"
![Implementing Queue with Linked List for Enqueue and Dequeue Operations](queue.assets/linkedlist_queue_step1.png)
![Enqueue and dequeue operations in linked list implementation of queue](queue.assets/linkedlist_queue_step1.png)
=== "push()"
![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png)
@@ -341,21 +389,21 @@ Below is the code for implementing a queue using a linked list:
[file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{}
```
### Implementation based on an array
### Array Implementation
Deleting the first element in an array has a time complexity of $O(n)$, which would make the dequeue operation inefficient. However, this problem can be cleverly avoided as follows.
Deleting the first element in an array has a time complexity of $O(n)$, which would make the dequeue operation inefficient. However, we can use the following clever method to avoid this problem.
We use a variable `front` to indicate the index of the front element and maintain a variable `size` to record the queue's length. Define `rear = front + size`, which points to the position immediately following the tail element.
We can use a variable `front` to point to the index of the front element and maintain a variable `size` to record the queue length. We define `rear = front + size`, which calculates the position right after the rear element.
With this design, **the effective interval of elements in the array is `[front, rear - 1]`**. The implementation methods for various operations are shown in the figure below.
Based on this design, **the valid interval containing elements in the array is `[front, rear - 1]`**. The implementation methods for various operations are shown in the figure below:
- Enqueue operation: Assign the input element to the `rear` index and increase `size` by 1.
- Dequeue operation: Simply increase `front` by 1 and decrease `size` by 1.
Both enqueue and dequeue operations only require a single operation, each with a time complexity of $O(1)$.
As you can see, both enqueue and dequeue operations require only one operation, with a time complexity of $O(1)$.
=== "ArrayQueue"
![Implementing Queue with Array for Enqueue and Dequeue Operations](queue.assets/array_queue_step1.png)
![Enqueue and dequeue operations in array implementation of queue](queue.assets/array_queue_step1.png)
=== "push()"
![array_queue_push](queue.assets/array_queue_step2_push.png)
@@ -363,19 +411,19 @@ Both enqueue and dequeue operations only require a single operation, each with a
=== "pop()"
![array_queue_pop](queue.assets/array_queue_step3_pop.png)
You might notice a problem: as enqueue and dequeue operations are continuously performed, both `front` and `rear` move to the right and **will eventually reach the end of the array and can't move further**. To resolve this, we can treat the array as a "circular array" where connecting the end of the array back to its beginning.
You may notice a problem: as we continuously enqueue and dequeue, both `front` and `rear` move to the right. **When they reach the end of the array, they cannot continue moving**. To solve this problem, we can treat the array as a "circular array" with head and tail connected.
In a circular array, `front` or `rear` needs to loop back to the start of the array upon reaching the end. This cyclical pattern can be achieved with a "modulo operation" as shown in the code below:
For a circular array, we need to let `front` or `rear` wrap around to the beginning of the array when they cross the end. This periodic pattern can be implemented using the "modulo operation," as shown in the code below:
```src
[file]{array_queue}-[class]{array_queue}-[func]{}
```
The above implementation of the queue still has its limitations: its length is fixed. However, this issue is not difficult to resolve. We can replace the array with a dynamic array that can expand itself if needed. Interested readers can try to implement this themselves.
The queue implemented above still has limitations: its length is immutable. However, this problem is not difficult to solve. We can replace the array with a dynamic array to introduce an expansion mechanism. Interested readers can try to implement this themselves.
The comparison of the two implementations is consistent with that of the stack and is not repeated here.
The comparison conclusions for the two implementations are consistent with those for stacks and will not be repeated here.
## Typical applications of queue
## Typical Applications of Queue
- **Amazon orders**: After shoppers place orders, these orders join a queue, and the system processes them in order. During events like Singles' Day, a massive number of orders are generated in a short time, making high concurrency a key challenge for engineers.
- **Various to-do lists**: Any scenario requiring a "first-come, first-served" functionality, such as a printer's task queue or a restaurant's food delivery queue, can effectively maintain the order of processing with a queue.
- **Taobao orders**. After shoppers place orders, the orders are added to a queue, and the system subsequently processes the orders in the queue according to their sequence. During Double Eleven, massive orders are generated in a short time, and high concurrency becomes a key challenge that engineers need to tackle.
- **Various to-do tasks**. Any scenario that needs to implement "first come, first served" functionality, such as a printer's task queue or a restaurant's order queue, can effectively maintain the processing order using queues.

View File

@@ -1,292 +1,292 @@
# Stack
A <u>stack</u> is a linear data structure that follows the principle of Last-In-First-Out (LIFO).
A <u>stack</u> is a linear data structure that follows the Last In First Out (LIFO) logic.
We can compare a stack to a pile of plates on a table. To access the bottom plate, one must first remove the plates on top. By replacing the plates with various types of elements (such as integers, characters, objects, etc.), we obtain the data structure known as a stack.
We can compare a stack to a pile of plates on a table. If we specify that only one plate can be moved at a time, then to get the bottom plate, we must first remove the plates above it one by one. If we replace the plates with various types of elements (such as integers, characters, objects, etc.), we get the stack data structure.
As shown in the figure below, we refer to the top of the pile of elements as the "top of the stack" and the bottom as the "bottom of the stack." The operation of adding elements to the top of the stack is called "push," and the operation of removing the top element is called "pop."
As shown in the figure below, we call the top of the stacked elements the "top" and the bottom the "base." The operation of adding an element to the top is called "push," and the operation of removing the top element is called "pop."
![Stack's last-in-first-out rule](stack.assets/stack_operations.png)
![LIFO rule of stack](stack.assets/stack_operations.png)
## Common operations on stack
## Common Stack Operations
The common operations on a stack are shown in the table below. The specific method names depend on the programming language used. Here, we use `push()`, `pop()`, and `peek()` as examples.
The common operations on a stack are shown in the table below. The specific method names depend on the programming language used. Here, we use the common naming convention of `push()`, `pop()`, and `peek()`.
<p align="center"> Table <id> &nbsp; Efficiency of stack operations </p>
<p align="center"> Table <id> &nbsp; Efficiency of Stack Operations </p>
| Method | Description | Time Complexity |
| -------- | ----------------------------------------------- | --------------- |
| `push()` | Push an element onto the stack (add to the top) | $O(1)$ |
| `pop()` | Pop the top element from the stack | $O(1)$ |
| `peek()` | Access the top element of the stack | $O(1)$ |
| Method | Description | Time Complexity |
| -------- | ---------------------------------------------- | --------------- |
| `push()` | Push element onto stack (add to top) | $O(1)$ |
| `pop()` | Pop top element from stack | $O(1)$ |
| `peek()` | Access top element | $O(1)$ |
Typically, we can directly use the stack class built into the programming language. However, some languages may not specifically provide a stack class. In these cases, we can use the language's "array" or "linked list" as a stack and ignore operations that are not related to stack logic in the program.
Typically, we can directly use the built-in stack class provided by the programming language. However, some languages may not provide a dedicated stack class. In these cases, we can use the language's "array" or "linked list" as a stack and ignore operations unrelated to the stack in the program logic.
=== "Python"
```python title="stack.py"
# Initialize the stack
# Python does not have a built-in stack class, so a list can be used as a stack
# Initialize stack
# Python does not have a built-in stack class, can use list as a stack
stack: list[int] = []
# Push elements onto the stack
# Push elements
stack.append(1)
stack.append(3)
stack.append(2)
stack.append(5)
stack.append(4)
# Access the top element of the stack
# Access top element
peek: int = stack[-1]
# Pop an element from the stack
# Pop element
pop: int = stack.pop()
# Get the length of the stack
# Get stack length
size: int = len(stack)
# Check if the stack is empty
# Check if empty
is_empty: bool = len(stack) == 0
```
=== "C++"
```cpp title="stack.cpp"
/* Initialize the stack */
/* Initialize stack */
stack<int> stack;
/* Push elements onto the stack */
/* Push elements */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* Access the top element of the stack */
/* Access top element */
int top = stack.top();
/* Pop an element from the stack */
/* Pop element */
stack.pop(); // No return value
/* Get the length of the stack */
/* Get stack length */
int size = stack.size();
/* Check if the stack is empty */
/* Check if empty */
bool empty = stack.empty();
```
=== "Java"
```java title="stack.java"
/* Initialize the stack */
/* Initialize stack */
Stack<Integer> stack = new Stack<>();
/* Push elements onto the stack */
/* Push elements */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* Access the top element of the stack */
/* Access top element */
int peek = stack.peek();
/* Pop an element from the stack */
/* Pop element */
int pop = stack.pop();
/* Get the length of the stack */
/* Get stack length */
int size = stack.size();
/* Check if the stack is empty */
/* Check if empty */
boolean isEmpty = stack.isEmpty();
```
=== "C#"
```csharp title="stack.cs"
/* Initialize the stack */
/* Initialize stack */
Stack<int> stack = new();
/* Push elements onto the stack */
/* Push elements */
stack.Push(1);
stack.Push(3);
stack.Push(2);
stack.Push(5);
stack.Push(4);
/* Access the top element of the stack */
/* Access top element */
int peek = stack.Peek();
/* Pop an element from the stack */
/* Pop element */
int pop = stack.Pop();
/* Get the length of the stack */
/* Get stack length */
int size = stack.Count;
/* Check if the stack is empty */
/* Check if empty */
bool isEmpty = stack.Count == 0;
```
=== "Go"
```go title="stack_test.go"
/* Initialize the stack */
// In Go, it is recommended to use a Slice as a stack
/* Initialize stack */
// In Go, it is recommended to use Slice as a stack
var stack []int
/* Push elements onto the stack */
/* Push elements */
stack = append(stack, 1)
stack = append(stack, 3)
stack = append(stack, 2)
stack = append(stack, 5)
stack = append(stack, 4)
/* Access the top element of the stack */
/* Access top element */
peek := stack[len(stack)-1]
/* Pop an element from the stack */
/* Pop element */
pop := stack[len(stack)-1]
stack = stack[:len(stack)-1]
/* Get the length of the stack */
/* Get stack length */
size := len(stack)
/* Check if the stack is empty */
/* Check if empty */
isEmpty := len(stack) == 0
```
=== "Swift"
```swift title="stack.swift"
/* Initialize the stack */
// Swift does not have a built-in stack class, so Array can be used as a stack
/* Initialize stack */
// Swift does not have a built-in stack class, can use Array as a stack
var stack: [Int] = []
/* Push elements onto the stack */
/* Push elements */
stack.append(1)
stack.append(3)
stack.append(2)
stack.append(5)
stack.append(4)
/* Access the top element of the stack */
/* Access top element */
let peek = stack.last!
/* Pop an element from the stack */
/* Pop element */
let pop = stack.removeLast()
/* Get the length of the stack */
/* Get stack length */
let size = stack.count
/* Check if the stack is empty */
/* Check if empty */
let isEmpty = stack.isEmpty
```
=== "JS"
```javascript title="stack.js"
/* Initialize the stack */
// JavaScript does not have a built-in stack class, so Array can be used as a stack
/* Initialize stack */
// JavaScript does not have a built-in stack class, can use Array as a stack
const stack = [];
/* Push elements onto the stack */
/* Push elements */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* Access the top element of the stack */
/* Access top element */
const peek = stack[stack.length-1];
/* Pop an element from the stack */
/* Pop element */
const pop = stack.pop();
/* Get the length of the stack */
/* Get stack length */
const size = stack.length;
/* Check if the stack is empty */
/* Check if empty */
const is_empty = stack.length === 0;
```
=== "TS"
```typescript title="stack.ts"
/* Initialize the stack */
// TypeScript does not have a built-in stack class, so Array can be used as a stack
/* Initialize stack */
// TypeScript does not have a built-in stack class, can use Array as a stack
const stack: number[] = [];
/* Push elements onto the stack */
/* Push elements */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* Access the top element of the stack */
/* Access top element */
const peek = stack[stack.length - 1];
/* Pop an element from the stack */
/* Pop element */
const pop = stack.pop();
/* Get the length of the stack */
/* Get stack length */
const size = stack.length;
/* Check if the stack is empty */
/* Check if empty */
const is_empty = stack.length === 0;
```
=== "Dart"
```dart title="stack.dart"
/* Initialize the stack */
// Dart does not have a built-in stack class, so List can be used as a stack
/* Initialize stack */
// Dart does not have a built-in stack class, can use List as a stack
List<int> stack = [];
/* Push elements onto the stack */
/* Push elements */
stack.add(1);
stack.add(3);
stack.add(2);
stack.add(5);
stack.add(4);
/* Access the top element of the stack */
/* Access top element */
int peek = stack.last;
/* Pop an element from the stack */
/* Pop element */
int pop = stack.removeLast();
/* Get the length of the stack */
/* Get stack length */
int size = stack.length;
/* Check if the stack is empty */
/* Check if empty */
bool isEmpty = stack.isEmpty;
```
=== "Rust"
```rust title="stack.rs"
/* Initialize the stack */
/* Initialize stack */
// Use Vec as a stack
let mut stack: Vec<i32> = Vec::new();
/* Push elements onto the stack */
/* Push elements */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
/* Access the top element of the stack */
/* Access top element */
let top = stack.last().unwrap();
/* Pop an element from the stack */
/* Pop element */
let pop = stack.pop().unwrap();
/* Get the length of the stack */
/* Get stack length */
let size = stack.len();
/* Check if the stack is empty */
/* Check if empty */
let is_empty = stack.is_empty();
```
@@ -299,7 +299,54 @@ Typically, we can directly use the stack class built into the programming langua
=== "Kotlin"
```kotlin title="stack.kt"
/* Initialize stack */
val stack = Stack<Int>()
/* Push elements */
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
/* Access top element */
val peek = stack.peek()
/* Pop element */
val pop = stack.pop()
/* Get stack length */
val size = stack.size
/* Check if empty */
val isEmpty = stack.isEmpty()
```
=== "Ruby"
```ruby title="stack.rb"
# Initialize stack
# Ruby does not have a built-in stack class, can use Array as a stack
stack = []
# Push elements
stack << 1
stack << 3
stack << 2
stack << 5
stack << 4
# Access top element
peek = stack.last
# Pop element
pop = stack.pop
# Get stack length
size = stack.length
# Check if empty
is_empty = stack.empty?
```
=== "Zig"
@@ -308,24 +355,24 @@ Typically, we can directly use the stack class built into the programming langua
```
??? pythontutor "Code Visualization"
??? pythontutor "Visualize Execution"
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%E6%A0%88%0A%20%20%20%20%23%20Python%20%E6%B2%A1%E6%9C%89%E5%86%85%E7%BD%AE%E7%9A%84%E6%A0%88%E7%B1%BB%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E5%BD%93%E4%BD%9C%E6%A0%88%E6%9D%A5%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%E6%A0%88%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%E6%A0%88%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%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%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
## Implementing a stack
## Stack Implementation
To gain a deeper understanding of how a stack operates, let's try implementing a stack class ourselves.
A stack follows the principle of Last-In-First-Out, which means we can only add or remove elements at the top of the stack. However, both arrays and linked lists allow adding and removing elements at any position, **therefore a stack can be seen as a restricted array or linked list**. In other words, we can "shield" certain irrelevant operations of an array or linked list, aligning their external behavior with the characteristics of a stack.
A stack follows the LIFO principle, so we can only add or remove elements at the top. However, both arrays and linked lists allow adding and removing elements at any position. **Therefore, a stack can be viewed as a restricted array or linked list**. In other words, we can "shield" some irrelevant operations of arrays or linked lists so that their external logic conforms to the characteristics of a stack.
### Implementation based on a linked list
### Linked List Implementation
When implementing a stack using a linked list, we can consider the head node of the list as the top of the stack and the tail node as the bottom of the stack.
When implementing a stack using a linked list, we can treat the head node of the linked list as the top of the stack and the tail node as the base.
As shown in the figure below, for the push operation, we simply insert elements at the head of the linked list. This method of node insertion is known as "head insertion." For the pop operation, we just need to remove the head node from the list.
As shown in the figure below, for the push operation, we simply insert an element at the head of the linked list. This node insertion method is called the "head insertion method." For the pop operation, we just need to remove the head node from the linked list.
=== "LinkedListStack"
![Implementing Stack with Linked List for Push and Pop Operations](stack.assets/linkedlist_stack_step1.png)
![Push and pop operations in linked list implementation of stack](stack.assets/linkedlist_stack_step1.png)
=== "push()"
![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png)
@@ -333,18 +380,18 @@ As shown in the figure below, for the push operation, we simply insert elements
=== "pop()"
![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png)
Below is an example code for implementing a stack based on a linked list:
Below is sample code for implementing a stack based on a linked list:
```src
[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{}
```
### Implementation based on an array
### Array Implementation
When implementing a stack using an array, we can consider the end of the array as the top of the stack. As shown in the figure below, push and pop operations correspond to adding and removing elements at the end of the array, respectively, both with a time complexity of $O(1)$.
When implementing a stack using an array, we can treat the end of the array as the top of the stack. As shown in the figure below, push and pop operations correspond to adding and removing elements at the end of the array, both with a time complexity of $O(1)$.
=== "ArrayStack"
![Implementing Stack with Array for Push and Pop Operations](stack.assets/array_stack_step1.png)
![Push and pop operations in array implementation of stack](stack.assets/array_stack_step1.png)
=== "push()"
![array_stack_push](stack.assets/array_stack_step2_push.png)
@@ -352,38 +399,38 @@ When implementing a stack using an array, we can consider the end of the array a
=== "pop()"
![array_stack_pop](stack.assets/array_stack_step3_pop.png)
Since the elements to be pushed onto the stack may continuously increase, we can use a dynamic array, thus avoiding the need to handle array expansion ourselves. Here is an example code:
Since elements pushed onto the stack may increase continuously, we can use a dynamic array, which eliminates the need to handle array expansion ourselves. Here is the sample code:
```src
[file]{array_stack}-[class]{array_stack}-[func]{}
```
## Comparison of the two implementations
## Comparison of the Two Implementations
**Supported Operations**
Both implementations support all the operations defined in a stack. The array implementation additionally supports random access, but this is beyond the scope of a stack definition and is generally not used.
Both implementations support all operations defined by the stack. The array implementation additionally supports random access, but this goes beyond the stack definition and is generally not used.
**Time Efficiency**
In the array-based implementation, both push and pop operations occur in pre-allocated contiguous memory, which has good cache locality and therefore higher efficiency. However, if the push operation exceeds the array capacity, it triggers a resizing mechanism, making the time complexity of that push operation $O(n)$.
In the array-based implementation, both push and pop operations occur in pre-allocated contiguous memory, which has good cache locality and is therefore more efficient. However, if pushing exceeds the array capacity, it triggers an expansion mechanism, causing the time complexity of that particular push operation to become $O(n)$.
In the linked list implementation, list expansion is very flexible, and there is no efficiency decrease issue as in array expansion. However, the push operation requires initializing a node object and modifying pointers, so its efficiency is relatively lower. If the elements being pushed are already node objects, then the initialization step can be skipped, improving efficiency.
In the linked list-based implementation, list expansion is very flexible, and there is no issue of reduced efficiency due to array expansion. However, the push operation requires initializing a node object and modifying pointers, so it is relatively less efficient. Nevertheless, if the pushed elements are already node objects, the initialization step can be omitted, thereby improving efficiency.
Thus, when the elements for push and pop operations are basic data types like `int` or `double`, we can draw the following conclusions:
In summary, when the elements pushed and popped are basic data types such as `int` or `double`, we can draw the following conclusions:
- The array-based stack implementation's efficiency decreases during expansion, but since expansion is a low-frequency operation, its average efficiency is higher.
- The linked list-based stack implementation provides more stable efficiency performance.
- The array-based stack implementation has reduced efficiency when expansion is triggered, but since expansion is an infrequent operation, the average efficiency is higher.
- The linked list-based stack implementation can provide more stable efficiency performance.
**Space Efficiency**
When initializing a list, the system allocates an "initial capacity," which might exceed the actual need; moreover, the expansion mechanism usually increases capacity by a specific factor (like doubling), which may also exceed the actual need. Therefore, **the array-based stack might waste some space**.
When initializing a list, the system allocates an "initial capacity" that may exceed the actual need. Additionally, the expansion mechanism typically expands at a specific ratio (e.g., 2x), and the capacity after expansion may also exceed actual needs. Therefore, **the array-based stack implementation may cause some space wastage**.
However, since linked list nodes require extra space for storing pointers, **the space occupied by linked list nodes is relatively larger**.
However, since linked list nodes need to store additional pointers, **the space occupied by linked list nodes is relatively large**.
In summary, we cannot simply determine which implementation is more memory-efficient. It requires analysis based on specific circumstances.
In summary, we cannot simply determine which implementation is more memory-efficient and need to analyze the specific situation.
## Typical applications of stack
## Typical Applications of Stack
- **Back and forward in browsers, undo and redo in software**. Every time we open a new webpage, the browser pushes the previous page onto the stack, allowing us to go back to the previous page through the back operation, which is essentially a pop operation. To support both back and forward, two stacks are needed to work together.
- **Memory management in programs**. Each time a function is called, the system adds a stack frame at the top of the stack to record the function's context information. In recursive functions, the downward recursion phase keeps pushing onto the stack, while the upward backtracking phase keeps popping from the stack.
- **Back and forward in browsers, undo and redo in software**. Every time we open a new webpage, the browser pushes the previous page onto the stack, allowing us to return to the previous page via the back operation. The back operation is essentially performing a pop. To support both back and forward, two stacks are needed to work together.
- **Program memory management**. Each time a function is called, the system adds a stack frame to the top of the stack to record the function's context information. During recursion, the downward recursive phase continuously performs push operations, while the upward backtracking phase continuously performs pop operations.

View File

@@ -1,31 +1,31 @@
# Summary
### Key review
### Key Review
- Stack is a data structure that follows the Last-In-First-Out (LIFO) principle and can be implemented using arrays or linked lists.
- In terms of time efficiency, the array implementation of the stack has a higher average efficiency. However, during expansion, the time complexity for a single push operation can degrade to $O(n)$. In contrast, the linked list implementation of a stack offers more stable efficiency.
- Regarding space efficiency, the array implementation of the stack may lead to a certain degree of space wastage. However, it's important to note that the memory space occupied by nodes in a linked list is generally larger than that for elements in an array.
- A queue is a data structure that follows the First-In-First-Out (FIFO) principle, and it can also be implemented using arrays or linked lists. The conclusions regarding time and space efficiency for queues are similar to those for stacks.
- A double-ended queue (deque) is a more flexible type of queue that allows adding and removing elements at both ends.
- A stack is a data structure that follows the LIFO principle and can be implemented using arrays or linked lists.
- In terms of time efficiency, the array implementation of a stack has higher average efficiency, but during expansion, the time complexity of a single push operation degrades to $O(n)$. In contrast, the linked list implementation of a stack provides more stable efficiency performance.
- In terms of space efficiency, the array implementation of a stack may lead to some degree of space wastage. However, it should be noted that the memory space occupied by linked list nodes is larger than that of array elements.
- A queue is a data structure that follows the FIFO principle and can also be implemented using arrays or linked lists. The conclusions regarding time efficiency and space efficiency comparisons for queues are similar to those for stacks mentioned above.
- A deque is a queue with greater flexibility that allows adding and removing elements at both ends.
### Q & A
**Q**: Is the browser's forward and backward functionality implemented with a doubly linked list?
A browser's forward and backward navigation is essentially a manifestation of the "stack" concept. When a user visits a new page, the page is added to the top of the stack; when they click the back button, the page is popped from the top of the stack. A double-ended queue (deque) can conveniently implement some additional operations, as mentioned in the "Double-Ended Queue" section.
The forward and backward functionality of a browser is essentially a manifestation of a "stack." When a user visits a new page, that page is added to the top of the stack; when the user clicks the back button, that page is popped from the top of the stack. Using a deque can conveniently implement some additional operations, as mentioned in the "Deque" section.
**Q**: After popping from a stack, is it necessary to free the memory of the popped node?
**Q**: After popping from the stack, do we need to free the memory of the popped node?
If the popped node will still be used later, it's not necessary to free its memory. In languages like Java and Python that have automatic garbage collection, manual memory release is not necessary; in C and C++, manual memory release is required.
If the popped node will still be needed later, then memory does not need to be freed. If it won't be used afterward, languages like Java and Python have automatic garbage collection, so manual memory deallocation is not required; in C and C++, manual memory deallocation is necessary.
**Q**: A double-ended queue seems like two stacks joined together. What are its uses?
**Q**: A deque seems like two stacks joined together. What is its purpose?
A double-ended queue, which is a combination of a stack and a queue or two stacks joined together, exhibits both stack and queue logic. Thus, it can implement all applications of stacks and queues while offering more flexibility.
A deque is like a combination of a stack and a queue, or two stacks joined together. It exhibits the logic of both stack and queue, so it can implement all applications of stacks and queues, and is more flexible.
**Q**: How exactly are undo and redo implemented?
**Q**: How are undo and redo specifically implemented?
Undo and redo operations are implemented using two stacks: Stack `A` for undo and Stack `B` for redo.
Use two stacks: stack `A` for undo and stack `B` for redo.
1. Each time a user performs an operation, it is pushed onto Stack `A`, and Stack `B` is cleared.
2. When the user executes an "undo", the most recent operation is popped from Stack `A` and pushed onto Stack `B`.
3. When the user executes a "redo", the most recent operation is popped from Stack `B` and pushed back onto Stack `A`.
1. Whenever the user performs an operation, push this operation onto stack `A` and clear stack `B`.
2. When the user performs "undo," pop the most recent operation from stack `A` and push it onto stack `B`.
3. When the user performs "redo," pop the most recent operation from stack `B` and push it onto stack `A`.