diff --git a/chapter_heap/build_heap.md b/chapter_heap/build_heap.md index f688a1198..aed127200 100644 --- a/chapter_heap/build_heap.md +++ b/chapter_heap/build_heap.md @@ -6,25 +6,26 @@ comments: true 在某些情况下,我们希望使用一个列表的所有元素来构建一个堆,这个过程被称为“建堆操作”。 -## 8.2.1 自上而下构建 +## 8.2.1 借助入堆操作实现 我们首先创建一个空堆,然后遍历列表,依次对每个元素执行“入堆操作”,即先将元素添加至堆的尾部,再对该元素执行“从底至顶”堆化。 -每当一个元素入堆,堆的长度就加一,因此堆是“自上而下”地构建的。 +每当一个元素入堆,堆的长度就加一。由于节点是从顶到底依次被添加进二叉树的,因此堆是“自上而下”地构建的。 设元素数量为 $n$ ,每个元素的入堆操作使用 $O(\log{n})$ 时间,因此该建堆方法的时间复杂度为 $O(n \log n)$ 。 -## 8.2.2 自下而上构建 +## 8.2.2 通过遍历堆化实现 实际上,我们可以实现一种更为高效的建堆方法,共分为两步。 -1. 将列表所有元素原封不动添加到堆中。 +1. 将列表所有元素原封不动添加到堆中,此时堆的性质尚未得到满足。 2. 倒序遍历堆(即层序遍历的倒序),依次对每个非叶节点执行“从顶至底堆化”。 -在倒序遍历中,堆是“自下而上”地构建的,需要重点理解以下两点。 +**每当堆化一个节点后,以该节点为根节点的子树就形成一个合法的子堆**。而由于是倒序遍历,因此堆是“自下而上”地被构建的。 -- 由于叶节点没有子节点,因此无需对它们执行堆化。最后一个节点的父节点是最后一个非叶节点。 -- 在倒序遍历中,我们能够保证当前节点之下的子树已经完成堆化(已经是合法的堆),而这是堆化当前节点的前置条件。 +之所以选择倒序遍历,是因为这样能够保证当前节点之下的子树已经是合法的子堆,这样堆化当前节点才是有效的。 + +值得说明的是,**叶节点没有子节点,天然就是合法的子堆,因此无需堆化**。如以下代码所示,最后一个非叶节点是最后一个节点的父节点,我们从它开始倒序遍历并执行堆化。 === "Python" diff --git a/chapter_heap/heap.md b/chapter_heap/heap.md index e8ae66689..8a77a5a8e 100644 --- a/chapter_heap/heap.md +++ b/chapter_heap/heap.md @@ -1135,7 +1135,7 @@ comments: true /* 元素出堆 */ void pop() { // 判空处理 - if (empty()) { + if (isEmpty()) { throw out_of_range("堆为空"); } // 交换根节点与最右叶节点(即交换首元素与尾元素) diff --git a/chapter_stack_and_queue/deque.md b/chapter_stack_and_queue/deque.md index 5f1b4d35b..1ae8c0d30 100644 --- a/chapter_stack_and_queue/deque.md +++ b/chapter_stack_and_queue/deque.md @@ -416,9 +416,8 @@ comments: true def pop(self, is_front: bool) -> int: """出队操作""" - # 若队列为空,直接返回 None if self.is_empty(): - return None + raise IndexError("双向队列为空") # 队首出队操作 if is_front: val: int = self.front.val # 暂存头节点值 @@ -450,11 +449,15 @@ comments: true def peek_first(self) -> int: """访问队首元素""" - return None if self.is_empty() else self.front.val + if self.is_empty(): + raise IndexError("双向队列为空") + return self.front.val def peek_last(self) -> int: """访问队尾元素""" - return None if self.is_empty() else self.rear.val + if self.is_empty(): + raise IndexError("双向队列为空") + return self.rear.val def to_array(self) -> list[int]: """返回数组用于打印""" @@ -544,9 +547,8 @@ comments: true /* 出队操作 */ int pop(bool isFront) { - // 若队列为空,直接返回 -1 if (isEmpty()) - return -1; + throw out_of_range("队列为空"); int val; // 队首出队操作 if (isFront) { @@ -587,12 +589,16 @@ comments: true /* 访问队首元素 */ 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; } /* 返回数组用于打印 */ @@ -675,10 +681,9 @@ comments: true } /* 出队操作 */ - private Integer pop(boolean isFront) { - // 若队列为空,直接返回 null + private int pop(boolean isFront) { if (isEmpty()) - return null; + throw new IndexOutOfBoundsException(); int val; // 队首出队操作 if (isFront) { @@ -706,23 +711,27 @@ comments: true } /* 队首出队 */ - 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; } /* 返回数组用于打印 */ @@ -812,34 +821,29 @@ comments: true /* 出队操作 */ 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; // 更新尾节点 } @@ -859,12 +863,16 @@ comments: true /* 访问队首元素 */ 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/chapter_stack_and_queue/queue.md b/chapter_stack_and_queue/queue.md index 18487bd6a..3dd6fede5 100755 --- a/chapter_stack_and_queue/queue.md +++ b/chapter_stack_and_queue/queue.md @@ -358,9 +358,8 @@ comments: true def peek(self) -> int: """访问队首元素""" - if self.size() == 0: - print("队列为空") - return False + if self.is_empty(): + raise IndexError("队列为空") return self.__front.val def to_list(self) -> list[int]: @@ -400,7 +399,7 @@ comments: true } /* 判断队列是否为空 */ - bool empty() { + bool isEmpty() { return queSize == 0; } @@ -502,7 +501,7 @@ comments: true /* 访问队首元素 */ public int peek() { - if (size() == 0) + if (isEmpty()) throw new IndexOutOfBoundsException(); return front.val; } @@ -570,7 +569,7 @@ comments: true /* 访问队首元素 */ public int peek() { - if (size() == 0 || front == null) + if (isEmpty()) throw new Exception(); return front.val; } @@ -1303,7 +1302,7 @@ comments: true } /* 判断队列是否为空 */ - bool empty() { + bool isEmpty() { return size() == 0; } @@ -1331,7 +1330,7 @@ comments: true /* 访问队首元素 */ int peek() { - if (empty()) + if (isEmpty()) throw out_of_range("队列为空"); return nums[front]; } diff --git a/chapter_stack_and_queue/stack.md b/chapter_stack_and_queue/stack.md index 47b1076b5..cd1c387af 100755 --- a/chapter_stack_and_queue/stack.md +++ b/chapter_stack_and_queue/stack.md @@ -344,16 +344,15 @@ comments: true def pop(self) -> int: """出栈""" - num: int = self.peek() + num = self.peek() self.__peek = self.__peek.next self.__size -= 1 return num def peek(self) -> int: """访问栈顶元素""" - # 判空处理 - if not self.__peek: - return None + if self.is_empty(): + raise IndexError("栈为空") return self.__peek.val def to_list(self) -> list[int]: @@ -393,7 +392,7 @@ comments: true } /* 判断栈是否为空 */ - bool empty() { + bool isEmpty() { return size() == 0; } @@ -417,7 +416,7 @@ comments: true /* 访问栈顶元素 */ int top() { - if (size() == 0) + if (isEmpty()) throw out_of_range("栈为空"); return stackTop->val; } @@ -475,7 +474,7 @@ comments: true /* 访问栈顶元素 */ public int peek() { - if (size() == 0) + if (isEmpty()) throw new IndexOutOfBoundsException(); return stackPeek.val; } @@ -525,9 +524,6 @@ comments: true /* 出栈 */ public int pop() { - if (stackPeek == null) - throw new Exception(); - int num = peek(); stackPeek = stackPeek.next; stkSize--; @@ -536,7 +532,7 @@ comments: true /* 访问栈顶元素 */ public int peek() { - if (size() == 0 || stackPeek == null) + if (isEmpty()) throw new Exception(); return stackPeek.val; } @@ -1139,8 +1135,8 @@ comments: true } /* 判断栈是否为空 */ - bool empty() { - return stack.empty(); + bool isEmpty() { + return stack.size() == 0; } /* 入栈 */ @@ -1156,7 +1152,7 @@ comments: true /* 访问栈顶元素 */ int top() { - if (empty()) + if (isEmpty()) throw out_of_range("栈为空"); return stack.back(); } diff --git a/chapter_stack_and_queue/summary.md b/chapter_stack_and_queue/summary.md index fab68752b..94340bcd1 100644 --- a/chapter_stack_and_queue/summary.md +++ b/chapter_stack_and_queue/summary.md @@ -25,3 +25,11 @@ comments: true !!! question "双向队列像是两个栈拼接在了一起,它的用途是什么?" 双向队列就像是栈和队列的组合,或者是两个栈拼在了一起。它表现的是栈 + 队列的逻辑,因此可以实现栈与队列的所有应用,并且更加灵活。 + +!!! question "撤销(undo)和反撤销(redo)具体是如何实现的?" + + 使用两个堆栈,栈 `A` 用于撤销,栈 `B` 用于反撤销。 + + 1. 每当用户执行一个操作,将这个操作压入栈 `A` ,并清空栈 `B` 。 + 2. 当用户执行“撤销”时,从栈 `A` 中弹出最近的操作,并将其压入栈 `B` 。 + 3. 当用户执行“反撤销”时,从栈 `B` 中弹出最近的操作,并将其压入栈 `A` 。 diff --git a/index.md b/index.md index eb668b3e6..e3b0538be 100644 --- a/index.md +++ b/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 完成(按照首字母顺序排列)。感谢他们付出的时间与精力,正是他们确保了各语言代码的规范与统一。