From 4037b872f430e3fdb20cb948c7cf80272331ba2f Mon Sep 17 00:00:00 2001 From: krahets Date: Tue, 9 Jan 2024 16:00:24 +0800 Subject: [PATCH] build --- docs-en/chapter_array_and_linkedlist/array.md | 24 +- .../linked_list.md | 16 +- docs-en/chapter_array_and_linkedlist/list.md | 4 +- .../iteration_and_recursion.md | 32 +- .../space_complexity.md | 24 +- .../time_complexity.md | 48 +- docs-en/chapter_stack_and_queue/deque.md | 1679 ++++++++++++++- docs-en/chapter_stack_and_queue/index.md | 16 +- docs-en/chapter_stack_and_queue/queue.md | 1821 ++++++++++++++++- docs-en/chapter_stack_and_queue/stack.md | 1401 ++++++++++++- docs-en/chapter_stack_and_queue/summary.md | 10 +- docs/chapter_array_and_linkedlist/array.md | 28 +- .../linked_list.md | 20 +- docs/chapter_array_and_linkedlist/list.md | 28 +- .../backtracking_algorithm.md | 16 +- docs/chapter_backtracking/n_queens_problem.md | 4 +- .../permutations_problem.md | 8 +- .../subset_sum_problem.md | 12 +- .../iteration_and_recursion.md | 32 +- .../space_complexity.md | 24 +- .../time_complexity.md | 48 +- .../basic_data_types.md | 4 +- .../binary_search_recur.md | 4 +- .../build_binary_tree_problem.md | 4 +- .../hanota_problem.md | 4 +- .../dp_problem_features.md | 12 +- .../dp_solution_pipeline.md | 16 +- .../edit_distance_problem.md | 8 +- .../intro_to_dynamic_programming.md | 20 +- .../knapsack_problem.md | 16 +- .../unbounded_knapsack_problem.md | 24 +- docs/chapter_graph/graph_operations.md | 8 +- docs/chapter_graph/graph_traversal.md | 8 +- .../fractional_knapsack_problem.md | 4 +- docs/chapter_greedy/greedy_algorithm.md | 4 +- docs/chapter_greedy/max_capacity_problem.md | 4 +- .../max_product_cutting_problem.md | 4 +- docs/chapter_hashing/hash_algorithm.md | 8 +- docs/chapter_hashing/hash_collision.md | 4 +- docs/chapter_hashing/hash_map.md | 12 +- docs/chapter_heap/build_heap.md | 4 +- docs/chapter_heap/heap.md | 16 +- docs/chapter_heap/top_k.md | 4 +- docs/chapter_searching/binary_search.md | 8 +- docs/chapter_searching/binary_search_edge.md | 8 +- .../binary_search_insertion.md | 8 +- .../replace_linear_by_hashing.md | 8 +- docs/chapter_sorting/bubble_sort.md | 8 +- docs/chapter_sorting/bucket_sort.md | 4 +- docs/chapter_sorting/counting_sort.md | 8 +- docs/chapter_sorting/heap_sort.md | 4 +- docs/chapter_sorting/insertion_sort.md | 4 +- docs/chapter_sorting/merge_sort.md | 4 +- docs/chapter_sorting/quick_sort.md | 16 +- docs/chapter_sorting/radix_sort.md | 4 +- docs/chapter_sorting/selection_sort.md | 4 +- docs/chapter_stack_and_queue/deque.md | 4 +- docs/chapter_stack_and_queue/queue.md | 12 +- docs/chapter_stack_and_queue/stack.md | 12 +- .../array_representation_of_tree.md | 4 +- docs/chapter_tree/binary_search_tree.md | 12 +- docs/chapter_tree/binary_tree.md | 8 +- docs/chapter_tree/binary_tree_traversal.md | 8 +- 63 files changed, 5195 insertions(+), 440 deletions(-) diff --git a/docs-en/chapter_array_and_linkedlist/array.md b/docs-en/chapter_array_and_linkedlist/array.md index 98bc1c4cf..b080c0d86 100755 --- a/docs-en/chapter_array_and_linkedlist/array.md +++ b/docs-en/chapter_array_and_linkedlist/array.md @@ -289,8 +289,8 @@ Accessing elements in an array is highly efficient, allowing us to randomly acce ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
### 3.   Inserting Elements @@ -471,8 +471,8 @@ It's important to note that due to the fixed length of an array, inserting an el ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
### 4.   Deleting Elements @@ -630,8 +630,8 @@ Please note that after deletion, the former last element becomes "meaningless," ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
In summary, the insertion and deletion operations in arrays present the following disadvantages: @@ -854,8 +854,8 @@ In most programming languages, we can traverse an array either by using indices ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
### 6.   Finding Elements @@ -1022,8 +1022,8 @@ Because arrays are linear data structures, this operation is commonly referred t ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
### 7.   Expanding Arrays @@ -1232,8 +1232,8 @@ To expand an array, it's necessary to create a larger array and then copy the e ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
## 4.1.2   Advantages and Limitations of Arrays diff --git a/docs-en/chapter_array_and_linkedlist/linked_list.md b/docs-en/chapter_array_and_linkedlist/linked_list.md index 93ed7d037..49f861d0e 100755 --- a/docs-en/chapter_array_and_linkedlist/linked_list.md +++ b/docs-en/chapter_array_and_linkedlist/linked_list.md @@ -542,8 +542,8 @@ In contrast, the time complexity of inserting an element in an array is $O(n)$, ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
### 3.   Deleting a Node @@ -732,8 +732,8 @@ Note that although node `P` still points to `n1` after the deletion operation is ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
### 4.   Accessing Nodes @@ -911,8 +911,8 @@ Note that although node `P` still points to `n1` after the deletion operation is ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
### 5.   Finding Nodes @@ -1113,8 +1113,8 @@ Traverse the linked list to find a node with a value equal to `target`, and outp ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
## 4.2.2   Arrays vs. Linked Lists diff --git a/docs-en/chapter_array_and_linkedlist/list.md b/docs-en/chapter_array_and_linkedlist/list.md index 93a720683..c576eeb2d 100755 --- a/docs-en/chapter_array_and_linkedlist/list.md +++ b/docs-en/chapter_array_and_linkedlist/list.md @@ -2121,5 +2121,5 @@ To deepen the understanding of how lists work, let's try implementing a simple v ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
diff --git a/docs-en/chapter_computational_complexity/iteration_and_recursion.md b/docs-en/chapter_computational_complexity/iteration_and_recursion.md index 3f8eddba7..783d94ad1 100644 --- a/docs-en/chapter_computational_complexity/iteration_and_recursion.md +++ b/docs-en/chapter_computational_complexity/iteration_and_recursion.md @@ -184,8 +184,8 @@ The following function implements the sum $1 + 2 + \dots + n$ using a `for` loop ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
The flowchart below represents this sum function. @@ -395,8 +395,8 @@ Below we use a `while` loop to implement the sum $1 + 2 + \dots + n$: ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
**The `while` loop is more flexible than the `for` loop**. In a `while` loop, we can freely design the initialization and update steps of the condition variable. @@ -619,8 +619,8 @@ For example, in the following code, the condition variable $i$ is updated twice ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
Overall, **`for` loops are more concise, while `while` loops are more flexible**. Both can implement iterative structures. Which one to use should be determined based on the specific requirements of the problem. @@ -838,8 +838,8 @@ We can nest one loop structure within another. Below is an example using `for` l ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
The flowchart below represents this nested loop. @@ -1048,8 +1048,8 @@ Observe the following code, where calling the function `recur(n)` completes the ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
The Figure 2-3 shows the recursive process of this function. @@ -1249,8 +1249,8 @@ For example, in calculating $1 + 2 + \dots + n$, we can make the result variable ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
The execution process of tail recursion is shown in the following figure. Comparing regular recursion and tail recursion, the point of the summation operation is different. @@ -1462,8 +1462,8 @@ Using the recursive relation, and considering the first two numbers as terminati ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
Observing the above code, we see that it recursively calls two functions within itself, **meaning that one call generates two branching calls**. As illustrated below, this continuous recursive calling eventually creates a "recursion tree" with a depth of $n$. @@ -1785,8 +1785,8 @@ Therefore, **we can use an explicit stack to simulate the behavior of the call s ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
Observing the above code, when recursion is transformed into iteration, the code becomes more complex. Although iteration and recursion can often be transformed into each other, it's not always advisable to do so for two reasons: diff --git a/docs-en/chapter_computational_complexity/space_complexity.md b/docs-en/chapter_computational_complexity/space_complexity.md index 19026a6ff..239a27ae1 100644 --- a/docs-en/chapter_computational_complexity/space_complexity.md +++ b/docs-en/chapter_computational_complexity/space_complexity.md @@ -1064,8 +1064,8 @@ Note that memory occupied by initializing variables or calling functions in a lo ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
### 2.   Linear Order $O(n)$ @@ -1333,8 +1333,8 @@ Linear order is common in arrays, linked lists, stacks, queues, etc., where the ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
As shown below, this function's recursive depth is $n$, meaning there are $n$ instances of unreturned `linear_recur()` function, using $O(n)$ size of stack frame space: @@ -1479,8 +1479,8 @@ As shown below, this function's recursive depth is $n$, meaning there are $n$ in ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
![Recursive Function Generating Linear Order Space Complexity](space_complexity.assets/space_complexity_recursive_linear.png){ class="animation-figure" } @@ -1704,8 +1704,8 @@ Quadratic order is common in matrices and graphs, where the number of elements i ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
As shown below, the recursive depth of this function is $n$, and in each recursive call, an array is initialized with lengths $n$, $n-1$, $\dots$, $2$, $1$, averaging $n/2$, thus overall occupying $O(n^2)$ space: @@ -1868,8 +1868,8 @@ As shown below, the recursive depth of this function is $n$, and in each recursi ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
![Recursive Function Generating Quadratic Order Space Complexity](space_complexity.assets/space_complexity_recursive_quadratic.png){ class="animation-figure" } @@ -2046,8 +2046,8 @@ Exponential order is common in binary trees. Observe the below image, a "full bi ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
![Full Binary Tree Generating Exponential Order Space Complexity](space_complexity.assets/space_complexity_exponential.png){ class="animation-figure" } diff --git a/docs-en/chapter_computational_complexity/time_complexity.md b/docs-en/chapter_computational_complexity/time_complexity.md index f60e98f14..0af6f5def 100644 --- a/docs-en/chapter_computational_complexity/time_complexity.md +++ b/docs-en/chapter_computational_complexity/time_complexity.md @@ -1121,8 +1121,8 @@ Constant order means the number of operations is independent of the input data s ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
### 2.   Linear Order $O(n)$ @@ -1278,8 +1278,8 @@ Linear order indicates the number of operations grows linearly with the input da ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
Operations like array traversal and linked list traversal have a time complexity of $O(n)$, where $n$ is the length of the array or list: @@ -1451,8 +1451,8 @@ Operations like array traversal and linked list traversal have a time complexity ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
It's important to note that **the input data size $n$ should be determined based on the type of input data**. For example, in the first example, $n$ represents the input data size, while in the second example, the length of the array $n$ is the data size. @@ -1653,8 +1653,8 @@ Quadratic order means the number of operations grows quadratically with the inpu ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
The following image compares constant order, linear order, and quadratic order time complexities. @@ -1938,8 +1938,8 @@ For instance, in bubble sort, the outer loop runs $n - 1$ times, and the inner l ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
### 4.   Exponential Order $O(2^n)$ @@ -2171,8 +2171,8 @@ The following image and code simulate the cell division process, with a time com ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
![Exponential Order Time Complexity](time_complexity.assets/time_complexity_exponential.png){ class="animation-figure" } @@ -2311,8 +2311,8 @@ In practice, exponential order often appears in recursive functions. For example ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
Exponential order growth is extremely rapid and is commonly seen in exhaustive search methods (brute force, backtracking, etc.). For large-scale problems, exponential order is unacceptable, often requiring dynamic programming or greedy algorithms as solutions. @@ -2493,8 +2493,8 @@ The following image and code simulate the "halving each round" process, with a t ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
![Logarithmic Order Time Complexity](time_complexity.assets/time_complexity_logarithmic.png){ class="animation-figure" } @@ -2633,8 +2633,8 @@ Like exponential order, logarithmic order also frequently appears in recursive f ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
Logarithmic order is typical in algorithms based on the divide-and-conquer strategy, embodying the "split into many" and "simplify complex problems" approach. It's slow-growing and is the most ideal time complexity after constant order. @@ -2831,8 +2831,8 @@ Linear-logarithmic order often appears in nested loops, with the complexities of ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
The image below demonstrates how linear-logarithmic order is generated. Each level of a binary tree has $n$ operations, and the tree has $\log_2 n + 1$ levels, resulting in a time complexity of $O(n \log n)$. @@ -3042,8 +3042,8 @@ Factorials are typically implemented using recursion. As shown in the image and ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
![Factorial Order Time Complexity](time_complexity.assets/time_complexity_factorial.png){ class="animation-figure" } @@ -3409,8 +3409,8 @@ The "worst-case time complexity" corresponds to the asymptotic upper bound, deno ??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
It's important to note that the best-case time complexity is rarely used in practice, as it is usually only achievable under very low probabilities and might be misleading. **The worst-case time complexity is more practical as it provides a safety value for efficiency**, allowing us to confidently use the algorithm. diff --git a/docs-en/chapter_stack_and_queue/deque.md b/docs-en/chapter_stack_and_queue/deque.md index b844db9ad..094ff065e 100644 --- a/docs-en/chapter_stack_and_queue/deque.md +++ b/docs-en/chapter_stack_and_queue/deque.md @@ -1,14 +1,22 @@ -# Double-Ended Queue +--- +comments: true +--- -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. +# 5.3   Double-Ended Queue -![Operations in Double-Ended Queue](deque.assets/deque_operations.png) +In a regular queue, we can only delete elements from the head or add elements to the tail. As shown in the Figure 5-7 , a "double-ended queue (deque)" offers more flexibility, allowing the addition or removal of elements at both the head and the tail. -## Common Operations in Double-Ended Queue +![Operations in Double-Ended Queue](deque.assets/deque_operations.png){ class="animation-figure" } + +

Figure 5-7   Operations in Double-Ended Queue

+ +## 5.3.1   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. -

Table   Efficiency of Double-Ended Queue Operations

+

Table 5-3   Efficiency of Double-Ended Queue Operations

+ +
| Method Name | Description | Time Complexity | | ------------- | --------------------------- | --------------- | @@ -19,6 +27,8 @@ The common operations in a double-ended queue are listed below, and the specific | `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" @@ -330,62 +340,1677 @@ Similarly, we can directly use the double-ended queue classes implemented in pro ``` -??? pythontutor "可视化运行" +??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+
Full Screen >
-## Implementing a Double-Ended Queue * +## 5.3.2   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 +### 1.   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. +As shown in the Figure 5-8 , 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) + ![Implementing Double-Ended Queue with Doubly Linked List for Enqueue and Dequeue Operations](deque.assets/linkedlist_deque.png){ class="animation-figure" } === "pushLast()" - ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_push_last.png) + ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_push_last.png){ class="animation-figure" } === "pushFirst()" - ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_push_first.png) + ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_push_first.png){ class="animation-figure" } === "popLast()" - ![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_pop_last.png) + ![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_pop_last.png){ class="animation-figure" } === "popFirst()" - ![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_pop_first.png) + ![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_pop_first.png){ class="animation-figure" } + +

Figure 5-8   Implementing Double-Ended Queue with Doubly Linked List for Enqueue and Dequeue Operations

