Add animation player (#1877)

* Add auto slide controller.

* Fix the animation blocks.

* renamed as animation_player

* Bug fixes

* Refine animation player controls
This commit is contained in:
Yudong Jin
2026-03-31 21:24:11 +08:00
committed by GitHub
parent e3c74cfa01
commit 6e600f5ba7
27 changed files with 597 additions and 160 deletions

View File

@@ -11,19 +11,19 @@
- **删除顶点**:在邻接矩阵中删除一行一列。当删除首行首列时达到最差情况,需要将 $(n-1)^2$ 个元素“向左上移动”,从而使用 $O(n^2)$ 时间。
- **初始化**:传入 $n$ 个顶点,初始化长度为 $n$ 的顶点列表 `vertices` ,使用 $O(n)$ 时间;初始化 $n \times n$ 大小的邻接矩阵 `adjMat` ,使用 $O(n^2)$ 时间。
=== "初始化邻接矩阵"
=== "<1>"
![邻接矩阵的初始化、增删边、增删顶点](graph_operations.assets/adjacency_matrix_step1_initialization.png)
=== "添加边"
=== "<2>"
![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png)
=== "删除边"
=== "<3>"
![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png)
=== "添加顶点"
=== "<4>"
![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png)
=== "删除顶点"
=== "<5>"
![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png)
以下是基于邻接矩阵表示图的实现代码:
@@ -42,19 +42,19 @@
- **删除顶点**:需遍历整个邻接表,删除包含指定顶点的所有边,使用 $O(n + m)$ 时间。
- **初始化**:在邻接表中创建 $n$ 个顶点和 $2m$ 条边,使用 $O(n + m)$ 时间。
=== "初始化邻接表"
=== "<1>"
![邻接表的初始化、增删边、增删顶点](graph_operations.assets/adjacency_list_step1_initialization.png)
=== "添加边"
=== "<2>"
![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png)
=== "删除边"
=== "<3>"
![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png)
=== "添加顶点"
=== "<4>"
![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png)
=== "删除顶点"
=== "<5>"
![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png)
以下是邻接表的代码实现。对比上图,实际代码有以下不同。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -399,19 +399,19 @@
如下图所示,我们将双向链表的头节点和尾节点视为双向队列的队首和队尾,同时实现在两端添加和删除节点的功能。
=== "LinkedListDeque"
=== "<1>"
![基于链表实现双向队列的入队出队操作](deque.assets/linkedlist_deque_step1.png)
=== "push_last()"
=== "<2>"
![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png)
=== "push_first()"
=== "<3>"
![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png)
=== "pop_last()"
=== "<4>"
![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png)
=== "pop_first()"
=== "<5>"
![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png)
实现代码如下所示:
@@ -424,19 +424,19 @@
如下图所示,与基于数组实现队列类似,我们也可以使用环形数组来实现双向队列。
=== "ArrayDeque"
=== "<1>"
![基于数组实现双向队列的入队出队操作](deque.assets/array_deque_step1.png)
=== "push_last()"
=== "<2>"
![array_deque_push_last](deque.assets/array_deque_step2_push_last.png)
=== "push_first()"
=== "<3>"
![array_deque_push_first](deque.assets/array_deque_step3_push_first.png)
=== "pop_last()"
=== "<4>"
![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png)
=== "pop_first()"
=== "<5>"
![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png)
在队列的实现基础上,仅需增加“队首入队”和“队尾出队”的方法:

View File

@@ -368,13 +368,13 @@
如下图所示,我们可以将链表的“头节点”和“尾节点”分别视为“队首”和“队尾”,规定队尾仅可添加节点,队首仅可删除节点。
=== "LinkedListQueue"
=== "<1>"
![基于链表实现队列的入队出队操作](queue.assets/linkedlist_queue_step1.png)
=== "push()"
=== "<2>"
![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png)
=== "pop()"
=== "<3>"
![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png)
以下是用链表实现队列的代码:
@@ -396,13 +396,13 @@
可以看到,入队和出队操作都只需进行一次操作,时间复杂度均为 $O(1)$ 。
=== "ArrayQueue"
=== "<1>"
![基于数组实现队列的入队出队操作](queue.assets/array_queue_step1.png)
=== "push()"
=== "<2>"
![array_queue_push](queue.assets/array_queue_step2_push.png)
=== "pop()"
=== "<3>"
![array_queue_pop](queue.assets/array_queue_step3_pop.png)
你可能会发现一个问题:在不断进行入队和出队的过程中,`front` 和 `rear` 都在向右移动,**当它们到达数组尾部时就无法继续移动了**。为了解决此问题,我们可以将数组视为首尾相接的“环形数组”。

View File

@@ -365,13 +365,13 @@
如下图所示,对于入栈操作,我们只需将元素插入链表头部,这种节点插入方法被称为“头插法”。而对于出栈操作,只需将头节点从链表中删除即可。
=== "LinkedListStack"
=== "<1>"
![基于链表实现栈的入栈出栈操作](stack.assets/linkedlist_stack_step1.png)
=== "push()"
=== "<2>"
![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png)
=== "pop()"
=== "<3>"
![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png)
以下是基于链表实现栈的示例代码:
@@ -384,13 +384,13 @@
使用数组实现栈时,我们可以将数组的尾部作为栈顶。如下图所示,入栈与出栈操作分别对应在数组尾部添加元素与删除元素,时间复杂度都为 $O(1)$ 。
=== "ArrayStack"
=== "<1>"
![基于数组实现栈的入栈出栈操作](stack.assets/array_stack_step1.png)
=== "push()"
=== "<2>"
![array_stack_push](stack.assets/array_stack_step2_push.png)
=== "pop()"
=== "<3>"
![array_stack_pop](stack.assets/array_stack_step3_pop.png)
由于入栈的元素可能会源源不断地增加,因此我们可以使用动态数组,这样就无须自行处理数组扩容问题。以下为示例代码:

View File

@@ -11,19 +11,19 @@ Given an undirected graph with $n$ vertices, the various operations are implemen
- **Removing a vertex**: Delete a row and a column in the adjacency matrix. The worst case occurs when removing the first row and column, requiring $(n-1)^2$ elements to be "moved up and to the left", thus using $O(n^2)$ time.
- **Initialization**: Pass in $n$ vertices, initialize a vertex list `vertices` of length $n$, using $O(n)$ time; initialize an adjacency matrix `adjMat` of size $n \times n$, using $O(n^2)$ time.
=== "Initialize adjacency matrix"
=== "<1>"
![Initialization, adding and removing edges, adding and removing vertices in adjacency matrix](graph_operations.assets/adjacency_matrix_step1_initialization.png)
=== "Add an edge"
=== "<2>"
![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png)
=== "Remove an edge"
=== "<3>"
![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png)
=== "Add a vertex"
=== "<4>"
![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png)
=== "Remove a vertex"
=== "<5>"
![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png)
The following is the implementation code for graphs represented using an adjacency matrix:
@@ -42,19 +42,19 @@ Given an undirected graph with a total of $n$ vertices and $m$ edges, the variou
- **Removing a vertex**: Traverse the entire adjacency list and remove all edges containing the specified vertex, using $O(n + m)$ time.
- **Initialization**: Create $n$ vertices and $2m$ edges in the adjacency list, using $O(n + m)$ time.
=== "Initialize adjacency list"
=== "<1>"
![Initialization, adding and removing edges, adding and removing vertices in adjacency list](graph_operations.assets/adjacency_list_step1_initialization.png)
=== "Add an edge"
=== "<2>"
![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png)
=== "Remove an edge"
=== "<3>"
![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png)
=== "Add a vertex"
=== "<4>"
![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png)
=== "Remove a vertex"
=== "<5>"
![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png)
The following is the adjacency list code implementation. Compared to the figure above, the actual code has the following differences.

View File

@@ -399,19 +399,19 @@ For a deque, both the front and rear can perform enqueue and dequeue operations.
As shown in the figure below, we treat the head and tail nodes of the doubly linked list as the front and rear of the deque, implementing functionality to add and remove nodes at both ends.
=== "LinkedListDeque"
=== "<1>"
![Enqueue and dequeue operations in linked list implementation of deque](deque.assets/linkedlist_deque_step1.png)
=== "push_last()"
=== "<2>"
![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png)
=== "push_first()"
=== "<3>"
![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png)
=== "pop_last()"
=== "<4>"
![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png)
=== "pop_first()"
=== "<5>"
![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png)
The implementation code is shown below:
@@ -424,19 +424,19 @@ The implementation code is shown below:
As shown in the figure below, similar to implementing a queue based on an array, we can also use a circular array to implement a deque.
=== "ArrayDeque"
=== "<1>"
![Enqueue and dequeue operations in array implementation of deque](deque.assets/array_deque_step1.png)
=== "push_last()"
=== "<2>"
![array_deque_push_last](deque.assets/array_deque_step2_push_last.png)
=== "push_first()"
=== "<3>"
![array_deque_push_first](deque.assets/array_deque_step3_push_first.png)
=== "pop_last()"
=== "<4>"
![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png)
=== "pop_first()"
=== "<5>"
![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png)
Based on the queue implementation, we only need to add methods for "enqueue at front" and "dequeue from rear":

View File

@@ -368,13 +368,13 @@ To implement a queue, we need a data structure that allows adding elements at on
As shown in the figure below, we can treat the "head node" and "tail node" of a linked list as the "front" and "rear" of the queue, respectively, with the rule that nodes can only be added at the rear and removed from the front.
=== "LinkedListQueue"
=== "<1>"
![Enqueue and dequeue operations in linked list implementation of queue](queue.assets/linkedlist_queue_step1.png)
=== "push()"
=== "<2>"
![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png)
=== "pop()"
=== "<3>"
![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png)
Below is the code for implementing a queue using a linked list:
@@ -396,13 +396,13 @@ Based on this design, **the valid interval containing elements in the array is `
As you can see, both enqueue and dequeue operations require only one operation, with a time complexity of $O(1)$.
=== "ArrayQueue"
=== "<1>"
![Enqueue and dequeue operations in array implementation of queue](queue.assets/array_queue_step1.png)
=== "push()"
=== "<2>"
![array_queue_push](queue.assets/array_queue_step2_push.png)
=== "pop()"
=== "<3>"
![array_queue_pop](queue.assets/array_queue_step3_pop.png)
You may notice a problem: as we continuously enqueue and dequeue, both `front` and `rear` move to the right. **When they reach the end of the array, they cannot continue moving**. To solve this problem, we can treat the array as a "circular array" with head and tail connected.

View File

@@ -365,13 +365,13 @@ When implementing a stack using a linked list, we can treat the head node of the
As shown in the figure below, for the push operation, we simply insert an element at the head of the linked list. This node insertion method is called the "head insertion method." For the pop operation, we just need to remove the head node from the linked list.
=== "LinkedListStack"
=== "<1>"
![Push and pop operations in linked list implementation of stack](stack.assets/linkedlist_stack_step1.png)
=== "push()"
=== "<2>"
![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png)
=== "pop()"
=== "<3>"
![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png)
Below is sample code for implementing a stack based on a linked list:
@@ -384,13 +384,13 @@ Below is sample code for implementing a stack based on a linked list:
When implementing a stack using an array, we can treat the end of the array as the top of the stack. As shown in the figure below, push and pop operations correspond to adding and removing elements at the end of the array, both with a time complexity of $O(1)$.
=== "ArrayStack"
=== "<1>"
![Push and pop operations in array implementation of stack](stack.assets/array_stack_step1.png)
=== "push()"
=== "<2>"
![array_stack_push](stack.assets/array_stack_step2_push.png)
=== "pop()"
=== "<3>"
![array_stack_pop](stack.assets/array_stack_step3_pop.png)
Since elements pushed onto the stack may increase continuously, we can use a dynamic array, which eliminates the need to handle array expansion ourselves. Here is the sample code:

View File

@@ -11,19 +11,19 @@
- **頂点の削除**:隣接行列から 1 行 1 列を削除します。先頭行と先頭列を削除する場合が最悪で、$(n-1)^2$ 個の要素を「左上へ移動」させる必要があるため、$O(n^2)$ 時間です。
- **初期化**$n$ 個の頂点を受け取り、長さ $n$ の頂点リスト `vertices` を初期化するのに $O(n)$ 時間、サイズ $n \times n$ の隣接行列 `adjMat` を初期化するのに $O(n^2)$ 時間かかります。
=== "隣接行列の初期化"
=== "<1>"
![隣接行列の初期化、辺の追加と削除、頂点の追加と削除](graph_operations.assets/adjacency_matrix_step1_initialization.png)
=== "辺の追加"
=== "<2>"
![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png)
=== "辺の削除"
=== "<3>"
![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png)
=== "頂点の追加"
=== "<4>"
![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png)
=== "頂点の削除"
=== "<5>"
![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png)
以下は、隣接行列でグラフを表した実装コードです:
@@ -42,19 +42,19 @@
- **頂点の削除**:隣接リスト全体を走査し、指定した頂点を含むすべての辺を削除する必要があるため、$O(n + m)$ 時間です。
- **初期化**:隣接リストに $n$ 個の頂点と $2m$ 本の辺を作成するため、$O(n + m)$ 時間です。
=== "隣接リストの初期化"
=== "<1>"
![隣接リストの初期化、辺の追加と削除、頂点の追加と削除](graph_operations.assets/adjacency_list_step1_initialization.png)
=== "辺の追加"
=== "<2>"
![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png)
=== "辺の削除"
=== "<3>"
![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png)
=== "頂点の追加"
=== "<4>"
![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png)
=== "頂点の削除"
=== "<5>"
![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png)
以下は隣接リストのコード実装です。上図と比べると、実際のコードには次の違いがあります。

View File

@@ -399,19 +399,19 @@
次の図に示すように、双方向連結リストの先頭ノードと末尾ノードを両端キューの先頭と末尾と見なし、両端でノードを追加および削除する機能を実現します。
=== "LinkedListDeque"
=== "<1>"
![連結リストによる両端キューのエンキューとデキュー](deque.assets/linkedlist_deque_step1.png)
=== "push_last()"
=== "<2>"
![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png)
=== "push_first()"
=== "<3>"
![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png)
=== "pop_last()"
=== "<4>"
![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png)
=== "pop_first()"
=== "<5>"
![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png)
実装コードは次のとおりです:
@@ -424,19 +424,19 @@
次の図に示すように、配列によるキュー実装と同様に、循環配列を使って両端キューを実装することもできます。
=== "ArrayDeque"
=== "<1>"
![配列による両端キューのエンキューとデキュー](deque.assets/array_deque_step1.png)
=== "push_last()"
=== "<2>"
![array_deque_push_last](deque.assets/array_deque_step2_push_last.png)
=== "push_first()"
=== "<3>"
![array_deque_push_first](deque.assets/array_deque_step3_push_first.png)
=== "pop_last()"
=== "<4>"
![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png)
=== "pop_first()"
=== "<5>"
![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png)
キュー実装を土台として、「先頭へのエンキュー」と「末尾からのデキュー」のメソッドを追加するだけで済みます:

View File

@@ -368,13 +368,13 @@
下図のように、連結リストの「先頭ノード」と「末尾ノード」をそれぞれ「キュー先頭」と「キュー末尾」とみなし、キュー末尾ではノードの追加のみ、キュー先頭ではノードの削除のみを行うようにします。
=== "LinkedListQueue"
=== "<1>"
![連結リストでキューを実装したエンキューとデキュー操作](queue.assets/linkedlist_queue_step1.png)
=== "push()"
=== "<2>"
![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png)
=== "pop()"
=== "<3>"
![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png)
以下は連結リストでキューを実装するコードです:
@@ -396,13 +396,13 @@
このように、エンキューとデキューはいずれも 1 回の操作だけで済み、時間計算量はともに $O(1)$ です。
=== "ArrayQueue"
=== "<1>"
![配列でキューを実装したエンキューとデキュー操作](queue.assets/array_queue_step1.png)
=== "push()"
=== "<2>"
![array_queue_push](queue.assets/array_queue_step2_push.png)
=== "pop()"
=== "<3>"
![array_queue_pop](queue.assets/array_queue_step3_pop.png)
ここで 1 つ問題があります。エンキューとデキューを繰り返すと、`front` と `rear` はどちらも右へ移動し続け、**配列の末尾に達するとそれ以上進めなくなります**。この問題を解決するために、配列を先頭と末尾がつながった「環状配列」とみなします。

View File

@@ -365,13 +365,13 @@
下図のように、プッシュ操作では要素を連結リストの先頭に挿入するだけでよく、このノード挿入方法は「頭部挿入法」と呼ばれます。ポップ操作では、先頭ノードを連結リストから削除するだけです。
=== "LinkedListStack"
=== "<1>"
![連結リストによるスタック実装のプッシュ・ポップ操作](stack.assets/linkedlist_stack_step1.png)
=== "push()"
=== "<2>"
![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png)
=== "pop()"
=== "<3>"
![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png)
以下は、連結リストによってスタックを実装したコード例です:
@@ -384,13 +384,13 @@
配列でスタックを実装する場合、配列の末尾をスタックトップとして扱えます。下図のように、プッシュとポップはそれぞれ配列末尾への要素追加と削除に対応し、どちらの時間計算量も $O(1)$ です。
=== "ArrayStack"
=== "<1>"
![配列によるスタック実装のプッシュ・ポップ操作](stack.assets/array_stack_step1.png)
=== "push()"
=== "<2>"
![array_stack_push](stack.assets/array_stack_step2_push.png)
=== "pop()"
=== "<3>"
![array_stack_pop](stack.assets/array_stack_step3_pop.png)
プッシュされる要素は際限なく増える可能性があるため、動的配列を使えば、配列の拡張を自前で処理する必要がありません。以下にコード例を示します:

View File

@@ -0,0 +1,253 @@
(() => {
const ANIMATION_LABEL_PATTERN = /^<\d+>$/;
const AUTO_SLIDE_INITIAL_DELAY_MS = 1000;
const AUTO_SLIDE_INTERVAL_MS = 1500;
const PLAY_LABEL = "播放幻灯片";
const PAUSE_LABEL = "暂停幻灯片";
const SVG_NS = "http://www.w3.org/2000/svg";
const FA_PLAY_PATH =
"M91.2 36.9c-12.4-6.8-27.4-6.5-39.6.7S32 57.9 32 72v368c0 14.1 7.5 27.2 19.6 34.4s27.2 7.5 39.6.7l336-184c12.8-7 20.8-20.5 20.8-35.1s-8-28.1-20.8-35.1z";
const FA_PAUSE_PATH =
"M48 32C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h64c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm224 0c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h64c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48z";
const FA_ARROW_LEFT_PATH =
"M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 288H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H109.3l105.4-105.4c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z";
const FA_ARROW_RIGHT_PATH =
"M502.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L402.7 224H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h370.7L297.3 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z";
const initializedSets = new WeakSet();
const controllers = new Set();
const getCheckedIndex = (inputs) => {
const checkedIndex = inputs.findIndex((input) => input.checked);
return checkedIndex === -1 ? 0 : checkedIndex;
};
const setCheckedIndex = (inputs, index) => {
const normalizedIndex = ((index % inputs.length) + inputs.length) % inputs.length;
inputs[normalizedIndex].checked = true;
return normalizedIndex;
};
const isAnimationSet = (tabbedSet) => {
const labels = Array.from(tabbedSet.querySelectorAll(":scope > .tabbed-labels > label"));
if (labels.length < 2) {
return false;
}
return labels.every((label) => ANIMATION_LABEL_PATTERN.test(label.textContent.trim()));
};
const createSvgIcon = ({ className, path, viewBox, width, height }) => {
const icon = document.createElementNS(SVG_NS, "svg");
const iconPath = document.createElementNS(SVG_NS, "path");
icon.setAttribute("class", className);
icon.setAttribute("aria-hidden", "true");
icon.setAttribute("viewBox", viewBox);
icon.setAttribute("focusable", "false");
if (width) {
icon.setAttribute("width", width);
}
if (height) {
icon.setAttribute("height", height);
}
iconPath.setAttribute("d", path);
icon.append(iconPath);
return { icon, iconPath };
};
const createIconButton = ({ className, ariaLabel, path }) => {
const button = document.createElement("button");
const { icon } = createSvgIcon({
className: "animation-controls__nav-icon",
path,
viewBox: "0 0 512 512",
});
button.type = "button";
button.className = `animation-controls__button ${className}`;
button.setAttribute("aria-label", ariaLabel);
button.append(icon);
return button;
};
const createPlayButton = () => {
const button = document.createElement("button");
const glyph = document.createElement("span");
const { icon, iconPath } = createSvgIcon({
className: "animation-controls__play-icon",
path: FA_PLAY_PATH,
viewBox: "0 0 448 512",
width: "12",
height: "12",
});
const srOnly = document.createElement("span");
button.type = "button";
button.className = "animation-controls__button animation-controls__play";
button.setAttribute("aria-label", PLAY_LABEL);
glyph.className = "animation-controls__play-glyph";
icon.setAttribute("preserveAspectRatio", "xMidYMid meet");
glyph.append(icon);
srOnly.className = "animation-controls__sr-only";
srOnly.textContent = PLAY_LABEL;
button.append(glyph, srOnly);
return { button, srOnly, icon, iconPath };
};
const initAnimationSet = (tabbedSet) => {
if (initializedSets.has(tabbedSet) || !isAnimationSet(tabbedSet)) {
return;
}
const inputs = Array.from(tabbedSet.querySelectorAll(':scope > input[type="radio"]'));
const tabbedContent = tabbedSet.querySelector(":scope > .tabbed-content");
if (inputs.length < 2 || !tabbedContent) {
return;
}
initializedSets.add(tabbedSet);
tabbedSet.dataset.autoSlide = "true";
const controls = document.createElement("div");
controls.className = "animation-controls";
const playControl = createPlayButton();
const playButton = playControl.button;
const nav = document.createElement("div");
nav.className = "animation-controls__nav";
const prevButton = createIconButton({
className: "animation-controls__prev",
ariaLabel: "上一页",
path: FA_ARROW_LEFT_PATH,
});
const pageIndicator = document.createElement("span");
pageIndicator.className = "animation-controls__page";
pageIndicator.setAttribute("aria-live", "polite");
const nextButton = createIconButton({
className: "animation-controls__next",
ariaLabel: "下一页",
path: FA_ARROW_RIGHT_PATH,
});
nav.append(prevButton, pageIndicator, nextButton);
controls.append(playButton, nav);
tabbedContent.insertAdjacentElement("afterend", controls);
const state = {
inputs,
currentIndex: getCheckedIndex(inputs),
intervalId: null,
timeoutId: null,
isPlaying: false,
};
const updatePlayButton = () => {
const label = state.isPlaying ? PAUSE_LABEL : PLAY_LABEL;
playButton.setAttribute("aria-label", label);
playButton.classList.toggle("is-playing", state.isPlaying);
playControl.srOnly.textContent = label;
playControl.icon.setAttribute("viewBox", state.isPlaying ? "0 0 384 512" : "0 0 448 512");
playControl.icon.setAttribute("width", state.isPlaying ? "10" : "12");
playControl.iconPath.setAttribute("d", state.isPlaying ? FA_PAUSE_PATH : FA_PLAY_PATH);
};
const updatePageIndicator = () => {
pageIndicator.textContent = `${state.currentIndex + 1} / ${inputs.length}`;
};
const stop = () => {
if (state.timeoutId !== null) {
window.clearTimeout(state.timeoutId);
state.timeoutId = null;
}
if (state.intervalId !== null) {
window.clearInterval(state.intervalId);
state.intervalId = null;
}
state.isPlaying = false;
updatePlayButton();
};
const syncCurrentIndex = () => {
state.currentIndex = getCheckedIndex(inputs);
};
const moveTo = (index) => {
state.currentIndex = setCheckedIndex(inputs, index);
updatePageIndicator();
};
const step = (delta) => {
moveTo(state.currentIndex + delta);
};
const start = () => {
if (state.isPlaying) {
return;
}
state.isPlaying = true;
updatePlayButton();
state.timeoutId = window.setTimeout(() => {
step(1);
state.timeoutId = null;
state.intervalId = window.setInterval(() => {
step(1);
}, AUTO_SLIDE_INTERVAL_MS);
}, AUTO_SLIDE_INITIAL_DELAY_MS);
};
playButton.addEventListener("click", () => {
if (state.isPlaying) {
stop();
return;
}
syncCurrentIndex();
start();
});
prevButton.addEventListener("click", () => {
syncCurrentIndex();
step(-1);
});
nextButton.addEventListener("click", () => {
syncCurrentIndex();
step(1);
});
inputs.forEach((input, index) => {
input.addEventListener("change", () => {
if (input.checked) {
state.currentIndex = index;
updatePageIndicator();
}
});
});
controllers.add(stop);
updatePlayButton();
updatePageIndicator();
};
const initAutoSlide = () => {
document.querySelectorAll(".tabbed-set").forEach(initAnimationSet);
};
document.addEventListener("visibilitychange", () => {
if (!document.hidden) {
return;
}
controllers.forEach((stop) => stop());
});
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initAutoSlide, { once: true });
} else {
initAutoSlide();
}
})();

View File

@@ -0,0 +1,178 @@
.md-typeset .tabbed-set[data-auto-slide="true"] {
margin-bottom: 1.5em;
overflow: hidden;
border-radius: 0.45rem;
background: var(--md-default-bg-color);
box-shadow: 0 0.12rem 0.45rem var(--md-default-fg-color--lightest);
}
.md-typeset .tabbed-set[data-auto-slide="true"] > .tabbed-labels {
display: none;
}
.md-typeset .tabbed-set[data-auto-slide="true"] > .tabbed-content {
margin-top: 0;
margin-bottom: 0;
background: var(--md-default-bg-color);
}
.md-typeset .tabbed-set[data-auto-slide="true"] > .tabbed-content > .tabbed-block > p {
margin: 0;
}
.md-typeset .tabbed-set[data-auto-slide="true"] .animation-figure {
border-radius: 0;
box-shadow: none;
}
.animation-controls {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.55rem;
width: 100%;
margin: 0 auto;
padding: 0.14rem 0.3rem;
border-radius: 0;
background: color-mix(in srgb, var(--md-code-bg-color) 97.5%, var(--md-default-fg-color) 2.5%);
box-sizing: border-box;
}
.animation-controls__nav {
display: flex;
align-items: center;
gap: 0.03rem;
margin-left: auto;
padding-left: 0.36rem;
}
.animation-controls__button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 1.64rem;
min-width: 1.64rem;
padding: 0.1rem;
border: 0;
background: transparent;
border-radius: 999px;
color: var(--md-default-fg-color);
cursor: pointer;
transition:
color 0.15s ease,
background-color 0.15s ease;
}
.animation-controls__button:hover,
.animation-controls__button:focus-visible {
background: color-mix(in srgb, var(--md-code-bg-color) 92%, var(--md-default-fg-color) 8%);
color: var(--md-accent-fg-color);
}
.animation-controls__play {
flex: 0 0 auto;
line-height: 0;
}
.animation-controls__play-glyph {
display: inline-flex;
align-items: center;
justify-content: center;
width: 0.72rem;
height: 0.72rem;
flex: 0 0 auto;
overflow: hidden;
}
.animation-controls__play-icon {
display: block;
width: 100%;
height: 100%;
fill: currentColor;
flex: 0 0 auto;
}
.animation-controls__play-glyph .animation-controls__play-icon {
transform: translateX(0.04rem);
}
.animation-controls__play.is-playing .animation-controls__play-glyph {
width: 0.6rem;
}
.animation-controls__play.is-playing .animation-controls__play-glyph .animation-controls__play-icon {
transform: none;
}
.animation-controls__nav-icon {
width: 0.72rem;
height: 0.72rem;
fill: currentColor;
flex: 0 0 auto;
}
.animation-controls__page {
min-width: 2.25rem;
padding: 0 0.01rem;
color: var(--md-typeset-color);
font-size: 0.75rem;
font-variant-numeric: tabular-nums;
line-height: 1;
text-align: center;
}
.animation-controls__sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
[data-md-color-scheme="slate"] .animation-controls {
background: color-mix(in srgb, var(--md-code-bg-color) 91%, black 9%);
}
[data-md-color-scheme="slate"] .animation-controls__button:hover,
[data-md-color-scheme="slate"] .animation-controls__button:focus-visible {
background: color-mix(in srgb, var(--md-code-bg-color) 92%, white 8%);
}
[data-md-color-scheme="slate"] .md-typeset .tabbed-set[data-auto-slide="true"] {
background: var(--md-code-bg-color);
box-shadow: 0 0.2rem 0.75rem rgb(0 0 0 / 0.18);
}
[data-md-color-scheme="slate"] .md-typeset .tabbed-set[data-auto-slide="true"] > .tabbed-content {
background: var(--md-code-bg-color);
}
@media screen and (max-width: 44.9375em) {
.animation-controls {
flex-wrap: nowrap;
justify-content: space-between;
gap: 0.32rem;
padding: 0.11rem 0.22rem;
}
.animation-controls__nav {
gap: 0.02rem;
padding-left: 0.16rem;
}
.animation-controls__button {
min-width: 1.48rem;
min-height: 1.48rem;
padding: 0.08rem;
}
.animation-controls__page {
min-width: 2rem;
padding: 0;
font-size: 0.7rem;
}
}

View File

@@ -1,6 +1,12 @@
[project]
extra_css = [
"stylesheets/extra.css",
"stylesheets/animation_player.css",
]
extra_javascript = [
"javascripts/mathjax.js",
"https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-mml-chtml.min.js",
"javascripts/animation_player.js",
]
[project.theme]

View File

@@ -11,19 +11,19 @@
- **Удаление вершины**: из матрицы смежности удаляется строка и столбец. При удалении первой строки и первого столбца достигается худший случай, когда требуется "сдвинуть влево вверх" $(n-1)^2$ элементов, поэтому используется $O(n^2)$ времени.
- **Инициализация**: передаются $n$ вершин, затем инициализируется список вершин `vertices` длины $n$ , что требует $O(n)$ времени; после этого инициализируется матрица смежности `adjMat` размера $n \times n$ , что требует $O(n^2)$ времени.
=== "Инициализация матрицы смежности"
=== "<1>"
![Инициализация матрицы смежности, добавление и удаление ребер и вершин](graph_operations.assets/adjacency_matrix_step1_initialization.png)
=== "Добавление ребра"
=== "<2>"
![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png)
=== "Удаление ребра"
=== "<3>"
![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png)
=== "Добавление вершины"
=== "<4>"
![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png)
=== "Удаление вершины"
=== "<5>"
![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png)
Ниже приведен код реализации графа на основе матрицы смежности:
@@ -42,19 +42,19 @@
- **Удаление вершины**: требуется пройти по всему списку смежности и удалить все ребра, содержащие указанную вершину; это требует $O(n + m)$ времени.
- **Инициализация**: в списке смежности создаются $n$ вершин и $2m$ ребер; это требует $O(n + m)$ времени.
=== "Инициализация списка смежности"
=== "<1>"
![Инициализация списка смежности, добавление и удаление ребер и вершин](graph_operations.assets/adjacency_list_step1_initialization.png)
=== "Добавление ребра"
=== "<2>"
![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png)
=== "Удаление ребра"
=== "<3>"
![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png)
=== "Добавление вершины"
=== "<4>"
![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png)
=== "Удаление вершины"
=== "<5>"
![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png)
Ниже приведен код списка смежности. По сравнению с рисунками выше, реальная реализация имеет следующие отличия.

View File

@@ -399,19 +399,19 @@
Как показано на рисунках ниже, мы рассматриваем головной и хвостовой узлы двусвязного списка как голову и хвост двусторонней очереди и одновременно реализуем функции добавления и удаления узлов с обеих сторон.
=== "LinkedListDeque"
=== "<1>"
![Операции enqueue и dequeue для двусторонней очереди на связном списке](deque.assets/linkedlist_deque_step1.png)
=== "push_last()"
=== "<2>"
![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png)
=== "push_first()"
=== "<3>"
![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png)
=== "pop_last()"
=== "<4>"
![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png)
=== "pop_first()"
=== "<5>"
![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png)
Код реализации приведен ниже:
@@ -424,19 +424,19 @@
Как показано на рисунках ниже, аналогично реализации обычной очереди на массиве мы также можем использовать кольцевой массив для реализации двусторонней очереди.
=== "ArrayDeque"
=== "<1>"
![Операции enqueue и dequeue для двусторонней очереди на массиве](deque.assets/array_deque_step1.png)
=== "push_last()"
=== "<2>"
![array_deque_push_last](deque.assets/array_deque_step2_push_last.png)
=== "push_first()"
=== "<3>"
![array_deque_push_first](deque.assets/array_deque_step3_push_first.png)
=== "pop_last()"
=== "<4>"
![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png)
=== "pop_first()"
=== "<5>"
![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png)
На основе реализации обычной очереди нужно лишь добавить методы добавления в голову очереди и удаления из хвоста:

View File

@@ -368,13 +368,13 @@
Как показано на рисунке ниже, мы можем рассматривать головной узел и хвостовой узел связного списка как голову очереди и хвост очереди соответственно, договорившись, что добавлять узлы можно только в хвост, а удалять - только из головы.
=== "LinkedListQueue"
=== "<1>"
![Операции enqueue и dequeue в реализации очереди на связном списке](queue.assets/linkedlist_queue_step1.png)
=== "push()"
=== "<2>"
![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png)
=== "pop()"
=== "<3>"
![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png)
Ниже приведен код реализации очереди на связном списке:
@@ -396,13 +396,13 @@
Можно увидеть, что и `enqueue` , и `dequeue` требуют всего одной операции, а значит обе имеют временную сложность $O(1)$ .
=== "ArrayQueue"
=== "<1>"
![Операции enqueue и dequeue в реализации очереди на массиве](queue.assets/array_queue_step1.png)
=== "push()"
=== "<2>"
![array_queue_push](queue.assets/array_queue_step2_push.png)
=== "pop()"
=== "<3>"
![array_queue_pop](queue.assets/array_queue_step3_pop.png)
Ты можешь заметить еще одну проблему: при непрерывных операциях `enqueue` и `dequeue` значения `front` и `rear` оба движутся вправо, и **когда они доходят до конца массива, дальше сдвигаться уже нельзя**. Чтобы решить эту проблему, можно рассматривать массив как кольцевой массив, у которого начало и конец соединены.

View File

@@ -365,13 +365,13 @@
Как показано на рисунке ниже, для операции `push` достаточно вставить элемент в голову связного списка. Такой способ вставки называется вставкой в голову. Для операции `pop` достаточно удалить головной узел из списка.
=== "LinkedListStack"
=== "<1>"
![Операции push и pop в реализации стека на связном списке](stack.assets/linkedlist_stack_step1.png)
=== "push()"
=== "<2>"
![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png)
=== "pop()"
=== "<3>"
![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png)
Ниже приведен пример кода реализации стека на основе связного списка:
@@ -384,13 +384,13 @@
Если реализовывать стек на основе массива, то хвост массива можно рассматривать как вершину стека. Как показано на рисунке ниже, операции `push` и `pop` соответствуют добавлению элемента в конец массива и удалению элемента из конца, обе имеют временную сложность $O(1)$ .
=== "ArrayStack"
=== "<1>"
![Операции push и pop в реализации стека на массиве](stack.assets/array_stack_step1.png)
=== "push()"
=== "<2>"
![array_stack_push](stack.assets/array_stack_step2_push.png)
=== "pop()"
=== "<3>"
![array_stack_pop](stack.assets/array_stack_step3_pop.png)
Поскольку количество элементов, помещаемых в стек, может непрерывно расти, мы можем использовать динамический массив и тем самым не заниматься расширением массива вручную. Ниже приведен пример кода:

View File

@@ -11,19 +11,19 @@
- **刪除頂點**:在鄰接矩陣中刪除一行一列。當刪除首行首列時達到最差情況,需要將 $(n-1)^2$ 個元素“向左上移動”,從而使用 $O(n^2)$ 時間。
- **初始化**:傳入 $n$ 個頂點,初始化長度為 $n$ 的頂點串列 `vertices` ,使用 $O(n)$ 時間;初始化 $n \times n$ 大小的鄰接矩陣 `adjMat` ,使用 $O(n^2)$ 時間。
=== "初始化鄰接矩陣"
=== "<1>"
![鄰接矩陣的初始化、增刪邊、增刪頂點](graph_operations.assets/adjacency_matrix_step1_initialization.png)
=== "新增邊"
=== "<2>"
![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png)
=== "刪除邊"
=== "<3>"
![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png)
=== "新增頂點"
=== "<4>"
![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png)
=== "刪除頂點"
=== "<5>"
![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png)
以下是基於鄰接矩陣表示圖的實現程式碼:
@@ -42,19 +42,19 @@
- **刪除頂點**:需走訪整個鄰接表,刪除包含指定頂點的所有邊,使用 $O(n + m)$ 時間。
- **初始化**:在鄰接表中建立 $n$ 個頂點和 $2m$ 條邊,使用 $O(n + m)$ 時間。
=== "初始化鄰接表"
=== "<1>"
![鄰接表的初始化、增刪邊、增刪頂點](graph_operations.assets/adjacency_list_step1_initialization.png)
=== "新增邊"
=== "<2>"
![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png)
=== "刪除邊"
=== "<3>"
![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png)
=== "新增頂點"
=== "<4>"
![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png)
=== "刪除頂點"
=== "<5>"
![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png)
以下是鄰接表的程式碼實現。對比上圖,實際程式碼有以下不同。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -399,19 +399,19 @@
如下圖所示,我們將雙向鏈結串列的頭節點和尾節點視為雙向佇列的佇列首和佇列尾,同時實現在兩端新增和刪除節點的功能。
=== "LinkedListDeque"
=== "<1>"
![基於鏈結串列實現雙向佇列的入列出列操作](deque.assets/linkedlist_deque_step1.png)
=== "push_last()"
=== "<2>"
![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png)
=== "push_first()"
=== "<3>"
![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png)
=== "pop_last()"
=== "<4>"
![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png)
=== "pop_first()"
=== "<5>"
![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png)
實現程式碼如下所示:
@@ -424,19 +424,19 @@
如下圖所示,與基於陣列實現佇列類似,我們也可以使用環形陣列來實現雙向佇列。
=== "ArrayDeque"
=== "<1>"
![基於陣列實現雙向佇列的入列出列操作](deque.assets/array_deque_step1.png)
=== "push_last()"
=== "<2>"
![array_deque_push_last](deque.assets/array_deque_step2_push_last.png)
=== "push_first()"
=== "<3>"
![array_deque_push_first](deque.assets/array_deque_step3_push_first.png)
=== "pop_last()"
=== "<4>"
![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png)
=== "pop_first()"
=== "<5>"
![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png)
在佇列的實現基礎上,僅需增加“佇列首入列”和“佇列尾出列”的方法:

View File

@@ -368,13 +368,13 @@
如下圖所示,我們可以將鏈結串列的“頭節點”和“尾節點”分別視為“佇列首”和“佇列尾”,規定佇列尾僅可新增節點,佇列首僅可刪除節點。
=== "LinkedListQueue"
=== "<1>"
![基於鏈結串列實現佇列的入列出列操作](queue.assets/linkedlist_queue_step1.png)
=== "push()"
=== "<2>"
![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png)
=== "pop()"
=== "<3>"
![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png)
以下是用鏈結串列實現佇列的程式碼:
@@ -396,13 +396,13 @@
可以看到,入列和出列操作都只需進行一次操作,時間複雜度均為 $O(1)$ 。
=== "ArrayQueue"
=== "<1>"
![基於陣列實現佇列的入列出列操作](queue.assets/array_queue_step1.png)
=== "push()"
=== "<2>"
![array_queue_push](queue.assets/array_queue_step2_push.png)
=== "pop()"
=== "<3>"
![array_queue_pop](queue.assets/array_queue_step3_pop.png)
你可能會發現一個問題:在不斷進行入列和出列的過程中,`front` 和 `rear` 都在向右移動,**當它們到達陣列尾部時就無法繼續移動了**。為了解決此問題,我們可以將陣列視為首尾相接的“環形陣列”。

View File

@@ -365,13 +365,13 @@
如下圖所示,對於入堆疊操作,我們只需將元素插入鏈結串列頭部,這種節點插入方法被稱為“頭插法”。而對於出堆疊操作,只需將頭節點從鏈結串列中刪除即可。
=== "LinkedListStack"
=== "<1>"
![基於鏈結串列實現堆疊的入堆疊出堆疊操作](stack.assets/linkedlist_stack_step1.png)
=== "push()"
=== "<2>"
![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png)
=== "pop()"
=== "<3>"
![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png)
以下是基於鏈結串列實現堆疊的示例程式碼:
@@ -384,13 +384,13 @@
使用陣列實現堆疊時,我們可以將陣列的尾部作為堆疊頂。如下圖所示,入堆疊與出堆疊操作分別對應在陣列尾部新增元素與刪除元素,時間複雜度都為 $O(1)$ 。
=== "ArrayStack"
=== "<1>"
![基於陣列實現堆疊的入堆疊出堆疊操作](stack.assets/array_stack_step1.png)
=== "push()"
=== "<2>"
![array_stack_push](stack.assets/array_stack_step2_push.png)
=== "pop()"
=== "<3>"
![array_stack_pop](stack.assets/array_stack_step3_pop.png)
由於入堆疊的元素可能會源源不斷地增加,因此我們可以使用動態陣列,這樣就無須自行處理陣列擴容問題。以下為示例程式碼: