From d190dbf3c134b546fee2c5fa1b39b6c2fc7d2c75 Mon Sep 17 00:00:00 2001 From: krahets Date: Thu, 14 Sep 2023 03:36:31 +0800 Subject: [PATCH] Several enhancements and fixes --- codes/cpp/chapter_heap/my_heap.cpp | 6 +++--- .../chapter_stack_and_queue/array_queue.cpp | 6 +++--- .../chapter_stack_and_queue/array_stack.cpp | 8 +++---- .../linkedlist_deque.cpp | 11 ++++++---- .../linkedlist_queue.cpp | 4 ++-- .../linkedlist_stack.cpp | 6 +++--- .../linkedlist_deque.cs | 21 +++++++++---------- .../linkedlist_queue.cs | 2 +- .../linkedlist_stack.cs | 5 +---- .../linkedlist_deque.java | 21 +++++++++++-------- .../linkedlist_queue.java | 2 +- .../linkedlist_stack.java | 2 +- .../linkedlist_deque.py | 3 +-- docs/chapter_array_and_linkedlist/summary.md | 8 +++---- docs/chapter_heap/build_heap.md | 15 ++++++------- docs/chapter_stack_and_queue/summary.md | 8 +++++++ docs/index.md | 19 +++++++++-------- docs/stylesheets/extra.css | 7 +++++-- 18 files changed, 84 insertions(+), 70 deletions(-) diff --git a/codes/cpp/chapter_heap/my_heap.cpp b/codes/cpp/chapter_heap/my_heap.cpp index 4cc11d43b..a59685f5b 100644 --- a/codes/cpp/chapter_heap/my_heap.cpp +++ b/codes/cpp/chapter_heap/my_heap.cpp @@ -78,7 +78,7 @@ class MaxHeap { } /* 判断堆是否为空 */ - bool empty() { + bool isEmpty() { return size() == 0; } @@ -98,7 +98,7 @@ class MaxHeap { /* 元素出堆 */ void pop() { // 判空处理 - if (empty()) { + if (isEmpty()) { throw out_of_range("堆为空"); } // 交换根节点与最右叶节点(即交换首元素与尾元素) @@ -149,7 +149,7 @@ int main() { cout << "\n堆元素数量为 " << size << endl; /* 判断堆是否为空 */ - bool isEmpty = maxHeap.empty(); + bool isEmpty = maxHeap.isEmpty(); cout << "\n堆是否为空 " << isEmpty << endl; return 0; diff --git a/codes/cpp/chapter_stack_and_queue/array_queue.cpp b/codes/cpp/chapter_stack_and_queue/array_queue.cpp index ea83bf770..27c59463d 100644 --- a/codes/cpp/chapter_stack_and_queue/array_queue.cpp +++ b/codes/cpp/chapter_stack_and_queue/array_queue.cpp @@ -37,7 +37,7 @@ class ArrayQueue { } /* 判断队列是否为空 */ - bool empty() { + bool isEmpty() { return size() == 0; } @@ -65,7 +65,7 @@ class ArrayQueue { /* 访问队首元素 */ int peek() { - if (empty()) + if (isEmpty()) throw out_of_range("队列为空"); return nums[front]; } @@ -110,7 +110,7 @@ int main() { cout << "队列长度 size = " << size << endl; /* 判断队列是否为空 */ - bool empty = queue->empty(); + bool empty = queue->isEmpty(); cout << "队列是否为空 = " << empty << endl; /* 测试环形数组 */ diff --git a/codes/cpp/chapter_stack_and_queue/array_stack.cpp b/codes/cpp/chapter_stack_and_queue/array_stack.cpp index 02df352c8..c5e5663c5 100644 --- a/codes/cpp/chapter_stack_and_queue/array_stack.cpp +++ b/codes/cpp/chapter_stack_and_queue/array_stack.cpp @@ -18,8 +18,8 @@ class ArrayStack { } /* 判断栈是否为空 */ - bool empty() { - return stack.empty(); + bool isEmpty() { + return stack.size() == 0; } /* 入栈 */ @@ -35,7 +35,7 @@ class ArrayStack { /* 访问栈顶元素 */ int top() { - if (empty()) + if (isEmpty()) throw out_of_range("栈为空"); return stack.back(); } @@ -74,7 +74,7 @@ int main() { cout << "栈的长度 size = " << size << endl; /* 判断是否为空 */ - bool empty = stack->empty(); + bool empty = stack->isEmpty(); cout << "栈是否为空 = " << empty << endl; // 释放内存 diff --git a/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp b/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp index d1217a130..4c1bb8726 100644 --- a/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp +++ b/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp @@ -81,9 +81,8 @@ class LinkedListDeque { /* 出队操作 */ int pop(bool isFront) { - // 若队列为空,直接返回 -1 if (isEmpty()) - return -1; + throw out_of_range("队列为空"); int val; // 队首出队操作 if (isFront) { @@ -124,12 +123,16 @@ class LinkedListDeque { /* 访问队首元素 */ int peekFirst() { - return isEmpty() ? -1 : front->val; + if (isEmpty()) + throw out_of_range("双向队列为空"); + return front->val; } /* 访问队尾元素 */ int peekLast() { - return isEmpty() ? -1 : rear->val; + if (isEmpty()) + throw out_of_range("双向队列为空"); + return rear->val; } /* 返回数组用于打印 */ diff --git a/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp b/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp index 400072d3e..a1769359a 100644 --- a/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp +++ b/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp @@ -30,7 +30,7 @@ class LinkedListQueue { } /* 判断队列是否为空 */ - bool empty() { + bool isEmpty() { return queSize == 0; } @@ -109,7 +109,7 @@ int main() { cout << "队列长度 size = " << size << endl; /* 判断队列是否为空 */ - bool empty = queue->empty(); + bool empty = queue->isEmpty(); cout << "队列是否为空 = " << empty << endl; // 释放内存 diff --git a/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp b/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp index 1ec5bee47..c51a46b78 100644 --- a/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp +++ b/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp @@ -29,7 +29,7 @@ class LinkedListStack { } /* 判断栈是否为空 */ - bool empty() { + bool isEmpty() { return size() == 0; } @@ -53,7 +53,7 @@ class LinkedListStack { /* 访问栈顶元素 */ int top() { - if (size() == 0) + if (isEmpty()) throw out_of_range("栈为空"); return stackTop->val; } @@ -98,7 +98,7 @@ int main() { cout << "栈的长度 size = " << size << endl; /* 判断是否为空 */ - bool empty = stack->empty(); + bool empty = stack->isEmpty(); cout << "栈是否为空 = " << empty << endl; // 释放内存 diff --git a/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs b/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs index c0e7ed514..ef9ab3f79 100644 --- a/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs +++ b/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs @@ -77,34 +77,29 @@ public class LinkedListDeque { /* 出队操作 */ private int? pop(bool isFront) { - // 若队列为空,直接返回 null - if (isEmpty()) { - return null; - } - + 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; // 更新尾节点 } @@ -124,12 +119,16 @@ public class LinkedListDeque { /* 访问队首元素 */ public int? peekFirst() { - return isEmpty() ? null : front.val; + if (isEmpty()) + throw new Exception(); + return front.val; } /* 访问队尾元素 */ public int? peekLast() { - return isEmpty() ? null : rear.val; + if (isEmpty()) + throw new Exception(); + return rear.val; } /* 返回数组用于打印 */ diff --git a/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs b/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs index 55824b4a3..3a20419f1 100644 --- a/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs +++ b/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs @@ -53,7 +53,7 @@ class LinkedListQueue { /* 访问队首元素 */ public int peek() { - if (size() == 0 || front == null) + if (isEmpty()) throw new Exception(); return front.val; } diff --git a/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs b/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs index 7014a53e0..9a3fa18b5 100644 --- a/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs +++ b/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs @@ -35,9 +35,6 @@ class LinkedListStack { /* 出栈 */ public int pop() { - if (stackPeek == null) - throw new Exception(); - int num = peek(); stackPeek = stackPeek.next; stkSize--; @@ -46,7 +43,7 @@ class LinkedListStack { /* 访问栈顶元素 */ public int peek() { - if (size() == 0 || stackPeek == null) + if (isEmpty()) throw new Exception(); return stackPeek.val; } diff --git a/codes/java/chapter_stack_and_queue/linkedlist_deque.java b/codes/java/chapter_stack_and_queue/linkedlist_deque.java index 41071a45b..6747b82c1 100644 --- a/codes/java/chapter_stack_and_queue/linkedlist_deque.java +++ b/codes/java/chapter_stack_and_queue/linkedlist_deque.java @@ -72,10 +72,9 @@ class LinkedListDeque { } /* 出队操作 */ - private Integer pop(boolean isFront) { - // 若队列为空,直接返回 null + private int pop(boolean isFront) { if (isEmpty()) - return null; + throw new IndexOutOfBoundsException(); int val; // 队首出队操作 if (isFront) { @@ -103,23 +102,27 @@ class LinkedListDeque { } /* 队首出队 */ - public Integer popFirst() { + public int popFirst() { return pop(true); } /* 队尾出队 */ - public Integer popLast() { + public int popLast() { return pop(false); } /* 访问队首元素 */ - public Integer peekFirst() { - return isEmpty() ? null : front.val; + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return front.val; } /* 访问队尾元素 */ - public Integer peekLast() { - return isEmpty() ? null : rear.val; + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return rear.val; } /* 返回数组用于打印 */ diff --git a/codes/java/chapter_stack_and_queue/linkedlist_queue.java b/codes/java/chapter_stack_and_queue/linkedlist_queue.java index 87682ccb1..fafde978a 100644 --- a/codes/java/chapter_stack_and_queue/linkedlist_queue.java +++ b/codes/java/chapter_stack_and_queue/linkedlist_queue.java @@ -55,7 +55,7 @@ class LinkedListQueue { /* 访问队首元素 */ public int peek() { - if (size() == 0) + if (isEmpty()) throw new IndexOutOfBoundsException(); return front.val; } diff --git a/codes/java/chapter_stack_and_queue/linkedlist_stack.java b/codes/java/chapter_stack_and_queue/linkedlist_stack.java index b214596b8..a874e4aed 100644 --- a/codes/java/chapter_stack_and_queue/linkedlist_stack.java +++ b/codes/java/chapter_stack_and_queue/linkedlist_stack.java @@ -46,7 +46,7 @@ class LinkedListStack { /* 访问栈顶元素 */ public int peek() { - if (size() == 0) + if (isEmpty()) throw new IndexOutOfBoundsException(); return stackPeek.val; } diff --git a/codes/python/chapter_stack_and_queue/linkedlist_deque.py b/codes/python/chapter_stack_and_queue/linkedlist_deque.py index 172034b1e..6e3642f86 100644 --- a/codes/python/chapter_stack_and_queue/linkedlist_deque.py +++ b/codes/python/chapter_stack_and_queue/linkedlist_deque.py @@ -62,9 +62,8 @@ class LinkedListDeque: def pop(self, is_front: bool) -> int: """出队操作""" - # 若队列为空,直接返回 None if self.is_empty(): - return None + raise IndexError("双向队列为空") # 队首出队操作 if is_front: val: int = self.front.val # 暂存头节点值 diff --git a/docs/chapter_array_and_linkedlist/summary.md b/docs/chapter_array_and_linkedlist/summary.md index a3c682d91..4b5bf57bf 100644 --- a/docs/chapter_array_and_linkedlist/summary.md +++ b/docs/chapter_array_and_linkedlist/summary.md @@ -11,11 +11,11 @@ !!! question "数组存储在栈上和存储在堆上,对时间效率和空间效率是否有影响?" - 栈内存分配由编译器自动完成,而堆内存由程序员在代码中分配(注意,这里的栈和堆和数据结构中的栈和堆不是同一概念)。 + 存储在栈上和堆上的数组都被存储在连续内存空间内,数据操作效率是基本一致的。然而,栈和堆具有各自的特点,从而导致以下不同点。 - 1. 栈不灵活,分配的内存大小不可更改;堆相对灵活,可以动态分配内存。 - 2. 栈是一块比较小的内存,容易出现内存不足;堆内存很大,但是由于是动态分配,容易碎片化,管理堆内存的难度更大、成本更高。 - 3. 访问栈比访问堆更快,因为栈内存较小、对缓存友好,堆帧分散在很大的空间内,会出现更多的缓存未命中。 + 1. 分配和释放效率:栈是一块较小的内存,分配由编译器自动完成;而堆内存相对更大,可以在代码中动态分配,更容易碎片化。因此,堆上的分配和释放操作通常比栈上的慢。 + 2. 大小限制:栈内存相对较小,堆的大小一般受限于可用内存。因此堆更加适合存储大型数组。 + 3. 灵活性:栈上的数组的大小需要在编译时确定,而堆上的数组的大小可以在运行时动态确定。 !!! question "为什么数组要求相同类型的元素,而在链表中却没有强调同类型呢?" diff --git a/docs/chapter_heap/build_heap.md b/docs/chapter_heap/build_heap.md index b044e7a71..75222aebb 100644 --- a/docs/chapter_heap/build_heap.md +++ b/docs/chapter_heap/build_heap.md @@ -2,25 +2,26 @@ 在某些情况下,我们希望使用一个列表的所有元素来构建一个堆,这个过程被称为“建堆操作”。 -## 自上而下构建 +## 借助入堆操作实现 我们首先创建一个空堆,然后遍历列表,依次对每个元素执行“入堆操作”,即先将元素添加至堆的尾部,再对该元素执行“从底至顶”堆化。 -每当一个元素入堆,堆的长度就加一,因此堆是“自上而下”地构建的。 +每当一个元素入堆,堆的长度就加一。由于节点是从顶到底依次被添加进二叉树的,因此堆是“自上而下”地构建的。 设元素数量为 $n$ ,每个元素的入堆操作使用 $O(\log{n})$ 时间,因此该建堆方法的时间复杂度为 $O(n \log n)$ 。 -## 自下而上构建 +## 通过遍历堆化实现 实际上,我们可以实现一种更为高效的建堆方法,共分为两步。 -1. 将列表所有元素原封不动添加到堆中。 +1. 将列表所有元素原封不动添加到堆中,此时堆的性质尚未得到满足。 2. 倒序遍历堆(即层序遍历的倒序),依次对每个非叶节点执行“从顶至底堆化”。 -在倒序遍历中,堆是“自下而上”地构建的,需要重点理解以下两点。 +**每当堆化一个节点后,以该节点为根节点的子树就形成一个合法的子堆**。而由于是倒序遍历,因此堆是“自下而上”地被构建的。 -- 由于叶节点没有子节点,因此无需对它们执行堆化。最后一个节点的父节点是最后一个非叶节点。 -- 在倒序遍历中,我们能够保证当前节点之下的子树已经完成堆化(已经是合法的堆),而这是堆化当前节点的前置条件。 +之所以选择倒序遍历,是因为这样能够保证当前节点之下的子树已经是合法的子堆,这样堆化当前节点才是有效的。 + +值得说明的是,**叶节点没有子节点,天然就是合法的子堆,因此无需堆化**。如以下代码所示,最后一个非叶节点是最后一个节点的父节点,我们从它开始倒序遍历并执行堆化。 === "Python" diff --git a/docs/chapter_stack_and_queue/summary.md b/docs/chapter_stack_and_queue/summary.md index c85a00cba..d0103220a 100644 --- a/docs/chapter_stack_and_queue/summary.md +++ b/docs/chapter_stack_and_queue/summary.md @@ -21,3 +21,11 @@ !!! question "双向队列像是两个栈拼接在了一起,它的用途是什么?" 双向队列就像是栈和队列的组合,或者是两个栈拼在了一起。它表现的是栈 + 队列的逻辑,因此可以实现栈与队列的所有应用,并且更加灵活。 + +!!! question "撤销(undo)和反撤销(redo)具体是如何实现的?" + + 使用两个堆栈,栈 `A` 用于撤销,栈 `B` 用于反撤销。 + + 1. 每当用户执行一个操作,将这个操作压入栈 `A` ,并清空栈 `B` 。 + 2. 当用户执行“撤销”时,从栈 `A` 中弹出最近的操作,并将其压入栈 `B` 。 + 3. 当用户执行“反撤销”时,从栈 `B` 中弹出最近的操作,并将其压入栈 `A` 。 diff --git a/docs/index.md b/docs/index.md index eb668b3e6..e3b0538be 100644 --- a/docs/index.md +++ b/docs/index.md @@ -106,19 +106,20 @@ hide:

-本书的代码审阅工作由 Gonglja, gvenusleo, justin‐tse, krahets, nuomi1, Reanon, sjinzh 完成(按照首字母顺序排列)。感谢他们付出的时间与精力,正是他们确保了各语言代码的规范与统一。 +本书的代码审阅工作由 Gonglja, gvenusleo, justin‐tse, krahets, night-cruise, nuomi1, Reanon, sjinzh 完成(按照首字母顺序排列)。感谢他们付出的时间与精力,正是他们确保了各语言代码的规范与统一。
- - - - - - - - + + + + + + + + +
Gonglja
Gonglja

C / C++
gvenusleo
gvenusleo

Dart
hpstory
hpstory

C#
justin-tse
justin-tse

JS / TS
krahets
krahets

Java / Python
nuomi1
nuomi1

Swift
Reanon
Reanon

Go / C
sjinzh
sjinzh

Rust / Zig
Gonglja
Gonglja

C, C++
gvenusleo
gvenusleo

Dart
hpstory
hpstory

C#
justin-tse
justin-tse

JS, TS
krahets
krahets

Java, Python
night-cruise
night-cruise

Rust
nuomi1
nuomi1

Swift
Reanon
Reanon

Go, C
sjinzh
sjinzh

Rust, Zig
diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 2a91fcc0f..b1e71c665 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -8,6 +8,8 @@ --md-default-fg-color: #1d1d20; --md-default-bg-color: #ffffff; + --md-code-fg-color: #1d1d20; + --md-accent-fg-color: #999; --md-admonition-fg-color: #1d1d20; @@ -23,9 +25,10 @@ --md-default-fg-color: #adbac7; --md-default-bg-color: #22272e; - --md-code-bg-color: #1c2329; + --md-code-bg-color: #2D333B; + --md-code-fg-color: #adbac7; - --md-accent-fg-color: #999; + --md-accent-fg-color: #aaa; --md-admonition-fg-color: #adbac7;