The implementation code is as follows: -```src -[file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{} -``` +=== "Python" -### Implementation Based on Array + ```python title="linkedlist_deque.py" + class ListNode: + """双向链表节点""" -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. + def __init__(self, val: int): + """构造方法""" + self.val: int = val + self.next: ListNode | None = None # 后继节点引用 + self.prev: ListNode | None = None # 前驱节点引用 + + class LinkedListDeque: + """基于双向链表实现的双向队列""" + + def __init__(self): + """构造方法""" + self._front: ListNode | None = None # 头节点 front + self._rear: ListNode | None = None # 尾节点 rear + self._size: int = 0 # 双向队列的长度 + + def size(self) -> int: + """获取双向队列的长度""" + return self._size + + def is_empty(self) -> bool: + """判断双向队列是否为空""" + return self.size() == 0 + + def push(self, num: int, is_front: bool): + """入队操作""" + node = ListNode(num) + # 若链表为空,则令 front 和 rear 都指向 node + if self.is_empty(): + self._front = self._rear = node + # 队首入队操作 + elif is_front: + # 将 node 添加至链表头部 + self._front.prev = node + node.next = self._front + self._front = node # 更新头节点 + # 队尾入队操作 + else: + # 将 node 添加至链表尾部 + self._rear.next = node + node.prev = self._rear + self._rear = node # 更新尾节点 + self._size += 1 # 更新队列长度 + + def push_first(self, num: int): + """队首入队""" + self.push(num, True) + + def push_last(self, num: int): + """队尾入队""" + self.push(num, False) + + def pop(self, is_front: bool) -> int: + """出队操作""" + if self.is_empty(): + raise IndexError("双向队列为空") + # 队首出队操作 + if is_front: + val: int = self._front.val # 暂存头节点值 + # 删除头节点 + fnext: ListNode | None = self._front.next + if fnext != None: + fnext.prev = None + self._front.next = None + self._front = fnext # 更新头节点 + # 队尾出队操作 + else: + val: int = self._rear.val # 暂存尾节点值 + # 删除尾节点 + rprev: ListNode | None = self._rear.prev + if rprev != None: + rprev.next = None + self._rear.prev = None + self._rear = rprev # 更新尾节点 + self._size -= 1 # 更新队列长度 + return val + + def pop_first(self) -> int: + """队首出队""" + return self.pop(True) + + def pop_last(self) -> int: + """队尾出队""" + return self.pop(False) + + def peek_first(self) -> int: + """访问队首元素""" + if self.is_empty(): + raise IndexError("双向队列为空") + return self._front.val + + def peek_last(self) -> int: + """访问队尾元素""" + if self.is_empty(): + raise IndexError("双向队列为空") + return self._rear.val + + def to_array(self) -> list[int]: + """返回数组用于打印""" + node = self._front + res = [0] * self.size() + for i in range(self.size()): + res[i] = node.val + node = node.next + return res + ``` + +=== "C++" + + ```cpp title="linkedlist_deque.cpp" + /* 双向链表节点 */ + struct DoublyListNode { + int val; // 节点值 + DoublyListNode *next; // 后继节点指针 + DoublyListNode *prev; // 前驱节点指针 + DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) { + } + }; + + /* 基于双向链表实现的双向队列 */ + class LinkedListDeque { + private: + DoublyListNode *front, *rear; // 头节点 front ,尾节点 rear + int queSize = 0; // 双向队列的长度 + + public: + /* 构造方法 */ + LinkedListDeque() : front(nullptr), rear(nullptr) { + } + + /* 析构方法 */ + ~LinkedListDeque() { + // 遍历链表删除节点,释放内存 + DoublyListNode *pre, *cur = front; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } + } + + /* 获取双向队列的长度 */ + int size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入队操作 */ + void push(int num, bool isFront) { + DoublyListNode *node = new DoublyListNode(num); + // 若链表为空,则令 front 和 rear 都指向 node + if (isEmpty()) + front = rear = node; + // 队首入队操作 + else if (isFront) { + // 将 node 添加至链表头部 + front->prev = node; + node->next = front; + front = node; // 更新头节点 + // 队尾入队操作 + } else { + // 将 node 添加至链表尾部 + rear->next = node; + node->prev = rear; + rear = node; // 更新尾节点 + } + queSize++; // 更新队列长度 + } + + /* 队首入队 */ + void pushFirst(int num) { + push(num, true); + } + + /* 队尾入队 */ + void pushLast(int num) { + push(num, false); + } + + /* 出队操作 */ + int pop(bool isFront) { + if (isEmpty()) + throw out_of_range("队列为空"); + int val; + // 队首出队操作 + if (isFront) { + val = front->val; // 暂存头节点值 + // 删除头节点 + DoublyListNode *fNext = front->next; + if (fNext != nullptr) { + fNext->prev = nullptr; + front->next = nullptr; + delete front; + } + front = fNext; // 更新头节点 + // 队尾出队操作 + } else { + val = rear->val; // 暂存尾节点值 + // 删除尾节点 + DoublyListNode *rPrev = rear->prev; + if (rPrev != nullptr) { + rPrev->next = nullptr; + rear->prev = nullptr; + delete rear; + } + rear = rPrev; // 更新尾节点 + } + queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + int popFirst() { + return pop(true); + } + + /* 队尾出队 */ + int popLast() { + return pop(false); + } + + /* 访问队首元素 */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("双向队列为空"); + return front->val; + } + + /* 访问队尾元素 */ + int peekLast() { + if (isEmpty()) + throw out_of_range("双向队列为空"); + return rear->val; + } + + /* 返回数组用于打印 */ + vector toVector() { + DoublyListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } + }; + ``` + +=== "Java" + + ```java title="linkedlist_deque.java" + /* 双向链表节点 */ + class ListNode { + int val; // 节点值 + ListNode next; // 后继节点引用 + ListNode prev; // 前驱节点引用 + + ListNode(int val) { + this.val = val; + prev = next = null; + } + } + + /* 基于双向链表实现的双向队列 */ + class LinkedListDeque { + private ListNode front, rear; // 头节点 front ,尾节点 rear + private int queSize = 0; // 双向队列的长度 + + public LinkedListDeque() { + front = rear = null; + } + + /* 获取双向队列的长度 */ + public int size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入队操作 */ + private void push(int num, boolean isFront) { + ListNode node = new ListNode(num); + // 若链表为空,则令 front 和 rear 都指向 node + if (isEmpty()) + front = rear = node; + // 队首入队操作 + else if (isFront) { + // 将 node 添加至链表头部 + front.prev = node; + node.next = front; + front = node; // 更新头节点 + // 队尾入队操作 + } else { + // 将 node 添加至链表尾部 + rear.next = node; + node.prev = rear; + rear = node; // 更新尾节点 + } + queSize++; // 更新队列长度 + } + + /* 队首入队 */ + public void pushFirst(int num) { + push(num, true); + } + + /* 队尾入队 */ + public void pushLast(int num) { + push(num, false); + } + + /* 出队操作 */ + private int pop(boolean isFront) { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + int val; + // 队首出队操作 + if (isFront) { + val = front.val; // 暂存头节点值 + // 删除头节点 + ListNode fNext = front.next; + if (fNext != null) { + fNext.prev = null; + front.next = null; + } + front = fNext; // 更新头节点 + // 队尾出队操作 + } else { + val = rear.val; // 暂存尾节点值 + // 删除尾节点 + ListNode rPrev = rear.prev; + if (rPrev != null) { + rPrev.next = null; + rear.prev = null; + } + rear = rPrev; // 更新尾节点 + } + queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + public int popFirst() { + return pop(true); + } + + /* 队尾出队 */ + public int popLast() { + return pop(false); + } + + /* 访问队首元素 */ + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return front.val; + } + + /* 访问队尾元素 */ + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return rear.val; + } + + /* 返回数组用于打印 */ + public int[] toArray() { + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } + } + ``` + +=== "C#" + + ```csharp title="linkedlist_deque.cs" + /* 双向链表节点 */ + class ListNode(int val) { + public int val = val; // 节点值 + public ListNode? next = null; // 后继节点引用 + public ListNode? prev = null; // 前驱节点引用 + } + + /* 基于双向链表实现的双向队列 */ + class LinkedListDeque { + ListNode? front, rear; // 头节点 front, 尾节点 rear + int queSize = 0; // 双向队列的长度 + + public LinkedListDeque() { + front = null; + rear = null; + } + + /* 获取双向队列的长度 */ + public int Size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入队操作 */ + void Push(int num, bool isFront) { + ListNode node = new(num); + // 若链表为空,则令 front 和 rear 都指向 node + if (IsEmpty()) { + front = node; + rear = node; + } + // 队首入队操作 + else if (isFront) { + // 将 node 添加至链表头部 + front!.prev = node; + node.next = front; + front = node; // 更新头节点 + } + // 队尾入队操作 + else { + // 将 node 添加至链表尾部 + rear!.next = node; + node.prev = rear; + rear = node; // 更新尾节点 + } + + queSize++; // 更新队列长度 + } + + /* 队首入队 */ + public void PushFirst(int num) { + Push(num, true); + } + + /* 队尾入队 */ + public void PushLast(int num) { + Push(num, false); + } + + /* 出队操作 */ + int? Pop(bool isFront) { + if (IsEmpty()) + throw new Exception(); + int? val; + // 队首出队操作 + if (isFront) { + val = front?.val; // 暂存头节点值 + // 删除头节点 + ListNode? fNext = front?.next; + if (fNext != null) { + fNext.prev = null; + front!.next = null; + } + front = fNext; // 更新头节点 + } + // 队尾出队操作 + else { + val = rear?.val; // 暂存尾节点值 + // 删除尾节点 + ListNode? rPrev = rear?.prev; + if (rPrev != null) { + rPrev.next = null; + rear!.prev = null; + } + rear = rPrev; // 更新尾节点 + } + + queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + public int? PopFirst() { + return Pop(true); + } + + /* 队尾出队 */ + public int? PopLast() { + return Pop(false); + } + + /* 访问队首元素 */ + public int? PeekFirst() { + if (IsEmpty()) + throw new Exception(); + return front?.val; + } + + /* 访问队尾元素 */ + public int? PeekLast() { + if (IsEmpty()) + throw new Exception(); + return rear?.val; + } + + /* 返回数组用于打印 */ + public int?[] ToArray() { + ListNode? node = front; + int?[] res = new int?[Size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node?.val; + node = node?.next; + } + + return res; + } + } + ``` + +=== "Go" + + ```go title="linkedlist_deque.go" + /* 基于双向链表实现的双向队列 */ + type linkedListDeque struct { + // 使用内置包 list + data *list.List + } + + /* 初始化双端队列 */ + func newLinkedListDeque() *linkedListDeque { + return &linkedListDeque{ + data: list.New(), + } + } + + /* 队首元素入队 */ + func (s *linkedListDeque) pushFirst(value any) { + s.data.PushFront(value) + } + + /* 队尾元素入队 */ + func (s *linkedListDeque) pushLast(value any) { + s.data.PushBack(value) + } + + /* 队首元素出队 */ + func (s *linkedListDeque) popFirst() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + s.data.Remove(e) + return e.Value + } + + /* 队尾元素出队 */ + func (s *linkedListDeque) popLast() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + s.data.Remove(e) + return e.Value + } + + /* 访问队首元素 */ + func (s *linkedListDeque) peekFirst() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + return e.Value + } + + /* 访问队尾元素 */ + func (s *linkedListDeque) peekLast() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + return e.Value + } + + /* 获取队列的长度 */ + func (s *linkedListDeque) size() int { + return s.data.Len() + } + + /* 判断队列是否为空 */ + func (s *linkedListDeque) isEmpty() bool { + return s.data.Len() == 0 + } + + /* 获取 List 用于打印 */ + func (s *linkedListDeque) toList() *list.List { + return s.data + } + ``` + +=== "Swift" + + ```swift title="linkedlist_deque.swift" + /* 双向链表节点 */ + class ListNode { + var val: Int // 节点值 + var next: ListNode? // 后继节点引用 + weak var prev: ListNode? // 前驱节点引用 + + init(val: Int) { + self.val = val + } + } + + /* 基于双向链表实现的双向队列 */ + class LinkedListDeque { + private var front: ListNode? // 头节点 front + private var rear: ListNode? // 尾节点 rear + private var queSize: Int // 双向队列的长度 + + init() { + queSize = 0 + } + + /* 获取双向队列的长度 */ + func size() -> Int { + queSize + } + + /* 判断双向队列是否为空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入队操作 */ + private func push(num: Int, isFront: Bool) { + let node = ListNode(val: num) + // 若链表为空,则令 front 和 rear 都指向 node + if isEmpty() { + front = node + rear = node + } + // 队首入队操作 + else if isFront { + // 将 node 添加至链表头部 + front?.prev = node + node.next = front + front = node // 更新头节点 + } + // 队尾入队操作 + else { + // 将 node 添加至链表尾部 + rear?.next = node + node.prev = rear + rear = node // 更新尾节点 + } + queSize += 1 // 更新队列长度 + } + + /* 队首入队 */ + func pushFirst(num: Int) { + push(num: num, isFront: true) + } + + /* 队尾入队 */ + func pushLast(num: Int) { + push(num: num, isFront: false) + } + + /* 出队操作 */ + private func pop(isFront: Bool) -> Int { + if isEmpty() { + fatalError("双向队列为空") + } + let val: Int + // 队首出队操作 + if isFront { + val = front!.val // 暂存头节点值 + // 删除头节点 + let fNext = front?.next + if fNext != nil { + fNext?.prev = nil + front?.next = nil + } + front = fNext // 更新头节点 + } + // 队尾出队操作 + else { + val = rear!.val // 暂存尾节点值 + // 删除尾节点 + let rPrev = rear?.prev + if rPrev != nil { + rPrev?.next = nil + rear?.prev = nil + } + rear = rPrev // 更新尾节点 + } + queSize -= 1 // 更新队列长度 + return val + } + + /* 队首出队 */ + func popFirst() -> Int { + pop(isFront: true) + } + + /* 队尾出队 */ + func popLast() -> Int { + pop(isFront: false) + } + + /* 访问队首元素 */ + func peekFirst() -> Int? { + isEmpty() ? nil : front?.val + } + + /* 访问队尾元素 */ + func peekLast() -> Int? { + isEmpty() ? nil : rear?.val + } + + /* 返回数组用于打印 */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } + } + ``` + +=== "JS" + + ```javascript title="linkedlist_deque.js" + /* 双向链表节点 */ + class ListNode { + prev; // 前驱节点引用 (指针) + next; // 后继节点引用 (指针) + val; // 节点值 + + constructor(val) { + this.val = val; + this.next = null; + this.prev = null; + } + } + + /* 基于双向链表实现的双向队列 */ + class LinkedListDeque { + #front; // 头节点 front + #rear; // 尾节点 rear + #queSize; // 双向队列的长度 + + constructor() { + this.#front = null; + this.#rear = null; + this.#queSize = 0; + } + + /* 队尾入队操作 */ + pushLast(val) { + const node = new ListNode(val); + // 若链表为空,则令 front 和 rear 都指向 node + if (this.#queSize === 0) { + this.#front = node; + this.#rear = node; + } else { + // 将 node 添加至链表尾部 + this.#rear.next = node; + node.prev = this.#rear; + this.#rear = node; // 更新尾节点 + } + this.#queSize++; + } + + /* 队首入队操作 */ + pushFirst(val) { + const node = new ListNode(val); + // 若链表为空,则令 front 和 rear 都指向 node + if (this.#queSize === 0) { + this.#front = node; + this.#rear = node; + } else { + // 将 node 添加至链表头部 + this.#front.prev = node; + node.next = this.#front; + this.#front = node; // 更新头节点 + } + this.#queSize++; + } + + /* 队尾出队操作 */ + popLast() { + if (this.#queSize === 0) { + return null; + } + const value = this.#rear.val; // 存储尾节点值 + // 删除尾节点 + let temp = this.#rear.prev; + if (temp !== null) { + temp.next = null; + this.#rear.prev = null; + } + this.#rear = temp; // 更新尾节点 + this.#queSize--; + return value; + } + + /* 队首出队操作 */ + popFirst() { + if (this.#queSize === 0) { + return null; + } + const value = this.#front.val; // 存储尾节点值 + // 删除头节点 + let temp = this.#front.next; + if (temp !== null) { + temp.prev = null; + this.#front.next = null; + } + this.#front = temp; // 更新头节点 + this.#queSize--; + return value; + } + + /* 访问队尾元素 */ + peekLast() { + return this.#queSize === 0 ? null : this.#rear.val; + } + + /* 访问队首元素 */ + peekFirst() { + return this.#queSize === 0 ? null : this.#front.val; + } + + /* 获取双向队列的长度 */ + size() { + return this.#queSize; + } + + /* 判断双向队列是否为空 */ + isEmpty() { + return this.#queSize === 0; + } + + /* 打印双向队列 */ + print() { + const arr = []; + let temp = this.#front; + while (temp !== null) { + arr.push(temp.val); + temp = temp.next; + } + console.log('[' + arr.join(', ') + ']'); + } + } + ``` + +=== "TS" + + ```typescript title="linkedlist_deque.ts" + /* 双向链表节点 */ + class ListNode { + prev: ListNode; // 前驱节点引用 (指针) + next: ListNode; // 后继节点引用 (指针) + val: number; // 节点值 + + constructor(val: number) { + this.val = val; + this.next = null; + this.prev = null; + } + } + + /* 基于双向链表实现的双向队列 */ + class LinkedListDeque { + private front: ListNode; // 头节点 front + private rear: ListNode; // 尾节点 rear + private queSize: number; // 双向队列的长度 + + constructor() { + this.front = null; + this.rear = null; + this.queSize = 0; + } + + /* 队尾入队操作 */ + pushLast(val: number): void { + const node: ListNode = new ListNode(val); + // 若链表为空,则令 front 和 rear 都指向 node + if (this.queSize === 0) { + this.front = node; + this.rear = node; + } else { + // 将 node 添加至链表尾部 + this.rear.next = node; + node.prev = this.rear; + this.rear = node; // 更新尾节点 + } + this.queSize++; + } + + /* 队首入队操作 */ + pushFirst(val: number): void { + const node: ListNode = new ListNode(val); + // 若链表为空,则令 front 和 rear 都指向 node + if (this.queSize === 0) { + this.front = node; + this.rear = node; + } else { + // 将 node 添加至链表头部 + this.front.prev = node; + node.next = this.front; + this.front = node; // 更新头节点 + } + this.queSize++; + } + + /* 队尾出队操作 */ + popLast(): number { + if (this.queSize === 0) { + return null; + } + const value: number = this.rear.val; // 存储尾节点值 + // 删除尾节点 + let temp: ListNode = this.rear.prev; + if (temp !== null) { + temp.next = null; + this.rear.prev = null; + } + this.rear = temp; // 更新尾节点 + this.queSize--; + return value; + } + + /* 队首出队操作 */ + popFirst(): number { + if (this.queSize === 0) { + return null; + } + const value: number = this.front.val; // 存储尾节点值 + // 删除头节点 + let temp: ListNode = this.front.next; + if (temp !== null) { + temp.prev = null; + this.front.next = null; + } + this.front = temp; // 更新头节点 + this.queSize--; + return value; + } + + /* 访问队尾元素 */ + peekLast(): number { + return this.queSize === 0 ? null : this.rear.val; + } + + /* 访问队首元素 */ + peekFirst(): number { + return this.queSize === 0 ? null : this.front.val; + } + + /* 获取双向队列的长度 */ + size(): number { + return this.queSize; + } + + /* 判断双向队列是否为空 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* 打印双向队列 */ + print(): void { + const arr: number[] = []; + let temp: ListNode = this.front; + while (temp !== null) { + arr.push(temp.val); + temp = temp.next; + } + console.log('[' + arr.join(', ') + ']'); + } + } + ``` + +=== "Dart" + + ```dart title="linkedlist_deque.dart" + /* 双向链表节点 */ + class ListNode { + int val; // 节点值 + ListNode? next; // 后继节点引用 + ListNode? prev; // 前驱节点引用 + + ListNode(this.val, {this.next, this.prev}); + } + + /* 基于双向链表实现的双向对列 */ + class LinkedListDeque { + late ListNode? _front; // 头节点 _front + late ListNode? _rear; // 尾节点 _rear + int _queSize = 0; // 双向队列的长度 + + LinkedListDeque() { + this._front = null; + this._rear = null; + } + + /* 获取双向队列长度 */ + int size() { + return this._queSize; + } + + /* 判断双向队列是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入队操作 */ + void push(int _num, bool isFront) { + final ListNode node = ListNode(_num); + if (isEmpty()) { + // 若链表为空,则令 _front 和 _rear 都指向 node + _front = _rear = node; + } else if (isFront) { + // 队首入队操作 + // 将 node 添加至链表头部 + _front!.prev = node; + node.next = _front; + _front = node; // 更新头节点 + } else { + // 队尾入队操作 + // 将 node 添加至链表尾部 + _rear!.next = node; + node.prev = _rear; + _rear = node; // 更新尾节点 + } + _queSize++; // 更新队列长度 + } + + /* 队首入队 */ + void pushFirst(int _num) { + push(_num, true); + } + + /* 队尾入队 */ + void pushLast(int _num) { + push(_num, false); + } + + /* 出队操作 */ + int? pop(bool isFront) { + // 若队列为空,直接返回 null + if (isEmpty()) { + return null; + } + final int val; + if (isFront) { + // 队首出队操作 + val = _front!.val; // 暂存头节点值 + // 删除头节点 + ListNode? fNext = _front!.next; + if (fNext != null) { + fNext.prev = null; + _front!.next = null; + } + _front = fNext; // 更新头节点 + } else { + // 队尾出队操作 + val = _rear!.val; // 暂存尾节点值 + // 删除尾节点 + ListNode? rPrev = _rear!.prev; + if (rPrev != null) { + rPrev.next = null; + _rear!.prev = null; + } + _rear = rPrev; // 更新尾节点 + } + _queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + int? popFirst() { + return pop(true); + } + + /* 队尾出队 */ + int? popLast() { + return pop(false); + } + + /* 访问队首元素 */ + int? peekFirst() { + return _front?.val; + } + + /* 访问队尾元素 */ + int? peekLast() { + return _rear?.val; + } + + /* 返回数组用于打印 */ + List toArray() { + ListNode? node = _front; + final List res = []; + for (int i = 0; i < _queSize; i++) { + res.add(node!.val); + node = node.next; + } + return res; + } + } + ``` + +=== "Rust" + + ```rust title="linkedlist_deque.rs" + /* 双向链表节点 */ + pub struct ListNode { + pub val: T, // 节点值 + pub next: Option>>>, // 后继节点指针 + pub prev: Option>>>, // 前驱节点指针 + } + + impl ListNode { + pub fn new(val: T) -> Rc>> { + Rc::new(RefCell::new(ListNode { + val, + next: None, + prev: None, + })) + } + } + + /* 基于双向链表实现的双向队列 */ + #[allow(dead_code)] + pub struct LinkedListDeque { + front: Option>>>, // 头节点 front + rear: Option>>>, // 尾节点 rear + que_size: usize, // 双向队列的长度 + } + + impl LinkedListDeque { + pub fn new() -> Self { + Self { + front: None, + rear: None, + que_size: 0, + } + } + + /* 获取双向队列的长度 */ + pub fn size(&self) -> usize { + return self.que_size; + } + + /* 判断双向队列是否为空 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* 入队操作 */ + pub fn push(&mut self, num: T, is_front: bool) { + let node = ListNode::new(num); + // 队首入队操作 + if is_front { + match self.front.take() { + // 若链表为空,则令 front 和 rear 都指向 node + None => { + self.rear = Some(node.clone()); + self.front = Some(node); + } + // 将 node 添加至链表头部 + Some(old_front) => { + old_front.borrow_mut().prev = Some(node.clone()); + node.borrow_mut().next = Some(old_front); + self.front = Some(node); // 更新头节点 + } + } + } + // 队尾入队操作 + else { + match self.rear.take() { + // 若链表为空,则令 front 和 rear 都指向 node + None => { + self.front = Some(node.clone()); + self.rear = Some(node); + } + // 将 node 添加至链表尾部 + Some(old_rear) => { + old_rear.borrow_mut().next = Some(node.clone()); + node.borrow_mut().prev = Some(old_rear); + self.rear = Some(node); // 更新尾节点 + } + } + } + self.que_size += 1; // 更新队列长度 + } + + /* 队首入队 */ + pub fn push_first(&mut self, num: T) { + self.push(num, true); + } + + /* 队尾入队 */ + pub fn push_last(&mut self, num: T) { + self.push(num, false); + } + + /* 出队操作 */ + pub fn pop(&mut self, is_front: bool) -> Option { + // 若队列为空,直接返回 None + if self.is_empty() { + return None + }; + // 队首出队操作 + if is_front { + self.front.take().map(|old_front| { + match old_front.borrow_mut().next.take() { + Some(new_front) => { + new_front.borrow_mut().prev.take(); + self.front = Some(new_front); // 更新头节点 + } + None => { + self.rear.take(); + } + } + self.que_size -= 1; // 更新队列长度 + Rc::try_unwrap(old_front).ok().unwrap().into_inner().val + }) + + } + // 队尾出队操作 + else { + self.rear.take().map(|old_rear| { + match old_rear.borrow_mut().prev.take() { + Some(new_rear) => { + new_rear.borrow_mut().next.take(); + self.rear = Some(new_rear); // 更新尾节点 + } + None => { + self.front.take(); + } + } + self.que_size -= 1; // 更新队列长度 + Rc::try_unwrap(old_rear).ok().unwrap().into_inner().val + }) + } + } + + /* 队首出队 */ + pub fn pop_first(&mut self) -> Option { + return self.pop(true); + } + + /* 队尾出队 */ + pub fn pop_last(&mut self) -> Option { + return self.pop(false); + } + + /* 访问队首元素 */ + pub fn peek_first(&self) -> Option<&Rc>>> { + self.front.as_ref() + } + + /* 访问队尾元素 */ + pub fn peek_last(&self) -> Option<&Rc>>> { + self.rear.as_ref() + } + + /* 返回数组用于打印 */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + if let Some(node) = head { + let mut nums = self.to_array(node.borrow().next.as_ref()); + nums.insert(0, node.borrow().val); + return nums; + } + return Vec::new(); + } + } + ``` + +=== "C" + + ```c title="linkedlist_deque.c" + /* 双向链表节点 */ + typedef struct DoublyListNode { + int val; // 节点值 + struct DoublyListNode *next; // 后继节点 + struct DoublyListNode *prev; // 前驱节点 + } DoublyListNode; + + /* 构造函数 */ + DoublyListNode *newDoublyListNode(int num) { + DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode)); + new->val = num; + new->next = NULL; + new->prev = NULL; + return new; + } + + /* 析构函数 */ + void delDoublyListNode(DoublyListNode *node) { + free(node); + } + + /* 基于双向链表实现的双向队列 */ + typedef struct { + DoublyListNode *front, *rear; // 头节点 front ,尾节点 rear + int queSize; // 双向队列的长度 + } LinkedListDeque; + + /* 构造函数 */ + LinkedListDeque *newLinkedListDeque() { + LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque)); + deque->front = NULL; + deque->rear = NULL; + deque->queSize = 0; + return deque; + } + + /* 析构函数 */ + void delLinkedListdeque(LinkedListDeque *deque) { + // 释放所有节点 + for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { + DoublyListNode *tmp = deque->front; + deque->front = deque->front->next; + free(tmp); + } + // 释放 deque 结构体 + free(deque); + } + + /* 获取队列的长度 */ + int size(LinkedListDeque *deque) { + return deque->queSize; + } + + /* 判断队列是否为空 */ + bool empty(LinkedListDeque *deque) { + return (size(deque) == 0); + } + + /* 入队 */ + void push(LinkedListDeque *deque, int num, bool isFront) { + DoublyListNode *node = newDoublyListNode(num); + // 若链表为空,则令 front 和 rear 都指向node + if (empty(deque)) { + deque->front = deque->rear = node; + } + // 队首入队操作 + else if (isFront) { + // 将 node 添加至链表头部 + deque->front->prev = node; + node->next = deque->front; + deque->front = node; // 更新头节点 + } + // 队尾入队操作 + else { + // 将 node 添加至链表尾部 + deque->rear->next = node; + node->prev = deque->rear; + deque->rear = node; + } + deque->queSize++; // 更新队列长度 + } + + /* 队首入队 */ + void pushFirst(LinkedListDeque *deque, int num) { + push(deque, num, true); + } + + /* 队尾入队 */ + void pushLast(LinkedListDeque *deque, int num) { + push(deque, num, false); + } + + /* 访问队首元素 */ + int peekFirst(LinkedListDeque *deque) { + assert(size(deque) && deque->front); + return deque->front->val; + } + + /* 访问队尾元素 */ + int peekLast(LinkedListDeque *deque) { + assert(size(deque) && deque->rear); + return deque->rear->val; + } + + /* 出队 */ + int pop(LinkedListDeque *deque, bool isFront) { + if (empty(deque)) + return -1; + int val; + // 队首出队操作 + if (isFront) { + val = peekFirst(deque); // 暂存头节点值 + DoublyListNode *fNext = deque->front->next; + if (fNext) { + fNext->prev = NULL; + deque->front->next = NULL; + delDoublyListNode(deque->front); + } + deque->front = fNext; // 更新头节点 + } + // 队尾出队操作 + else { + val = peekLast(deque); // 暂存尾节点值 + DoublyListNode *rPrev = deque->rear->prev; + if (rPrev) { + rPrev->next = NULL; + deque->rear->prev = NULL; + delDoublyListNode(deque->rear); + } + deque->rear = rPrev; // 更新尾节点 + } + deque->queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + int popFirst(LinkedListDeque *deque) { + return pop(deque, true); + } + + /* 队尾出队 */ + int popLast(LinkedListDeque *deque) { + return pop(deque, false); + } + + /* 打印队列 */ + void printLinkedListDeque(LinkedListDeque *deque) { + int *arr = malloc(sizeof(int) * deque->queSize); + // 拷贝链表中的数据到数组 + int i; + DoublyListNode *node; + for (i = 0, node = deque->front; i < deque->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, deque->queSize); + free(arr); + } + ``` + +=== "Zig" + + ```zig title="linkedlist_deque.zig" + // 双向链表节点 + fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = undefined, // 节点值 + next: ?*Self = null, // 后继节点指针 + prev: ?*Self = null, // 前驱节点指针 + + // Initialize a list node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + self.prev = null; + } + }; + } + + // 基于双向链表实现的双向队列 + fn LinkedListDeque(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*ListNode(T) = null, // 头节点 front + rear: ?*ListNode(T) = null, // 尾节点 rear + que_size: usize = 0, // 双向队列的长度 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 内存分配器 + + // 构造函数(分配内存+初始化队列) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.front = null; + self.rear = null; + self.que_size = 0; + } + + // 析构函数(释放内存) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 获取双向队列的长度 + pub fn size(self: *Self) usize { + return self.que_size; + } + + // 判断双向队列是否为空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 入队操作 + pub fn push(self: *Self, num: T, is_front: bool) !void { + var node = try self.mem_allocator.create(ListNode(T)); + node.init(num); + // 若链表为空,则令 front 和 rear 都指向 node + if (self.isEmpty()) { + self.front = node; + self.rear = node; + // 队首入队操作 + } else if (is_front) { + // 将 node 添加至链表头部 + self.front.?.prev = node; + node.next = self.front; + self.front = node; // 更新头节点 + // 队尾入队操作 + } else { + // 将 node 添加至链表尾部 + self.rear.?.next = node; + node.prev = self.rear; + self.rear = node; // 更新尾节点 + } + self.que_size += 1; // 更新队列长度 + } + + // 队首入队 + pub fn pushFirst(self: *Self, num: T) !void { + try self.push(num, true); + } + + // 队尾入队 + pub fn pushLast(self: *Self, num: T) !void { + try self.push(num, false); + } + + // 出队操作 + pub fn pop(self: *Self, is_front: bool) T { + if (self.isEmpty()) @panic("双向队列为空"); + var val: T = undefined; + // 队首出队操作 + if (is_front) { + val = self.front.?.val; // 暂存头节点值 + // 删除头节点 + var fNext = self.front.?.next; + if (fNext != null) { + fNext.?.prev = null; + self.front.?.next = null; + } + self.front = fNext; // 更新头节点 + // 队尾出队操作 + } else { + val = self.rear.?.val; // 暂存尾节点值 + // 删除尾节点 + var rPrev = self.rear.?.prev; + if (rPrev != null) { + rPrev.?.next = null; + self.rear.?.prev = null; + } + self.rear = rPrev; // 更新尾节点 + } + self.que_size -= 1; // 更新队列长度 + return val; + } + + // 队首出队 + pub fn popFirst(self: *Self) T { + return self.pop(true); + } + + // 队尾出队 + pub fn popLast(self: *Self) T { + return self.pop(false); + } + + // 访问队首元素 + pub fn peekFirst(self: *Self) T { + if (self.isEmpty()) @panic("双向队列为空"); + return self.front.?.val; + } + + // 访问队尾元素 + pub fn peekLast(self: *Self) T { + if (self.isEmpty()) @panic("双向队列为空"); + return self.rear.?.val; + } + + // 返回数组用于打印 + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; + } + ``` + +### 2.   Implementation Based on Array + +As shown in the Figure 5-9 , 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) + ![Implementing Double-Ended Queue with Array for Enqueue and Dequeue Operations](deque.assets/array_deque.png){ class="animation-figure" } === "pushLast()" - ![array_deque_push_last](deque.assets/array_deque_push_last.png) + ![array_deque_push_last](deque.assets/array_deque_push_last.png){ class="animation-figure" } === "pushFirst()" - ![array_deque_push_first](deque.assets/array_deque_push_first.png) + ![array_deque_push_first](deque.assets/array_deque_push_first.png){ class="animation-figure" } === "popLast()" - ![array_deque_pop_last](deque.assets/array_deque_pop_last.png) + ![array_deque_pop_last](deque.assets/array_deque_pop_last.png){ class="animation-figure" } === "popFirst()" - ![array_deque_pop_first](deque.assets/array_deque_pop_first.png) + ![array_deque_pop_first](deque.assets/array_deque_pop_first.png){ class="animation-figure" } + +

