translation: Add the initial translation of the chapter of stack and queue (#1033)
* Update the format of Q&As in docs-en * Fix the code comments of JavaScript and TypeScript * Add the initial translation of the chapter of stack and queuepull/1034/head
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 19 KiB |
@ -0,0 +1,399 @@
|
||||
# Double-Ended Queue
|
||||
|
||||
In a regular queue, we can only delete elements from the head or add elements to the tail. As shown in the figure below, a "double-ended queue (deque)" offers more flexibility, allowing the addition or removal of elements at both the head and the tail.
|
||||
|
||||
![Operations in Double-Ended Queue](deque.assets/deque_operations.png)
|
||||
|
||||
## Common Operations in Double-Ended Queue
|
||||
|
||||
The common operations in a double-ended queue are listed below, and the specific method names depend on the programming language used.
|
||||
|
||||
<p align="center"> Table <id> Efficiency of Double-Ended Queue Operations </p>
|
||||
|
||||
| Method Name | Description | Time Complexity |
|
||||
| ------------- | --------------------------- | --------------- |
|
||||
| `pushFirst()` | Add an element to the front | $O(1)$ |
|
||||
| `pushLast()` | Add an element to the rear | $O(1)$ |
|
||||
| `popFirst()` | Remove the front element | $O(1)$ |
|
||||
| `popLast()` | Remove the rear element | $O(1)$ |
|
||||
| `peekFirst()` | Access the front element | $O(1)$ |
|
||||
| `peekLast()` | Access the rear element | $O(1)$ |
|
||||
|
||||
Similarly, we can directly use the double-ended queue classes implemented in programming languages:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title="deque.py"
|
||||
from collections import deque
|
||||
|
||||
# Initialize the deque
|
||||
deque: deque[int] = deque()
|
||||
|
||||
# Enqueue elements
|
||||
deque.append(2) # Add to the rear
|
||||
deque.append(5)
|
||||
deque.append(4)
|
||||
deque.appendleft(3) # Add to the front
|
||||
deque.appendleft(1)
|
||||
|
||||
# Access elements
|
||||
front: int = deque[0] # Front element
|
||||
rear: int = deque[-1] # Rear element
|
||||
|
||||
# Dequeue elements
|
||||
pop_front: int = deque.popleft() # Front element dequeued
|
||||
pop_rear: int = deque.pop() # Rear element dequeued
|
||||
|
||||
# Get the length of the deque
|
||||
size: int = len(deque)
|
||||
|
||||
# Check if the deque is empty
|
||||
is_empty: bool = len(deque) == 0
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title="deque.cpp"
|
||||
/* Initialize the deque */
|
||||
deque<int> deque;
|
||||
|
||||
/* Enqueue elements */
|
||||
deque.push_back(2); // Add to the rear
|
||||
deque.push_back(5);
|
||||
deque.push_back(4);
|
||||
deque.push_front(3); // Add to the front
|
||||
deque.push_front(1);
|
||||
|
||||
/* Access elements */
|
||||
int front = deque.front(); // Front element
|
||||
int back = deque.back(); // Rear element
|
||||
|
||||
/* Dequeue elements */
|
||||
deque.pop_front(); // Front element dequeued
|
||||
deque.pop_back(); // Rear element dequeued
|
||||
|
||||
/* Get the length of the deque */
|
||||
int size = deque.size();
|
||||
|
||||
/* Check if the deque is empty */
|
||||
bool empty = deque.empty();
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="deque.java"
|
||||
/* Initialize the deque */
|
||||
Deque<Integer> deque = new LinkedList<>();
|
||||
|
||||
/* Enqueue elements */
|
||||
deque.offerLast(2); // Add to the rear
|
||||
deque.offerLast(5);
|
||||
deque.offerLast(4);
|
||||
deque.offerFirst(3); // Add to the front
|
||||
deque.offerFirst(1);
|
||||
|
||||
/* Access elements */
|
||||
int peekFirst = deque.peekFirst(); // Front element
|
||||
int peekLast = deque.peekLast(); // Rear element
|
||||
|
||||
/* Dequeue elements */
|
||||
int popFirst = deque.pollFirst(); // Front element dequeued
|
||||
int popLast = deque.pollLast(); // Rear element dequeued
|
||||
|
||||
/* Get the length of the deque */
|
||||
int size = deque.size();
|
||||
|
||||
/* Check if the deque is empty */
|
||||
boolean isEmpty = deque.isEmpty();
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title="deque.cs"
|
||||
/* Initialize the deque */
|
||||
// In C#, LinkedList is used as a deque
|
||||
LinkedList<int> deque = new();
|
||||
|
||||
/* Enqueue elements */
|
||||
deque.AddLast(2); // Add to the rear
|
||||
deque.AddLast(5);
|
||||
deque.AddLast(4);
|
||||
deque.AddFirst(3); // Add to the front
|
||||
deque.AddFirst(1);
|
||||
|
||||
/* Access elements */
|
||||
int peekFirst = deque.First.Value; // Front element
|
||||
int peekLast = deque.Last.Value; // Rear element
|
||||
|
||||
/* Dequeue elements */
|
||||
deque.RemoveFirst(); // Front element dequeued
|
||||
deque.RemoveLast(); // Rear element dequeued
|
||||
|
||||
/* Get the length of the deque */
|
||||
int size = deque.Count;
|
||||
|
||||
/* Check if the deque is empty */
|
||||
bool isEmpty = deque.Count == 0;
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title="deque_test.go"
|
||||
/* Initialize the deque */
|
||||
// In Go, use list as a deque
|
||||
deque := list.New()
|
||||
|
||||
/* Enqueue elements */
|
||||
deque.PushBack(2) // Add to the rear
|
||||
deque.PushBack(5)
|
||||
deque.PushBack(4)
|
||||
deque.PushFront(3) // Add to the front
|
||||
deque.PushFront(1)
|
||||
|
||||
/* Access elements */
|
||||
front := deque.Front() // Front element
|
||||
rear := deque.Back() // Rear element
|
||||
|
||||
/* Dequeue elements */
|
||||
deque.Remove(front) // Front element dequeued
|
||||
deque.Remove(rear) // Rear element dequeued
|
||||
|
||||
/* Get the length of the deque */
|
||||
size := deque.Len()
|
||||
|
||||
/* Check if the 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
|
||||
var deque: [Int] = []
|
||||
|
||||
/* Enqueue elements */
|
||||
deque.append(2) // Add to the rear
|
||||
deque.append(5)
|
||||
deque.append(4)
|
||||
deque.insert(3, at: 0) // Add to the front
|
||||
deque.insert(1, at: 0)
|
||||
|
||||
/* Access elements */
|
||||
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() // Front element dequeued
|
||||
let popLast = deque.removeLast() // Rear element dequeued
|
||||
|
||||
/* Get the length of the deque */
|
||||
let size = deque.count
|
||||
|
||||
/* Check if the 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
|
||||
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
|
||||
deque.unshift(3);
|
||||
deque.unshift(1);
|
||||
|
||||
/* Access elements */
|
||||
const peekFirst = deque[0]; // Front element
|
||||
const peekLast = deque[deque.length - 1]; // Rear element
|
||||
|
||||
/* Dequeue elements */
|
||||
// Note that shift() has a time complexity of O(n) as it's an array
|
||||
const popFront = deque.shift(); // Front element dequeued
|
||||
const popBack = deque.pop(); // Rear element dequeued
|
||||
|
||||
/* Get the length of the deque */
|
||||
const size = deque.length;
|
||||
|
||||
/* Check if the 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
|
||||
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
|
||||
deque.unshift(3);
|
||||
deque.unshift(1);
|
||||
|
||||
/* Access elements */
|
||||
const peekFirst: number = deque[0]; // Front element
|
||||
const peekLast: number = deque[deque.length - 1]; // Rear element
|
||||
|
||||
/* Dequeue elements */
|
||||
// Note that shift() has a time complexity of O(n) as it's an array
|
||||
const popFront: number = deque.shift() as number; // Front element dequeued
|
||||
const popBack: number = deque.pop() as number; // Rear element dequeued
|
||||
|
||||
/* Get the length of the deque */
|
||||
const size: number = deque.length;
|
||||
|
||||
/* Check if the deque is empty */
|
||||
const isEmpty: boolean = size === 0;
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title="deque.dart"
|
||||
/* Initialize the deque */
|
||||
// In Dart, Queue is defined as a deque
|
||||
Queue<int> deque = Queue<int>();
|
||||
|
||||
/* Enqueue elements */
|
||||
deque.addLast(2); // Add to the rear
|
||||
deque.addLast(5);
|
||||
deque.addLast(4);
|
||||
deque.addFirst(3); // Add to the front
|
||||
deque.addFirst(1);
|
||||
|
||||
/* Access elements */
|
||||
int peekFirst = deque.first; // Front element
|
||||
int peekLast = deque.last; // Rear element
|
||||
|
||||
/* Dequeue elements */
|
||||
int popFirst = deque.removeFirst(); // Front element dequeued
|
||||
int popLast = deque.removeLast(); // Rear element dequeued
|
||||
|
||||
/* Get the length of the deque */
|
||||
int size = deque.length;
|
||||
|
||||
/* Check if the deque is empty */
|
||||
bool isEmpty = deque.isEmpty;
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="deque.rs"
|
||||
/* Initialize the deque */
|
||||
let mut deque: VecDeque<u32> = VecDeque::new();
|
||||
|
||||
/* Enqueue elements */
|
||||
deque.push_back(2); // Add to the rear
|
||||
deque.push_back(5);
|
||||
deque.push_back(4);
|
||||
deque.push_front(3); // Add to the front
|
||||
deque.push_front(1);
|
||||
|
||||
/* Access elements */
|
||||
if let Some(front) = deque.front() { // Front element
|
||||
}
|
||||
if let Some(rear) = deque.back() { // Rear element
|
||||
}
|
||||
|
||||
/* Dequeue elements */
|
||||
if let Some(pop_front) = deque.pop_front() { // Front element dequeued
|
||||
}
|
||||
if let Some(pop_rear) = deque.pop_back() { // Rear element dequeued
|
||||
}
|
||||
|
||||
/* Get the length of the deque */
|
||||
let size = deque.len();
|
||||
|
||||
/* Check if the deque is empty */
|
||||
let is_empty = deque.is_empty();
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="deque.c"
|
||||
// C does not provide a built-in deque
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title="deque.zig"
|
||||
|
||||
```
|
||||
|
||||
??? pythontutor "可视化运行"
|
||||
|
||||
<iframe width="800" height="600" frameborder="0" src="https://pythontutor.com/iframe-embed.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&codeDivHeight=370&codeDivWidth=300&cumulative=false&curInstr=3&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe>
|
||||
|
||||
## Implementing a Double-Ended Queue *
|
||||
|
||||
The implementation of a double-ended queue is similar to that of a regular queue, with the choice of either linked lists or arrays as the underlying data structure.
|
||||
|
||||
### Implementation Based on Doubly Linked List
|
||||
|
||||
Recall from the previous section that we used a regular singly linked list to implement a queue, as it conveniently allows for deleting the head node (corresponding to dequeue operation) and adding new nodes after the tail node (corresponding to enqueue operation).
|
||||
|
||||
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 another symmetric direction of operations. For this, we use a "doubly linked list" as the underlying data structure of the double-ended queue.
|
||||
|
||||
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.
|
||||
|
||||
=== "LinkedListDeque"
|
||||
![Implementing Double-Ended Queue with Doubly Linked List for Enqueue and Dequeue Operations](deque.assets/linkedlist_deque.png)
|
||||
|
||||
=== "pushLast()"
|
||||
![linkedlist_deque_push_last](deque.assets/linkedlist_deque_push_last.png)
|
||||
|
||||
=== "pushFirst()"
|
||||
![linkedlist_deque_push_first](deque.assets/linkedlist_deque_push_first.png)
|
||||
|
||||
=== "popLast()"
|
||||
![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_pop_last.png)
|
||||
|
||||
=== "popFirst()"
|
||||
![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_pop_first.png)
|
||||
|
||||
The implementation code is as follows:
|
||||
|
||||
```src
|
||||
[file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{}
|
||||
```
|
||||
|
||||
### Implementation Based on Array
|
||||
|
||||
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.
|
||||
|
||||
=== "ArrayDeque"
|
||||
![Implementing Double-Ended Queue with Array for Enqueue and Dequeue Operations](deque.assets/array_deque.png)
|
||||
|
||||
=== "pushLast()"
|
||||
![array_deque_push_last](deque.assets/array_deque_push_last.png)
|
||||
|
||||
=== "pushFirst()"
|
||||
![array_deque_push_first](deque.assets/array_deque_push_first.png)
|
||||
|
||||
=== "popLast()"
|
||||
![array_deque_pop_last](deque.assets/array_deque_pop_last.png)
|
||||
|
||||
=== "popFirst()"
|
||||
![array_deque_pop_first](deque.assets/array_deque_pop_first.png)
|
||||
|
||||
The implementation only needs to add methods for "front enqueue" and "rear dequeue":
|
||||
|
||||
```src
|
||||
[file]{array_deque}-[func]{}
|
||||
```
|
||||
|
||||
## Applications of Double-Ended Queue
|
||||
|
||||
The double-ended queue combines the logic of both stacks and queues, **thus it can implement all the application scenarios of these two, while offering greater flexibility**.
|
||||
|
||||
We know that the "undo" feature in software 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 length of the stack 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, which is 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.
|
@ -0,0 +1,13 @@
|
||||
# Stack and Queue
|
||||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![Stack and Queue](../assets/covers/chapter_stack_and_queue.jpg)
|
||||
|
||||
</div>
|
||||
|
||||
!!! abstract
|
||||
|
||||
Stacks are like stacking cats, while queues are like cats lining up.
|
||||
|
||||
They respectively represent the logical relationships of Last-In-First-Out (LIFO) and First-In-First-Out (FIFO).
|
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1,375 @@
|
||||
# Queue
|
||||
|
||||
"Queue" 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 back of the queue, and 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 tail of the queue is termed "enqueue," and the operation of removing elements from the head is termed "dequeue."
|
||||
|
||||
![Queue's First-In-First-Out Rule](queue.assets/queue_operations.png)
|
||||
|
||||
## Common Operations on Queue
|
||||
|
||||
The common operations on a queue are shown in the table below. Note that method names may vary across different programming languages. Here, we adopt the same naming convention as used for stacks.
|
||||
|
||||
<p align="center"> Table <id> 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)$ |
|
||||
|
||||
We can directly use the ready-made queue classes in programming languages:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title="queue.py"
|
||||
from collections import deque
|
||||
|
||||
# Initialize the 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
|
||||
que: deque[int] = deque()
|
||||
|
||||
# Enqueue elements
|
||||
que.append(1)
|
||||
que.append(3)
|
||||
que.append(2)
|
||||
que.append(5)
|
||||
que.append(4)
|
||||
|
||||
# Access the front element
|
||||
front: int = que[0]
|
||||
|
||||
# Dequeue an element
|
||||
pop: int = que.popleft()
|
||||
|
||||
# Get the length of the queue
|
||||
size: int = len(que)
|
||||
|
||||
# Check if the queue is empty
|
||||
is_empty: bool = len(que) == 0
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title="queue.cpp"
|
||||
/* Initialize the queue */
|
||||
queue<int> queue;
|
||||
|
||||
/* Enqueue elements */
|
||||
queue.push(1);
|
||||
queue.push(3);
|
||||
queue.push(2);
|
||||
queue.push(5);
|
||||
queue.push(4);
|
||||
|
||||
/* Access the front element */
|
||||
int front = queue.front();
|
||||
|
||||
/* Dequeue an element */
|
||||
queue.pop();
|
||||
|
||||
/* Get the length of the queue */
|
||||
int size = queue.size();
|
||||
|
||||
/* Check if the queue is empty */
|
||||
bool empty = queue.empty();
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="queue.java"
|
||||
/* Initialize the queue */
|
||||
Queue<Integer> queue = new LinkedList<>();
|
||||
|
||||
/* Enqueue elements */
|
||||
queue.offer(1);
|
||||
queue.offer(3);
|
||||
queue.offer(2);
|
||||
queue.offer(5);
|
||||
queue.offer(4);
|
||||
|
||||
/* Access the front element */
|
||||
int peek = queue.peek();
|
||||
|
||||
/* Dequeue an element */
|
||||
int pop = queue.poll();
|
||||
|
||||
/* Get the length of the queue */
|
||||
int size = queue.size();
|
||||
|
||||
/* Check if the queue is empty */
|
||||
boolean isEmpty = queue.isEmpty();
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title="queue.cs"
|
||||
/* Initialize the queue */
|
||||
Queue<int> queue = new();
|
||||
|
||||
/* Enqueue elements */
|
||||
queue.Enqueue(1);
|
||||
queue.Enqueue(3);
|
||||
queue.Enqueue(2);
|
||||
queue.Enqueue(5);
|
||||
queue.Enqueue(4);
|
||||
|
||||
/* Access the front element */
|
||||
int peek = queue.Peek();
|
||||
|
||||
/* Dequeue an element */
|
||||
int pop = queue.Dequeue();
|
||||
|
||||
/* Get the length of the queue */
|
||||
int size = queue.Count;
|
||||
|
||||
/* Check if the queue is empty */
|
||||
bool isEmpty = queue.Count == 0;
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title="queue_test.go"
|
||||
/* Initialize the queue */
|
||||
// In Go, use list as a queue
|
||||
queue := list.New()
|
||||
|
||||
/* Enqueue elements */
|
||||
queue.PushBack(1)
|
||||
queue.PushBack(3)
|
||||
queue.PushBack(2)
|
||||
queue.PushBack(5)
|
||||
queue.PushBack(4)
|
||||
|
||||
/* Access the front element */
|
||||
peek := queue.Front()
|
||||
|
||||
/* Dequeue an element */
|
||||
pop := queue.Front()
|
||||
queue.Remove(pop)
|
||||
|
||||
/* Get the length of the queue */
|
||||
size := queue.Len()
|
||||
|
||||
/* Check if the 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
|
||||
var queue: [Int] = []
|
||||
|
||||
/* Enqueue elements */
|
||||
queue.append(1)
|
||||
queue.append(3)
|
||||
queue.append(2)
|
||||
queue.append(5)
|
||||
queue.append(4)
|
||||
|
||||
/* Access the front element */
|
||||
let peek = queue.first!
|
||||
|
||||
/* Dequeue an element */
|
||||
// Since it's an array, removeFirst has a complexity of O(n)
|
||||
let pool = queue.removeFirst()
|
||||
|
||||
/* Get the length of the queue */
|
||||
let size = queue.count
|
||||
|
||||
/* Check if the 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
|
||||
const queue = [];
|
||||
|
||||
/* Enqueue elements */
|
||||
queue.push(1);
|
||||
queue.push(3);
|
||||
queue.push(2);
|
||||
queue.push(5);
|
||||
queue.push(4);
|
||||
|
||||
/* Access the 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)
|
||||
const pop = queue.shift();
|
||||
|
||||
/* Get the length of the queue */
|
||||
const size = queue.length;
|
||||
|
||||
/* Check if the 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
|
||||
const queue: number[] = [];
|
||||
|
||||
/* Enqueue elements */
|
||||
queue.push(1);
|
||||
queue.push(3);
|
||||
queue.push(2);
|
||||
queue.push(5);
|
||||
queue.push(4);
|
||||
|
||||
/* Access the 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)
|
||||
const pop = queue.shift();
|
||||
|
||||
/* Get the length of the queue */
|
||||
const size = queue.length;
|
||||
|
||||
/* Check if the 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
|
||||
Queue<int> queue = Queue();
|
||||
|
||||
/* Enqueue elements */
|
||||
queue.add(1);
|
||||
queue.add(3);
|
||||
queue.add(2);
|
||||
queue.add(5);
|
||||
queue.add(4);
|
||||
|
||||
/* Access the front element */
|
||||
int peek = queue.first;
|
||||
|
||||
/* Dequeue an element */
|
||||
int pop = queue.removeFirst();
|
||||
|
||||
/* Get the length of the queue */
|
||||
int size = queue.length;
|
||||
|
||||
/* Check if the 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
|
||||
let mut deque: VecDeque<u32> = VecDeque::new();
|
||||
|
||||
/* Enqueue elements */
|
||||
deque.push_back(1);
|
||||
deque.push_back(3);
|
||||
deque.push_back(2);
|
||||
deque.push_back(5);
|
||||
deque.push_back(4);
|
||||
|
||||
/* Access the front element */
|
||||
if let Some(front) = deque.front() {
|
||||
}
|
||||
|
||||
/* Dequeue an element */
|
||||
if let Some(pop) = deque.pop_front() {
|
||||
}
|
||||
|
||||
/* Get the length of the queue */
|
||||
let size = deque.len();
|
||||
|
||||
/* Check if the queue is empty */
|
||||
let is_empty = deque.is_empty();
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="queue.c"
|
||||
// C does not provide a built-in queue
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title="queue.zig"
|
||||
|
||||
```
|
||||
|
||||
??? pythontutor "可视化运行"
|
||||
|
||||
<iframe width="800" height="600" frameborder="0" src="https://pythontutor.com/iframe-embed.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&codeDivHeight=370&codeDivWidth=300&cumulative=false&curInstr=3&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe>
|
||||
|
||||
## Implementing a Queue
|
||||
|
||||
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.
|
||||
|
||||
### Implementation Based on Linked List
|
||||
|
||||
As shown in the figure below, we can consider the "head node" and "tail node" of a linked list as the "head" and "tail" of the queue, respectively. We restrict the operations so that nodes can only be added at the tail and removed at the head.
|
||||
|
||||
=== "LinkedListQueue"
|
||||
![Implementing Queue with Linked List for Enqueue and Dequeue Operations](queue.assets/linkedlist_queue.png)
|
||||
|
||||
=== "push()"
|
||||
![linkedlist_queue_push](queue.assets/linkedlist_queue_push.png)
|
||||
|
||||
=== "pop()"
|
||||
![linkedlist_queue_pop](queue.assets/linkedlist_queue_pop.png)
|
||||
|
||||
Below is the code for implementing a queue using a linked list:
|
||||
|
||||
```src
|
||||
[file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{}
|
||||
```
|
||||
|
||||
### Implementation Based on Array
|
||||
|
||||
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.
|
||||
|
||||
We can use a variable `front` to point to the index of the head element and maintain a `size` variable to record the length of the queue. Define `rear = front + size`, which points to the position right after the tail 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.
|
||||
|
||||
- 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)$.
|
||||
|
||||
=== "ArrayQueue"
|
||||
![Implementing Queue with Array for Enqueue and Dequeue Operations](queue.assets/array_queue.png)
|
||||
|
||||
=== "push()"
|
||||
![array_queue_push](queue.assets/array_queue_push.png)
|
||||
|
||||
=== "pop()"
|
||||
![array_queue_pop](queue.assets/array_queue_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 issue, we can treat the array as a "circular array."
|
||||
|
||||
For 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:
|
||||
|
||||
```src
|
||||
[file]{array_queue}-[class]{array_queue}-[func]{}
|
||||
```
|
||||
|
||||
The above implementation of the queue still has limitations: its length is fixed. However, this issue is not difficult to resolve. 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.
|
||||
|
||||
## 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.
|
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,383 @@
|
||||
# Stack
|
||||
|
||||
"Stack" is a linear data structure that follows the principle of Last-In-First-Out (LIFO).
|
||||
|
||||
We can compare a stack to a pile of plates on a table. To access the bottom plate, one must remove the plates on top. If we replace the plates with various types of elements (such as integers, characters, objects, etc.), we obtain the data structure known as a stack.
|
||||
|
||||
As shown in the following figure, 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."
|
||||
|
||||
![Stack's Last-In-First-Out Rule](stack.assets/stack_operations.png)
|
||||
|
||||
## Common Operations on Stack
|
||||
|
||||
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.
|
||||
|
||||
<p align="center"> Table <id> 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)$ |
|
||||
|
||||
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.
|
||||
|
||||
=== "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
|
||||
stack: list[int] = []
|
||||
|
||||
# Push elements onto the stack
|
||||
stack.append(1)
|
||||
stack.append(3)
|
||||
stack.append(2)
|
||||
stack.append(5)
|
||||
stack.append(4)
|
||||
|
||||
# Access the top element of the stack
|
||||
peek: int = stack[-1]
|
||||
|
||||
# Pop an element from the stack
|
||||
pop: int = stack.pop()
|
||||
|
||||
# Get the length of the stack
|
||||
size: int = len(stack)
|
||||
|
||||
# Check if the stack is empty
|
||||
is_empty: bool = len(stack) == 0
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title="stack.cpp"
|
||||
/* Initialize the stack */
|
||||
stack<int> stack;
|
||||
|
||||
/* Push elements onto the stack */
|
||||
stack.push(1);
|
||||
stack.push(3);
|
||||
stack.push(2);
|
||||
stack.push(5);
|
||||
stack.push(4);
|
||||
|
||||
/* Access the top element of the stack */
|
||||
int top = stack.top();
|
||||
|
||||
/* Pop an element from the stack */
|
||||
stack.pop(); // No return value
|
||||
|
||||
/* Get the length of the stack */
|
||||
int size = stack.size();
|
||||
|
||||
/* Check if the stack is empty */
|
||||
bool empty = stack.empty();
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="stack.java"
|
||||
/* Initialize the stack */
|
||||
Stack<Integer> stack = new Stack<>();
|
||||
|
||||
/* Push elements onto the stack */
|
||||
stack.push(1);
|
||||
stack.push(3);
|
||||
stack.push(2);
|
||||
stack.push(5);
|
||||
stack.push(4);
|
||||
|
||||
/* Access the top element of the stack */
|
||||
int peek = stack.peek();
|
||||
|
||||
/* Pop an element from the stack */
|
||||
int pop = stack.pop();
|
||||
|
||||
/* Get the length of the stack */
|
||||
int size = stack.size();
|
||||
|
||||
/* Check if the stack is empty */
|
||||
boolean isEmpty = stack.isEmpty();
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title="stack.cs"
|
||||
/* Initialize the stack */
|
||||
Stack<int> stack = new();
|
||||
|
||||
/* Push elements onto the stack */
|
||||
stack.Push(1);
|
||||
stack.Push(3);
|
||||
stack.Push(2);
|
||||
stack.Push(5);
|
||||
stack.Push(4);
|
||||
|
||||
/* Access the top element of the stack */
|
||||
int peek = stack.Peek();
|
||||
|
||||
/* Pop an element from the stack */
|
||||
int pop = stack.Pop();
|
||||
|
||||
/* Get the length of the stack */
|
||||
int size = stack.Count;
|
||||
|
||||
/* Check if the stack is 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
|
||||
var stack []int
|
||||
|
||||
/* Push elements onto the stack */
|
||||
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 */
|
||||
peek := stack[len(stack)-1]
|
||||
|
||||
/* Pop an element from the stack */
|
||||
pop := stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
|
||||
/* Get the length of the stack */
|
||||
size := len(stack)
|
||||
|
||||
/* Check if the stack is 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
|
||||
var stack: [Int] = []
|
||||
|
||||
/* Push elements onto the stack */
|
||||
stack.append(1)
|
||||
stack.append(3)
|
||||
stack.append(2)
|
||||
stack.append(5)
|
||||
stack.append(4)
|
||||
|
||||
/* Access the top element of the stack */
|
||||
let peek = stack.last!
|
||||
|
||||
/* Pop an element from the stack */
|
||||
let pop = stack.removeLast()
|
||||
|
||||
/* Get the length of the stack */
|
||||
let size = stack.count
|
||||
|
||||
/* Check if the stack is 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
|
||||
const stack = [];
|
||||
|
||||
/* Push elements onto the stack */
|
||||
stack.push(1);
|
||||
stack.push(3);
|
||||
stack.push(2);
|
||||
stack.push(5);
|
||||
stack.push(4);
|
||||
|
||||
/* Access the top element of the stack */
|
||||
const peek = stack[stack.length-1];
|
||||
|
||||
/* Pop an element from the stack */
|
||||
const pop = stack.pop();
|
||||
|
||||
/* Get the length of the stack */
|
||||
const size = stack.length;
|
||||
|
||||
/* Check if the stack is 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
|
||||
const stack: number[] = [];
|
||||
|
||||
/* Push elements onto the stack */
|
||||
stack.push(1);
|
||||
stack.push(3);
|
||||
stack.push(2);
|
||||
stack.push(5);
|
||||
stack.push(4);
|
||||
|
||||
/* Access the top element of the stack */
|
||||
const peek = stack[stack.length - 1];
|
||||
|
||||
/* Pop an element from the stack */
|
||||
const pop = stack.pop();
|
||||
|
||||
/* Get the length of the stack */
|
||||
const size = stack.length;
|
||||
|
||||
/* Check if the stack is 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
|
||||
List<int> stack = [];
|
||||
|
||||
/* Push elements onto the stack */
|
||||
stack.add(1);
|
||||
stack.add(3);
|
||||
stack.add(2);
|
||||
stack.add(5);
|
||||
stack.add(4);
|
||||
|
||||
/* Access the top element of the stack */
|
||||
int peek = stack.last;
|
||||
|
||||
/* Pop an element from the stack */
|
||||
int pop = stack.removeLast();
|
||||
|
||||
/* Get the length of the stack */
|
||||
int size = stack.length;
|
||||
|
||||
/* Check if the stack is empty */
|
||||
bool isEmpty = stack.isEmpty;
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="stack.rs"
|
||||
/* Initialize the stack */
|
||||
// Use Vec as a stack
|
||||
let mut stack: Vec<i32> = Vec::new();
|
||||
|
||||
/* Push elements onto the stack */
|
||||
stack.push(1);
|
||||
stack.push(3);
|
||||
stack.push(2);
|
||||
stack.push(5);
|
||||
stack.push(4);
|
||||
|
||||
/* Access the top element of the stack */
|
||||
let top = stack.last().unwrap();
|
||||
|
||||
/* Pop an element from the stack */
|
||||
let pop = stack.pop().unwrap();
|
||||
|
||||
/* Get the length of the stack */
|
||||
let size = stack.len();
|
||||
|
||||
/* Check if the stack is empty */
|
||||
let is_empty = stack.is_empty();
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="stack.c"
|
||||
// C does not provide a built-in stack
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
```zig title="stack.zig"
|
||||
|
||||
```
|
||||
|
||||
??? pythontutor "可视化运行"
|
||||
|
||||
<iframe width="800" height="600" frameborder="0" src="https://pythontutor.com/iframe-embed.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&codeDivHeight=370&codeDivWidth=300&cumulative=false&curInstr=2&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe>
|
||||
|
||||
## Implementing a Stack
|
||||
|
||||
To understand the mechanics of a stack more deeply, 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 "mask" some unrelated operations of arrays or linked lists to make their logic conform to the characteristics of a stack.
|
||||
|
||||
### Implementation Based on Linked List
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
=== "LinkedListStack"
|
||||
![Implementing Stack with Linked List for Push and Pop Operations](stack.assets/linkedlist_stack.png)
|
||||
|
||||
=== "push()"
|
||||
![linkedlist_stack_push](stack.assets/linkedlist_stack_push.png)
|
||||
|
||||
=== "pop()"
|
||||
![linkedlist_stack_pop](stack.assets/linkedlist_stack_pop.png)
|
||||
|
||||
Below is an example code for implementing a stack based on a linked list:
|
||||
|
||||
```src
|
||||
[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{}
|
||||
```
|
||||
|
||||
### Implementation Based on Array
|
||||
|
||||
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)$.
|
||||
|
||||
=== "ArrayStack"
|
||||
![Implementing Stack with Array for Push and Pop Operations](stack.assets/array_stack.png)
|
||||
|
||||
=== "push()"
|
||||
![array_stack_push](stack.assets/array_stack_push.png)
|
||||
|
||||
=== "pop()"
|
||||
![array_stack_pop](stack.assets/array_stack_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:
|
||||
|
||||
```src
|
||||
[file]{array_stack}-[class]{array_stack}-[func]{}
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
**Time Efficiency**
|
||||
|
||||
In the array-based implementation, both push and pop operations occur in pre-allocated continuous 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 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.
|
||||
|
||||
Thus, when the elements for push and pop operations are basic data types like `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.
|
||||
|
||||
**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**.
|
||||
|
||||
However, since linked list nodes require extra space for storing pointers, **the space occupied by linked list nodes is relatively larger**.
|
||||
|
||||
In summary, we cannot simply determine which implementation is more memory-efficient. It requires analysis based on specific circumstances.
|
||||
|
||||
## 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.
|
@ -0,0 +1,31 @@
|
||||
# Summary
|
||||
|
||||
### Key Review
|
||||
|
||||
- A stack is a data structure that follows the Last-In-First-Out (LIFO) principle and can be implemented using either 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 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 a stack may lead to some level 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 either arrays or linked lists. The conclusions regarding time and space efficiency for queues are similar to those for stacks.
|
||||
- A double-ended queue is a more flexible type of queue that allows adding and removing elements from both ends.
|
||||
|
||||
### Q & A
|
||||
|
||||
**Q**: Is the browser's forward and backward functionality implemented with a doubly linked list?
|
||||
|
||||
The forward and backward functionality of a browser fundamentally represents the "stack" concept. When a user visits a new page, it is added to the top of the stack; when they click the back button, the page is popped from the top. A double-ended queue can conveniently implement some additional operations, as mentioned in the "Double-Ended Queue" section.
|
||||
|
||||
**Q**: After popping from a stack, is it necessary 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 isn't required; in C and C++, manual memory release is necessary if the node will no longer be used.
|
||||
|
||||
**Q**: A double-ended queue seems like two stacks joined together. What are its uses?
|
||||
|
||||
A double-ended queue is essentially a combination of a stack and a queue, or like two stacks joined together. It exhibits both stack and queue logic, therefore enabling the implementation of all applications of stacks and queues with added flexibility.
|
||||
|
||||
**Q**: How exactly are undo and redo implemented?
|
||||
|
||||
Undo and redo are implemented using 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.
|