Figure 5-9   Implementing Double-Ended Queue with Array for Enqueue and Dequeue Operations

The implementation only needs to add methods for "front enqueue" and "rear dequeue": @@ -393,7 +2018,7 @@ The implementation only needs to add methods for "front enqueue" and "rear deque [file]{array_deque}-[func]{} ``` -## Applications of Double-Ended Queue +## 5.3.3   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**. diff --git a/docs-en/chapter_stack_and_queue/index.md b/docs-en/chapter_stack_and_queue/index.md index d6586aac8..04c2f508b 100644 --- a/docs-en/chapter_stack_and_queue/index.md +++ b/docs-en/chapter_stack_and_queue/index.md @@ -1,8 +1,13 @@ -# Stack and Queue +--- +comments: true +icon: material/stack-overflow +--- + +# Chapter 5.   Stack and Queue
-![Stack and Queue](../assets/covers/chapter_stack_and_queue.jpg) +![Stack and Queue](../assets/covers/chapter_stack_and_queue.jpg){ class="cover-image" }
@@ -11,3 +16,10 @@ 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). + +## Chapter Contents + +- [5.1   Stack](https://www.hello-algo.com/en/chapter_stack_and_queue/stack/) +- [5.2   Queue](https://www.hello-algo.com/en/chapter_stack_and_queue/queue/) +- [5.3   Double-ended Queue](https://www.hello-algo.com/en/chapter_stack_and_queue/deque/) +- [5.4   Summary](https://www.hello-algo.com/en/chapter_stack_and_queue/summary/) diff --git a/docs-en/chapter_stack_and_queue/queue.md b/docs-en/chapter_stack_and_queue/queue.md index b491e632c..99415aff6 100755 --- a/docs-en/chapter_stack_and_queue/queue.md +++ b/docs-en/chapter_stack_and_queue/queue.md @@ -1,16 +1,24 @@ -# Queue +--- +comments: true +--- + +# 5.2   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." +As shown in the Figure 5-4 , 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) +![Queue's First-In-First-Out Rule](queue.assets/queue_operations.png){ class="animation-figure" } -## Common Operations on Queue +

Figure 5-4   Queue's First-In-First-Out Rule

-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. +## 5.2.1   Common Operations on Queue -

Table   Efficiency of Queue Operations

+The common operations on a queue are shown in the Table 5-2 . Note that method names may vary across different programming languages. Here, we adopt the same naming convention as used for stacks. + +

Table 5-2   Efficiency of Queue Operations

+ +
| Method Name | Description | Time Complexity | | ----------- | -------------------------------------- | --------------- | @@ -18,6 +26,8 @@ The common operations on a queue are shown in the table below. Note that method | `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" @@ -308,41 +318,912 @@ We can directly use the ready-made queue classes in programming languages: ``` -??? pythontutor "可视化运行" +??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+ -## Implementing a Queue +## 5.2.2   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 +### 1.   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. +As shown in the Figure 5-5 , 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) + ![Implementing Queue with Linked List for Enqueue and Dequeue Operations](queue.assets/linkedlist_queue.png){ class="animation-figure" } === "push()" - ![linkedlist_queue_push](queue.assets/linkedlist_queue_push.png) + ![linkedlist_queue_push](queue.assets/linkedlist_queue_push.png){ class="animation-figure" } === "pop()" - ![linkedlist_queue_pop](queue.assets/linkedlist_queue_pop.png) + ![linkedlist_queue_pop](queue.assets/linkedlist_queue_pop.png){ class="animation-figure" } + +

Figure 5-5   Implementing Queue with Linked List for Enqueue and Dequeue Operations

Below is the code for implementing a queue using a linked list: -```src -[file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{} -``` +=== "Python" -### Implementation Based on Array + ```python title="linkedlist_queue.py" + class LinkedListQueue: + """基于链表实现的队列""" + + def __init__(self): + """构造方法""" + self._front: ListNode | None = None # 头节点 front + self._rear: ListNode | None = None # 尾节点 rear + self._size: int = 0 + + def size(self) -> int: + """获取队列的长度""" + return self._size + + def is_empty(self) -> bool: + """判断队列是否为空""" + return not self._front + + def push(self, num: int): + """入队""" + # 在尾节点后添加 num + node = ListNode(num) + # 如果队列为空,则令头、尾节点都指向该节点 + if self._front is None: + self._front = node + self._rear = node + # 如果队列不为空,则将该节点添加到尾节点后 + else: + self._rear.next = node + self._rear = node + self._size += 1 + + def pop(self) -> int: + """出队""" + num = self.peek() + # 删除头节点 + self._front = self._front.next + self._size -= 1 + return num + + def peek(self) -> int: + """访问队首元素""" + if self.is_empty(): + raise IndexError("队列为空") + return self._front.val + + def to_list(self) -> list[int]: + """转化为列表用于打印""" + queue = [] + temp = self._front + while temp: + queue.append(temp.val) + temp = temp.next + return queue + ``` + +=== "C++" + + ```cpp title="linkedlist_queue.cpp" + /* 基于链表实现的队列 */ + class LinkedListQueue { + private: + ListNode *front, *rear; // 头节点 front ,尾节点 rear + int queSize; + + public: + LinkedListQueue() { + front = nullptr; + rear = nullptr; + queSize = 0; + } + + ~LinkedListQueue() { + // 遍历链表删除节点,释放内存 + freeMemoryLinkedList(front); + } + + /* 获取队列的长度 */ + int size() { + return queSize; + } + + /* 判断队列是否为空 */ + bool isEmpty() { + return queSize == 0; + } + + /* 入队 */ + void push(int num) { + // 在尾节点后添加 num + ListNode *node = new ListNode(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (front == nullptr) { + front = node; + rear = node; + } + // 如果队列不为空,则将该节点添加到尾节点后 + else { + rear->next = node; + rear = node; + } + queSize++; + } + + /* 出队 */ + int pop() { + int num = peek(); + // 删除头节点 + ListNode *tmp = front; + front = front->next; + // 释放内存 + delete tmp; + queSize--; + return num; + } + + /* 访问队首元素 */ + int peek() { + if (size() == 0) + throw out_of_range("队列为空"); + return front->val; + } + + /* 将链表转化为 Vector 并返回 */ + vector toVector() { + ListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } + }; + ``` + +=== "Java" + + ```java title="linkedlist_queue.java" + /* 基于链表实现的队列 */ + class LinkedListQueue { + private ListNode front, rear; // 头节点 front ,尾节点 rear + private int queSize = 0; + + public LinkedListQueue() { + front = null; + rear = null; + } + + /* 获取队列的长度 */ + public int size() { + return queSize; + } + + /* 判断队列是否为空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入队 */ + public void push(int num) { + // 在尾节点后添加 num + ListNode node = new ListNode(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (front == null) { + front = node; + rear = node; + // 如果队列不为空,则将该节点添加到尾节点后 + } else { + rear.next = node; + rear = node; + } + queSize++; + } + + /* 出队 */ + public int pop() { + int num = peek(); + // 删除头节点 + front = front.next; + queSize--; + return num; + } + + /* 访问队首元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return front.val; + } + + /* 将链表转化为 Array 并返回 */ + public int[] toArray() { + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } + } + ``` + +=== "C#" + + ```csharp title="linkedlist_queue.cs" + /* 基于链表实现的队列 */ + class LinkedListQueue { + ListNode? front, rear; // 头节点 front ,尾节点 rear + int queSize = 0; + + public LinkedListQueue() { + front = null; + rear = null; + } + + /* 获取队列的长度 */ + public int Size() { + return queSize; + } + + /* 判断队列是否为空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入队 */ + public void Push(int num) { + // 在尾节点后添加 num + ListNode node = new(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (front == null) { + front = node; + rear = node; + // 如果队列不为空,则将该节点添加到尾节点后 + } else if (rear != null) { + rear.next = node; + rear = node; + } + queSize++; + } + + /* 出队 */ + public int Pop() { + int num = Peek(); + // 删除头节点 + front = front?.next; + queSize--; + return num; + } + + /* 访问队首元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return front!.val; + } + + /* 将链表转化为 Array 并返回 */ + public int[] ToArray() { + if (front == null) + return []; + + ListNode? node = front; + int[] res = new int[Size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node!.val; + node = node.next; + } + return res; + } + } + ``` + +=== "Go" + + ```go title="linkedlist_queue.go" + /* 基于链表实现的队列 */ + type linkedListQueue struct { + // 使用内置包 list 来实现队列 + data *list.List + } + + /* 初始化队列 */ + func newLinkedListQueue() *linkedListQueue { + return &linkedListQueue{ + data: list.New(), + } + } + + /* 入队 */ + func (s *linkedListQueue) push(value any) { + s.data.PushBack(value) + } + + /* 出队 */ + func (s *linkedListQueue) pop() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + s.data.Remove(e) + return e.Value + } + + /* 访问队首元素 */ + func (s *linkedListQueue) peek() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + return e.Value + } + + /* 获取队列的长度 */ + func (s *linkedListQueue) size() int { + return s.data.Len() + } + + /* 判断队列是否为空 */ + func (s *linkedListQueue) isEmpty() bool { + return s.data.Len() == 0 + } + + /* 获取 List 用于打印 */ + func (s *linkedListQueue) toList() *list.List { + return s.data + } + ``` + +=== "Swift" + + ```swift title="linkedlist_queue.swift" + /* 基于链表实现的队列 */ + class LinkedListQueue { + private var front: ListNode? // 头节点 + private var rear: ListNode? // 尾节点 + private var _size = 0 + + init() {} + + /* 获取队列的长度 */ + func size() -> Int { + _size + } + + /* 判断队列是否为空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入队 */ + func push(num: Int) { + // 在尾节点后添加 num + let node = ListNode(x: num) + // 如果队列为空,则令头、尾节点都指向该节点 + if front == nil { + front = node + rear = node + } + // 如果队列不为空,则将该节点添加到尾节点后 + else { + rear?.next = node + rear = node + } + _size += 1 + } + + /* 出队 */ + @discardableResult + func pop() -> Int { + let num = peek() + // 删除头节点 + front = front?.next + _size -= 1 + return num + } + + /* 访问队首元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("队列为空") + } + return front!.val + } + + /* 将链表转化为 Array 并返回 */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } + } + ``` + +=== "JS" + + ```javascript title="linkedlist_queue.js" + /* 基于链表实现的队列 */ + class LinkedListQueue { + #front; // 头节点 #front + #rear; // 尾节点 #rear + #queSize = 0; + + constructor() { + this.#front = null; + this.#rear = null; + } + + /* 获取队列的长度 */ + get size() { + return this.#queSize; + } + + /* 判断队列是否为空 */ + isEmpty() { + return this.size === 0; + } + + /* 入队 */ + push(num) { + // 在尾节点后添加 num + const node = new ListNode(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (!this.#front) { + this.#front = node; + this.#rear = node; + // 如果队列不为空,则将该节点添加到尾节点后 + } else { + this.#rear.next = node; + this.#rear = node; + } + this.#queSize++; + } + + /* 出队 */ + pop() { + const num = this.peek(); + // 删除头节点 + this.#front = this.#front.next; + this.#queSize--; + return num; + } + + /* 访问队首元素 */ + peek() { + if (this.size === 0) throw new Error('队列为空'); + return this.#front.val; + } + + /* 将链表转化为 Array 并返回 */ + toArray() { + let node = this.#front; + const res = new Array(this.size); + for (let i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } + } + ``` + +=== "TS" + + ```typescript title="linkedlist_queue.ts" + /* 基于链表实现的队列 */ + class LinkedListQueue { + private front: ListNode | null; // 头节点 front + private rear: ListNode | null; // 尾节点 rear + private queSize: number = 0; + + constructor() { + this.front = null; + this.rear = null; + } + + /* 获取队列的长度 */ + get size(): number { + return this.queSize; + } + + /* 判断队列是否为空 */ + isEmpty(): boolean { + return this.size === 0; + } + + /* 入队 */ + push(num: number): void { + // 在尾节点后添加 num + const node = new ListNode(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (!this.front) { + this.front = node; + this.rear = node; + // 如果队列不为空,则将该节点添加到尾节点后 + } else { + this.rear!.next = node; + this.rear = node; + } + this.queSize++; + } + + /* 出队 */ + pop(): number { + const num = this.peek(); + if (!this.front) throw new Error('队列为空'); + // 删除头节点 + this.front = this.front.next; + this.queSize--; + return num; + } + + /* 访问队首元素 */ + peek(): number { + if (this.size === 0) throw new Error('队列为空'); + return this.front!.val; + } + + /* 将链表转化为 Array 并返回 */ + toArray(): number[] { + let node = this.front; + const res = new Array(this.size); + for (let i = 0; i < res.length; i++) { + res[i] = node!.val; + node = node!.next; + } + return res; + } + } + ``` + +=== "Dart" + + ```dart title="linkedlist_queue.dart" + /* 基于链表实现的队列 */ + class LinkedListQueue { + ListNode? _front; // 头节点 _front + ListNode? _rear; // 尾节点 _rear + int _queSize = 0; // 队列长度 + + LinkedListQueue() { + _front = null; + _rear = null; + } + + /* 获取队列的长度 */ + int size() { + return _queSize; + } + + /* 判断队列是否为空 */ + bool isEmpty() { + return _queSize == 0; + } + + /* 入队 */ + void push(int _num) { + // 在尾节点后添加 _num + final node = ListNode(_num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (_front == null) { + _front = node; + _rear = node; + } else { + // 如果队列不为空,则将该节点添加到尾节点后 + _rear!.next = node; + _rear = node; + } + _queSize++; + } + + /* 出队 */ + int pop() { + final int _num = peek(); + // 删除头节点 + _front = _front!.next; + _queSize--; + return _num; + } + + /* 访问队首元素 */ + int peek() { + if (_queSize == 0) { + throw Exception('队列为空'); + } + return _front!.val; + } + + /* 将链表转化为 Array 并返回 */ + List toArray() { + ListNode? node = _front; + final List queue = []; + while (node != null) { + queue.add(node.val); + node = node.next; + } + return queue; + } + } + ``` + +=== "Rust" + + ```rust title="linkedlist_queue.rs" + /* 基于链表实现的队列 */ + #[allow(dead_code)] + pub struct LinkedListQueue { + front: Option>>>, // 头节点 front + rear: Option>>>, // 尾节点 rear + que_size: usize, // 队列的长度 + } + + impl LinkedListQueue { + pub fn new() -> Self { + Self { + front: None, + rear: None, + que_size: 0, + } + } + + /* 获取队列的长度 */ + pub fn size(&self) -> usize { + return self.que_size; + } + + /* 判断队列是否为空 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* 入队 */ + pub fn push(&mut self, num: T) { + // 在尾节点后添加 num + let new_rear = ListNode::new(num); + match self.rear.take() { + // 如果队列不为空,则将该节点添加到尾节点后 + Some(old_rear) => { + old_rear.borrow_mut().next = Some(new_rear.clone()); + self.rear = Some(new_rear); + } + // 如果队列为空,则令头、尾节点都指向该节点 + None => { + self.front = Some(new_rear.clone()); + self.rear = Some(new_rear); + } + } + self.que_size += 1; + } + + /* 出队 */ + pub fn pop(&mut self) -> Option { + self.front.take().map(|old_front| { + match old_front.borrow_mut().next.take() { + Some(new_front) => { + self.front = Some(new_front); + } + None => { + self.rear.take(); + } + } + self.que_size -= 1; + Rc::try_unwrap(old_front).ok().unwrap().into_inner().val + }) + } + + /* 访问队首元素 */ + pub fn peek(&self) -> Option<&Rc>>> { + self.front.as_ref() + } + + /* 将链表转化为 Array 并返回 */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + if let Some(node) = head { + let mut nums = self.to_array(node.borrow().next.as_ref()); + nums.insert(0, node.borrow().val); + return nums; + } + return Vec::new(); + } + } + ``` + +=== "C" + + ```c title="linkedlist_queue.c" + /* 基于链表实现的队列 */ + typedef struct { + ListNode *front, *rear; + int queSize; + } LinkedListQueue; + + /* 构造函数 */ + LinkedListQueue *newLinkedListQueue() { + LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue)); + queue->front = NULL; + queue->rear = NULL; + queue->queSize = 0; + return queue; + } + + /* 析构函数 */ + void delLinkedListQueue(LinkedListQueue *queue) { + // 释放所有节点 + for (int i = 0; i < queue->queSize && queue->front != NULL; i++) { + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + } + // 释放 queue 结构体 + free(queue); + } + + /* 获取队列的长度 */ + int size(LinkedListQueue *queue) { + return queue->queSize; + } + + /* 判断队列是否为空 */ + bool empty(LinkedListQueue *queue) { + return (size(queue) == 0); + } + + /* 入队 */ + void push(LinkedListQueue *queue, int num) { + // 尾节点处添加 node + ListNode *node = newListNode(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (queue->front == NULL) { + queue->front = node; + queue->rear = node; + } + // 如果队列不为空,则将该节点添加到尾节点后 + else { + queue->rear->next = node; + queue->rear = node; + } + queue->queSize++; + } + + /* 访问队首元素 */ + int peek(LinkedListQueue *queue) { + assert(size(queue) && queue->front); + return queue->front->val; + } + + /* 出队 */ + int pop(LinkedListQueue *queue) { + int num = peek(queue); + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + queue->queSize--; + return num; + } + + /* 打印队列 */ + void printLinkedListQueue(LinkedListQueue *queue) { + int *arr = malloc(sizeof(int) * queue->queSize); + // 拷贝链表中的数据到数组 + int i; + ListNode *node; + for (i = 0, node = queue->front; i < queue->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, queue->queSize); + free(arr); + } + ``` + +=== "Zig" + + ```zig title="linkedlist_queue.zig" + // 基于链表实现的队列 + fn LinkedListQueue(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*inc.ListNode(T) = null, // 头节点 front + rear: ?*inc.ListNode(T) = null, // 尾节点 rear + que_size: usize = 0, // 队列的长度 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 内存分配器 + + // 构造函数(分配内存+初始化队列) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.front = null; + self.rear = null; + self.que_size = 0; + } + + // 析构函数(释放内存) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 获取队列的长度 + pub fn size(self: *Self) usize { + return self.que_size; + } + + // 判断队列是否为空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 访问队首元素 + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("队列为空"); + return self.front.?.val; + } + + // 入队 + pub fn push(self: *Self, num: T) !void { + // 在尾节点后添加 num + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (self.front == null) { + self.front = node; + self.rear = node; + // 如果队列不为空,则将该节点添加到尾节点后 + } else { + self.rear.?.next = node; + self.rear = node; + } + self.que_size += 1; + } + + // 出队 + pub fn pop(self: *Self) T { + var num = self.peek(); + // 删除头节点 + self.front = self.front.?.next; + self.que_size -= 1; + return num; + } + + // 将链表转换为数组 + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; + } + ``` + +??? pythontutor "Visualizing Code" + +
+ + +### 2.   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. +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 5-6 . - 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. @@ -350,27 +1231,913 @@ With this design, **the effective interval of elements in the array is `[front, 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) + ![Implementing Queue with Array for Enqueue and Dequeue Operations](queue.assets/array_queue.png){ class="animation-figure" } === "push()" - ![array_queue_push](queue.assets/array_queue_push.png) + ![array_queue_push](queue.assets/array_queue_push.png){ class="animation-figure" } === "pop()" - ![array_queue_pop](queue.assets/array_queue_pop.png) + ![array_queue_pop](queue.assets/array_queue_pop.png){ class="animation-figure" } + +

Figure 5-6   Implementing Queue with Array for Enqueue and Dequeue Operations

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]{} -``` +=== "Python" + + ```python title="array_queue.py" + class ArrayQueue: + """基于环形数组实现的队列""" + + def __init__(self, size: int): + """构造方法""" + self._nums: list[int] = [0] * size # 用于存储队列元素的数组 + self._front: int = 0 # 队首指针,指向队首元素 + self._size: int = 0 # 队列长度 + + def capacity(self) -> int: + """获取队列的容量""" + return len(self._nums) + + def size(self) -> int: + """获取队列的长度""" + return self._size + + def is_empty(self) -> bool: + """判断队列是否为空""" + return self._size == 0 + + def push(self, num: int): + """入队""" + if self._size == self.capacity(): + raise IndexError("队列已满") + # 计算队尾指针,指向队尾索引 + 1 + # 通过取余操作实现 rear 越过数组尾部后回到头部 + rear: int = (self._front + self._size) % self.capacity() + # 将 num 添加至队尾 + self._nums[rear] = num + self._size += 1 + + def pop(self) -> int: + """出队""" + num: int = self.peek() + # 队首指针向后移动一位,若越过尾部,则返回到数组头部 + self._front = (self._front + 1) % self.capacity() + self._size -= 1 + return num + + def peek(self) -> int: + """访问队首元素""" + if self.is_empty(): + raise IndexError("队列为空") + return self._nums[self._front] + + def to_list(self) -> list[int]: + """返回列表用于打印""" + res = [0] * self.size() + j: int = self._front + for i in range(self.size()): + res[i] = self._nums[(j % self.capacity())] + j += 1 + return res + ``` + +=== "C++" + + ```cpp title="array_queue.cpp" + /* 基于环形数组实现的队列 */ + class ArrayQueue { + private: + int *nums; // 用于存储队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 队列长度 + int queCapacity; // 队列容量 + + public: + ArrayQueue(int capacity) { + // 初始化数组 + nums = new int[capacity]; + queCapacity = capacity; + front = queSize = 0; + } + + ~ArrayQueue() { + delete[] nums; + } + + /* 获取队列的容量 */ + int capacity() { + return queCapacity; + } + + /* 获取队列的长度 */ + int size() { + return queSize; + } + + /* 判断队列是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入队 */ + void push(int num) { + if (queSize == queCapacity) { + cout << "队列已满" << endl; + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + int rear = (front + queSize) % queCapacity; + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } + + /* 出队 */ + int pop() { + int num = peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + front = (front + 1) % queCapacity; + queSize--; + return num; + } + + /* 访问队首元素 */ + int peek() { + if (isEmpty()) + throw out_of_range("队列为空"); + return nums[front]; + } + + /* 将数组转化为 Vector 并返回 */ + vector toVector() { + // 仅转换有效长度范围内的列表元素 + vector arr(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + arr[i] = nums[j % queCapacity]; + } + return arr; + } + }; + ``` + +=== "Java" + + ```java title="array_queue.java" + /* 基于环形数组实现的队列 */ + class ArrayQueue { + private int[] nums; // 用于存储队列元素的数组 + private int front; // 队首指针,指向队首元素 + private int queSize; // 队列长度 + + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* 获取队列的容量 */ + public int capacity() { + return nums.length; + } + + /* 获取队列的长度 */ + public int size() { + return queSize; + } + + /* 判断队列是否为空 */ + public boolean isEmpty() { + return queSize == 0; + } + + /* 入队 */ + public void push(int num) { + if (queSize == capacity()) { + System.out.println("队列已满"); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + int rear = (front + queSize) % capacity(); + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } + + /* 出队 */ + public int pop() { + int num = peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + front = (front + 1) % capacity(); + queSize--; + return num; + } + + /* 访问队首元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return nums[front]; + } + + /* 返回数组 */ + public int[] toArray() { + // 仅转换有效长度范围内的列表元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[j % capacity()]; + } + return res; + } + } + ``` + +=== "C#" + + ```csharp title="array_queue.cs" + /* 基于环形数组实现的队列 */ + class ArrayQueue { + int[] nums; // 用于存储队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 队列长度 + + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* 获取队列的容量 */ + int Capacity() { + return nums.Length; + } + + /* 获取队列的长度 */ + public int Size() { + return queSize; + } + + /* 判断队列是否为空 */ + public bool IsEmpty() { + return queSize == 0; + } + + /* 入队 */ + public void Push(int num) { + if (queSize == Capacity()) { + Console.WriteLine("队列已满"); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + int rear = (front + queSize) % Capacity(); + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } + + /* 出队 */ + public int Pop() { + int num = Peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + front = (front + 1) % Capacity(); + queSize--; + return num; + } + + /* 访问队首元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return nums[front]; + } + + /* 返回数组 */ + public int[] ToArray() { + // 仅转换有效长度范围内的列表元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[j % this.Capacity()]; + } + return res; + } + } + ``` + +=== "Go" + + ```go title="array_queue.go" + /* 基于环形数组实现的队列 */ + type arrayQueue struct { + nums []int // 用于存储队列元素的数组 + front int // 队首指针,指向队首元素 + queSize int // 队列长度 + queCapacity int // 队列容量(即最大容纳元素数量) + } + + /* 初始化队列 */ + func newArrayQueue(queCapacity int) *arrayQueue { + return &arrayQueue{ + nums: make([]int, queCapacity), + queCapacity: queCapacity, + front: 0, + queSize: 0, + } + } + + /* 获取队列的长度 */ + func (q *arrayQueue) size() int { + return q.queSize + } + + /* 判断队列是否为空 */ + func (q *arrayQueue) isEmpty() bool { + return q.queSize == 0 + } + + /* 入队 */ + func (q *arrayQueue) push(num int) { + // 当 rear == queCapacity 表示队列已满 + if q.queSize == q.queCapacity { + return + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + rear := (q.front + q.queSize) % q.queCapacity + // 将 num 添加至队尾 + q.nums[rear] = num + q.queSize++ + } + + /* 出队 */ + func (q *arrayQueue) pop() any { + num := q.peek() + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + q.front = (q.front + 1) % q.queCapacity + q.queSize-- + return num + } + + /* 访问队首元素 */ + func (q *arrayQueue) peek() any { + if q.isEmpty() { + return nil + } + return q.nums[q.front] + } + + /* 获取 Slice 用于打印 */ + func (q *arrayQueue) toSlice() []int { + rear := (q.front + q.queSize) + if rear >= q.queCapacity { + rear %= q.queCapacity + return append(q.nums[q.front:], q.nums[:rear]...) + } + return q.nums[q.front:rear] + } + ``` + +=== "Swift" + + ```swift title="array_queue.swift" + /* 基于环形数组实现的队列 */ + class ArrayQueue { + private var nums: [Int] // 用于存储队列元素的数组 + private var front = 0 // 队首指针,指向队首元素 + private var queSize = 0 // 队列长度 + + init(capacity: Int) { + // 初始化数组 + nums = Array(repeating: 0, count: capacity) + } + + /* 获取队列的容量 */ + func capacity() -> Int { + nums.count + } + + /* 获取队列的长度 */ + func size() -> Int { + queSize + } + + /* 判断队列是否为空 */ + func isEmpty() -> Bool { + queSize == 0 + } + + /* 入队 */ + func push(num: Int) { + if size() == capacity() { + print("队列已满") + return + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + let rear = (front + queSize) % capacity() + // 将 num 添加至队尾 + nums[rear] = num + queSize += 1 + } + + /* 出队 */ + @discardableResult + func pop() -> Int { + let num = peek() + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + front = (front + 1) % capacity() + queSize -= 1 + return num + } + + /* 访问队首元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("队列为空") + } + return nums[front] + } + + /* 返回数组 */ + func toArray() -> [Int] { + // 仅转换有效长度范围内的列表元素 + var res = Array(repeating: 0, count: queSize) + for (i, j) in sequence(first: (0, front), next: { $0 < self.queSize - 1 ? ($0 + 1, $1 + 1) : nil }) { + res[i] = nums[j % capacity()] + } + return res + } + } + ``` + +=== "JS" + + ```javascript title="array_queue.js" + /* 基于环形数组实现的队列 */ + class ArrayQueue { + #nums; // 用于存储队列元素的数组 + #front = 0; // 队首指针,指向队首元素 + #queSize = 0; // 队列长度 + + constructor(capacity) { + this.#nums = new Array(capacity); + } + + /* 获取队列的容量 */ + get capacity() { + return this.#nums.length; + } + + /* 获取队列的长度 */ + get size() { + return this.#queSize; + } + + /* 判断队列是否为空 */ + isEmpty() { + return this.#queSize === 0; + } + + /* 入队 */ + push(num) { + if (this.size === this.capacity) { + console.log('队列已满'); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + const rear = (this.#front + this.size) % this.capacity; + // 将 num 添加至队尾 + this.#nums[rear] = num; + this.#queSize++; + } + + /* 出队 */ + pop() { + const num = this.peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + this.#front = (this.#front + 1) % this.capacity; + this.#queSize--; + return num; + } + + /* 访问队首元素 */ + peek() { + if (this.isEmpty()) throw new Error('队列为空'); + return this.#nums[this.#front]; + } + + /* 返回 Array */ + toArray() { + // 仅转换有效长度范围内的列表元素 + const arr = new Array(this.size); + for (let i = 0, j = this.#front; i < this.size; i++, j++) { + arr[i] = this.#nums[j % this.capacity]; + } + return arr; + } + } + ``` + +=== "TS" + + ```typescript title="array_queue.ts" + /* 基于环形数组实现的队列 */ + class ArrayQueue { + private nums: number[]; // 用于存储队列元素的数组 + private front: number; // 队首指针,指向队首元素 + private queSize: number; // 队列长度 + + constructor(capacity: number) { + this.nums = new Array(capacity); + this.front = this.queSize = 0; + } + + /* 获取队列的容量 */ + get capacity(): number { + return this.nums.length; + } + + /* 获取队列的长度 */ + get size(): number { + return this.queSize; + } + + /* 判断队列是否为空 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* 入队 */ + push(num: number): void { + if (this.size === this.capacity) { + console.log('队列已满'); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + const rear = (this.front + this.queSize) % this.capacity; + // 将 num 添加至队尾 + this.nums[rear] = num; + this.queSize++; + } + + /* 出队 */ + pop(): number { + const num = this.peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + this.front = (this.front + 1) % this.capacity; + this.queSize--; + return num; + } + + /* 访问队首元素 */ + peek(): number { + if (this.isEmpty()) throw new Error('队列为空'); + return this.nums[this.front]; + } + + /* 返回 Array */ + toArray(): number[] { + // 仅转换有效长度范围内的列表元素 + const arr = new Array(this.size); + for (let i = 0, j = this.front; i < this.size; i++, j++) { + arr[i] = this.nums[j % this.capacity]; + } + return arr; + } + } + ``` + +=== "Dart" + + ```dart title="array_queue.dart" + /* 基于环形数组实现的队列 */ + class ArrayQueue { + late List _nums; // 用于储存队列元素的数组 + late int _front; // 队首指针,指向队首元素 + late int _queSize; // 队列长度 + + ArrayQueue(int capacity) { + _nums = List.filled(capacity, 0); + _front = _queSize = 0; + } + + /* 获取队列的容量 */ + int capaCity() { + return _nums.length; + } + + /* 获取队列的长度 */ + int size() { + return _queSize; + } + + /* 判断队列是否为空 */ + bool isEmpty() { + return _queSize == 0; + } + + /* 入队 */ + void push(int _num) { + if (_queSize == capaCity()) { + throw Exception("队列已满"); + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + int rear = (_front + _queSize) % capaCity(); + // 将 _num 添加至队尾 + _nums[rear] = _num; + _queSize++; + } + + /* 出队 */ + int pop() { + int _num = peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + _front = (_front + 1) % capaCity(); + _queSize--; + return _num; + } + + /* 访问队首元素 */ + int peek() { + if (isEmpty()) { + throw Exception("队列为空"); + } + return _nums[_front]; + } + + /* 返回 Array */ + List toArray() { + // 仅转换有效长度范围内的列表元素 + final List res = List.filled(_queSize, 0); + for (int i = 0, j = _front; i < _queSize; i++, j++) { + res[i] = _nums[j % capaCity()]; + } + return res; + } + } + ``` + +=== "Rust" + + ```rust title="array_queue.rs" + /* 基于环形数组实现的队列 */ + struct ArrayQueue { + nums: Vec, // 用于存储队列元素的数组 + front: i32, // 队首指针,指向队首元素 + que_size: i32, // 队列长度 + que_capacity: i32, // 队列容量 + } + + impl ArrayQueue { + /* 构造方法 */ + fn new(capacity: i32) -> ArrayQueue { + ArrayQueue { + nums: vec![0; capacity as usize], + front: 0, + que_size: 0, + que_capacity: capacity, + } + } + + /* 获取队列的容量 */ + fn capacity(&self) -> i32 { + self.que_capacity + } + + /* 获取队列的长度 */ + fn size(&self) -> i32 { + self.que_size + } + + /* 判断队列是否为空 */ + fn is_empty(&self) -> bool { + self.que_size == 0 + } + + /* 入队 */ + fn push(&mut self, num: i32) { + if self.que_size == self.capacity() { + println!("队列已满"); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + let rear = (self.front + self.que_size) % self.que_capacity; + // 将 num 添加至队尾 + self.nums[rear as usize] = num; + self.que_size += 1; + } + + /* 出队 */ + fn pop(&mut self) -> i32 { + let num = self.peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + self.front = (self.front + 1) % self.que_capacity; + self.que_size -= 1; + num + } + + /* 访问队首元素 */ + fn peek(&self) -> i32 { + if self.is_empty() { + panic!("index out of bounds"); + } + self.nums[self.front as usize] + } + + /* 返回数组 */ + fn to_vector(&self) -> Vec { + let cap = self.que_capacity; + let mut j = self.front; + let mut arr = vec![0; self.que_size as usize]; + for i in 0..self.que_size { + arr[i as usize] = self.nums[(j % cap) as usize]; + j += 1; + } + arr + } + } + ``` + +=== "C" + + ```c title="array_queue.c" + /* 基于环形数组实现的队列 */ + typedef struct { + int *nums; // 用于存储队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 尾指针,指向队尾 + 1 + int queCapacity; // 队列容量 + } ArrayQueue; + + /* 构造函数 */ + ArrayQueue *newArrayQueue(int capacity) { + ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue)); + // 初始化数组 + queue->queCapacity = capacity; + queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity); + queue->front = queue->queSize = 0; + return queue; + } + + /* 析构函数 */ + void delArrayQueue(ArrayQueue *queue) { + free(queue->nums); + free(queue); + } + + /* 获取队列的容量 */ + int capacity(ArrayQueue *queue) { + return queue->queCapacity; + } + + /* 获取队列的长度 */ + int size(ArrayQueue *queue) { + return queue->queSize; + } + + /* 判断队列是否为空 */ + bool empty(ArrayQueue *queue) { + return queue->queSize == 0; + } + + /* 访问队首元素 */ + int peek(ArrayQueue *queue) { + assert(size(queue) != 0); + return queue->nums[queue->front]; + } + + /* 入队 */ + void push(ArrayQueue *queue, int num) { + if (size(queue) == capacity(queue)) { + printf("队列已满\r\n"); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + int rear = (queue->front + queue->queSize) % queue->queCapacity; + // 将 num 添加至队尾 + queue->nums[rear] = num; + queue->queSize++; + } + + /* 出队 */ + int pop(ArrayQueue *queue) { + int num = peek(queue); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + queue->front = (queue->front + 1) % queue->queCapacity; + queue->queSize--; + return num; + } + ``` + +=== "Zig" + + ```zig title="array_queue.zig" + // 基于环形数组实现的队列 + fn ArrayQueue(comptime T: type) type { + return struct { + const Self = @This(); + + nums: []T = undefined, // 用于存储队列元素的数组 + cap: usize = 0, // 队列容量 + front: usize = 0, // 队首指针,指向队首元素 + queSize: usize = 0, // 尾指针,指向队尾 + 1 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 内存分配器 + + // 构造函数(分配内存+初始化数组) + pub fn init(self: *Self, allocator: std.mem.Allocator, cap: usize) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.cap = cap; + self.nums = try self.mem_allocator.alloc(T, self.cap); + @memset(self.nums, @as(T, 0)); + } + + // 析构函数(释放内存) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 获取队列的容量 + pub fn capacity(self: *Self) usize { + return self.cap; + } + + // 获取队列的长度 + pub fn size(self: *Self) usize { + return self.queSize; + } + + // 判断队列是否为空 + pub fn isEmpty(self: *Self) bool { + return self.queSize == 0; + } + + // 入队 + pub fn push(self: *Self, num: T) !void { + if (self.size() == self.capacity()) { + std.debug.print("队列已满\n", .{}); + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + var rear = (self.front + self.queSize) % self.capacity(); + // 在尾节点后添加 num + self.nums[rear] = num; + self.queSize += 1; + } + + // 出队 + pub fn pop(self: *Self) T { + var num = self.peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + self.front = (self.front + 1) % self.capacity(); + self.queSize -= 1; + return num; + } + + // 访问队首元素 + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("队列为空"); + return self.nums[self.front]; + } + + // 返回数组 + pub fn toArray(self: *Self) ![]T { + // 仅转换有效长度范围内的列表元素 + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + var j: usize = self.front; + while (i < self.size()) : ({ i += 1; j += 1; }) { + res[i] = self.nums[j % self.capacity()]; + } + return res; + } + }; + } + ``` + +??? pythontutor "Visualizing Code" + +
+ 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 +## 5.2.3   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. diff --git a/docs-en/chapter_stack_and_queue/stack.md b/docs-en/chapter_stack_and_queue/stack.md index 08a6460f5..45ee29c7a 100755 --- a/docs-en/chapter_stack_and_queue/stack.md +++ b/docs-en/chapter_stack_and_queue/stack.md @@ -1,4 +1,8 @@ -# Stack +--- +comments: true +--- + +# 5.1   Stack "Stack" is a linear data structure that follows the principle of Last-In-First-Out (LIFO). @@ -6,13 +10,17 @@ We can compare a stack to a pile of plates on a table. To access the bottom plat 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) +![Stack's Last-In-First-Out Rule](stack.assets/stack_operations.png){ class="animation-figure" } -## Common Operations on Stack +

Figure 5-1   Stack's Last-In-First-Out Rule

-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. +## 5.1.1   Common Operations on Stack -

Table   Efficiency of Stack Operations

+The common operations on a stack are shown in the Table 5-1 . The specific method names depend on the programming language used. Here, we use `push()`, `pop()`, and `peek()` as examples. + +

Table 5-1   Efficiency of Stack Operations

+ +
| Method | Description | Time Complexity | | -------- | ----------------------------------------------- | --------------- | @@ -20,6 +28,8 @@ The common operations on a stack are shown in the table below. The specific meth | `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" @@ -302,58 +312,1395 @@ Typically, we can directly use the stack class built into the programming langua ``` -??? pythontutor "可视化运行" +??? pythontutor "Visualizing Code" -
- 全屏观看 > +
+ -## Implementing a Stack +## 5.1.2   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 +### 1.   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. +As shown in the Figure 5-2 , 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) + ![Implementing Stack with Linked List for Push and Pop Operations](stack.assets/linkedlist_stack.png){ class="animation-figure" } === "push()" - ![linkedlist_stack_push](stack.assets/linkedlist_stack_push.png) + ![linkedlist_stack_push](stack.assets/linkedlist_stack_push.png){ class="animation-figure" } === "pop()" - ![linkedlist_stack_pop](stack.assets/linkedlist_stack_pop.png) + ![linkedlist_stack_pop](stack.assets/linkedlist_stack_pop.png){ class="animation-figure" } + +

Figure 5-2   Implementing Stack with Linked List for Push and Pop Operations

Below is an example code for implementing a stack based on a linked list: -```src -[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{} -``` +=== "Python" -### Implementation Based on Array + ```python title="linkedlist_stack.py" + class LinkedListStack: + """基于链表实现的栈""" -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)$. + def __init__(self): + """构造方法""" + self._peek: ListNode | None = None + self._size: int = 0 + + def size(self) -> int: + """获取栈的长度""" + return self._size + + def is_empty(self) -> bool: + """判断栈是否为空""" + return not self._peek + + def push(self, val: int): + """入栈""" + node = ListNode(val) + node.next = self._peek + self._peek = node + self._size += 1 + + def pop(self) -> int: + """出栈""" + num = self.peek() + self._peek = self._peek.next + self._size -= 1 + return num + + def peek(self) -> int: + """访问栈顶元素""" + if self.is_empty(): + raise IndexError("栈为空") + return self._peek.val + + def to_list(self) -> list[int]: + """转化为列表用于打印""" + arr = [] + node = self._peek + while node: + arr.append(node.val) + node = node.next + arr.reverse() + return arr + ``` + +=== "C++" + + ```cpp title="linkedlist_stack.cpp" + /* 基于链表实现的栈 */ + class LinkedListStack { + private: + ListNode *stackTop; // 将头节点作为栈顶 + int stkSize; // 栈的长度 + + public: + LinkedListStack() { + stackTop = nullptr; + stkSize = 0; + } + + ~LinkedListStack() { + // 遍历链表删除节点,释放内存 + freeMemoryLinkedList(stackTop); + } + + /* 获取栈的长度 */ + int size() { + return stkSize; + } + + /* 判断栈是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入栈 */ + void push(int num) { + ListNode *node = new ListNode(num); + node->next = stackTop; + stackTop = node; + stkSize++; + } + + /* 出栈 */ + int pop() { + int num = top(); + ListNode *tmp = stackTop; + stackTop = stackTop->next; + // 释放内存 + delete tmp; + stkSize--; + return num; + } + + /* 访问栈顶元素 */ + int top() { + if (isEmpty()) + throw out_of_range("栈为空"); + return stackTop->val; + } + + /* 将 List 转化为 Array 并返回 */ + vector toVector() { + ListNode *node = stackTop; + vector res(size()); + for (int i = res.size() - 1; i >= 0; i--) { + res[i] = node->val; + node = node->next; + } + return res; + } + }; + ``` + +=== "Java" + + ```java title="linkedlist_stack.java" + /* 基于链表实现的栈 */ + class LinkedListStack { + private ListNode stackPeek; // 将头节点作为栈顶 + private int stkSize = 0; // 栈的长度 + + public LinkedListStack() { + stackPeek = null; + } + + /* 获取栈的长度 */ + public int size() { + return stkSize; + } + + /* 判断栈是否为空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入栈 */ + public void push(int num) { + ListNode node = new ListNode(num); + node.next = stackPeek; + stackPeek = node; + stkSize++; + } + + /* 出栈 */ + public int pop() { + int num = peek(); + stackPeek = stackPeek.next; + stkSize--; + return num; + } + + /* 访问栈顶元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stackPeek.val; + } + + /* 将 List 转化为 Array 并返回 */ + public int[] toArray() { + ListNode node = stackPeek; + int[] res = new int[size()]; + for (int i = res.length - 1; i >= 0; i--) { + res[i] = node.val; + node = node.next; + } + return res; + } + } + ``` + +=== "C#" + + ```csharp title="linkedlist_stack.cs" + /* 基于链表实现的栈 */ + class LinkedListStack { + ListNode? stackPeek; // 将头节点作为栈顶 + int stkSize = 0; // 栈的长度 + + public LinkedListStack() { + stackPeek = null; + } + + /* 获取栈的长度 */ + public int Size() { + return stkSize; + } + + /* 判断栈是否为空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入栈 */ + public void Push(int num) { + ListNode node = new(num) { + next = stackPeek + }; + stackPeek = node; + stkSize++; + } + + /* 出栈 */ + public int Pop() { + int num = Peek(); + stackPeek = stackPeek!.next; + stkSize--; + return num; + } + + /* 访问栈顶元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return stackPeek!.val; + } + + /* 将 List 转化为 Array 并返回 */ + public int[] ToArray() { + if (stackPeek == null) + return []; + + ListNode? node = stackPeek; + int[] res = new int[Size()]; + for (int i = res.Length - 1; i >= 0; i--) { + res[i] = node!.val; + node = node.next; + } + return res; + } + } + ``` + +=== "Go" + + ```go title="linkedlist_stack.go" + /* 基于链表实现的栈 */ + type linkedListStack struct { + // 使用内置包 list 来实现栈 + data *list.List + } + + /* 初始化栈 */ + func newLinkedListStack() *linkedListStack { + return &linkedListStack{ + data: list.New(), + } + } + + /* 入栈 */ + func (s *linkedListStack) push(value int) { + s.data.PushBack(value) + } + + /* 出栈 */ + func (s *linkedListStack) pop() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + s.data.Remove(e) + return e.Value + } + + /* 访问栈顶元素 */ + func (s *linkedListStack) peek() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + return e.Value + } + + /* 获取栈的长度 */ + func (s *linkedListStack) size() int { + return s.data.Len() + } + + /* 判断栈是否为空 */ + func (s *linkedListStack) isEmpty() bool { + return s.data.Len() == 0 + } + + /* 获取 List 用于打印 */ + func (s *linkedListStack) toList() *list.List { + return s.data + } + ``` + +=== "Swift" + + ```swift title="linkedlist_stack.swift" + /* 基于链表实现的栈 */ + class LinkedListStack { + private var _peek: ListNode? // 将头节点作为栈顶 + private var _size = 0 // 栈的长度 + + init() {} + + /* 获取栈的长度 */ + func size() -> Int { + _size + } + + /* 判断栈是否为空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入栈 */ + func push(num: Int) { + let node = ListNode(x: num) + node.next = _peek + _peek = node + _size += 1 + } + + /* 出栈 */ + @discardableResult + func pop() -> Int { + let num = peek() + _peek = _peek?.next + _size -= 1 + return num + } + + /* 访问栈顶元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("栈为空") + } + return _peek!.val + } + + /* 将 List 转化为 Array 并返回 */ + func toArray() -> [Int] { + var node = _peek + var res = Array(repeating: 0, count: _size) + for i in sequence(first: res.count - 1, next: { $0 >= 0 + 1 ? $0 - 1 : nil }) { + res[i] = node!.val + node = node?.next + } + return res + } + } + ``` + +=== "JS" + + ```javascript title="linkedlist_stack.js" + /* 基于链表实现的栈 */ + class LinkedListStack { + #stackPeek; // 将头节点作为栈顶 + #stkSize = 0; // 栈的长度 + + constructor() { + this.#stackPeek = null; + } + + /* 获取栈的长度 */ + get size() { + return this.#stkSize; + } + + /* 判断栈是否为空 */ + isEmpty() { + return this.size === 0; + } + + /* 入栈 */ + push(num) { + const node = new ListNode(num); + node.next = this.#stackPeek; + this.#stackPeek = node; + this.#stkSize++; + } + + /* 出栈 */ + pop() { + const num = this.peek(); + this.#stackPeek = this.#stackPeek.next; + this.#stkSize--; + return num; + } + + /* 访问栈顶元素 */ + peek() { + if (!this.#stackPeek) throw new Error('栈为空'); + return this.#stackPeek.val; + } + + /* 将链表转化为 Array 并返回 */ + toArray() { + let node = this.#stackPeek; + const res = new Array(this.size); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = node.val; + node = node.next; + } + return res; + } + } + ``` + +=== "TS" + + ```typescript title="linkedlist_stack.ts" + /* 基于链表实现的栈 */ + class LinkedListStack { + private stackPeek: ListNode | null; // 将头节点作为栈顶 + private stkSize: number = 0; // 栈的长度 + + constructor() { + this.stackPeek = null; + } + + /* 获取栈的长度 */ + get size(): number { + return this.stkSize; + } + + /* 判断栈是否为空 */ + isEmpty(): boolean { + return this.size === 0; + } + + /* 入栈 */ + push(num: number): void { + const node = new ListNode(num); + node.next = this.stackPeek; + this.stackPeek = node; + this.stkSize++; + } + + /* 出栈 */ + pop(): number { + const num = this.peek(); + if (!this.stackPeek) throw new Error('栈为空'); + this.stackPeek = this.stackPeek.next; + this.stkSize--; + return num; + } + + /* 访问栈顶元素 */ + peek(): number { + if (!this.stackPeek) throw new Error('栈为空'); + return this.stackPeek.val; + } + + /* 将链表转化为 Array 并返回 */ + toArray(): number[] { + let node = this.stackPeek; + const res = new Array(this.size); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = node!.val; + node = node!.next; + } + return res; + } + } + ``` + +=== "Dart" + + ```dart title="linkedlist_stack.dart" + /* 基于链表类实现的栈 */ + class LinkedListStack { + ListNode? _stackPeek; // 将头节点作为栈顶 + int _stkSize = 0; // 栈的长度 + + LinkedListStack() { + _stackPeek = null; + } + + /* 获取栈的长度 */ + int size() { + return _stkSize; + } + + /* 判断栈是否为空 */ + bool isEmpty() { + return _stkSize == 0; + } + + /* 入栈 */ + void push(int _num) { + final ListNode node = ListNode(_num); + node.next = _stackPeek; + _stackPeek = node; + _stkSize++; + } + + /* 出栈 */ + int pop() { + final int _num = peek(); + _stackPeek = _stackPeek!.next; + _stkSize--; + return _num; + } + + /* 访问栈顶元素 */ + int peek() { + if (_stackPeek == null) { + throw Exception("栈为空"); + } + return _stackPeek!.val; + } + + /* 将链表转化为 List 并返回 */ + List toList() { + ListNode? node = _stackPeek; + List list = []; + while (node != null) { + list.add(node.val); + node = node.next; + } + list = list.reversed.toList(); + return list; + } + } + ``` + +=== "Rust" + + ```rust title="linkedlist_stack.rs" + /* 基于链表实现的栈 */ + #[allow(dead_code)] + pub struct LinkedListStack { + stack_peek: Option>>>, // 将头节点作为栈顶 + stk_size: usize, // 栈的长度 + } + + impl LinkedListStack { + pub fn new() -> Self { + Self { + stack_peek: None, + stk_size: 0, + } + } + + /* 获取栈的长度 */ + pub fn size(&self) -> usize { + return self.stk_size; + } + + /* 判断栈是否为空 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* 入栈 */ + pub fn push(&mut self, num: T) { + let node = ListNode::new(num); + node.borrow_mut().next = self.stack_peek.take(); + self.stack_peek = Some(node); + self.stk_size += 1; + } + + /* 出栈 */ + pub fn pop(&mut self) -> Option { + self.stack_peek.take().map(|old_head| { + match old_head.borrow_mut().next.take() { + Some(new_head) => { + self.stack_peek = Some(new_head); + } + None => { + self.stack_peek = None; + } + } + self.stk_size -= 1; + Rc::try_unwrap(old_head).ok().unwrap().into_inner().val + }) + } + + /* 访问栈顶元素 */ + pub fn peek(&self) -> Option<&Rc>>> { + self.stack_peek.as_ref() + } + + /* 将 List 转化为 Array 并返回 */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + if let Some(node) = head { + let mut nums = self.to_array(node.borrow().next.as_ref()); + nums.push(node.borrow().val); + return nums; + } + return Vec::new(); + } + } + ``` + +=== "C" + + ```c title="linkedlist_stack.c" + /* 基于链表实现的栈 */ + typedef struct { + ListNode *top; // 将头节点作为栈顶 + int size; // 栈的长度 + } LinkedListStack; + + /* 构造函数 */ + LinkedListStack *newLinkedListStack() { + LinkedListStack *s = malloc(sizeof(LinkedListStack)); + s->top = NULL; + s->size = 0; + return s; + } + + /* 析构函数 */ + void delLinkedListStack(LinkedListStack *s) { + while (s->top) { + ListNode *n = s->top->next; + free(s->top); + s->top = n; + } + free(s); + } + + /* 获取栈的长度 */ + int size(LinkedListStack *s) { + return s->size; + } + + /* 判断栈是否为空 */ + bool isEmpty(LinkedListStack *s) { + return size(s) == 0; + } + + /* 入栈 */ + void push(LinkedListStack *s, int num) { + ListNode *node = (ListNode *)malloc(sizeof(ListNode)); + node->next = s->top; // 更新新加节点指针域 + node->val = num; // 更新新加节点数据域 + s->top = node; // 更新栈顶 + s->size++; // 更新栈大小 + } + + /* 访问栈顶元素 */ + int peek(LinkedListStack *s) { + if (s->size == 0) { + printf("栈为空\n"); + return INT_MAX; + } + return s->top->val; + } + + /* 出栈 */ + int pop(LinkedListStack *s) { + int val = peek(s); + ListNode *tmp = s->top; + s->top = s->top->next; + // 释放内存 + free(tmp); + s->size--; + return val; + } + ``` + +=== "Zig" + + ```zig title="linkedlist_stack.zig" + // 基于链表实现的栈 + fn LinkedListStack(comptime T: type) type { + return struct { + const Self = @This(); + + stack_top: ?*inc.ListNode(T) = null, // 将头节点作为栈顶 + stk_size: usize = 0, // 栈的长度 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 内存分配器 + + // 构造函数(分配内存+初始化栈) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.stack_top = null; + self.stk_size = 0; + } + + // 析构函数(释放内存) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 获取栈的长度 + pub fn size(self: *Self) usize { + return self.stk_size; + } + + // 判断栈是否为空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 访问栈顶元素 + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("栈为空"); + return self.stack_top.?.val; + } + + // 入栈 + pub fn push(self: *Self, num: T) !void { + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + node.next = self.stack_top; + self.stack_top = node; + self.stk_size += 1; + } + + // 出栈 + pub fn pop(self: *Self) T { + var num = self.peek(); + self.stack_top = self.stack_top.?.next; + self.stk_size -= 1; + return num; + } + + // 将栈转换为数组 + pub fn toArray(self: *Self) ![]T { + var node = self.stack_top; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[res.len - i - 1] = node.?.val; + node = node.?.next; + } + return res; + } + }; + } + ``` + +??? pythontutor "Visualizing Code" + +
+ + +### 2.   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 5-3 , 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) + ![Implementing Stack with Array for Push and Pop Operations](stack.assets/array_stack.png){ class="animation-figure" } === "push()" - ![array_stack_push](stack.assets/array_stack_push.png) + ![array_stack_push](stack.assets/array_stack_push.png){ class="animation-figure" } === "pop()" - ![array_stack_pop](stack.assets/array_stack_pop.png) + ![array_stack_pop](stack.assets/array_stack_pop.png){ class="animation-figure" } + +

Figure 5-3   Implementing Stack with Array for Push and Pop Operations

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]{} -``` +=== "Python" -## Comparison of the Two Implementations + ```python title="array_stack.py" + class ArrayStack: + """基于数组实现的栈""" + + def __init__(self): + """构造方法""" + self._stack: list[int] = [] + + def size(self) -> int: + """获取栈的长度""" + return len(self._stack) + + def is_empty(self) -> bool: + """判断栈是否为空""" + return self._stack == [] + + def push(self, item: int): + """入栈""" + self._stack.append(item) + + def pop(self) -> int: + """出栈""" + if self.is_empty(): + raise IndexError("栈为空") + return self._stack.pop() + + def peek(self) -> int: + """访问栈顶元素""" + if self.is_empty(): + raise IndexError("栈为空") + return self._stack[-1] + + def to_list(self) -> list[int]: + """返回列表用于打印""" + return self._stack + ``` + +=== "C++" + + ```cpp title="array_stack.cpp" + /* 基于数组实现的栈 */ + class ArrayStack { + private: + vector stack; + + public: + /* 获取栈的长度 */ + int size() { + return stack.size(); + } + + /* 判断栈是否为空 */ + bool isEmpty() { + return stack.size() == 0; + } + + /* 入栈 */ + void push(int num) { + stack.push_back(num); + } + + /* 出栈 */ + int pop() { + int num = top(); + stack.pop_back(); + return num; + } + + /* 访问栈顶元素 */ + int top() { + if (isEmpty()) + throw out_of_range("栈为空"); + return stack.back(); + } + + /* 返回 Vector */ + vector toVector() { + return stack; + } + }; + ``` + +=== "Java" + + ```java title="array_stack.java" + /* 基于数组实现的栈 */ + class ArrayStack { + private ArrayList stack; + + public ArrayStack() { + // 初始化列表(动态数组) + stack = new ArrayList<>(); + } + + /* 获取栈的长度 */ + public int size() { + return stack.size(); + } + + /* 判断栈是否为空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入栈 */ + public void push(int num) { + stack.add(num); + } + + /* 出栈 */ + public int pop() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stack.remove(size() - 1); + } + + /* 访问栈顶元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stack.get(size() - 1); + } + + /* 将 List 转化为 Array 并返回 */ + public Object[] toArray() { + return stack.toArray(); + } + } + ``` + +=== "C#" + + ```csharp title="array_stack.cs" + /* 基于数组实现的栈 */ + class ArrayStack { + List stack; + public ArrayStack() { + // 初始化列表(动态数组) + stack = []; + } + + /* 获取栈的长度 */ + public int Size() { + return stack.Count; + } + + /* 判断栈是否为空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入栈 */ + public void Push(int num) { + stack.Add(num); + } + + /* 出栈 */ + public int Pop() { + if (IsEmpty()) + throw new Exception(); + var val = Peek(); + stack.RemoveAt(Size() - 1); + return val; + } + + /* 访问栈顶元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return stack[Size() - 1]; + } + + /* 将 List 转化为 Array 并返回 */ + public int[] ToArray() { + return [.. stack]; + } + } + ``` + +=== "Go" + + ```go title="array_stack.go" + /* 基于数组实现的栈 */ + type arrayStack struct { + data []int // 数据 + } + + /* 初始化栈 */ + func newArrayStack() *arrayStack { + return &arrayStack{ + // 设置栈的长度为 0,容量为 16 + data: make([]int, 0, 16), + } + } + + /* 栈的长度 */ + func (s *arrayStack) size() int { + return len(s.data) + } + + /* 栈是否为空 */ + func (s *arrayStack) isEmpty() bool { + return s.size() == 0 + } + + /* 入栈 */ + func (s *arrayStack) push(v int) { + // 切片会自动扩容 + s.data = append(s.data, v) + } + + /* 出栈 */ + func (s *arrayStack) pop() any { + val := s.peek() + s.data = s.data[:len(s.data)-1] + return val + } + + /* 获取栈顶元素 */ + func (s *arrayStack) peek() any { + if s.isEmpty() { + return nil + } + val := s.data[len(s.data)-1] + return val + } + + /* 获取 Slice 用于打印 */ + func (s *arrayStack) toSlice() []int { + return s.data + } + ``` + +=== "Swift" + + ```swift title="array_stack.swift" + /* 基于数组实现的栈 */ + class ArrayStack { + private var stack: [Int] + + init() { + // 初始化列表(动态数组) + stack = [] + } + + /* 获取栈的长度 */ + func size() -> Int { + stack.count + } + + /* 判断栈是否为空 */ + func isEmpty() -> Bool { + stack.isEmpty + } + + /* 入栈 */ + func push(num: Int) { + stack.append(num) + } + + /* 出栈 */ + @discardableResult + func pop() -> Int { + if isEmpty() { + fatalError("栈为空") + } + return stack.removeLast() + } + + /* 访问栈顶元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("栈为空") + } + return stack.last! + } + + /* 将 List 转化为 Array 并返回 */ + func toArray() -> [Int] { + stack + } + } + ``` + +=== "JS" + + ```javascript title="array_stack.js" + /* 基于数组实现的栈 */ + class ArrayStack { + #stack; + constructor() { + this.#stack = []; + } + + /* 获取栈的长度 */ + get size() { + return this.#stack.length; + } + + /* 判断栈是否为空 */ + isEmpty() { + return this.#stack.length === 0; + } + + /* 入栈 */ + push(num) { + this.#stack.push(num); + } + + /* 出栈 */ + pop() { + if (this.isEmpty()) throw new Error('栈为空'); + return this.#stack.pop(); + } + + /* 访问栈顶元素 */ + top() { + if (this.isEmpty()) throw new Error('栈为空'); + return this.#stack[this.#stack.length - 1]; + } + + /* 返回 Array */ + toArray() { + return this.#stack; + } + } + ``` + +=== "TS" + + ```typescript title="array_stack.ts" + /* 基于数组实现的栈 */ + class ArrayStack { + private stack: number[]; + constructor() { + this.stack = []; + } + + /* 获取栈的长度 */ + get size(): number { + return this.stack.length; + } + + /* 判断栈是否为空 */ + isEmpty(): boolean { + return this.stack.length === 0; + } + + /* 入栈 */ + push(num: number): void { + this.stack.push(num); + } + + /* 出栈 */ + pop(): number | undefined { + if (this.isEmpty()) throw new Error('栈为空'); + return this.stack.pop(); + } + + /* 访问栈顶元素 */ + top(): number | undefined { + if (this.isEmpty()) throw new Error('栈为空'); + return this.stack[this.stack.length - 1]; + } + + /* 返回 Array */ + toArray() { + return this.stack; + } + } + ``` + +=== "Dart" + + ```dart title="array_stack.dart" + /* 基于数组实现的栈 */ + class ArrayStack { + late List _stack; + ArrayStack() { + _stack = []; + } + + /* 获取栈的长度 */ + int size() { + return _stack.length; + } + + /* 判断栈是否为空 */ + bool isEmpty() { + return _stack.isEmpty; + } + + /* 入栈 */ + void push(int _num) { + _stack.add(_num); + } + + /* 出栈 */ + int pop() { + if (isEmpty()) { + throw Exception("栈为空"); + } + return _stack.removeLast(); + } + + /* 访问栈顶元素 */ + int peek() { + if (isEmpty()) { + throw Exception("栈为空"); + } + return _stack.last; + } + + /* 将栈转化为 Array 并返回 */ + List toArray() => _stack; + } + ``` + +=== "Rust" + + ```rust title="array_stack.rs" + /* 基于数组实现的栈 */ + struct ArrayStack { + stack: Vec, + } + + impl ArrayStack { + /* 初始化栈 */ + fn new() -> ArrayStack { + ArrayStack:: { stack: Vec::::new() } + } + + /* 获取栈的长度 */ + fn size(&self) -> usize { + self.stack.len() + } + + /* 判断栈是否为空 */ + fn is_empty(&self) -> bool { + self.size() == 0 + } + + /* 入栈 */ + fn push(&mut self, num: T) { + self.stack.push(num); + } + + /* 出栈 */ + fn pop(&mut self) -> Option { + match self.stack.pop() { + Some(num) => Some(num), + None => None, + } + } + + /* 访问栈顶元素 */ + fn peek(&self) -> Option<&T> { + if self.is_empty() { panic!("栈为空") }; + self.stack.last() + } + + /* 返回 &Vec */ + fn to_array(&self) -> &Vec { + &self.stack + } + } + ``` + +=== "C" + + ```c title="array_stack.c" + /* 基于数组实现的栈 */ + typedef struct { + int *data; + int size; + } ArrayStack; + + /* 构造函数 */ + ArrayStack *newArrayStack() { + ArrayStack *stack = malloc(sizeof(ArrayStack)); + // 初始化一个大容量,避免扩容 + stack->data = malloc(sizeof(int) * MAX_SIZE); + stack->size = 0; + return stack; + } + + /* 析构函数 */ + void delArrayStack(ArrayStack *stack) { + free(stack->data); + free(stack); + } + + /* 获取栈的长度 */ + int size(ArrayStack *stack) { + return stack->size; + } + + /* 判断栈是否为空 */ + bool isEmpty(ArrayStack *stack) { + return stack->size == 0; + } + + /* 入栈 */ + void push(ArrayStack *stack, int num) { + if (stack->size == MAX_SIZE) { + printf("栈已满\n"); + return; + } + stack->data[stack->size] = num; + stack->size++; + } + + /* 访问栈顶元素 */ + int peek(ArrayStack *stack) { + if (stack->size == 0) { + printf("栈为空\n"); + return INT_MAX; + } + return stack->data[stack->size - 1]; + } + + /* 出栈 */ + int pop(ArrayStack *stack) { + int val = peek(stack); + stack->size--; + return val; + } + ``` + +=== "Zig" + + ```zig title="array_stack.zig" + // 基于数组实现的栈 + fn ArrayStack(comptime T: type) type { + return struct { + const Self = @This(); + + stack: ?std.ArrayList(T) = null, + + // 构造方法(分配内存+初始化栈) + pub fn init(self: *Self, allocator: std.mem.Allocator) void { + if (self.stack == null) { + self.stack = std.ArrayList(T).init(allocator); + } + } + + // 析构方法(释放内存) + pub fn deinit(self: *Self) void { + if (self.stack == null) return; + self.stack.?.deinit(); + } + + // 获取栈的长度 + pub fn size(self: *Self) usize { + return self.stack.?.items.len; + } + + // 判断栈是否为空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 访问栈顶元素 + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("栈为空"); + return self.stack.?.items[self.size() - 1]; + } + + // 入栈 + pub fn push(self: *Self, num: T) !void { + try self.stack.?.append(num); + } + + // 出栈 + pub fn pop(self: *Self) T { + var num = self.stack.?.pop(); + return num; + } + + // 返回 ArrayList + pub fn toList(self: *Self) std.ArrayList(T) { + return self.stack.?; + } + }; + } + ``` + +??? pythontutor "Visualizing Code" + +
+ + +## 5.1.3   Comparison of the Two Implementations **Supported Operations** @@ -378,7 +1725,7 @@ However, since linked list nodes require extra space for storing pointers, **the In summary, we cannot simply determine which implementation is more memory-efficient. It requires analysis based on specific circumstances. -## Typical Applications of Stack +## 5.1.4   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. diff --git a/docs-en/chapter_stack_and_queue/summary.md b/docs-en/chapter_stack_and_queue/summary.md index 706434032..0173d6f13 100644 --- a/docs-en/chapter_stack_and_queue/summary.md +++ b/docs-en/chapter_stack_and_queue/summary.md @@ -1,6 +1,10 @@ -# Summary +--- +comments: true +--- -### Key Review +# 5.4   Summary + +### 1.   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. @@ -8,7 +12,7 @@ - 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 +### 2.   Q & A **Q**: Is the browser's forward and backward functionality implemented with a doubly linked list? diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 7f7a6bd2d..a8082e560 100755 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -121,8 +121,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 2.   访问元素 @@ -294,8 +294,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 3.   插入元素 @@ -476,8 +476,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 4.   删除元素 @@ -635,8 +635,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 总的来看,数组的插入与删除操作有以下缺点。 @@ -859,8 +859,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 6.   查找元素 @@ -1027,8 +1027,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 7.   扩容数组 @@ -1237,8 +1237,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 4.1.2   数组的优点与局限性 diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index 8a64cd9c3..72016b527 100755 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -398,8 +398,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 数组整体是一个变量,比如数组 `nums` 包含元素 `nums[0]` 和 `nums[1]` 等,而链表是由多个独立的节点对象组成的。**我们通常将头节点当作链表的代称**,比如以上代码中的链表可记作链表 `n0` 。 @@ -547,8 +547,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 3.   删除节点 @@ -737,8 +737,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 4.   访问节点 @@ -916,8 +916,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 5.   查找节点 @@ -1118,8 +1118,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 4.2.2   数组 vs. 链表 diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index 218e169c8..c3c87e3d9 100755 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -141,8 +141,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 2.   访问元素 @@ -265,8 +265,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 3.   插入与删除元素 @@ -500,8 +500,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 4.   遍历列表 @@ -688,8 +688,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 5.   拼接列表 @@ -794,8 +794,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 6.   排序列表 @@ -886,8 +886,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 4.3.2   列表实现 @@ -2151,5 +2151,5 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ diff --git a/docs/chapter_backtracking/backtracking_algorithm.md b/docs/chapter_backtracking/backtracking_algorithm.md index 448d79bfa..4548f99ba 100644 --- a/docs/chapter_backtracking/backtracking_algorithm.md +++ b/docs/chapter_backtracking/backtracking_algorithm.md @@ -208,8 +208,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ![在前序遍历中搜索节点](backtracking_algorithm.assets/preorder_find_nodes.png){ class="animation-figure" } @@ -479,8 +479,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 在每次“尝试”中,我们通过将当前节点添加进 `path` 来记录路径;而在“回退”前,我们需要将该节点从 `path` 中弹出,**以恢复本次尝试之前的状态**。 @@ -791,8 +791,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ “剪枝”是一个非常形象的名词。如图 13-3 所示,在搜索过程中,**我们“剪掉”了不满足约束条件的搜索分支**,避免许多无意义的尝试,从而提高了搜索效率。 @@ -1672,8 +1672,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 根据题意,我们在找到值为 $7$ 的节点后应该继续搜索,**因此需要将记录解之后的 `return` 语句删除**。图 13-4 对比了保留或删除 `return` 语句的搜索过程。 diff --git a/docs/chapter_backtracking/n_queens_problem.md b/docs/chapter_backtracking/n_queens_problem.md index 9c1c72b8f..84b572626 100644 --- a/docs/chapter_backtracking/n_queens_problem.md +++ b/docs/chapter_backtracking/n_queens_problem.md @@ -664,8 +664,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 逐行放置 $n$ 次,考虑列约束,则从第一行到最后一行分别有 $n$、$n-1$、$\dots$、$2$、$1$ 个选择,**因此时间复杂度为 $O(n!)$** 。实际上,根据对角线约束的剪枝也能够大幅缩小搜索空间,因而搜索效率往往优于以上时间复杂度。 diff --git a/docs/chapter_backtracking/permutations_problem.md b/docs/chapter_backtracking/permutations_problem.md index 7a378527f..950196f76 100644 --- a/docs/chapter_backtracking/permutations_problem.md +++ b/docs/chapter_backtracking/permutations_problem.md @@ -473,8 +473,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 13.2.2   考虑相等元素的情况 @@ -949,8 +949,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 假设元素两两之间互不相同,则 $n$ 个元素共有 $n!$ 种排列(阶乘);在记录结果时,需要复制长度为 $n$ 的列表,使用 $O(n)$ 时间。**因此时间复杂度为 $O(n!n)$** 。 diff --git a/docs/chapter_backtracking/subset_sum_problem.md b/docs/chapter_backtracking/subset_sum_problem.md index ab897c23f..bc3923be5 100644 --- a/docs/chapter_backtracking/subset_sum_problem.md +++ b/docs/chapter_backtracking/subset_sum_problem.md @@ -430,8 +430,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 向以上代码输入数组 $[3, 4, 5]$ 和目标元素 $9$ ,输出结果为 $[3, 3, 3], [4, 5], [5, 4]$ 。**虽然成功找出了所有和为 $9$ 的子集,但其中存在重复的子集 $[4, 5]$ 和 $[5, 4]$** 。 @@ -913,8 +913,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 13-12 所示为将数组 $[3, 4, 5]$ 和目标元素 $9$ 输入以上代码后的整体回溯过程。 @@ -1437,8 +1437,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 13-14 展示了数组 $[4, 4, 5]$ 和目标元素 $9$ 的回溯过程,共包含四种剪枝操作。请你将图示与代码注释相结合,理解整个搜索过程,以及每种剪枝操作是如何工作的。 diff --git a/docs/chapter_computational_complexity/iteration_and_recursion.md b/docs/chapter_computational_complexity/iteration_and_recursion.md index c5e938ec9..98a3f369c 100644 --- a/docs/chapter_computational_complexity/iteration_and_recursion.md +++ b/docs/chapter_computational_complexity/iteration_and_recursion.md @@ -184,8 +184,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 2-1 是该求和函数的流程框图。 @@ -395,8 +395,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ **`while` 循环比 `for` 循环的自由度更高**。在 `while` 循环中,我们可以自由地设计条件变量的初始化和更新步骤。 @@ -619,8 +619,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 总的来说,**`for` 循环的代码更加紧凑,`while` 循环更加灵活**,两者都可以实现迭代结构。选择使用哪一个应该根据特定问题的需求来决定。 @@ -838,8 +838,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 2-2 是该嵌套循环的流程框图。 @@ -1048,8 +1048,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 2-3 展示了该函数的递归过程。 @@ -1249,8 +1249,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 尾递归的执行过程如图 2-5 所示。对比普通递归和尾递归,两者的求和操作的执行点是不同的。 @@ -1462,8 +1462,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 观察以上代码,我们在函数内递归调用了两个函数,**这意味着从一个调用产生了两个调用分支**。如图 2-6 所示,这样不断递归调用下去,最终将产生一棵层数为 $n$ 的「递归树 recursion tree」。 @@ -1785,8 +1785,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 观察以上代码,当递归转化为迭代后,代码变得更加复杂了。尽管迭代和递归在很多情况下可以互相转化,但不一定值得这样做,有以下两点原因。 diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index be097c5e0..7b48b00ef 100755 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -1063,8 +1063,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 2.   线性阶 $O(n)$ @@ -1332,8 +1332,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 如图 2-17 所示,此函数的递归深度为 $n$ ,即同时存在 $n$ 个未返回的 `linear_recur()` 函数,使用 $O(n)$ 大小的栈帧空间: @@ -1478,8 +1478,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ![递归函数产生的线性阶空间复杂度](space_complexity.assets/space_complexity_recursive_linear.png){ class="animation-figure" } @@ -1703,8 +1703,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 如图 2-18 所示,该函数的递归深度为 $n$ ,在每个递归函数中都初始化了一个数组,长度分别为 $n$、$n-1$、$\dots$、$2$、$1$ ,平均长度为 $n / 2$ ,因此总体占用 $O(n^2)$ 空间: @@ -1867,8 +1867,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ![递归函数产生的平方阶空间复杂度](space_complexity.assets/space_complexity_recursive_quadratic.png){ class="animation-figure" } @@ -2045,8 +2045,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ![满二叉树产生的指数阶空间复杂度](space_complexity.assets/space_complexity_exponential.png){ class="animation-figure" } diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index 24f21f52b..8d2782cb4 100755 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -1125,8 +1125,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 2.   线性阶 $O(n)$ @@ -1282,8 +1282,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 遍历数组和遍历链表等操作的时间复杂度均为 $O(n)$ ,其中 $n$ 为数组或链表的长度: @@ -1455,8 +1455,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 值得注意的是,**输入数据大小 $n$ 需根据输入数据的类型来具体确定**。比如在第一个示例中,变量 $n$ 为输入数据大小;在第二个示例中,数组长度 $n$ 为数据大小。 @@ -1657,8 +1657,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 2-10 对比了常数阶、线性阶和平方阶三种时间复杂度。 @@ -1942,8 +1942,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 4.   指数阶 $O(2^n)$ @@ -2175,8 +2175,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ![指数阶的时间复杂度](time_complexity.assets/time_complexity_exponential.png){ class="animation-figure" } @@ -2315,8 +2315,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 指数阶增长非常迅速,在穷举法(暴力搜索、回溯等)中比较常见。对于数据规模较大的问题,指数阶是不可接受的,通常需要使用动态规划或贪心算法等来解决。 @@ -2497,8 +2497,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ![对数阶的时间复杂度](time_complexity.assets/time_complexity_logarithmic.png){ class="animation-figure" } @@ -2637,8 +2637,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 对数阶常出现于基于分治策略的算法中,体现了“一分为多”和“化繁为简”的算法思想。它增长缓慢,是仅次于常数阶的理想的时间复杂度。 @@ -2835,8 +2835,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 2-13 展示了线性对数阶的生成方式。二叉树的每一层的操作总数都为 $n$ ,树共有 $\log_2 n + 1$ 层,因此时间复杂度为 $O(n \log n)$ 。 @@ -3046,8 +3046,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ![阶乘阶的时间复杂度](time_complexity.assets/time_complexity_factorial.png){ class="animation-figure" } @@ -3413,8 +3413,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 值得说明的是,我们在实际中很少使用最佳时间复杂度,因为通常只有在很小概率下才能达到,可能会带来一定的误导性。**而最差时间复杂度更为实用,因为它给出了一个效率安全值**,让我们可以放心地使用算法。 diff --git a/docs/chapter_data_structure/basic_data_types.md b/docs/chapter_data_structure/basic_data_types.md index 21e2b2a05..652a1e375 100644 --- a/docs/chapter_data_structure/basic_data_types.md +++ b/docs/chapter_data_structure/basic_data_types.md @@ -169,5 +169,5 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ diff --git a/docs/chapter_divide_and_conquer/binary_search_recur.md b/docs/chapter_divide_and_conquer/binary_search_recur.md index 173d0a376..938787cdf 100644 --- a/docs/chapter_divide_and_conquer/binary_search_recur.md +++ b/docs/chapter_divide_and_conquer/binary_search_recur.md @@ -392,5 +392,5 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/docs/chapter_divide_and_conquer/build_binary_tree_problem.md index 9e7725ff1..c89ae45fe 100644 --- a/docs/chapter_divide_and_conquer/build_binary_tree_problem.md +++ b/docs/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -447,8 +447,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 12-8 展示了构建二叉树的递归过程,各个节点是在向下“递”的过程中建立的,而各条边(引用)是在向上“归”的过程中建立的。 diff --git a/docs/chapter_divide_and_conquer/hanota_problem.md b/docs/chapter_divide_and_conquer/hanota_problem.md index 917e7575d..9a1abd336 100644 --- a/docs/chapter_divide_and_conquer/hanota_problem.md +++ b/docs/chapter_divide_and_conquer/hanota_problem.md @@ -485,8 +485,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 如图 12-15 所示,汉诺塔问题形成一棵高度为 $n$ 的递归树,每个节点代表一个子问题,对应一个开启的 `dfs()` 函数,**因此时间复杂度为 $O(2^n)$ ,空间复杂度为 $O(n)$** 。 diff --git a/docs/chapter_dynamic_programming/dp_problem_features.md b/docs/chapter_dynamic_programming/dp_problem_features.md index 0bcd0bc0e..a4cc48cdf 100644 --- a/docs/chapter_dynamic_programming/dp_problem_features.md +++ b/docs/chapter_dynamic_programming/dp_problem_features.md @@ -303,8 +303,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 14-7 展示了以上代码的动态规划过程。 @@ -541,8 +541,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 14.2.2   无后效性 @@ -878,8 +878,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 在上面的案例中,由于仅需多考虑前面一个状态,因此我们仍然可以通过扩展状态定义,使得问题重新满足无后效性。然而,某些问题具有非常严重的“有后效性”。 diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.md b/docs/chapter_dynamic_programming/dp_solution_pipeline.md index 5a586198e..09174da92 100644 --- a/docs/chapter_dynamic_programming/dp_solution_pipeline.md +++ b/docs/chapter_dynamic_programming/dp_solution_pipeline.md @@ -368,8 +368,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 14-14 给出了以 $dp[2, 1]$ 为根节点的递归树,其中包含一些重叠子问题,其数量会随着网格 `grid` 的尺寸变大而急剧增多。 @@ -704,8 +704,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 如图 14-15 所示,在引入记忆化后,所有子问题的解只需计算一次,因此时间复杂度取决于状态总数,即网格尺寸 $O(nm)$ 。 @@ -1056,8 +1056,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 14-16 展示了最小路径和的状态转移过程,其遍历了整个网格,**因此时间复杂度为 $O(nm)$** 。 @@ -1421,5 +1421,5 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.md b/docs/chapter_dynamic_programming/edit_distance_problem.md index 57d68008e..201ca05bb 100644 --- a/docs/chapter_dynamic_programming/edit_distance_problem.md +++ b/docs/chapter_dynamic_programming/edit_distance_problem.md @@ -452,8 +452,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 如图 14-30 所示,编辑距离问题的状态转移过程与背包问题非常类似,都可以看作填写一个二维网格的过程。 @@ -910,5 +910,5 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ diff --git a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md index ddb619ce9..d1f1e1384 100644 --- a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md +++ b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -387,8 +387,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 14.1.1   方法一:暴力搜索 @@ -645,8 +645,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 14-3 展示了暴力搜索形成的递归树。对于问题 $dp[n]$ ,其递归树的深度为 $n$ ,时间复杂度为 $O(2^n)$ 。指数阶属于爆炸式增长,如果我们输入一个比较大的 $n$ ,则会陷入漫长的等待之中。 @@ -987,8 +987,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 观察图 14-4 ,**经过记忆化处理后,所有重叠子问题都只需计算一次,时间复杂度优化至 $O(n)$** ,这是一个巨大的飞跃。 @@ -1246,8 +1246,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 14-5 模拟了以上代码的执行过程。 @@ -1469,8 +1469,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 观察以上代码,由于省去了数组 `dp` 占用的空间,因此空间复杂度从 $O(n)$ 降至 $O(1)$ 。 diff --git a/docs/chapter_dynamic_programming/knapsack_problem.md b/docs/chapter_dynamic_programming/knapsack_problem.md index 08d0c56f7..b48b9887e 100644 --- a/docs/chapter_dynamic_programming/knapsack_problem.md +++ b/docs/chapter_dynamic_programming/knapsack_problem.md @@ -318,8 +318,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 如图 14-18 所示,由于每个物品都会产生不选和选两条搜索分支,因此时间复杂度为 $O(2^n)$ 。 @@ -661,8 +661,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 14-19 展示了在记忆化搜索中被剪掉的搜索分支。 @@ -985,8 +985,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 如图 14-20 所示,时间复杂度和空间复杂度都由数组 `dp` 大小决定,即 $O(n \times cap)$ 。 @@ -1343,5 +1343,5 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md index fd2b3f071..e1f86a78a 100644 --- a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md +++ b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md @@ -349,8 +349,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 3.   空间优化 @@ -674,8 +674,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 14.5.2   零钱兑换问题 @@ -1094,8 +1094,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 图 14-25 展示了零钱兑换的动态规划过程,和完全背包问题非常相似。 @@ -1478,8 +1478,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 14.5.3   零钱兑换问题 II @@ -1854,8 +1854,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 3.   空间优化 @@ -2164,5 +2164,5 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ diff --git a/docs/chapter_graph/graph_operations.md b/docs/chapter_graph/graph_operations.md index 0d021481c..7cbaaa226 100644 --- a/docs/chapter_graph/graph_operations.md +++ b/docs/chapter_graph/graph_operations.md @@ -1047,8 +1047,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 9.2.2   基于邻接表的实现 @@ -2072,8 +2072,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 9.2.3   效率对比 diff --git a/docs/chapter_graph/graph_traversal.md b/docs/chapter_graph/graph_traversal.md index 9331e1b55..438079dda 100644 --- a/docs/chapter_graph/graph_traversal.md +++ b/docs/chapter_graph/graph_traversal.md @@ -420,8 +420,8 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先 ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 代码相对抽象,建议对照图 9-10 来加深理解。 @@ -828,8 +828,8 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先 ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 深度优先遍历的算法流程如图 9-12 所示。 diff --git a/docs/chapter_greedy/fractional_knapsack_problem.md b/docs/chapter_greedy/fractional_knapsack_problem.md index 5171dd476..74c924c43 100644 --- a/docs/chapter_greedy/fractional_knapsack_problem.md +++ b/docs/chapter_greedy/fractional_knapsack_problem.md @@ -466,8 +466,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 除排序之外,在最差情况下,需要遍历整个物品列表,**因此时间复杂度为 $O(n)$** ,其中 $n$ 为物品数量。 diff --git a/docs/chapter_greedy/greedy_algorithm.md b/docs/chapter_greedy/greedy_algorithm.md index a409fcd03..9924a8022 100644 --- a/docs/chapter_greedy/greedy_algorithm.md +++ b/docs/chapter_greedy/greedy_algorithm.md @@ -291,8 +291,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 你可能会不由地发出感叹:So clean !贪心算法仅用约十行代码就解决了零钱兑换问题。 diff --git a/docs/chapter_greedy/max_capacity_problem.md b/docs/chapter_greedy/max_capacity_problem.md index 7268cb9bc..13f80ac58 100644 --- a/docs/chapter_greedy/max_capacity_problem.md +++ b/docs/chapter_greedy/max_capacity_problem.md @@ -376,8 +376,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 3.   正确性证明 diff --git a/docs/chapter_greedy/max_product_cutting_problem.md b/docs/chapter_greedy/max_product_cutting_problem.md index 43c6d650c..9a74ba2f2 100644 --- a/docs/chapter_greedy/max_product_cutting_problem.md +++ b/docs/chapter_greedy/max_product_cutting_problem.md @@ -351,8 +351,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ![最大切分乘积的计算方法](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png){ class="animation-figure" } diff --git a/docs/chapter_hashing/hash_algorithm.md b/docs/chapter_hashing/hash_algorithm.md index 4a51db2e3..8ed1a7fcb 100644 --- a/docs/chapter_hashing/hash_algorithm.md +++ b/docs/chapter_hashing/hash_algorithm.md @@ -568,8 +568,8 @@ index = hash(key) % capacity ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 观察发现,每种哈希算法的最后一步都是对大质数 $1000000007$ 取模,以确保哈希值在合适的范围内。值得思考的是,为什么要强调对质数取模,或者说对合数取模的弊端是什么?这是一个有趣的问题。 @@ -877,8 +877,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 在许多编程语言中,**只有不可变对象才可作为哈希表的 `key`** 。假如我们将列表(动态数组)作为 `key` ,当列表的内容发生变化时,它的哈希值也随之改变,我们就无法在哈希表中查询到原先的 `value` 了。 diff --git a/docs/chapter_hashing/hash_collision.md b/docs/chapter_hashing/hash_collision.md index 608e7f136..43bbeb51c 100644 --- a/docs/chapter_hashing/hash_collision.md +++ b/docs/chapter_hashing/hash_collision.md @@ -1318,8 +1318,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 值得注意的是,当链表很长时,查询效率 $O(n)$ 很差。**此时可以将链表转换为“AVL 树”或“红黑树”**,从而将查询操作的时间复杂度优化至 $O(\log n)$ 。 diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index 97dde1482..680d031bc 100755 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -285,8 +285,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 哈希表有三种常用的遍历方式:遍历键值对、遍历键和遍历值。示例代码如下: @@ -481,8 +481,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 6.1.2   哈希表简单实现 @@ -1645,8 +1645,8 @@ index = hash(key) % capacity ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 6.1.3   哈希冲突与扩容 diff --git a/docs/chapter_heap/build_heap.md b/docs/chapter_heap/build_heap.md index db2bddb81..6d9c198fa 100644 --- a/docs/chapter_heap/build_heap.md +++ b/docs/chapter_heap/build_heap.md @@ -204,8 +204,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 8.2.3   复杂度分析 diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md index 50fe00552..d43f3a998 100644 --- a/docs/chapter_heap/heap.md +++ b/docs/chapter_heap/heap.md @@ -355,8 +355,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 8.1.2   堆的实现 @@ -716,8 +716,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 3.   元素入堆 @@ -1094,8 +1094,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 4.   堆顶元素出堆 @@ -1614,8 +1614,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 8.1.3   堆的常见应用 diff --git a/docs/chapter_heap/top_k.md b/docs/chapter_heap/top_k.md index c8e6b6753..c1f0bc4dc 100644 --- a/docs/chapter_heap/top_k.md +++ b/docs/chapter_heap/top_k.md @@ -419,8 +419,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 总共执行了 $n$ 轮入堆和出堆,堆的最大长度为 $k$ ,因此时间复杂度为 $O(n \log k)$ 。该方法的效率很高,当 $k$ 较小时,时间复杂度趋向 $O(n)$ ;当 $k$ 较大时,时间复杂度不会超过 $O(n \log n)$ 。 diff --git a/docs/chapter_searching/binary_search.md b/docs/chapter_searching/binary_search.md index 973e1360d..164336676 100755 --- a/docs/chapter_searching/binary_search.md +++ b/docs/chapter_searching/binary_search.md @@ -336,8 +336,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ **时间复杂度为 $O(\log n)$** :在二分循环中,区间每轮缩小一半,循环次数为 $\log_2 n$ 。 @@ -632,8 +632,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 如图 10-3 所示,在两种区间表示下,二分查找算法的初始化、循环条件和缩小区间操作皆有所不同。 diff --git a/docs/chapter_searching/binary_search_edge.md b/docs/chapter_searching/binary_search_edge.md index 046e8a2a9..a40825c2d 100644 --- a/docs/chapter_searching/binary_search_edge.md +++ b/docs/chapter_searching/binary_search_edge.md @@ -201,8 +201,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 10.3.2   查找右边界 @@ -426,8 +426,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 2.   转化为查找元素 diff --git a/docs/chapter_searching/binary_search_insertion.md b/docs/chapter_searching/binary_search_insertion.md index a32bfea4d..78e64af05 100644 --- a/docs/chapter_searching/binary_search_insertion.md +++ b/docs/chapter_searching/binary_search_insertion.md @@ -274,8 +274,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 10.2.2   存在重复元素的情况 @@ -576,8 +576,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ !!! tip diff --git a/docs/chapter_searching/replace_linear_by_hashing.md b/docs/chapter_searching/replace_linear_by_hashing.md index c78391c73..33b7a8306 100755 --- a/docs/chapter_searching/replace_linear_by_hashing.md +++ b/docs/chapter_searching/replace_linear_by_hashing.md @@ -231,8 +231,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 此方法的时间复杂度为 $O(n^2)$ ,空间复杂度为 $O(1)$ ,在大数据量下非常耗时。 @@ -510,8 +510,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 此方法通过哈希查找将时间复杂度从 $O(n^2)$ 降至 $O(n)$ ,大幅提升运行效率。 diff --git a/docs/chapter_sorting/bubble_sort.md b/docs/chapter_sorting/bubble_sort.md index c65f97752..e8c461a9a 100755 --- a/docs/chapter_sorting/bubble_sort.md +++ b/docs/chapter_sorting/bubble_sort.md @@ -279,8 +279,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 11.3.2   效率优化 @@ -564,8 +564,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 11.3.3   算法特性 diff --git a/docs/chapter_sorting/bucket_sort.md b/docs/chapter_sorting/bucket_sort.md index 420c5da74..2d8376fce 100644 --- a/docs/chapter_sorting/bucket_sort.md +++ b/docs/chapter_sorting/bucket_sort.md @@ -396,8 +396,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 11.8.2   算法特性 diff --git a/docs/chapter_sorting/counting_sort.md b/docs/chapter_sorting/counting_sort.md index dd0d6b76f..f7a7c1417 100644 --- a/docs/chapter_sorting/counting_sort.md +++ b/docs/chapter_sorting/counting_sort.md @@ -323,8 +323,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ !!! note "计数排序与桶排序的联系" @@ -785,8 +785,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 11.9.3   算法特性 diff --git a/docs/chapter_sorting/heap_sort.md b/docs/chapter_sorting/heap_sort.md index dd8fb0fae..8a6db3d70 100644 --- a/docs/chapter_sorting/heap_sort.md +++ b/docs/chapter_sorting/heap_sort.md @@ -544,8 +544,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 11.7.2   算法特性 diff --git a/docs/chapter_sorting/insertion_sort.md b/docs/chapter_sorting/insertion_sort.md index 6d9239861..bd24e05ec 100755 --- a/docs/chapter_sorting/insertion_sort.md +++ b/docs/chapter_sorting/insertion_sort.md @@ -252,8 +252,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 11.4.2   算法特性 diff --git a/docs/chapter_sorting/merge_sort.md b/docs/chapter_sorting/merge_sort.md index 4164a4967..68b6aea38 100755 --- a/docs/chapter_sorting/merge_sort.md +++ b/docs/chapter_sorting/merge_sort.md @@ -635,8 +635,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 11.6.2   算法特性 diff --git a/docs/chapter_sorting/quick_sort.md b/docs/chapter_sorting/quick_sort.md index ceca7a5a1..5c57aac63 100755 --- a/docs/chapter_sorting/quick_sort.md +++ b/docs/chapter_sorting/quick_sort.md @@ -360,8 +360,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 11.5.1   算法流程 @@ -593,8 +593,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 11.5.2   算法特性 @@ -1054,8 +1054,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 11.5.5   尾递归优化 @@ -1319,5 +1319,5 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ diff --git a/docs/chapter_sorting/radix_sort.md b/docs/chapter_sorting/radix_sort.md index fe66d4a48..2b9c02087 100644 --- a/docs/chapter_sorting/radix_sort.md +++ b/docs/chapter_sorting/radix_sort.md @@ -686,8 +686,8 @@ $$ ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ !!! question "为什么从最低位开始排序?" diff --git a/docs/chapter_sorting/selection_sort.md b/docs/chapter_sorting/selection_sort.md index 4c7eef4dc..7fafc6e6c 100644 --- a/docs/chapter_sorting/selection_sort.md +++ b/docs/chapter_sorting/selection_sort.md @@ -286,8 +286,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 11.2.1   算法特性 diff --git a/docs/chapter_stack_and_queue/deque.md b/docs/chapter_stack_and_queue/deque.md index 923dfa894..5c3aa8596 100644 --- a/docs/chapter_stack_and_queue/deque.md +++ b/docs/chapter_stack_and_queue/deque.md @@ -342,8 +342,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 5.3.2   双向队列实现 * diff --git a/docs/chapter_stack_and_queue/queue.md b/docs/chapter_stack_and_queue/queue.md index 474a6e633..1a14b375c 100755 --- a/docs/chapter_stack_and_queue/queue.md +++ b/docs/chapter_stack_and_queue/queue.md @@ -320,8 +320,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 5.2.2   队列实现 @@ -1214,8 +1214,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 2.   基于数组的实现 @@ -2130,8 +2130,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 以上实现的队列仍然具有局限性:其长度不可变。然而,这个问题不难解决,我们可以将数组替换为动态数组,从而引入扩容机制。有兴趣的读者可以尝试自行实现。 diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md index 552e246a1..6ad379018 100755 --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -314,8 +314,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 5.1.2   栈的实现 @@ -1086,8 +1086,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 2.   基于数组的实现 @@ -1697,8 +1697,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 5.1.3   两种实现对比 diff --git a/docs/chapter_tree/array_representation_of_tree.md b/docs/chapter_tree/array_representation_of_tree.md index fc3679cf5..9e2fb4576 100644 --- a/docs/chapter_tree/array_representation_of_tree.md +++ b/docs/chapter_tree/array_representation_of_tree.md @@ -1163,8 +1163,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ## 7.3.3   优点与局限性 diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index 7067dd2d4..db5fa10c9 100755 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -316,8 +316,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 2.   插入节点 @@ -745,8 +745,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ 与查找节点相同,插入节点使用 $O(\log n)$ 时间。 @@ -1467,8 +1467,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 4.   中序遍历有序 diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index 2a0649847..5a4544484 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -413,8 +413,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 2.   插入与删除节点 @@ -561,8 +561,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ !!! note diff --git a/docs/chapter_tree/binary_tree_traversal.md b/docs/chapter_tree/binary_tree_traversal.md index 6aaa49e12..7d81bbdd1 100755 --- a/docs/chapter_tree/binary_tree_traversal.md +++ b/docs/chapter_tree/binary_tree_traversal.md @@ -326,8 +326,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ ### 2.   复杂度分析 @@ -761,8 +761,8 @@ comments: true ??? pythontutor "可视化运行" -
- 全屏观看 > +
+ !!! tip