diff --git a/chapter_array_and_linkedlist/linked_list.md b/chapter_array_and_linkedlist/linked_list.md index 0d1de0b80..3282f4669 100755 --- a/chapter_array_and_linkedlist/linked_list.md +++ b/chapter_array_and_linkedlist/linked_list.md @@ -6,9 +6,9 @@ comments: true 内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当我们需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。 -与数组相比,链表更具灵活性,因为它可以存储在非连续的内存空间。「链表 Linked List」是一种线性数据结构,其每个元素都是一个结点对象,各个结点之间通过指针连接,从当前结点通过指针可以访问到下一个结点。由于指针记录了下个结点的内存地址,因此无需保证内存地址的连续性,从而可以将各个结点分散存储在内存各处。 +与数组相比,链表更具灵活性,因为它可以存储在非连续的内存空间。「链表 Linked List」是一种线性数据结构,其每个元素都是一个节点对象,各个节点之间通过指针连接,从当前节点通过指针可以访问到下一个节点。由于指针记录了下个节点的内存地址,因此无需保证内存地址的连续性,从而可以将各个节点分散存储在内存各处。 -链表「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」,或称指向下一结点的「引用 Reference」。 +链表「节点 Node」包含两项数据,一是节点「值 Value」,二是指向下一节点的「指针 Pointer」,或称指向下一节点的「引用 Reference」。  @@ -17,10 +17,10 @@ comments: true === "Java" ```java title="" - /* 链表结点类 */ + /* 链表节点类 */ class ListNode { - int val; // 结点值 - ListNode next; // 指向下一结点的指针(引用) + int val; // 节点值 + ListNode next; // 指向下一节点的指针(引用) ListNode(int x) { val = x; } // 构造函数 } ``` @@ -28,10 +28,10 @@ comments: true === "C++" ```cpp title="" - /* 链表结点结构体 */ + /* 链表节点结构体 */ struct ListNode { - int val; // 结点值 - ListNode *next; // 指向下一结点的指针(引用) + int val; // 节点值 + ListNode *next; // 指向下一节点的指针(引用) ListNode(int x) : val(x), next(nullptr) {} // 构造函数 }; ``` @@ -39,20 +39,20 @@ comments: true === "Python" ```python title="" - """ 链表结点类 """ + """ 链表节点类 """ class ListNode: def __init__(self, val: int): - self.val: int = val # 结点值 - self.next: Optional[ListNode] = None # 指向下一结点的指针(引用) + self.val: int = val # 节点值 + self.next: Optional[ListNode] = None # 指向下一节点的指针(引用) ``` === "Go" ```go title="" - /* 链表结点结构体 */ + /* 链表节点结构体 */ type ListNode struct { - Val int // 结点值 - Next *ListNode // 指向下一结点的指针(引用) + Val int // 节点值 + Next *ListNode // 指向下一节点的指针(引用) } // NewListNode 构造函数,创建一个新的链表 @@ -67,13 +67,13 @@ comments: true === "JavaScript" ```javascript title="" - /* 链表结点类 */ + /* 链表节点类 */ class ListNode { val; next; constructor(val, next) { - this.val = (val === undefined ? 0 : val); // 结点值 - this.next = (next === undefined ? null : next); // 指向下一结点的引用 + this.val = (val === undefined ? 0 : val); // 节点值 + this.next = (next === undefined ? null : next); // 指向下一节点的引用 } } ``` @@ -81,13 +81,13 @@ comments: true === "TypeScript" ```typescript title="" - /* 链表结点类 */ + /* 链表节点类 */ class ListNode { val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = next === undefined ? null : next; // 指向下一结点的引用 + this.val = val === undefined ? 0 : val; // 节点值 + this.next = next === undefined ? null : next; // 指向下一节点的引用 } } ``` @@ -95,16 +95,16 @@ comments: true === "C" ```c title="" - /* 链表结点结构体 */ + /* 链表节点结构体 */ struct ListNode { - int val; // 结点值 - struct ListNode *next; // 指向下一结点的指针(引用) + int val; // 节点值 + struct ListNode *next; // 指向下一节点的指针(引用) }; // typedef 作用是为一种数据类型定义一个新名字 typedef struct ListNode ListNode; - /* 构造函数,初始化一个新结点 */ + /* 构造函数,初始化一个新节点 */ ListNode *newListNode(int val) { ListNode *node, *next; node = (ListNode *) malloc(sizeof(ListNode)); @@ -117,11 +117,11 @@ comments: true === "C#" ```csharp title="" - /* 链表结点类 */ + /* 链表节点类 */ class ListNode { - int val; // 结点值 - ListNode next; // 指向下一结点的引用 + int val; // 节点值 + ListNode next; // 指向下一节点的引用 ListNode(int x) => val = x; //构造函数 } ``` @@ -129,10 +129,10 @@ comments: true === "Swift" ```swift title="" - /* 链表结点类 */ + /* 链表节点类 */ class ListNode { - var val: Int // 结点值 - var next: ListNode? // 指向下一结点的指针(引用) + var val: Int // 节点值 + var next: ListNode? // 指向下一节点的指针(引用) init(x: Int) { // 构造函数 val = x @@ -143,13 +143,13 @@ comments: true === "Zig" ```zig title="" - // 链表结点类 + // 链表节点类 pub fn ListNode(comptime T: type) type { return struct { const Self = @This(); - val: T = 0, // 结点值 - next: ?*Self = null, // 指向下一结点的指针(引用) + val: T = 0, // 节点值 + next: ?*Self = null, // 指向下一节点的指针(引用) // 构造函数 pub fn init(self: *Self, x: i32) void { @@ -160,21 +160,21 @@ comments: true } ``` -!!! question "尾结点指向什么?" +!!! question "尾节点指向什么?" - 我们将链表的最后一个结点称为「尾结点」,其指向的是“空”,在 Java, C++, Python 中分别记为 `null`, `nullptr`, `None` 。在不引起歧义的前提下,本书都使用 `null` 来表示空。 + 我们将链表的最后一个节点称为「尾节点」,其指向的是“空”,在 Java, C++, Python 中分别记为 `null`, `nullptr`, `None` 。在不引起歧义的前提下,本书都使用 `null` 来表示空。 !!! question "如何称呼链表?" - 在编程语言中,数组整体就是一个变量,例如数组 `nums` ,包含各个元素 `nums[0]` , `nums[1]` 等等。而链表是由多个结点对象组成,我们通常将头结点当作链表的代称,例如头结点 `head` 和链表 `head` 实际上是同义的。 + 在编程语言中,数组整体就是一个变量,例如数组 `nums` ,包含各个元素 `nums[0]` , `nums[1]` 等等。而链表是由多个节点对象组成,我们通常将头节点当作链表的代称,例如头节点 `head` 和链表 `head` 实际上是同义的。 -**链表初始化方法**。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的头结点(即首个结点)出发,通过指针 `next` 依次访问所有结点。 +**链表初始化方法**。建立链表分为两步,第一步是初始化各个节点对象,第二步是构建引用指向关系。完成后,即可以从链表的头节点(即首个节点)出发,通过指针 `next` 依次访问所有节点。 === "Java" ```java title="linked_list.java" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 ListNode n0 = new ListNode(1); ListNode n1 = new ListNode(3); ListNode n2 = new ListNode(2); @@ -191,7 +191,7 @@ comments: true ```cpp title="linked_list.cpp" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 ListNode* n0 = new ListNode(1); ListNode* n1 = new ListNode(3); ListNode* n2 = new ListNode(2); @@ -208,7 +208,7 @@ comments: true ```python title="linked_list.py" """ 初始化链表 1 -> 3 -> 2 -> 5 -> 4 """ - # 初始化各个结点 + # 初始化各个节点 n0 = ListNode(1) n1 = ListNode(3) n2 = ListNode(2) @@ -225,7 +225,7 @@ comments: true ```go title="linked_list.go" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 n0 := NewListNode(1) n1 := NewListNode(3) n2 := NewListNode(2) @@ -242,7 +242,7 @@ comments: true ```javascript title="linked_list.js" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); @@ -259,7 +259,7 @@ comments: true ```typescript title="linked_list.ts" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 const n0 = new ListNode(1); const n1 = new ListNode(3); const n2 = new ListNode(2); @@ -276,7 +276,7 @@ comments: true ```c title="linked_list.c" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 ListNode* n0 = newListNode(1); ListNode* n1 = newListNode(3); ListNode* n2 = newListNode(2); @@ -293,7 +293,7 @@ comments: true ```csharp title="linked_list.cs" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 ListNode n0 = new ListNode(1); ListNode n1 = new ListNode(3); ListNode n2 = new ListNode(2); @@ -310,7 +310,7 @@ comments: true ```swift title="linked_list.swift" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ - // 初始化各个结点 + // 初始化各个节点 let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) let n2 = ListNode(x: 2) @@ -327,7 +327,7 @@ comments: true ```zig title="linked_list.zig" // 初始化链表 - // 初始化各个结点 + // 初始化各个节点 var n0 = inc.ListNode(i32){.val = 1}; var n1 = inc.ListNode(i32){.val = 3}; var n2 = inc.ListNode(i32){.val = 2}; @@ -342,16 +342,16 @@ comments: true ## 4.2.1. 链表优点 -**链表中插入与删除结点的操作效率高**。例如,如果我们想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。 +**链表中插入与删除节点的操作效率高**。例如,如果我们想在链表中间的两个节点 `A` , `B` 之间插入一个新节点 `P` ,我们只需要改变两个节点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。 - + -
Fig. 链表插入结点
+Fig. 链表插入节点
=== "Java" ```java title="linked_list.java" - /* 在链表的结点 n0 之后插入结点 P */ + /* 在链表的节点 n0 之后插入节点 P */ void insert(ListNode n0, ListNode P) { ListNode n1 = n0.next; P.next = n1; @@ -362,7 +362,7 @@ comments: true === "C++" ```cpp title="linked_list.cpp" - /* 在链表的结点 n0 之后插入结点 P */ + /* 在链表的节点 n0 之后插入节点 P */ void insert(ListNode* n0, ListNode* P) { ListNode* n1 = n0->next; P->next = n1; @@ -374,7 +374,7 @@ comments: true ```python title="linked_list.py" def insert(n0: ListNode, P: ListNode) -> None: - """ 在链表的结点 n0 之后插入结点 P """ + """ 在链表的节点 n0 之后插入节点 P """ n1 = n0.next P.next = n1 n0.next = P @@ -383,7 +383,7 @@ comments: true === "Go" ```go title="linked_list.go" - /* 在链表的结点 n0 之后插入结点 P */ + /* 在链表的节点 n0 之后插入节点 P */ func insertNode(n0 *ListNode, P *ListNode) { n1 := n0.Next P.Next = n1 @@ -394,7 +394,7 @@ comments: true === "JavaScript" ```javascript title="linked_list.js" - /* 在链表的结点 n0 之后插入结点 P */ + /* 在链表的节点 n0 之后插入节点 P */ function insert(n0, P) { const n1 = n0.next; P.next = n1; @@ -405,7 +405,7 @@ comments: true === "TypeScript" ```typescript title="linked_list.ts" - /* 在链表的结点 n0 之后插入结点 P */ + /* 在链表的节点 n0 之后插入节点 P */ function insert(n0: ListNode, P: ListNode): void { const n1 = n0.next; P.next = n1; @@ -422,7 +422,7 @@ comments: true === "C#" ```csharp title="linked_list.cs" - /* 在链表的结点 n0 之后插入结点 P */ + /* 在链表的节点 n0 之后插入节点 P */ void insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; @@ -434,7 +434,7 @@ comments: true === "Swift" ```swift title="linked_list.swift" - /* 在链表的结点 n0 之后插入结点 P */ + /* 在链表的节点 n0 之后插入节点 P */ func insert(n0: ListNode, P: ListNode) { let n1 = n0.next P.next = n1 @@ -445,7 +445,7 @@ comments: true === "Zig" ```zig title="linked_list.zig" - // 在链表的结点 n0 之后插入结点 P + // 在链表的节点 n0 之后插入节点 P fn insert(n0: ?*inc.ListNode(i32), P: ?*inc.ListNode(i32)) void { var n1 = n0.?.next; P.?.next = n1; @@ -453,16 +453,16 @@ comments: true } ``` -在链表中删除结点也非常方便,只需改变一个结点的指针即可。如下图所示,尽管在删除操作完成后,结点 `P` 仍然指向 `n1`,但实际上 `P` 已经不再属于此链表,因为遍历此链表时无法访问到 `P`。 +在链表中删除节点也非常方便,只需改变一个节点的指针即可。如下图所示,尽管在删除操作完成后,节点 `P` 仍然指向 `n1`,但实际上 `P` 已经不再属于此链表,因为遍历此链表时无法访问到 `P`。 - + -Fig. 链表删除结点
+Fig. 链表删除节点
=== "Java" ```java title="linked_list.java" - /* 删除链表的结点 n0 之后的首个结点 */ + /* 删除链表的节点 n0 之后的首个节点 */ void remove(ListNode n0) { if (n0.next == null) return; @@ -476,7 +476,7 @@ comments: true === "C++" ```cpp title="linked_list.cpp" - /* 删除链表的结点 n0 之后的首个结点 */ + /* 删除链表的节点 n0 之后的首个节点 */ void remove(ListNode* n0) { if (n0->next == nullptr) return; @@ -493,7 +493,7 @@ comments: true ```python title="linked_list.py" def remove(n0: ListNode) -> None: - """ 删除链表的结点 n0 之后的首个结点 """ + """ 删除链表的节点 n0 之后的首个节点 """ if not n0.next: return # n0 -> P -> n1 @@ -505,7 +505,7 @@ comments: true === "Go" ```go title="linked_list.go" - /* 删除链表的结点 n0 之后的首个结点 */ + /* 删除链表的节点 n0 之后的首个节点 */ func removeNode(n0 *ListNode) { if n0.Next == nil { return @@ -520,7 +520,7 @@ comments: true === "JavaScript" ```javascript title="linked_list.js" - /* 删除链表的结点 n0 之后的首个结点 */ + /* 删除链表的节点 n0 之后的首个节点 */ function remove(n0) { if (!n0.next) return; @@ -534,7 +534,7 @@ comments: true === "TypeScript" ```typescript title="linked_list.ts" - /* 删除链表的结点 n0 之后的首个结点 */ + /* 删除链表的节点 n0 之后的首个节点 */ function remove(n0: ListNode): void { if (!n0.next) { return; @@ -555,7 +555,7 @@ comments: true === "C#" ```csharp title="linked_list.cs" - /* 删除链表的结点 n0 之后的首个结点 */ + /* 删除链表的节点 n0 之后的首个节点 */ void remove(ListNode n0) { if (n0.next == null) @@ -570,7 +570,7 @@ comments: true === "Swift" ```swift title="linked_list.swift" - /* 删除链表的结点 n0 之后的首个结点 */ + /* 删除链表的节点 n0 之后的首个节点 */ func remove(n0: ListNode) { if n0.next == nil { return @@ -586,7 +586,7 @@ comments: true === "Zig" ```zig title="linked_list.zig" - // 删除链表的结点 n0 之后的首个结点 + // 删除链表的节点 n0 之后的首个节点 fn remove(n0: ?*inc.ListNode(i32)) void { if (n0.?.next == null) return; // n0 -> P -> n1 @@ -598,12 +598,12 @@ comments: true ## 4.2.2. 链表缺点 -**链表访问结点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而,链表无法直接访问任意结点,这是因为系统需要从头结点出发,逐个向后遍历直至找到目标结点。例如,若要访问链表索引为 `index`(即第 `index + 1` 个)的结点,则需要向后遍历 `index` 轮。 +**链表访问节点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而,链表无法直接访问任意节点,这是因为系统需要从头节点出发,逐个向后遍历直至找到目标节点。例如,若要访问链表索引为 `index`(即第 `index + 1` 个)的节点,则需要向后遍历 `index` 轮。 === "Java" ```java title="linked_list.java" - /* 访问链表中索引为 index 的结点 */ + /* 访问链表中索引为 index 的节点 */ ListNode access(ListNode head, int index) { for (int i = 0; i < index; i++) { if (head == null) @@ -617,7 +617,7 @@ comments: true === "C++" ```cpp title="linked_list.cpp" - /* 访问链表中索引为 index 的结点 */ + /* 访问链表中索引为 index 的节点 */ ListNode* access(ListNode* head, int index) { for (int i = 0; i < index; i++) { if (head == nullptr) @@ -632,7 +632,7 @@ comments: true ```python title="linked_list.py" def access(head: ListNode, index: int) -> ListNode | None: - """ 访问链表中索引为 index 的结点 """ + """ 访问链表中索引为 index 的节点 """ for _ in range(index): if not head: return None @@ -643,7 +643,7 @@ comments: true === "Go" ```go title="linked_list.go" - /* 访问链表中索引为 index 的结点 */ + /* 访问链表中索引为 index 的节点 */ func access(head *ListNode, index int) *ListNode { for i := 0; i < index; i++ { if head == nil { @@ -658,7 +658,7 @@ comments: true === "JavaScript" ```javascript title="linked_list.js" - /* 访问链表中索引为 index 的结点 */ + /* 访问链表中索引为 index 的节点 */ function access(head, index) { for (let i = 0; i < index; i++) { if (!head) { @@ -673,7 +673,7 @@ comments: true === "TypeScript" ```typescript title="linked_list.ts" - /* 访问链表中索引为 index 的结点 */ + /* 访问链表中索引为 index 的节点 */ function access(head: ListNode | null, index: number): ListNode | null { for (let i = 0; i < index; i++) { if (!head) { @@ -694,7 +694,7 @@ comments: true === "C#" ```csharp title="linked_list.cs" - /* 访问链表中索引为 index 的结点 */ + /* 访问链表中索引为 index 的节点 */ ListNode? access(ListNode head, int index) { for (int i = 0; i < index; i++) @@ -710,7 +710,7 @@ comments: true === "Swift" ```swift title="linked_list.swift" - /* 访问链表中索引为 index 的结点 */ + /* 访问链表中索引为 index 的节点 */ func access(head: ListNode, index: Int) -> ListNode? { var head: ListNode? = head for _ in 0 ..< index { @@ -726,7 +726,7 @@ comments: true === "Zig" ```zig title="linked_list.zig" - // 访问链表中索引为 index 的结点 + // 访问链表中索引为 index 的节点 fn access(node: ?*inc.ListNode(i32), index: i32) ?*inc.ListNode(i32) { var head = node; var i: i32 = 0; @@ -738,16 +738,16 @@ comments: true } ``` -**链表的内存占用较大**。链表以结点为单位,每个结点除了保存值之外,还需额外保存指针(引用)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。 +**链表的内存占用较大**。链表以节点为单位,每个节点除了保存值之外,还需额外保存指针(引用)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。 ## 4.2.3. 链表常用操作 -**遍历链表查找**。遍历链表,查找链表内值为 `target` 的结点,输出结点在链表中的索引。 +**遍历链表查找**。遍历链表,查找链表内值为 `target` 的节点,输出节点在链表中的索引。 === "Java" ```java title="linked_list.java" - /* 在链表中查找值为 target 的首个结点 */ + /* 在链表中查找值为 target 的首个节点 */ int find(ListNode head, int target) { int index = 0; while (head != null) { @@ -763,7 +763,7 @@ comments: true === "C++" ```cpp title="linked_list.cpp" - /* 在链表中查找值为 target 的首个结点 */ + /* 在链表中查找值为 target 的首个节点 */ int find(ListNode* head, int target) { int index = 0; while (head != nullptr) { @@ -780,7 +780,7 @@ comments: true ```python title="linked_list.py" def find(head: ListNode, target: int) -> int: - """ 在链表中查找值为 target 的首个结点 """ + """ 在链表中查找值为 target 的首个节点 """ index = 0 while head: if head.val == target: @@ -793,7 +793,7 @@ comments: true === "Go" ```go title="linked_list.go" - /* 在链表中查找值为 target 的首个结点 */ + /* 在链表中查找值为 target 的首个节点 */ func findNode(head *ListNode, target int) int { index := 0 for head != nil { @@ -810,7 +810,7 @@ comments: true === "JavaScript" ```javascript title="linked_list.js" - /* 在链表中查找值为 target 的首个结点 */ + /* 在链表中查找值为 target 的首个节点 */ function find(head, target) { let index = 0; while (head !== null) { @@ -827,7 +827,7 @@ comments: true === "TypeScript" ```typescript title="linked_list.ts" - /* 在链表中查找值为 target 的首个结点 */ + /* 在链表中查找值为 target 的首个节点 */ function find(head: ListNode | null, target: number): number { let index = 0; while (head !== null) { @@ -850,7 +850,7 @@ comments: true === "C#" ```csharp title="linked_list.cs" - /* 在链表中查找值为 target 的首个结点 */ + /* 在链表中查找值为 target 的首个节点 */ int find(ListNode head, int target) { int index = 0; @@ -868,7 +868,7 @@ comments: true === "Swift" ```swift title="linked_list.swift" - /* 在链表中查找值为 target 的首个结点 */ + /* 在链表中查找值为 target 的首个节点 */ func find(head: ListNode, target: Int) -> Int { var head: ListNode? = head var index = 0 @@ -886,7 +886,7 @@ comments: true === "Zig" ```zig title="linked_list.zig" - // 在链表中查找值为 target 的首个结点 + // 在链表中查找值为 target 的首个节点 fn find(node: ?*inc.ListNode(i32), target: i32) i32 { var head = node; var index: i32 = 0; @@ -901,20 +901,20 @@ comments: true ## 4.2.4. 常见链表类型 -**单向链表**。即上述介绍的普通链表。单向链表的结点包含值和指向下一结点的指针(引用)两项数据。我们将首个结点称为头结点,将最后一个结点成为尾结点,尾结点指向 `null` 。 +**单向链表**。即上述介绍的普通链表。单向链表的节点包含值和指向下一节点的指针(引用)两项数据。我们将首个节点称为头节点,将最后一个节点成为尾节点,尾节点指向 `null` 。 -**环形链表**。如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,任意结点都可以视作头结点。 +**环形链表**。如果我们令单向链表的尾节点指向头节点(即首尾相接),则得到一个环形链表。在环形链表中,任意节点都可以视作头节点。 -**双向链表**。与单向链表相比,双向链表记录了两个方向的指针(引用)。双向链表的结点定义同时包含指向后继结点(下一结点)和前驱结点(上一结点)的指针。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。 +**双向链表**。与单向链表相比,双向链表记录了两个方向的指针(引用)。双向链表的节点定义同时包含指向后继节点(下一节点)和前驱节点(上一节点)的指针。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。 === "Java" ```java title="" - /* 双向链表结点类 */ + /* 双向链表节点类 */ class ListNode { - int val; // 结点值 - ListNode next; // 指向后继结点的指针(引用) - ListNode prev; // 指向前驱结点的指针(引用) + int val; // 节点值 + ListNode next; // 指向后继节点的指针(引用) + ListNode prev; // 指向前驱节点的指针(引用) ListNode(int x) { val = x; } // 构造函数 } ``` @@ -922,11 +922,11 @@ comments: true === "C++" ```cpp title="" - /* 双向链表结点结构体 */ + /* 双向链表节点结构体 */ struct ListNode { - int val; // 结点值 - ListNode *next; // 指向后继结点的指针(引用) - ListNode *prev; // 指向前驱结点的指针(引用) + int val; // 节点值 + ListNode *next; // 指向后继节点的指针(引用) + ListNode *prev; // 指向前驱节点的指针(引用) ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 构造函数 }; ``` @@ -934,22 +934,22 @@ comments: true === "Python" ```python title="" - """ 双向链表结点类 """ + """ 双向链表节点类 """ class ListNode: def __init__(self, val: int): - self.val: int = val # 结点值 - self.next: Optional[ListNode] = None # 指向后继结点的指针(引用) - self.prev: Optional[ListNode] = None # 指向前驱结点的指针(引用) + self.val: int = val # 节点值 + self.next: Optional[ListNode] = None # 指向后继节点的指针(引用) + self.prev: Optional[ListNode] = None # 指向前驱节点的指针(引用) ``` === "Go" ```go title="" - /* 双向链表结点结构体 */ + /* 双向链表节点结构体 */ type DoublyListNode struct { - Val int // 结点值 - Next *DoublyListNode // 指向后继结点的指针(引用) - Prev *DoublyListNode // 指向前驱结点的指针(引用) + Val int // 节点值 + Next *DoublyListNode // 指向后继节点的指针(引用) + Prev *DoublyListNode // 指向前驱节点的指针(引用) } // NewDoublyListNode 初始化 @@ -965,15 +965,15 @@ comments: true === "JavaScript" ```javascript title="" - /* 双向链表结点类 */ + /* 双向链表节点类 */ class ListNode { val; next; prev; constructor(val, next) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = next === undefined ? null : next; // 指向后继结点的指针(引用) - this.prev = prev === undefined ? null : prev; // 指向前驱结点的指针(引用) + this.val = val === undefined ? 0 : val; // 节点值 + this.next = next === undefined ? null : next; // 指向后继节点的指针(引用) + this.prev = prev === undefined ? null : prev; // 指向前驱节点的指针(引用) } } ``` @@ -981,15 +981,15 @@ comments: true === "TypeScript" ```typescript title="" - /* 双向链表结点类 */ + /* 双向链表节点类 */ class ListNode { val: number; next: ListNode | null; prev: ListNode | null; constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = next === undefined ? null : next; // 指向后继结点的指针(引用) - this.prev = prev === undefined ? null : prev; // 指向前驱结点的指针(引用) + this.val = val === undefined ? 0 : val; // 节点值 + this.next = next === undefined ? null : next; // 指向后继节点的指针(引用) + this.prev = prev === undefined ? null : prev; // 指向前驱节点的指针(引用) } } ``` @@ -1003,11 +1003,11 @@ comments: true === "C#" ```csharp title="" - /* 双向链表结点类 */ + /* 双向链表节点类 */ class ListNode { - int val; // 结点值 - ListNode next; // 指向后继结点的指针(引用) - ListNode prev; // 指向前驱结点的指针(引用) + int val; // 节点值 + ListNode next; // 指向后继节点的指针(引用) + ListNode prev; // 指向前驱节点的指针(引用) ListNode(int x) => val = x; // 构造函数 } ``` @@ -1015,11 +1015,11 @@ comments: true === "Swift" ```swift title="" - /* 双向链表结点类 */ + /* 双向链表节点类 */ class ListNode { - var val: Int // 结点值 - var next: ListNode? // 指向后继结点的指针(引用) - var prev: ListNode? // 指向前驱结点的指针(引用) + var val: Int // 节点值 + var next: ListNode? // 指向后继节点的指针(引用) + var prev: ListNode? // 指向前驱节点的指针(引用) init(x: Int) { // 构造函数 val = x @@ -1030,14 +1030,14 @@ comments: true === "Zig" ```zig title="" - // 双向链表结点类 + // 双向链表节点类 pub fn ListNode(comptime T: type) type { return struct { const Self = @This(); - val: T = 0, // 结点值 - next: ?*Self = null, // 指向后继结点的指针(引用) - prev: ?*Self = null, // 指向前驱结点的指针(引用) + val: T = 0, // 节点值 + next: ?*Self = null, // 指向后继节点的指针(引用) + prev: ?*Self = null, // 指向前驱节点的指针(引用) // 构造函数 pub fn init(self: *Self, x: i32) void { diff --git a/chapter_array_and_linkedlist/summary.md b/chapter_array_and_linkedlist/summary.md index 3c4cf80ce..994990aa2 100644 --- a/chapter_array_and_linkedlist/summary.md +++ b/chapter_array_and_linkedlist/summary.md @@ -6,7 +6,7 @@ comments: true - 数组和链表是两种基本数据结构,分别代表数据在计算机内存中的连续空间存储和离散空间存储方式。两者的优缺点呈现出互补的特性。 - 数组支持随机访问、占用内存较少;但插入和删除元素效率低,且初始化后长度不可变。 -- 链表通过更改指针实现高效的结点插入与删除,且可以灵活调整长度;但结点访问效率低、占用内存较多。常见的链表类型包括单向链表、循环链表、双向链表。 +- 链表通过更改指针实现高效的节点插入与删除,且可以灵活调整长度;但节点访问效率低、占用内存较多。常见的链表类型包括单向链表、循环链表、双向链表。 - 动态数组,又称列表,是基于数组实现的一种数据结构。它保留了数组的优势,同时可以灵活调整长度。列表的出现极大地提高了数组的易用性,但可能导致部分内存空间浪费。 - 下表总结并对比了数组与链表的各项特性。 @@ -23,7 +23,7 @@ comments: true !!! note "缓存局部性" - 在计算机中,数据读写速度排序是“硬盘 < 内存 < CPU 缓存”。当我们访问数组元素时,计算机不仅会加载它,还会缓存其周围的其它数据,从而借助高速缓存来提升后续操作的执行速度。链表则不然,计算机只能挨个地缓存各个结点,这样的多次“搬运”降低了整体效率。 + 在计算机中,数据读写速度排序是“硬盘 < 内存 < CPU 缓存”。当我们访问数组元素时,计算机不仅会加载它,还会缓存其周围的其它数据,从而借助高速缓存来提升后续操作的执行速度。链表则不然,计算机只能挨个地缓存各个节点,这样的多次“搬运”降低了整体效率。 - 下表对比了数组与链表在各种操作上的效率。 diff --git a/chapter_computational_complexity/space_complexity.md b/chapter_computational_complexity/space_complexity.md index 72f1e4695..0e2df14af 100755 --- a/chapter_computational_complexity/space_complexity.md +++ b/chapter_computational_complexity/space_complexity.md @@ -84,8 +84,8 @@ comments: true """ 类 """ class Node: def __init__(self, x: int): - self.val: int = x # 结点值 - self.next: Optional[Node] = None # 指向下一结点的指针(引用) + self.val: int = x # 节点值 + self.next: Optional[Node] = None # 指向下一节点的指针(引用) """ 函数 """ def function() -> int: @@ -137,8 +137,8 @@ comments: true val; next; constructor(val) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = null; // 指向下一结点的引用 + this.val = val === undefined ? 0 : val; // 节点值 + this.next = null; // 指向下一节点的引用 } } @@ -165,8 +165,8 @@ comments: true val: number; next: Node | null; constructor(val?: number) { - this.val = val === undefined ? 0 : val; // 结点值 - this.next = null; // 指向下一结点的引用 + this.val = val === undefined ? 0 : val; // 节点值 + this.next = null; // 指向下一节点的引用 } } @@ -1375,7 +1375,7 @@ $$ ### 指数阶 $O(2^n)$ -指数阶常见于二叉树。高度为 $n$ 的「满二叉树」的结点数量为 $2^n - 1$ ,占用 $O(2^n)$ 空间。 +指数阶常见于二叉树。高度为 $n$ 的「满二叉树」的节点数量为 $2^n - 1$ ,占用 $O(2^n)$ 空间。 === "Java" diff --git a/chapter_data_structure/classification_of_data_structure.md b/chapter_data_structure/classification_of_data_structure.md index 68044f7f3..99b92f861 100644 --- a/chapter_data_structure/classification_of_data_structure.md +++ b/chapter_data_structure/classification_of_data_structure.md @@ -8,7 +8,7 @@ comments: true ## 3.2.1. 逻辑结构:线性与非线性 -**「逻辑结构」揭示了数据元素之间的逻辑关系**。在数组和链表中,数据按照顺序依次排列,体现了数据之间的线性关系;而在树中,数据从顶部向下按层次排列,表现出祖先与后代之间的派生关系;图则由结点和边构成,反映了复杂的网络关系。 +**「逻辑结构」揭示了数据元素之间的逻辑关系**。在数组和链表中,数据按照顺序依次排列,体现了数据之间的线性关系;而在树中,数据从顶部向下按层次排列,表现出祖先与后代之间的派生关系;图则由节点和边构成,反映了复杂的网络关系。 逻辑结构通常分为「线性」和「非线性」两类。线性结构比较直观,指数据在逻辑关系上呈线性排列;非线性结构则相反,呈非线性排列,例如网状或树状结构。 diff --git a/chapter_graph/graph.md b/chapter_graph/graph.md index f4fd6c084..4fabc408b 100644 --- a/chapter_graph/graph.md +++ b/chapter_graph/graph.md @@ -18,7 +18,7 @@ $$Fig. 链表、树、图之间的关系
-那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作结点,把「边」看作连接各个结点的指针,则可将「图」看作是一种从「链表」拓展而来的数据结构。**相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,从而更为复杂**。 +那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作节点,把「边」看作连接各个节点的指针,则可将「图」看作是一种从「链表」拓展而来的数据结构。**相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,从而更为复杂**。 ## 9.1.1. 图常见类型 @@ -76,7 +76,7 @@ $$ ### 邻接表 -「邻接表 Adjacency List」使用 $n$ 个链表来表示图,链表结点表示顶点。第 $i$ 条链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点(即与该顶点相连的顶点)。 +「邻接表 Adjacency List」使用 $n$ 个链表来表示图,链表节点表示顶点。第 $i$ 条链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点(即与该顶点相连的顶点)。  diff --git a/chapter_graph/graph_operations.md b/chapter_graph/graph_operations.md index 2ecee1b5c..9b4506225 100644 --- a/chapter_graph/graph_operations.md +++ b/chapter_graph/graph_operations.md @@ -780,7 +780,7 @@ comments: true - **添加边**:在顶点对应链表的末尾添加边即可,使用 $O(1)$ 时间。因为是无向图,所以需要同时添加两个方向的边。 - **删除边**:在顶点对应链表中查找并删除指定边,使用 $O(m)$ 时间。在无向图中,需要同时删除两个方向的边。 -- **添加顶点**:在邻接表中添加一个链表,并将新增顶点作为链表头结点,使用 $O(1)$ 时间。 +- **添加顶点**:在邻接表中添加一个链表,并将新增顶点作为链表头节点,使用 $O(1)$ 时间。 - **删除顶点**:需遍历整个邻接表,删除包含指定顶点的所有边,使用 $O(1)$ 时间。 - **初始化**:在邻接表中创建 $n$ 个顶点和 $2m$ 条边,使用 $O(n + m)$ 时间。 @@ -799,7 +799,7 @@ comments: true === "删除顶点"  -以下是基于邻接表实现图的代码示例。细心的同学可能注意到,**我们在邻接表中使用 `Vertex` 结点类来表示顶点**,这样做的原因有: +以下是基于邻接表实现图的代码示例。细心的同学可能注意到,**我们在邻接表中使用 `Vertex` 节点类来表示顶点**,这样做的原因有: - 如果我们选择通过顶点值来区分不同顶点,那么值重复的顶点将无法被区分。 - 如果类似邻接矩阵那样,使用顶点列表索引来区分不同顶点。那么,假设我们想要删除索引为 $i$ 的顶点,则需要遍历整个邻接表,将其中 $> i$ 的索引全部减 $1$,这样操作效率较低。 @@ -889,7 +889,7 @@ comments: true // 邻接表,key: 顶点,value:该顶点的所有邻接顶点 unordered_mapFig. 完美二叉树的各层结点数量
+Fig. 完美二叉树的各层节点数量
-因此,我们可以将各层的“结点数量 $\times$ 结点高度”求和,**从而得到所有结点的堆化迭代次数的总和**。 +因此,我们可以将各层的“节点数量 $\times$ 节点高度”求和,**从而得到所有节点的堆化迭代次数的总和**。 $$ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1 @@ -201,4 +201,4 @@ T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline \end{aligned} $$ -进一步地,高度为 $h$ 的完美二叉树的结点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$ 。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。 +进一步地,高度为 $h$ 的完美二叉树的节点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$ 。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。 diff --git a/chapter_heap/heap.md b/chapter_heap/heap.md index 88b993af0..4c7829710 100644 --- a/chapter_heap/heap.md +++ b/chapter_heap/heap.md @@ -6,8 +6,8 @@ comments: true 「堆 Heap」是一棵限定条件下的「完全二叉树」。根据成立条件,堆主要分为两种类型: -- 「大顶堆 Max Heap」,任意结点的值 $\geq$ 其子结点的值; -- 「小顶堆 Min Heap」,任意结点的值 $\leq$ 其子结点的值; +- 「大顶堆 Max Heap」,任意节点的值 $\geq$ 其子节点的值; +- 「小顶堆 Min Heap」,任意节点的值 $\leq$ 其子节点的值;  @@ -15,9 +15,9 @@ comments: true ## 8.1.1. 堆术语与性质 -- 由于堆是完全二叉树,因此最底层结点靠左填充,其它层结点皆被填满。 -- 二叉树中的根结点对应「堆顶」,底层最靠右结点对应「堆底」。 -- 对于大顶堆 / 小顶堆,其堆顶元素(即根结点)的值最大 / 最小。 +- 由于堆是完全二叉树,因此最底层节点靠左填充,其它层节点皆被填满。 +- 二叉树中的根节点对应「堆顶」,底层最靠右节点对应「堆底」。 +- 对于大顶堆 / 小顶堆,其堆顶元素(即根节点)的值最大 / 最小。 ## 8.1.2. 堆常用操作 @@ -314,9 +314,9 @@ comments: true 在二叉树章节我们学过,「完全二叉树」非常适合使用「数组」来表示,而堆恰好是一棵完全二叉树,**因而我们采用「数组」来存储「堆」**。 -**二叉树指针**。使用数组表示二叉树时,元素代表结点值,索引代表结点在二叉树中的位置,**而结点指针通过索引映射公式来实现**。 +**二叉树指针**。使用数组表示二叉树时,元素代表节点值,索引代表节点在二叉树中的位置,**而节点指针通过索引映射公式来实现**。 -具体地,给定索引 $i$ ,那么其左子结点索引为 $2i + 1$ 、右子结点索引为 $2i + 2$ 、父结点索引为 $(i - 1) / 2$ (向下整除)。当索引越界时,代表空结点或结点不存在。 +具体地,给定索引 $i$ ,那么其左子节点索引为 $2i + 1$ 、右子节点索引为 $2i + 2$ 、父节点索引为 $(i - 1) / 2$ (向下整除)。当索引越界时,代表空节点或节点不存在。  @@ -327,17 +327,17 @@ comments: true === "Java" ```java title="my_heap.java" - /* 获取左子结点索引 */ + /* 获取左子节点索引 */ int left(int i) { return 2 * i + 1; } - /* 获取右子结点索引 */ + /* 获取右子节点索引 */ int right(int i) { return 2 * i + 2; } - /* 获取父结点索引 */ + /* 获取父节点索引 */ int parent(int i) { return (i - 1) / 2; // 向下整除 } @@ -346,17 +346,17 @@ comments: true === "C++" ```cpp title="my_heap.cpp" - /* 获取左子结点索引 */ + /* 获取左子节点索引 */ int left(int i) { return 2 * i + 1; } - /* 获取右子结点索引 */ + /* 获取右子节点索引 */ int right(int i) { return 2 * i + 2; } - /* 获取父结点索引 */ + /* 获取父节点索引 */ int parent(int i) { return (i - 1) / 2; // 向下取整 } @@ -366,32 +366,32 @@ comments: true ```python title="my_heap.py" def left(self, i: int) -> int: - """ 获取左子结点索引 """ + """ 获取左子节点索引 """ return 2 * i + 1 def right(self, i: int) -> int: - """ 获取右子结点索引 """ + """ 获取右子节点索引 """ return 2 * i + 2 def parent(self, i: int) -> int: - """ 获取父结点索引 """ + """ 获取父节点索引 """ return (i - 1) // 2 # 向下整除 ``` === "Go" ```go title="my_heap.go" - /* 获取左子结点索引 */ + /* 获取左子节点索引 */ func (h *maxHeap) left(i int) int { return 2*i + 1 } - /* 获取右子结点索引 */ + /* 获取右子节点索引 */ func (h *maxHeap) right(i int) int { return 2*i + 2 } - /* 获取父结点索引 */ + /* 获取父节点索引 */ func (h *maxHeap) parent(i int) int { // 向下整除 return (i - 1) / 2 @@ -401,17 +401,17 @@ comments: true === "JavaScript" ```javascript title="my_heap.js" - /* 获取左子结点索引 */ + /* 获取左子节点索引 */ #left(i) { return 2 * i + 1; } - /* 获取右子结点索引 */ + /* 获取右子节点索引 */ #right(i) { return 2 * i + 2; } - /* 获取父结点索引 */ + /* 获取父节点索引 */ #parent(i) { return Math.floor((i - 1) / 2); // 向下整除 } @@ -420,17 +420,17 @@ comments: true === "TypeScript" ```typescript title="my_heap.ts" - /* 获取左子结点索引 */ + /* 获取左子节点索引 */ left(i: number): number { return 2 * i + 1; } - /* 获取右子结点索引 */ + /* 获取右子节点索引 */ right(i: number): number { return 2 * i + 2; } - /* 获取父结点索引 */ + /* 获取父节点索引 */ parent(i: number): number { return Math.floor((i - 1) / 2); // 向下整除 } @@ -449,19 +449,19 @@ comments: true === "C#" ```csharp title="my_heap.cs" - /* 获取左子结点索引 */ + /* 获取左子节点索引 */ int left(int i) { return 2 * i + 1; } - /* 获取右子结点索引 */ + /* 获取右子节点索引 */ int right(int i) { return 2 * i + 2; } - /* 获取父结点索引 */ + /* 获取父节点索引 */ int parent(int i) { return (i - 1) / 2; // 向下整除 @@ -471,17 +471,17 @@ comments: true === "Swift" ```swift title="my_heap.swift" - /* 获取左子结点索引 */ + /* 获取左子节点索引 */ func left(i: Int) -> Int { 2 * i + 1 } - /* 获取右子结点索引 */ + /* 获取右子节点索引 */ func right(i: Int) -> Int { 2 * i + 2 } - /* 获取父结点索引 */ + /* 获取父节点索引 */ func parent(i: Int) -> Int { (i - 1) / 2 // 向下整除 } @@ -490,17 +490,17 @@ comments: true === "Zig" ```zig title="my_heap.zig" - // 获取左子结点索引 + // 获取左子节点索引 fn left(i: usize) usize { return 2 * i + 1; } - // 获取右子结点索引 + // 获取右子节点索引 fn right(i: usize) usize { return 2 * i + 2; } - // 获取父结点索引 + // 获取父节点索引 fn parent(i: usize) usize { // return (i - 1) / 2; // 向下整除 return @divFloor(i - 1, 2); @@ -509,7 +509,7 @@ comments: true ### 访问堆顶元素 -堆顶元素是二叉树的根结点,即列表首元素。 +堆顶元素是二叉树的根节点,即列表首元素。 === "Java" @@ -600,9 +600,9 @@ comments: true ### 元素入堆 -给定元素 `val` ,我们先将其添加到堆底。添加后,由于 `val` 可能大于堆中其它元素,此时堆的成立条件可能已经被破坏,**因此需要修复从插入结点到根结点这条路径上的各个结点**,该操作被称为「堆化 Heapify」。 +给定元素 `val` ,我们先将其添加到堆底。添加后,由于 `val` 可能大于堆中其它元素,此时堆的成立条件可能已经被破坏,**因此需要修复从插入节点到根节点这条路径上的各个节点**,该操作被称为「堆化 Heapify」。 -考虑从入堆结点开始,**从底至顶执行堆化**。具体地,比较插入结点与其父结点的值,若插入结点更大则将它们交换;并循环以上操作,从底至顶地修复堆中的各个结点;直至越过根结点时结束,或当遇到无需交换的结点时提前结束。 +考虑从入堆节点开始,**从底至顶执行堆化**。具体地,比较插入节点与其父节点的值,若插入节点更大则将它们交换;并循环以上操作,从底至顶地修复堆中的各个节点;直至越过根节点时结束,或当遇到无需交换的节点时提前结束。 === "<1>"  @@ -622,28 +622,28 @@ comments: true === "<6>"  -设结点总数为 $n$ ,则树的高度为 $O(\log n)$ ,易得堆化操作的循环轮数最多为 $O(\log n)$ ,**因而元素入堆操作的时间复杂度为 $O(\log n)$** 。 +设节点总数为 $n$ ,则树的高度为 $O(\log n)$ ,易得堆化操作的循环轮数最多为 $O(\log n)$ ,**因而元素入堆操作的时间复杂度为 $O(\log n)$** 。 === "Java" ```java title="my_heap.java" /* 元素入堆 */ void push(int val) { - // 添加结点 + // 添加节点 maxHeap.add(val); // 从底至顶堆化 siftUp(size() - 1); } - /* 从结点 i 开始,从底至顶堆化 */ + /* 从节点 i 开始,从底至顶堆化 */ void siftUp(int i) { while (true) { - // 获取结点 i 的父结点 + // 获取节点 i 的父节点 int p = parent(i); - // 当“越过根结点”或“结点无需修复”时,结束堆化 + // 当“越过根节点”或“节点无需修复”时,结束堆化 if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) break; - // 交换两结点 + // 交换两节点 swap(i, p); // 循环向上堆化 i = p; @@ -656,21 +656,21 @@ comments: true ```cpp title="my_heap.cpp" /* 元素入堆 */ void push(int val) { - // 添加结点 + // 添加节点 maxHeap.push_back(val); // 从底至顶堆化 siftUp(size() - 1); } - /* 从结点 i 开始,从底至顶堆化 */ + /* 从节点 i 开始,从底至顶堆化 */ void siftUp(int i) { while (true) { - // 获取结点 i 的父结点 + // 获取节点 i 的父节点 int p = parent(i); - // 当“越过根结点”或“结点无需修复”时,结束堆化 + // 当“越过根节点”或“节点无需修复”时,结束堆化 if (p < 0 || maxHeap[i] <= maxHeap[p]) break; - // 交换两结点 + // 交换两节点 swap(maxHeap[i], maxHeap[p]); // 循环向上堆化 i = p; @@ -683,20 +683,20 @@ comments: true ```python title="my_heap.py" def push(self, val: int): """ 元素入堆 """ - # 添加结点 + # 添加节点 self.max_heap.append(val) # 从底至顶堆化 self.sift_up(self.size() - 1) def sift_up(self, i: int): - """ 从结点 i 开始,从底至顶堆化 """ + """ 从节点 i 开始,从底至顶堆化 """ while True: - # 获取结点 i 的父结点 + # 获取节点 i 的父节点 p = self.parent(i) - # 当“越过根结点”或“结点无需修复”时,结束堆化 + # 当“越过根节点”或“节点无需修复”时,结束堆化 if p < 0 or self.max_heap[i] <= self.max_heap[p]: break - # 交换两结点 + # 交换两节点 self.swap(i, p) # 循环向上堆化 i = p @@ -707,22 +707,22 @@ comments: true ```go title="my_heap.go" /* 元素入堆 */ func (h *maxHeap) push(val any) { - // 添加结点 + // 添加节点 h.data = append(h.data, val) // 从底至顶堆化 h.siftUp(len(h.data) - 1) } - /* 从结点 i 开始,从底至顶堆化 */ + /* 从节点 i 开始,从底至顶堆化 */ func (h *maxHeap) siftUp(i int) { for true { - // 获取结点 i 的父结点 + // 获取节点 i 的父节点 p := h.parent(i) - // 当“越过根结点”或“结点无需修复”时,结束堆化 + // 当“越过根节点”或“节点无需修复”时,结束堆化 if p < 0 || h.data[i].(int) <= h.data[p].(int) { break } - // 交换两结点 + // 交换两节点 h.swap(i, p) // 循环向上堆化 i = p @@ -735,20 +735,20 @@ comments: true ```javascript title="my_heap.js" /* 元素入堆 */ push(val) { - // 添加结点 + // 添加节点 this.#maxHeap.push(val); // 从底至顶堆化 this.#siftUp(this.size() - 1); } - /* 从结点 i 开始,从底至顶堆化 */ + /* 从节点 i 开始,从底至顶堆化 */ #siftUp(i) { while (true) { - // 获取结点 i 的父结点 + // 获取节点 i 的父节点 const p = this.#parent(i); - // 当“越过根结点”或“结点无需修复”时,结束堆化 + // 当“越过根节点”或“节点无需修复”时,结束堆化 if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break; - // 交换两结点 + // 交换两节点 this.#swap(i, p); // 循环向上堆化 i = p; @@ -761,20 +761,20 @@ comments: true ```typescript title="my_heap.ts" /* 元素入堆 */ push(val: number): void { - // 添加结点 + // 添加节点 this.maxHeap.push(val); // 从底至顶堆化 this.siftUp(this.size() - 1); } - /* 从结点 i 开始,从底至顶堆化 */ + /* 从节点 i 开始,从底至顶堆化 */ siftUp(i: number): void { while (true) { - // 获取结点 i 的父结点 + // 获取节点 i 的父节点 const p = this.parent(i); - // 当“越过根结点”或“结点无需修复”时,结束堆化 + // 当“越过根节点”或“节点无需修复”时,结束堆化 if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break; - // 交换两结点 + // 交换两节点 this.swap(i, p); // 循环向上堆化 i = p; @@ -796,23 +796,23 @@ comments: true /* 元素入堆 */ void push(int val) { - // 添加结点 + // 添加节点 maxHeap.Add(val); // 从底至顶堆化 siftUp(size() - 1); } - /* 从结点 i 开始,从底至顶堆化 */ + /* 从节点 i 开始,从底至顶堆化 */ void siftUp(int i) { while (true) { - // 获取结点 i 的父结点 + // 获取节点 i 的父节点 int p = parent(i); - // 若“越过根结点”或“结点无需修复”,则结束堆化 + // 若“越过根节点”或“节点无需修复”,则结束堆化 if (p < 0 || maxHeap[i] <= maxHeap[p]) break; - // 交换两结点 + // 交换两节点 swap(i, p); // 循环向上堆化 i = p; @@ -825,23 +825,23 @@ comments: true ```swift title="my_heap.swift" /* 元素入堆 */ func push(val: Int) { - // 添加结点 + // 添加节点 maxHeap.append(val) // 从底至顶堆化 siftUp(i: size() - 1) } - /* 从结点 i 开始,从底至顶堆化 */ + /* 从节点 i 开始,从底至顶堆化 */ func siftUp(i: Int) { var i = i while true { - // 获取结点 i 的父结点 + // 获取节点 i 的父节点 let p = parent(i: i) - // 当“越过根结点”或“结点无需修复”时,结束堆化 + // 当“越过根节点”或“节点无需修复”时,结束堆化 if p < 0 || maxHeap[i] <= maxHeap[p] { break } - // 交换两结点 + // 交换两节点 swap(i: i, j: p) // 循环向上堆化 i = p @@ -854,21 +854,21 @@ comments: true ```zig title="my_heap.zig" // 元素入堆 fn push(self: *Self, val: T) !void { - // 添加结点 + // 添加节点 try self.max_heap.?.append(val); // 从底至顶堆化 try self.siftUp(self.size() - 1); } - // 从结点 i 开始,从底至顶堆化 + // 从节点 i 开始,从底至顶堆化 fn siftUp(self: *Self, i_: usize) !void { var i = i_; while (true) { - // 获取结点 i 的父结点 + // 获取节点 i 的父节点 var p = parent(i); - // 当“越过根结点”或“结点无需修复”时,结束堆化 + // 当“越过根节点”或“节点无需修复”时,结束堆化 if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; - // 交换两结点 + // 交换两节点 try self.swap(i, p); // 循环向上堆化 i = p; @@ -878,13 +878,13 @@ comments: true ### 堆顶元素出堆 -堆顶元素是二叉树根结点,即列表首元素,如果我们直接将首元素从列表中删除,则二叉树中所有结点都会随之发生移位(索引发生变化),这样后续使用堆化修复就很麻烦了。为了尽量减少元素索引变动,采取以下操作步骤: +堆顶元素是二叉树根节点,即列表首元素,如果我们直接将首元素从列表中删除,则二叉树中所有节点都会随之发生移位(索引发生变化),这样后续使用堆化修复就很麻烦了。为了尽量减少元素索引变动,采取以下操作步骤: -1. 交换堆顶元素与堆底元素(即交换根结点与最右叶结点); +1. 交换堆顶元素与堆底元素(即交换根节点与最右叶节点); 2. 交换完成后,将堆底从列表中删除(注意,因为已经交换,实际上删除的是原来的堆顶元素); -3. 从根结点开始,**从顶至底执行堆化**; +3. 从根节点开始,**从顶至底执行堆化**; -顾名思义,**从顶至底堆化的操作方向与从底至顶堆化相反**,我们比较根结点的值与其两个子结点的值,将最大的子结点与根结点执行交换,并循环以上操作,直到越过叶结点时结束,或当遇到无需交换的结点时提前结束。 +顾名思义,**从顶至底堆化的操作方向与从底至顶堆化相反**,我们比较根节点的值与其两个子节点的值,将最大的子节点与根节点执行交换,并循环以上操作,直到越过叶节点时结束,或当遇到无需交换的节点时提前结束。 === "<1>"  @@ -926,9 +926,9 @@ comments: true // 判空处理 if (isEmpty()) throw new EmptyStackException(); - // 交换根结点与最右叶结点(即交换首元素与尾元素) + // 交换根节点与最右叶节点(即交换首元素与尾元素) swap(0, size() - 1); - // 删除结点 + // 删除节点 int val = maxHeap.remove(size() - 1); // 从顶至底堆化 siftDown(0); @@ -936,18 +936,18 @@ comments: true return val; } - /* 从结点 i 开始,从顶至底堆化 */ + /* 从节点 i 开始,从顶至底堆化 */ void siftDown(int i) { while (true) { - // 判断结点 i, l, r 中值最大的结点,记为 ma + // 判断节点 i, l, r 中值最大的节点,记为 ma int l = left(i), r = right(i), ma = i; if (l < size() && maxHeap.get(l) > maxHeap.get(ma)) ma = l; if (r < size() && maxHeap.get(r) > maxHeap.get(ma)) ma = r; - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 + // 若节点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 if (ma == i) break; - // 交换两结点 + // 交换两节点 swap(i, ma); // 循环向下堆化 i = ma; @@ -964,25 +964,25 @@ comments: true if (empty()) { throw out_of_range("堆为空"); } - // 交换根结点与最右叶结点(即交换首元素与尾元素) + // 交换根节点与最右叶节点(即交换首元素与尾元素) swap(maxHeap[0], maxHeap[size() - 1]); - // 删除结点 + // 删除节点 maxHeap.pop_back(); // 从顶至底堆化 siftDown(0); } - /* 从结点 i 开始,从顶至底堆化 */ + /* 从节点 i 开始,从顶至底堆化 */ void siftDown(int i) { while (true) { - // 判断结点 i, l, r 中值最大的结点,记为 ma + // 判断节点 i, l, r 中值最大的节点,记为 ma int l = left(i), r = right(i), ma = i; - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 + // 若节点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l; if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r; - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 + // 若节点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 if (ma == i) break; swap(maxHeap[i], maxHeap[ma]); @@ -999,9 +999,9 @@ comments: true """ 元素出堆 """ # 判空处理 assert not self.is_empty() - # 交换根结点与最右叶结点(即交换首元素与尾元素) + # 交换根节点与最右叶节点(即交换首元素与尾元素) self.swap(0, self.size() - 1) - # 删除结点 + # 删除节点 val = self.max_heap.pop() # 从顶至底堆化 self.sift_down(0) @@ -1009,18 +1009,18 @@ comments: true return val def sift_down(self, i: int): - """ 从结点 i 开始,从顶至底堆化 """ + """ 从节点 i 开始,从顶至底堆化 """ while True: - # 判断结点 i, l, r 中值最大的结点,记为 ma + # 判断节点 i, l, r 中值最大的节点,记为 ma l, r, ma = self.left(i), self.right(i), i if l < self.size() and self.max_heap[l] > self.max_heap[ma]: ma = l if r < self.size() and self.max_heap[r] > self.max_heap[ma]: ma = r - # 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 + # 若节点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 if ma == i: break - # 交换两结点 + # 交换两节点 self.swap(i, ma) # 循环向下堆化 i = ma @@ -1036,9 +1036,9 @@ comments: true fmt.Println("error") return nil } - // 交换根结点与最右叶结点(即交换首元素与尾元素) + // 交换根节点与最右叶节点(即交换首元素与尾元素) h.swap(0, h.size()-1) - // 删除结点 + // 删除节点 val := h.data[len(h.data)-1] h.data = h.data[:len(h.data)-1] // 从顶至底堆化 @@ -1048,10 +1048,10 @@ comments: true return val } - /* 从结点 i 开始,从顶至底堆化 */ + /* 从节点 i 开始,从顶至底堆化 */ func (h *maxHeap) siftDown(i int) { for true { - // 判断结点 i, l, r 中值最大的结点,记为 max + // 判断节点 i, l, r 中值最大的节点,记为 max l, r, max := h.left(i), h.right(i), i if l < h.size() && h.data[l].(int) > h.data[max].(int) { max = l @@ -1059,11 +1059,11 @@ comments: true if r < h.size() && h.data[r].(int) > h.data[max].(int) { max = r } - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 + // 若节点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 if max == i { break } - // 交换两结点 + // 交换两节点 h.swap(i, max) // 循环向下堆化 i = max @@ -1078,9 +1078,9 @@ comments: true pop() { // 判空处理 if (this.isEmpty()) throw new Error("堆为空"); - // 交换根结点与最右叶结点(即交换首元素与尾元素) + // 交换根节点与最右叶节点(即交换首元素与尾元素) this.#swap(0, this.size() - 1); - // 删除结点 + // 删除节点 const val = this.#maxHeap.pop(); // 从顶至底堆化 this.#siftDown(0); @@ -1088,18 +1088,18 @@ comments: true return val; } - /* 从结点 i 开始,从顶至底堆化 */ + /* 从节点 i 开始,从顶至底堆化 */ #siftDown(i) { while (true) { - // 判断结点 i, l, r 中值最大的结点,记为 ma + // 判断节点 i, l, r 中值最大的节点,记为 ma const l = this.#left(i), r = this.#right(i); let ma = i; if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l; if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r; - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 + // 若节点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 if (ma == i) break; - // 交换两结点 + // 交换两节点 this.#swap(i, ma); // 循环向下堆化 i = ma; @@ -1114,9 +1114,9 @@ comments: true pop(): number { // 判空处理 if (this.isEmpty()) throw new RangeError('Heap is empty.'); - // 交换根结点与最右叶结点(即交换首元素与尾元素) + // 交换根节点与最右叶节点(即交换首元素与尾元素) this.swap(0, this.size() - 1); - // 删除结点 + // 删除节点 const val = this.maxHeap.pop(); // 从顶至底堆化 this.siftDown(0); @@ -1124,18 +1124,18 @@ comments: true return val; } - /* 从结点 i 开始,从顶至底堆化 */ + /* 从节点 i 开始,从顶至底堆化 */ siftDown(i: number): void { while (true) { - // 判断结点 i, l, r 中值最大的结点,记为 ma + // 判断节点 i, l, r 中值最大的节点,记为 ma const l = this.left(i), r = this.right(i); let ma = i; if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l; if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r; - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 + // 若节点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 if (ma == i) break; - // 交换两结点 + // 交换两节点 this.swap(i, ma); // 循环向下堆化 i = ma; @@ -1160,9 +1160,9 @@ comments: true // 判空处理 if (isEmpty()) throw new IndexOutOfRangeException(); - // 交换根结点与最右叶结点(即交换首元素与尾元素) + // 交换根节点与最右叶节点(即交换首元素与尾元素) swap(0, size() - 1); - // 删除结点 + // 删除节点 int val = maxHeap.Last(); maxHeap.RemoveAt(size() - 1); // 从顶至底堆化 @@ -1171,20 +1171,20 @@ comments: true return val; } - /* 从结点 i 开始,从顶至底堆化 */ + /* 从节点 i 开始,从顶至底堆化 */ void siftDown(int i) { while (true) { - // 判断结点 i, l, r 中值最大的结点,记为 ma + // 判断节点 i, l, r 中值最大的节点,记为 ma int l = left(i), r = right(i), ma = i; if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l; if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r; - // 若“结点 i 最大”或“越过叶结点”,则结束堆化 + // 若“节点 i 最大”或“越过叶节点”,则结束堆化 if (ma == i) break; - // 交换两结点 + // 交换两节点 swap(i, ma); // 循环向下堆化 i = ma; @@ -1201,9 +1201,9 @@ comments: true if isEmpty() { fatalError("堆为空") } - // 交换根结点与最右叶结点(即交换首元素与尾元素) + // 交换根节点与最右叶节点(即交换首元素与尾元素) swap(i: 0, j: size() - 1) - // 删除结点 + // 删除节点 let val = maxHeap.remove(at: size() - 1) // 从顶至底堆化 siftDown(i: 0) @@ -1211,11 +1211,11 @@ comments: true return val } - /* 从结点 i 开始,从顶至底堆化 */ + /* 从节点 i 开始,从顶至底堆化 */ func siftDown(i: Int) { var i = i while true { - // 判断结点 i, l, r 中值最大的结点,记为 ma + // 判断节点 i, l, r 中值最大的节点,记为 ma let l = left(i: i) let r = right(i: i) var ma = i @@ -1225,11 +1225,11 @@ comments: true if r < size(), maxHeap[r] > maxHeap[ma] { ma = r } - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 + // 若节点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 if ma == i { break } - // 交换两结点 + // 交换两节点 swap(i: i, j: ma) // 循环向下堆化 i = ma @@ -1244,9 +1244,9 @@ comments: true fn pop(self: *Self) !T { // 判断处理 if (self.isEmpty()) unreachable; - // 交换根结点与最右叶结点(即交换首元素与尾元素) + // 交换根节点与最右叶节点(即交换首元素与尾元素) try self.swap(0, self.size() - 1); - // 删除结点 + // 删除节点 var val = self.max_heap.?.pop(); // 从顶至底堆化 try self.siftDown(0); @@ -1254,19 +1254,19 @@ comments: true return val; } - // 从结点 i 开始,从顶至底堆化 + // 从节点 i 开始,从顶至底堆化 fn siftDown(self: *Self, i_: usize) !void { var i = i_; while (true) { - // 判断结点 i, l, r 中值最大的结点,记为 ma + // 判断节点 i, l, r 中值最大的节点,记为 ma var l = left(i); var r = right(i); var ma = i; if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; - // 若结点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 + // 若节点 i 最大或索引 l, r 越界,则无需继续堆化,跳出 if (ma == i) break; - // 交换两结点 + // 交换两节点 try self.swap(i, ma); // 循环向下堆化 i = ma; diff --git a/chapter_searching/hashing_search.md b/chapter_searching/hashing_search.md index 974f9e934..f2ad3d30f 100755 --- a/chapter_searching/hashing_search.md +++ b/chapter_searching/hashing_search.md @@ -130,18 +130,18 @@ comments: true } ``` -再比如,如果我们想要给定一个目标结点值 `target` ,获取对应的链表结点对象,那么也可以使用哈希查找实现。 +再比如,如果我们想要给定一个目标节点值 `target` ,获取对应的链表节点对象,那么也可以使用哈希查找实现。 - + -Fig. 哈希查找链表结点
+Fig. 哈希查找链表节点
=== "Java" ```java title="hashing_search.java" /* 哈希查找(链表) */ ListNode hashingSearchLinkedList(MapFig. AVL 树在删除结点后发生退化
+Fig. AVL 树在删除节点后发生退化
-再比如,在以下完美二叉树中插入两个结点后,树严重向左偏斜,查找操作的时间复杂度也随之发生劣化。 +再比如,在以下完美二叉树中插入两个节点后,树严重向左偏斜,查找操作的时间复杂度也随之发生劣化。 - + -Fig. AVL 树在插入结点后发生退化
+Fig. AVL 树在插入节点后发生退化
-G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorithm for the organization of information" 中提出了「AVL 树」。**论文中描述了一系列操作,使得在不断添加与删除结点后,AVL 树仍然不会发生退化**,进而使得各种操作的时间复杂度均能保持在 $O(\log n)$ 级别。 +G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorithm for the organization of information" 中提出了「AVL 树」。**论文中描述了一系列操作,使得在不断添加与删除节点后,AVL 树仍然不会发生退化**,进而使得各种操作的时间复杂度均能保持在 $O(\log n)$ 级别。 换言之,在频繁增删查改的使用场景中,AVL 树可始终保持很高的数据增删查改效率,具有很好的应用价值。 @@ -26,19 +26,19 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit 「AVL 树」既是「二叉搜索树」又是「平衡二叉树」,同时满足这两种二叉树的所有性质,因此又被称为「平衡二叉搜索树」。 -### 结点高度 +### 节点高度 -在 AVL 树的操作中,需要获取结点「高度 Height」,所以给 AVL 树的结点类添加 `height` 变量。 +在 AVL 树的操作中,需要获取节点「高度 Height」,所以给 AVL 树的节点类添加 `height` 变量。 === "Java" ```java title="" - /* AVL 树结点类 */ + /* AVL 树节点类 */ class TreeNode { - public int val; // 结点值 - public int height; // 结点高度 - public TreeNode left; // 左子结点 - public TreeNode right; // 右子结点 + public int val; // 节点值 + public int height; // 节点高度 + public TreeNode left; // 左子节点 + public TreeNode right; // 右子节点 public TreeNode(int x) { val = x; } } ``` @@ -46,12 +46,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "C++" ```cpp title="" - /* AVL 树结点类 */ + /* AVL 树节点类 */ struct TreeNode { - int val{}; // 结点值 - int height = 0; // 结点高度 - TreeNode *left{}; // 左子结点 - TreeNode *right{}; // 右子结点 + int val{}; // 节点值 + int height = 0; // 节点高度 + TreeNode *left{}; // 左子节点 + TreeNode *right{}; // 右子节点 TreeNode() = default; explicit TreeNode(int x) : val(x){} }; @@ -60,24 +60,24 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "Python" ```python title="" - """ AVL 树结点类 """ + """ AVL 树节点类 """ class TreeNode: def __init__(self, val: int): - self.val: int = val # 结点值 - self.height: int = 0 # 结点高度 - self.left: Optional[TreeNode] = None # 左子结点引用 - self.right: Optional[TreeNode] = None # 右子结点引用 + self.val: int = val # 节点值 + self.height: int = 0 # 节点高度 + self.left: Optional[TreeNode] = None # 左子节点引用 + self.right: Optional[TreeNode] = None # 右子节点引用 ``` === "Go" ```go title="" - /* AVL 树结点类 */ + /* AVL 树节点类 */ type TreeNode struct { - Val int // 结点值 - Height int // 结点高度 - Left *TreeNode // 左子结点引用 - Right *TreeNode // 右子结点引用 + Val int // 节点值 + Height int // 节点高度 + Left *TreeNode // 左子节点引用 + Right *TreeNode // 右子节点引用 } ``` @@ -85,10 +85,10 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```javascript title="" class TreeNode { - val; // 结点值 - height; //结点高度 - left; // 左子结点指针 - right; // 右子结点指针 + val; // 节点值 + height; //节点高度 + left; // 左子节点指针 + right; // 右子节点指针 constructor(val, left, right, height) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; @@ -102,10 +102,10 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```typescript title="" class TreeNode { - val: number; // 结点值 - height: number; // 结点高度 - left: TreeNode | null; // 左子结点指针 - right: TreeNode | null; // 右子结点指针 + val: number; // 节点值 + height: number; // 节点高度 + left: TreeNode | null; // 左子节点指针 + right: TreeNode | null; // 右子节点指针 constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; @@ -124,12 +124,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "C#" ```csharp title="" - /* AVL 树结点类 */ + /* AVL 树节点类 */ class TreeNode { - public int val; // 结点值 - public int height; // 结点高度 - public TreeNode? left; // 左子结点 - public TreeNode? right; // 右子结点 + public int val; // 节点值 + public int height; // 节点高度 + public TreeNode? left; // 左子节点 + public TreeNode? right; // 右子节点 public TreeNode(int x) { val = x; } } ``` @@ -137,12 +137,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "Swift" ```swift title="" - /* AVL 树结点类 */ + /* AVL 树节点类 */ class TreeNode { - var val: Int // 结点值 - var height: Int // 结点高度 - var left: TreeNode? // 左子结点 - var right: TreeNode? // 右子结点 + var val: Int // 节点值 + var height: Int // 节点高度 + var left: TreeNode? // 左子节点 + var right: TreeNode? // 右子节点 init(x: Int) { val = x @@ -157,20 +157,20 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ``` -「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1**。我们封装两个工具函数,分别用于获取与更新结点的高度。 +「节点高度」是最远叶节点到该节点的距离,即走过的「边」的数量。需要特别注意,**叶节点的高度为 0 ,空节点的高度为 -1**。我们封装两个工具函数,分别用于获取与更新节点的高度。 === "Java" ```java title="avl_tree.java" - /* 获取结点高度 */ + /* 获取节点高度 */ int height(TreeNode node) { - // 空结点高度为 -1 ,叶结点高度为 0 + // 空节点高度为 -1 ,叶节点高度为 0 return node == null ? -1 : node.height; } - /* 更新结点高度 */ + /* 更新节点高度 */ void updateHeight(TreeNode node) { - // 结点高度等于最高子树高度 + 1 + // 节点高度等于最高子树高度 + 1 node.height = Math.max(height(node.left), height(node.right)) + 1; } ``` @@ -178,15 +178,15 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "C++" ```cpp title="avl_tree.cpp" - /* 获取结点高度 */ + /* 获取节点高度 */ int height(TreeNode* node) { - // 空结点高度为 -1 ,叶结点高度为 0 + // 空节点高度为 -1 ,叶节点高度为 0 return node == nullptr ? -1 : node->height; } - /* 更新结点高度 */ + /* 更新节点高度 */ void updateHeight(TreeNode* node) { - // 结点高度等于最高子树高度 + 1 + // 节点高度等于最高子树高度 + 1 node->height = max(height(node->left), height(node->right)) + 1; } ``` @@ -195,35 +195,35 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```python title="avl_tree.py" def height(self, node: TreeNode | None) -> int: - """ 获取结点高度 """ - # 空结点高度为 -1 ,叶结点高度为 0 + """ 获取节点高度 """ + # 空节点高度为 -1 ,叶节点高度为 0 if node is not None: return node.height return -1 def __update_height(self, node: TreeNode | None): - """ 更新结点高度 """ - # 结点高度等于最高子树高度 + 1 + """ 更新节点高度 """ + # 节点高度等于最高子树高度 + 1 node.height = max([self.height(node.left), self.height(node.right)]) + 1 ``` === "Go" ```go title="avl_tree.go" - /* 获取结点高度 */ + /* 获取节点高度 */ func (t *aVLTree) height(node *TreeNode) int { - // 空结点高度为 -1 ,叶结点高度为 0 + // 空节点高度为 -1 ,叶节点高度为 0 if node != nil { return node.Height } return -1 } - /* 更新结点高度 */ + /* 更新节点高度 */ func (t *aVLTree) updateHeight(node *TreeNode) { lh := t.height(node.Left) rh := t.height(node.Right) - // 结点高度等于最高子树高度 + 1 + // 节点高度等于最高子树高度 + 1 if lh > rh { node.Height = lh + 1 } else { @@ -235,15 +235,15 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "JavaScript" ```javascript title="avl_tree.js" - /* 获取结点高度 */ + /* 获取节点高度 */ height(node) { - // 空结点高度为 -1 ,叶结点高度为 0 + // 空节点高度为 -1 ,叶节点高度为 0 return node === null ? -1 : node.height; } - /* 更新结点高度 */ + /* 更新节点高度 */ #updateHeight(node) { - // 结点高度等于最高子树高度 + 1 + // 节点高度等于最高子树高度 + 1 node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; } ``` @@ -251,15 +251,15 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "TypeScript" ```typescript title="avl_tree.ts" - /* 获取结点高度 */ + /* 获取节点高度 */ height(node: TreeNode): number { - // 空结点高度为 -1 ,叶结点高度为 0 + // 空节点高度为 -1 ,叶节点高度为 0 return node === null ? -1 : node.height; } - /* 更新结点高度 */ + /* 更新节点高度 */ updateHeight(node: TreeNode): void { - // 结点高度等于最高子树高度 + 1 + // 节点高度等于最高子树高度 + 1 node.height = Math.max(this.height(node.left), this.height(node.right)) + 1; } ``` @@ -275,17 +275,17 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "C#" ```csharp title="avl_tree.cs" - /* 获取结点高度 */ + /* 获取节点高度 */ int height(TreeNode? node) { - // 空结点高度为 -1 ,叶结点高度为 0 + // 空节点高度为 -1 ,叶节点高度为 0 return node == null ? -1 : node.height; } - /* 更新结点高度 */ + /* 更新节点高度 */ void updateHeight(TreeNode node) { - // 结点高度等于最高子树高度 + 1 + // 节点高度等于最高子树高度 + 1 node.height = Math.Max(height(node.left), height(node.right)) + 1; } ``` @@ -293,15 +293,15 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "Swift" ```swift title="avl_tree.swift" - /* 获取结点高度 */ + /* 获取节点高度 */ func height(node: TreeNode?) -> Int { - // 空结点高度为 -1 ,叶结点高度为 0 + // 空节点高度为 -1 ,叶节点高度为 0 node == nil ? -1 : node!.height } - /* 更新结点高度 */ + /* 更新节点高度 */ func updateHeight(node: TreeNode?) { - // 结点高度等于最高子树高度 + 1 + // 节点高度等于最高子树高度 + 1 node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 } ``` @@ -309,32 +309,32 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "Zig" ```zig title="avl_tree.zig" - // 获取结点高度 + // 获取节点高度 fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { _ = self; - // 空结点高度为 -1 ,叶结点高度为 0 + // 空节点高度为 -1 ,叶节点高度为 0 return if (node == null) -1 else node.?.height; } - // 更新结点高度 + // 更新节点高度 fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { - // 结点高度等于最高子树高度 + 1 + // 节点高度等于最高子树高度 + 1 node.?.height = std.math.max(self.height(node.?.left), self.height(node.?.right)) + 1; } ``` -### 结点平衡因子 +### 节点平衡因子 -结点的「平衡因子 Balance Factor」是 **结点的左子树高度减去右子树高度**,并定义空结点的平衡因子为 0 。同样地,我们将获取结点平衡因子封装成函数,以便后续使用。 +节点的「平衡因子 Balance Factor」是 **节点的左子树高度减去右子树高度**,并定义空节点的平衡因子为 0 。同样地,我们将获取节点平衡因子封装成函数,以便后续使用。 === "Java" ```java title="avl_tree.java" /* 获取平衡因子 */ int balanceFactor(TreeNode node) { - // 空结点平衡因子为 0 + // 空节点平衡因子为 0 if (node == null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 + // 节点平衡因子 = 左子树高度 - 右子树高度 return height(node.left) - height(node.right); } ``` @@ -344,9 +344,9 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```cpp title="avl_tree.cpp" /* 获取平衡因子 */ int balanceFactor(TreeNode* node) { - // 空结点平衡因子为 0 + // 空节点平衡因子为 0 if (node == nullptr) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 + // 节点平衡因子 = 左子树高度 - 右子树高度 return height(node->left) - height(node->right); } ``` @@ -356,10 +356,10 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```python title="avl_tree.py" def balance_factor(self, node: TreeNode | None) -> int: """ 获取平衡因子 """ - # 空结点平衡因子为 0 + # 空节点平衡因子为 0 if node is None: return 0 - # 结点平衡因子 = 左子树高度 - 右子树高度 + # 节点平衡因子 = 左子树高度 - 右子树高度 return self.height(node.left) - self.height(node.right) ``` @@ -368,11 +368,11 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```go title="avl_tree.go" /* 获取平衡因子 */ func (t *aVLTree) balanceFactor(node *TreeNode) int { - // 空结点平衡因子为 0 + // 空节点平衡因子为 0 if node == nil { return 0 } - // 结点平衡因子 = 左子树高度 - 右子树高度 + // 节点平衡因子 = 左子树高度 - 右子树高度 return t.height(node.Left) - t.height(node.Right) } ``` @@ -382,9 +382,9 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```javascript title="avl_tree.js" /* 获取平衡因子 */ balanceFactor(node) { - // 空结点平衡因子为 0 + // 空节点平衡因子为 0 if (node === null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 + // 节点平衡因子 = 左子树高度 - 右子树高度 return this.height(node.left) - this.height(node.right); } ``` @@ -394,9 +394,9 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```typescript title="avl_tree.ts" /* 获取平衡因子 */ balanceFactor(node: TreeNode): number { - // 空结点平衡因子为 0 + // 空节点平衡因子为 0 if (node === null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 + // 节点平衡因子 = 左子树高度 - 右子树高度 return this.height(node.left) - this.height(node.right); } ``` @@ -413,9 +413,9 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit /* 获取平衡因子 */ int balanceFactor(TreeNode? node) { - // 空结点平衡因子为 0 + // 空节点平衡因子为 0 if (node == null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 + // 节点平衡因子 = 左子树高度 - 右子树高度 return height(node.left) - height(node.right); } ``` @@ -425,9 +425,9 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```swift title="avl_tree.swift" /* 获取平衡因子 */ func balanceFactor(node: TreeNode?) -> Int { - // 空结点平衡因子为 0 + // 空节点平衡因子为 0 guard let node = node else { return 0 } - // 结点平衡因子 = 左子树高度 - 右子树高度 + // 节点平衡因子 = 左子树高度 - 右子树高度 return height(node: node.left) - height(node: node.right) } ``` @@ -437,26 +437,26 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```zig title="avl_tree.zig" // 获取平衡因子 fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { - // 空结点平衡因子为 0 + // 空节点平衡因子为 0 if (node == null) return 0; - // 结点平衡因子 = 左子树高度 - 右子树高度 + // 节点平衡因子 = 左子树高度 - 右子树高度 return self.height(node.?.left) - self.height(node.?.right); } ``` !!! note - 设平衡因子为 $f$ ,则一棵 AVL 树的任意结点的平衡因子皆满足 $-1 \le f \le 1$ 。 + 设平衡因子为 $f$ ,则一棵 AVL 树的任意节点的平衡因子皆满足 $-1 \le f \le 1$ 。 ## 7.4.2. AVL 树旋转 -AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影响二叉树中序遍历序列的前提下,使失衡结点重新恢复平衡**。换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。 +AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影响二叉树中序遍历序列的前提下,使失衡节点重新恢复平衡**。换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。 -我们将平衡因子的绝对值 $> 1$ 的结点称为「失衡结点」。根据结点的失衡情况,旋转操作分为 **右旋、左旋、先右旋后左旋、先左旋后右旋**,接下来我们来一起来看看它们是如何操作的。 +我们将平衡因子的绝对值 $> 1$ 的节点称为「失衡节点」。根据节点的失衡情况,旋转操作分为 **右旋、左旋、先右旋后左旋、先左旋后右旋**,接下来我们来一起来看看它们是如何操作的。 ### Case 1 - 右旋 -如下图所示(结点下方为「平衡因子」),从底至顶看,二叉树中首个失衡结点是 **结点 3**。我们聚焦在以该失衡结点为根结点的子树上,将该结点记为 `node` ,将其左子结点记为 `child` ,执行「右旋」操作。完成右旋后,该子树已经恢复平衡,并且仍然为二叉搜索树。 +如下图所示(节点下方为「平衡因子」),从底至顶看,二叉树中首个失衡节点是 **节点 3**。我们聚焦在以该失衡节点为根节点的子树上,将该节点记为 `node` ,将其左子节点记为 `child` ,执行「右旋」操作。完成右旋后,该子树已经恢复平衡,并且仍然为二叉搜索树。 === "<1>"  @@ -470,13 +470,13 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "<4>"  -进而,如果结点 `child` 本身有右子结点(记为 `grandChild` ),则需要在「右旋」中添加一步:将 `grandChild` 作为 `node` 的左子结点。 +进而,如果节点 `child` 本身有右子节点(记为 `grandChild` ),则需要在「右旋」中添加一步:将 `grandChild` 作为 `node` 的左子节点。 Fig. 有 grandChild 的右旋操作
-“向右旋转”是一种形象化的说法,实际需要通过修改结点指针实现,代码如下所示。 +“向右旋转”是一种形象化的说法,实际需要通过修改节点指针实现,代码如下所示。 === "Java" @@ -488,10 +488,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向右旋转 child.right = node; node.left = grandChild; - // 更新结点高度 + // 更新节点高度 updateHeight(node); updateHeight(child); - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child; } ``` @@ -506,10 +506,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向右旋转 child->right = node; node->left = grandChild; - // 更新结点高度 + // 更新节点高度 updateHeight(node); updateHeight(child); - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child; } ``` @@ -524,10 +524,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 # 以 child 为原点,将 node 向右旋转 child.right = node node.left = grand_child - # 更新结点高度 + # 更新节点高度 self.__update_height(node) self.__update_height(child) - # 返回旋转后子树的根结点 + # 返回旋转后子树的根节点 return child ``` @@ -541,10 +541,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向右旋转 child.Right = node node.Left = grandChild - // 更新结点高度 + // 更新节点高度 t.updateHeight(node) t.updateHeight(child) - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child } ``` @@ -559,10 +559,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向右旋转 child.right = node; node.left = grandChild; - // 更新结点高度 + // 更新节点高度 this.#updateHeight(node); this.#updateHeight(child); - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child; } ``` @@ -577,10 +577,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向右旋转 child.right = node; node.left = grandChild; - // 更新结点高度 + // 更新节点高度 this.updateHeight(node); this.updateHeight(child); - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child; } ``` @@ -602,10 +602,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向右旋转 child.right = node; node.left = grandChild; - // 更新结点高度 + // 更新节点高度 updateHeight(node); updateHeight(child); - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child; } ``` @@ -620,10 +620,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向右旋转 child?.right = node node?.left = grandChild - // 更新结点高度 + // 更新节点高度 updateHeight(node: node) updateHeight(node: child) - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child } ``` @@ -638,10 +638,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向右旋转 child.?.right = node; node.?.left = grandChild; - // 更新结点高度 + // 更新节点高度 self.updateHeight(node); self.updateHeight(child); - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child; } ``` @@ -654,7 +654,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影Fig. 左旋操作
-同理,若结点 `child` 本身有左子结点(记为 `grandChild` ),则需要在「左旋」中添加一步:将 `grandChild` 作为 `node` 的右子结点。 +同理,若节点 `child` 本身有左子节点(记为 `grandChild` ),则需要在「左旋」中添加一步:将 `grandChild` 作为 `node` 的右子节点。  @@ -672,10 +672,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向左旋转 child.left = node; node.right = grandChild; - // 更新结点高度 + // 更新节点高度 updateHeight(node); updateHeight(child); - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child; } ``` @@ -690,10 +690,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向左旋转 child->left = node; node->right = grandChild; - // 更新结点高度 + // 更新节点高度 updateHeight(node); updateHeight(child); - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child; } ``` @@ -708,10 +708,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 # 以 child 为原点,将 node 向左旋转 child.left = node node.right = grand_child - # 更新结点高度 + # 更新节点高度 self.__update_height(node) self.__update_height(child) - # 返回旋转后子树的根结点 + # 返回旋转后子树的根节点 return child ``` @@ -725,10 +725,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向左旋转 child.Left = node node.Right = grandChild - // 更新结点高度 + // 更新节点高度 t.updateHeight(node) t.updateHeight(child) - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child } ``` @@ -743,10 +743,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向左旋转 child.left = node; node.right = grandChild; - // 更新结点高度 + // 更新节点高度 this.#updateHeight(node); this.#updateHeight(child); - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child; } ``` @@ -761,10 +761,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向左旋转 child.left = node; node.right = grandChild; - // 更新结点高度 + // 更新节点高度 this.updateHeight(node); this.updateHeight(child); - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child; } ``` @@ -786,10 +786,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向左旋转 child.left = node; node.right = grandChild; - // 更新结点高度 + // 更新节点高度 updateHeight(node); updateHeight(child); - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child; } ``` @@ -804,10 +804,10 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向左旋转 child?.left = node node?.right = grandChild - // 更新结点高度 + // 更新节点高度 updateHeight(node: node) updateHeight(node: child) - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child } ``` @@ -822,17 +822,17 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 以 child 为原点,将 node 向左旋转 child.?.left = node; node.?.right = grandChild; - // 更新结点高度 + // 更新节点高度 self.updateHeight(node); self.updateHeight(child); - // 返回旋转后子树的根结点 + // 返回旋转后子树的根节点 return child; } ``` ### Case 3 - 先左后右 -对于下图的失衡结点 3 ,**单一使用左旋或右旋都无法使子树恢复平衡**,此时需要「先左旋后右旋」,即先对 `child` 执行「左旋」,再对 `node` 执行「右旋」。 +对于下图的失衡节点 3 ,**单一使用左旋或右旋都无法使子树恢复平衡**,此时需要「先左旋后右旋」,即先对 `child` 执行「左旋」,再对 `node` 执行「右旋」。  @@ -854,11 +854,11 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影Fig. AVL 树的四种旋转情况
-具体地,在代码中使用 **失衡结点的平衡因子、较高一侧子结点的平衡因子** 来确定失衡结点属于上图中的哪种情况。 +具体地,在代码中使用 **失衡节点的平衡因子、较高一侧子节点的平衡因子** 来确定失衡节点属于上图中的哪种情况。Fig. 在二叉搜索树中插入结点
+Fig. 在二叉搜索树中插入节点
=== "Java" ```java title="binary_search_tree.java" - /* 插入结点 */ + /* 插入节点 */ TreeNode insert(int num) { // 若树为空,直接提前返回 if (root == null) return null; TreeNode cur = root, pre = null; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != null) { - // 找到重复结点,直接返回 + // 找到重复节点,直接返回 if (cur.val == num) return null; pre = cur; // 插入位置在 cur 的右子树中 @@ -271,7 +271,7 @@ comments: true // 插入位置在 cur 的左子树中 else cur = cur.left; } - // 插入结点 val + // 插入节点 val TreeNode node = new TreeNode(num); if (pre.val < num) pre.right = node; else pre.left = node; @@ -282,14 +282,14 @@ comments: true === "C++" ```cpp title="binary_search_tree.cpp" - /* 插入结点 */ + /* 插入节点 */ TreeNode* insert(int num) { // 若树为空,直接提前返回 if (root == nullptr) return nullptr; TreeNode *cur = root, *pre = nullptr; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != nullptr) { - // 找到重复结点,直接返回 + // 找到重复节点,直接返回 if (cur->val == num) return nullptr; pre = cur; // 插入位置在 cur 的右子树中 @@ -297,7 +297,7 @@ comments: true // 插入位置在 cur 的左子树中 else cur = cur->left; } - // 插入结点 val + // 插入节点 val TreeNode* node = new TreeNode(num); if (pre->val < num) pre->right = node; else pre->left = node; @@ -309,15 +309,15 @@ comments: true ```python title="binary_search_tree.py" def insert(self, num: int) -> TreeNode | None: - """ 插入结点 """ + """ 插入节点 """ # 若树为空,直接提前返回 if self.__root is None: return None - # 循环查找,越过叶结点后跳出 + # 循环查找,越过叶节点后跳出 cur, pre = self.__root, None while cur is not None: - # 找到重复结点,直接返回 + # 找到重复节点,直接返回 if cur.val == num: return None pre = cur @@ -328,7 +328,7 @@ comments: true else: cur = cur.left - # 插入结点 val + # 插入节点 val node = TreeNode(num) if pre.val < num: pre.right = node @@ -340,16 +340,16 @@ comments: true === "Go" ```go title="binary_search_tree.go" - /* 插入结点 */ + /* 插入节点 */ func (bst *binarySearchTree) insert(num int) *TreeNode { cur := bst.root // 若树为空,直接提前返回 if cur == nil { return nil } - // 待插入结点之前的结点位置 + // 待插入节点之前的节点位置 var pre *TreeNode = nil - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 for cur != nil { if cur.Val == num { return nil @@ -361,7 +361,7 @@ comments: true cur = cur.Left } } - // 插入结点 + // 插入节点 node := NewTreeNode(num) if pre.Val < num { pre.Right = node @@ -375,14 +375,14 @@ comments: true === "JavaScript" ```javascript title="binary_search_tree.js" - /* 插入结点 */ + /* 插入节点 */ function insert(num) { // 若树为空,直接提前返回 if (root === null) return null; let cur = root, pre = null; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur !== null) { - // 找到重复结点,直接返回 + // 找到重复节点,直接返回 if (cur.val === num) return null; pre = cur; // 插入位置在 cur 的右子树中 @@ -390,7 +390,7 @@ comments: true // 插入位置在 cur 的左子树中 else cur = cur.left; } - // 插入结点 val + // 插入节点 val let node = new TreeNode(num); if (pre.val < num) pre.right = node; else pre.left = node; @@ -401,7 +401,7 @@ comments: true === "TypeScript" ```typescript title="binary_search_tree.ts" - /* 插入结点 */ + /* 插入节点 */ function insert(num: number): TreeNode | null { // 若树为空,直接提前返回 if (root === null) { @@ -409,10 +409,10 @@ comments: true } let cur = root, pre: TreeNode | null = null; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur !== null) { if (cur.val === num) { - return null; // 找到重复结点,直接返回 + return null; // 找到重复节点,直接返回 } pre = cur; if (cur.val < num) { @@ -421,7 +421,7 @@ comments: true cur = cur.left as TreeNode; // 插入位置在 cur 的左子树中 } } - // 插入结点 val + // 插入节点 val let node = new TreeNode(num); if (pre!.val < num) { pre!.right = node; @@ -441,16 +441,16 @@ comments: true === "C#" ```csharp title="binary_search_tree.cs" - /* 插入结点 */ + /* 插入节点 */ TreeNode? insert(int num) { // 若树为空,直接提前返回 if (root == null) return null; TreeNode? cur = root, pre = null; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != null) { - // 找到重复结点,直接返回 + // 找到重复节点,直接返回 if (cur.val == num) return null; pre = cur; // 插入位置在 cur 的右子树中 @@ -459,7 +459,7 @@ comments: true else cur = cur.left; } - // 插入结点 val + // 插入节点 val TreeNode node = new TreeNode(num); if (pre != null) { @@ -473,7 +473,7 @@ comments: true === "Swift" ```swift title="binary_search_tree.swift" - /* 插入结点 */ + /* 插入节点 */ func insert(num: Int) -> TreeNode? { // 若树为空,直接提前返回 if root == nil { @@ -481,9 +481,9 @@ comments: true } var cur = root var pre: TreeNode? - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while cur != nil { - // 找到重复结点,直接返回 + // 找到重复节点,直接返回 if cur!.val == num { return nil } @@ -497,7 +497,7 @@ comments: true cur = cur?.left } } - // 插入结点 val + // 插入节点 val let node = TreeNode(x: num) if pre!.val < num { pre?.right = node @@ -511,15 +511,15 @@ comments: true === "Zig" ```zig title="binary_search_tree.zig" - // 插入结点 + // 插入节点 fn insert(self: *Self, num: T) !?*inc.TreeNode(T) { // 若树为空,直接提前返回 if (self.root == null) return null; var cur = self.root; var pre: ?*inc.TreeNode(T) = null; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != null) { - // 找到重复结点,直接返回 + // 找到重复节点,直接返回 if (cur.?.val == num) return null; pre = cur; // 插入位置在 cur 的右子树中 @@ -530,7 +530,7 @@ comments: true cur = cur.?.left; } } - // 插入结点 val + // 插入节点 val var node = try self.mem_allocator.create(inc.TreeNode(T)); node.init(num); if (pre.?.val < num) { @@ -542,34 +542,34 @@ comments: true } ``` -为了插入结点,需要借助 **辅助结点 `pre`** 保存上一轮循环的结点,这样在遍历到 $\text{null}$ 时,我们也可以获取到其父结点,从而完成结点插入操作。 +为了插入节点,需要借助 **辅助节点 `pre`** 保存上一轮循环的节点,这样在遍历到 $\text{null}$ 时,我们也可以获取到其父节点,从而完成节点插入操作。 -与查找结点相同,插入结点使用 $O(\log n)$ 时间。 +与查找节点相同,插入节点使用 $O(\log n)$ 时间。 -### 删除结点 +### 删除节点 -与插入结点一样,我们需要在删除操作后维持二叉搜索树的“左子树 < 根结点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除结点。接下来,根据待删除结点的子结点数量,删除操作需要分为三种情况: +与插入节点一样,我们需要在删除操作后维持二叉搜索树的“左子树 < 根节点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除节点。接下来,根据待删除节点的子节点数量,删除操作需要分为三种情况: -**当待删除结点的子结点数量 $= 0$ 时**,表明待删除结点是叶结点,直接删除即可。 +**当待删除节点的子节点数量 $= 0$ 时**,表明待删除节点是叶节点,直接删除即可。 - + -Fig. 在二叉搜索树中删除结点(度为 0)
+Fig. 在二叉搜索树中删除节点(度为 0)
-**当待删除结点的子结点数量 $= 1$ 时**,将待删除结点替换为其子结点即可。 +**当待删除节点的子节点数量 $= 1$ 时**,将待删除节点替换为其子节点即可。 - + -Fig. 在二叉搜索树中删除结点(度为 1)
+Fig. 在二叉搜索树中删除节点(度为 1)
-**当待删除结点的子结点数量 $= 2$ 时**,删除操作分为三步: +**当待删除节点的子节点数量 $= 2$ 时**,删除操作分为三步: -1. 找到待删除结点在 **中序遍历序列** 中的下一个结点,记为 `nex` ; -2. 在树中递归删除结点 `nex` ; -3. 使用 `nex` 替换待删除结点; +1. 找到待删除节点在 **中序遍历序列** 中的下一个节点,记为 `nex` ; +2. 在树中递归删除节点 `nex` ; +3. 使用 `nex` 替换待删除节点; === "<1>" -  +  === "<2>"  @@ -580,42 +580,42 @@ comments: true === "<4>"  -删除结点操作也使用 $O(\log n)$ 时间,其中查找待删除结点 $O(\log n)$ ,获取中序遍历后继结点 $O(\log n)$ 。 +删除节点操作也使用 $O(\log n)$ 时间,其中查找待删除节点 $O(\log n)$ ,获取中序遍历后继节点 $O(\log n)$ 。 === "Java" ```java title="binary_search_tree.java" - /* 删除结点 */ + /* 删除节点 */ TreeNode remove(int num) { // 若树为空,直接提前返回 if (root == null) return null; TreeNode cur = root, pre = null; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != null) { - // 找到待删除结点,跳出循环 + // 找到待删除节点,跳出循环 if (cur.val == num) break; pre = cur; - // 待删除结点在 cur 的右子树中 + // 待删除节点在 cur 的右子树中 if (cur.val < num) cur = cur.right; - // 待删除结点在 cur 的左子树中 + // 待删除节点在 cur 的左子树中 else cur = cur.left; } - // 若无待删除结点,则直接返回 + // 若无待删除节点,则直接返回 if (cur == null) return null; - // 子结点数量 = 0 or 1 + // 子节点数量 = 0 or 1 if (cur.left == null || cur.right == null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 TreeNode child = cur.left != null ? cur.left : cur.right; - // 删除结点 cur + // 删除节点 cur if (pre.left == cur) pre.left = child; else pre.right = child; } - // 子结点数量 = 2 + // 子节点数量 = 2 else { - // 获取中序遍历中 cur 的下一个结点 + // 获取中序遍历中 cur 的下一个节点 TreeNode nex = getInOrderNext(cur.right); int tmp = nex.val; - // 递归删除结点 nex + // 递归删除节点 nex remove(nex.val); // 将 nex 的值复制给 cur cur.val = tmp; @@ -623,10 +623,10 @@ comments: true return cur; } - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ + /* 获取中序遍历中的下一个节点(仅适用于 root 有左子节点的情况) */ TreeNode getInOrderNext(TreeNode root) { if (root == null) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 + // 循环访问左子节点,直到叶节点时为最小节点,跳出 while (root.left != null) { root = root.left; } @@ -637,39 +637,39 @@ comments: true === "C++" ```cpp title="binary_search_tree.cpp" - /* 删除结点 */ + /* 删除节点 */ TreeNode* remove(int num) { // 若树为空,直接提前返回 if (root == nullptr) return nullptr; TreeNode *cur = root, *pre = nullptr; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != nullptr) { - // 找到待删除结点,跳出循环 + // 找到待删除节点,跳出循环 if (cur->val == num) break; pre = cur; - // 待删除结点在 cur 的右子树中 + // 待删除节点在 cur 的右子树中 if (cur->val < num) cur = cur->right; - // 待删除结点在 cur 的左子树中 + // 待删除节点在 cur 的左子树中 else cur = cur->left; } - // 若无待删除结点,则直接返回 + // 若无待删除节点,则直接返回 if (cur == nullptr) return nullptr; - // 子结点数量 = 0 or 1 + // 子节点数量 = 0 or 1 if (cur->left == nullptr || cur->right == nullptr) { - // 当子结点数量 = 0 / 1 时, child = nullptr / 该子结点 + // 当子节点数量 = 0 / 1 时, child = nullptr / 该子节点 TreeNode* child = cur->left != nullptr ? cur->left : cur->right; - // 删除结点 cur + // 删除节点 cur if (pre->left == cur) pre->left = child; else pre->right = child; // 释放内存 delete cur; } - // 子结点数量 = 2 + // 子节点数量 = 2 else { - // 获取中序遍历中 cur 的下一个结点 + // 获取中序遍历中 cur 的下一个节点 TreeNode* nex = getInOrderNext(cur->right); int tmp = nex->val; - // 递归删除结点 nex + // 递归删除节点 nex remove(nex->val); // 将 nex 的值复制给 cur cur->val = tmp; @@ -677,10 +677,10 @@ comments: true return cur; } - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ + /* 获取中序遍历中的下一个节点(仅适用于 root 有左子节点的情况) */ TreeNode* getInOrderNext(TreeNode* root) { if (root == nullptr) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 + // 循环访问左子节点,直到叶节点时为最小节点,跳出 while (root->left != nullptr) { root = root->left; } @@ -692,51 +692,51 @@ comments: true ```python title="binary_search_tree.py" def remove(self, num: int) -> TreeNode | None: - """ 删除结点 """ + """ 删除节点 """ # 若树为空,直接提前返回 if self.__root is None: return None - # 循环查找,越过叶结点后跳出 + # 循环查找,越过叶节点后跳出 cur, pre = self.__root, None while cur is not None: - # 找到待删除结点,跳出循环 + # 找到待删除节点,跳出循环 if cur.val == num: break pre = cur - if cur.val < num: # 待删除结点在 cur 的右子树中 + if cur.val < num: # 待删除节点在 cur 的右子树中 cur = cur.right - else: # 待删除结点在 cur 的左子树中 + else: # 待删除节点在 cur 的左子树中 cur = cur.left - # 若无待删除结点,则直接返回 + # 若无待删除节点,则直接返回 if cur is None: return None - # 子结点数量 = 0 or 1 + # 子节点数量 = 0 or 1 if cur.left is None or cur.right is None: - # 当子结点数量 = 0 / 1 时, child = null / 该子结点 + # 当子节点数量 = 0 / 1 时, child = null / 该子节点 child = cur.left or cur.right - # 删除结点 cur + # 删除节点 cur if pre.left == cur: pre.left = child else: pre.right = child - # 子结点数量 = 2 + # 子节点数量 = 2 else: - # 获取中序遍历中 cur 的下一个结点 + # 获取中序遍历中 cur 的下一个节点 nex: TreeNode = self.get_inorder_next(cur.right) tmp: int = nex.val - # 递归删除结点 nex + # 递归删除节点 nex self.remove(nex.val) # 将 nex 的值复制给 cur cur.val = tmp return cur def get_inorder_next(self, root: TreeNode | None) -> TreeNode | None: - """ 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) """ + """ 获取中序遍历中的下一个节点(仅适用于 root 有左子节点的情况) """ if root is None: return root - # 循环访问左子结点,直到叶结点时为最小结点,跳出 + # 循环访问左子节点,直到叶节点时为最小节点,跳出 while root.left is not None: root = root.left return root @@ -745,54 +745,54 @@ comments: true === "Go" ```go title="binary_search_tree.go" - /* 删除结点 */ + /* 删除节点 */ func (bst *binarySearchTree) remove(num int) *TreeNode { cur := bst.root // 若树为空,直接提前返回 if cur == nil { return nil } - // 待删除结点之前的结点位置 + // 待删除节点之前的节点位置 var pre *TreeNode = nil - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 for cur != nil { if cur.Val == num { break } pre = cur if cur.Val < num { - // 待删除结点在右子树中 + // 待删除节点在右子树中 cur = cur.Right } else { - // 待删除结点在左子树中 + // 待删除节点在左子树中 cur = cur.Left } } - // 若无待删除结点,则直接返回 + // 若无待删除节点,则直接返回 if cur == nil { return nil } - // 子结点数为 0 或 1 + // 子节点数为 0 或 1 if cur.Left == nil || cur.Right == nil { var child *TreeNode = nil - // 取出待删除结点的子结点 + // 取出待删除节点的子节点 if cur.Left != nil { child = cur.Left } else { child = cur.Right } - // 将子结点替换为待删除结点 + // 将子节点替换为待删除节点 if pre.Left == cur { pre.Left = child } else { pre.Right = child } - // 子结点数为 2 + // 子节点数为 2 } else { - // 获取中序遍历中待删除结点 cur 的下一个结点 + // 获取中序遍历中待删除节点 cur 的下一个节点 next := bst.getInOrderNext(cur) temp := next.Val - // 递归删除结点 next + // 递归删除节点 next bst.remove(next.Val) // 将 next 的值复制给 cur cur.Val = temp @@ -800,12 +800,12 @@ comments: true return cur } - /* 获取中序遍历的下一个结点(仅适用于 root 有左子结点的情况) */ + /* 获取中序遍历的下一个节点(仅适用于 root 有左子节点的情况) */ func (bst *binarySearchTree) getInOrderNext(node *TreeNode) *TreeNode { if node == nil { return node } - // 循环访问左子结点,直到叶结点时为最小结点,跳出 + // 循环访问左子节点,直到叶节点时为最小节点,跳出 for node.Left != nil { node = node.Left } @@ -816,37 +816,37 @@ comments: true === "JavaScript" ```javascript title="binary_search_tree.js" - /* 删除结点 */ + /* 删除节点 */ function remove(num) { // 若树为空,直接提前返回 if (root === null) return null; let cur = root, pre = null; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur !== null) { - // 找到待删除结点,跳出循环 + // 找到待删除节点,跳出循环 if (cur.val === num) break; pre = cur; - // 待删除结点在 cur 的右子树中 + // 待删除节点在 cur 的右子树中 if (cur.val < num) cur = cur.right; - // 待删除结点在 cur 的左子树中 + // 待删除节点在 cur 的左子树中 else cur = cur.left; } - // 若无待删除结点,则直接返回 + // 若无待删除节点,则直接返回 if (cur === null) return null; - // 子结点数量 = 0 or 1 + // 子节点数量 = 0 or 1 if (cur.left === null || cur.right === null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 let child = cur.left !== null ? cur.left : cur.right; - // 删除结点 cur + // 删除节点 cur if (pre.left === cur) pre.left = child; else pre.right = child; } - // 子结点数量 = 2 + // 子节点数量 = 2 else { - // 获取中序遍历中 cur 的下一个结点 + // 获取中序遍历中 cur 的下一个节点 let nex = getInOrderNext(cur.right); let tmp = nex.val; - // 递归删除结点 nex + // 递归删除节点 nex remove(nex.val); // 将 nex 的值复制给 cur cur.val = tmp; @@ -854,10 +854,10 @@ comments: true return cur; } - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ + /* 获取中序遍历中的下一个节点(仅适用于 root 有左子节点的情况) */ function getInOrderNext(root) { if (root === null) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 + // 循环访问左子节点,直到叶节点时为最小节点,跳出 while (root.left !== null) { root = root.left; } @@ -868,7 +868,7 @@ comments: true === "TypeScript" ```typescript title="binary_search_tree.ts" - /* 删除结点 */ + /* 删除节点 */ function remove(num: number): TreeNode | null { // 若树为空,直接提前返回 if (root === null) { @@ -876,40 +876,40 @@ comments: true } let cur = root, pre: TreeNode | null = null; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur !== null) { - // 找到待删除结点,跳出循环 + // 找到待删除节点,跳出循环 if (cur.val === num) { break; } pre = cur; if (cur.val < num) { - cur = cur.right as TreeNode; // 待删除结点在 cur 的右子树中 + cur = cur.right as TreeNode; // 待删除节点在 cur 的右子树中 } else { - cur = cur.left as TreeNode; // 待删除结点在 cur 的左子树中 + cur = cur.left as TreeNode; // 待删除节点在 cur 的左子树中 } } - // 若无待删除结点,则直接返回 + // 若无待删除节点,则直接返回 if (cur === null) { return null; } - // 子结点数量 = 0 or 1 + // 子节点数量 = 0 or 1 if (cur.left === null || cur.right === null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 let child = cur.left !== null ? cur.left : cur.right; - // 删除结点 cur + // 删除节点 cur if (pre!.left === cur) { pre!.left = child; } else { pre!.right = child; } } - // 子结点数量 = 2 + // 子节点数量 = 2 else { - // 获取中序遍历中 cur 的下一个结点 + // 获取中序遍历中 cur 的下一个节点 let next = getInOrderNext(cur.right); let tmp = next!.val; - // 递归删除结点 nex + // 递归删除节点 nex remove(next!.val); // 将 nex 的值复制给 cur cur.val = tmp; @@ -917,12 +917,12 @@ comments: true return cur; } - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ + /* 获取中序遍历中的下一个节点(仅适用于 root 有左子节点的情况) */ function getInOrderNext(root: TreeNode | null): TreeNode | null { if (root === null) { return null; } - // 循环访问左子结点,直到叶结点时为最小结点,跳出 + // 循环访问左子节点,直到叶节点时为最小节点,跳出 while (root.left !== null) { root = root.left; } @@ -941,31 +941,31 @@ comments: true === "C#" ```csharp title="binary_search_tree.cs" - /* 删除结点 */ + /* 删除节点 */ TreeNode? remove(int num) { // 若树为空,直接提前返回 if (root == null) return null; TreeNode? cur = root, pre = null; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != null) { - // 找到待删除结点,跳出循环 + // 找到待删除节点,跳出循环 if (cur.val == num) break; pre = cur; - // 待删除结点在 cur 的右子树中 + // 待删除节点在 cur 的右子树中 if (cur.val < num) cur = cur.right; - // 待删除结点在 cur 的左子树中 + // 待删除节点在 cur 的左子树中 else cur = cur.left; } - // 若无待删除结点,则直接返回 + // 若无待删除节点,则直接返回 if (cur == null || pre == null) return null; - // 子结点数量 = 0 or 1 + // 子节点数量 = 0 or 1 if (cur.left == null || cur.right == null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 TreeNode? child = cur.left != null ? cur.left : cur.right; - // 删除结点 cur + // 删除节点 cur if (pre.left == cur) { pre.left = child; @@ -975,15 +975,15 @@ comments: true pre.right = child; } } - // 子结点数量 = 2 + // 子节点数量 = 2 else { - // 获取中序遍历中 cur 的下一个结点 + // 获取中序遍历中 cur 的下一个节点 TreeNode? nex = getInOrderNext(cur.right); if (nex != null) { int tmp = nex.val; - // 递归删除结点 nex + // 递归删除节点 nex remove(nex.val); // 将 nex 的值复制给 cur cur.val = tmp; @@ -992,11 +992,11 @@ comments: true return cur; } - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ + /* 获取中序遍历中的下一个节点(仅适用于 root 有左子节点的情况) */ TreeNode? getInOrderNext(TreeNode? root) { if (root == null) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 + // 循环访问左子节点,直到叶节点时为最小节点,跳出 while (root.left != null) { root = root.left; @@ -1008,7 +1008,7 @@ comments: true === "Swift" ```swift title="binary_search_tree.swift" - /* 删除结点 */ + /* 删除节点 */ @discardableResult func remove(num: Int) -> TreeNode? { // 若树为空,直接提前返回 @@ -1017,43 +1017,43 @@ comments: true } var cur = root var pre: TreeNode? - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while cur != nil { - // 找到待删除结点,跳出循环 + // 找到待删除节点,跳出循环 if cur!.val == num { break } pre = cur - // 待删除结点在 cur 的右子树中 + // 待删除节点在 cur 的右子树中 if cur!.val < num { cur = cur?.right } - // 待删除结点在 cur 的左子树中 + // 待删除节点在 cur 的左子树中 else { cur = cur?.left } } - // 若无待删除结点,则直接返回 + // 若无待删除节点,则直接返回 if cur == nil { return nil } - // 子结点数量 = 0 or 1 + // 子节点数量 = 0 or 1 if cur?.left == nil || cur?.right == nil { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 let child = cur?.left != nil ? cur?.left : cur?.right - // 删除结点 cur + // 删除节点 cur if pre?.left === cur { pre?.left = child } else { pre?.right = child } } - // 子结点数量 = 2 + // 子节点数量 = 2 else { - // 获取中序遍历中 cur 的下一个结点 + // 获取中序遍历中 cur 的下一个节点 let nex = getInOrderNext(root: cur?.right) let tmp = nex!.val - // 递归删除结点 nex + // 递归删除节点 nex remove(num: nex!.val) // 将 nex 的值复制给 cur cur?.val = tmp @@ -1061,13 +1061,13 @@ comments: true return cur } - /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ + /* 获取中序遍历中的下一个节点(仅适用于 root 有左子节点的情况) */ func getInOrderNext(root: TreeNode?) -> TreeNode? { var root = root if root == nil { return root } - // 循环访问左子结点,直到叶结点时为最小结点,跳出 + // 循环访问左子节点,直到叶节点时为最小节点,跳出 while root?.left != nil { root = root?.left } @@ -1078,43 +1078,43 @@ comments: true === "Zig" ```zig title="binary_search_tree.zig" - // 删除结点 + // 删除节点 fn remove(self: *Self, num: T) ?*inc.TreeNode(T) { // 若树为空,直接提前返回 if (self.root == null) return null; var cur = self.root; var pre: ?*inc.TreeNode(T) = null; - // 循环查找,越过叶结点后跳出 + // 循环查找,越过叶节点后跳出 while (cur != null) { - // 找到待删除结点,跳出循环 + // 找到待删除节点,跳出循环 if (cur.?.val == num) break; pre = cur; - // 待删除结点在 cur 的右子树中 + // 待删除节点在 cur 的右子树中 if (cur.?.val < num) { cur = cur.?.right; - // 待删除结点在 cur 的左子树中 + // 待删除节点在 cur 的左子树中 } else { cur = cur.?.left; } } - // 若无待删除结点,则直接返回 + // 若无待删除节点,则直接返回 if (cur == null) return null; - // 子结点数量 = 0 or 1 + // 子节点数量 = 0 or 1 if (cur.?.left == null or cur.?.right == null) { - // 当子结点数量 = 0 / 1 时, child = null / 该子结点 + // 当子节点数量 = 0 / 1 时, child = null / 该子节点 var child = if (cur.?.left != null) cur.?.left else cur.?.right; - // 删除结点 cur + // 删除节点 cur if (pre.?.left == cur) { pre.?.left = child; } else { pre.?.right = child; } - // 子结点数量 = 2 + // 子节点数量 = 2 } else { - // 获取中序遍历中 cur 的下一个结点 + // 获取中序遍历中 cur 的下一个节点 var nex = self.getInOrderNext(cur.?.right); var tmp = nex.?.val; - // 递归删除结点 nex + // 递归删除节点 nex _ = self.remove(nex.?.val); // 将 nex 的值复制给 cur cur.?.val = tmp; @@ -1122,12 +1122,12 @@ comments: true return cur; } - // 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) + // 获取中序遍历中的下一个节点(仅适用于 root 有左子节点的情况) fn getInOrderNext(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { _ = self; var node_tmp = node; if (node_tmp == null) return null; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 + // 循环访问左子节点,直到叶节点时为最小节点,跳出 while (node_tmp.?.left != null) { node_tmp = node_tmp.?.left; } @@ -1137,7 +1137,7 @@ comments: true ### 排序 -我们知道,「中序遍历」遵循“左 $\rightarrow$ 根 $\rightarrow$ 右”的遍历优先级,而二叉搜索树遵循“左子结点 $<$ 根结点 $<$ 右子结点”的大小关系。因此,在二叉搜索树中进行中序遍历时,总是会优先遍历下一个最小结点,从而得出一条重要性质:**二叉搜索树的中序遍历序列是升序的**。 +我们知道,「中序遍历」遵循“左 $\rightarrow$ 根 $\rightarrow$ 右”的遍历优先级,而二叉搜索树遵循“左子节点 $<$ 根节点 $<$ 右子节点”的大小关系。因此,在二叉搜索树中进行中序遍历时,总是会优先遍历下一个最小节点,从而得出一条重要性质:**二叉搜索树的中序遍历序列是升序的**。 借助中序遍历升序的性质,我们在二叉搜索树中获取有序数据仅需 $O(n)$ 时间,而无需额外排序,非常高效。 @@ -1176,9 +1176,9 @@ comments: true ## 7.3.3. 二叉搜索树的退化 -理想情况下,我们希望二叉搜索树的是“左右平衡”的(详见「平衡二叉树」章节),此时可以在 $\log n$ 轮循环内查找任意结点。 +理想情况下,我们希望二叉搜索树的是“左右平衡”的(详见「平衡二叉树」章节),此时可以在 $\log n$ 轮循环内查找任意节点。 -如果我们动态地在二叉搜索树中插入与删除结点,**则可能导致二叉树退化为链表**,此时各种操作的时间复杂度也退化之 $O(n)$ 。 +如果我们动态地在二叉搜索树中插入与删除节点,**则可能导致二叉树退化为链表**,此时各种操作的时间复杂度也退化之 $O(n)$ 。 !!! note diff --git a/chapter_tree/binary_tree.md b/chapter_tree/binary_tree.md index 29657f0aa..72a7738c3 100644 --- a/chapter_tree/binary_tree.md +++ b/chapter_tree/binary_tree.md @@ -4,16 +4,16 @@ comments: true # 7.1. 二叉树 -「二叉树 Binary Tree」是一种非线性数据结构,代表着祖先与后代之间的派生关系,体现着“一分为二”的分治逻辑。类似于链表,二叉树也是以结点为单位存储的,结点包含「值」和两个「指针」。 +「二叉树 Binary Tree」是一种非线性数据结构,代表着祖先与后代之间的派生关系,体现着“一分为二”的分治逻辑。类似于链表,二叉树也是以节点为单位存储的,节点包含「值」和两个「指针」。 === "Java" ```java title="" - /* 二叉树结点类 */ + /* 二叉树节点类 */ class TreeNode { - int val; // 结点值 - TreeNode left; // 左子结点指针 - TreeNode right; // 右子结点指针 + int val; // 节点值 + TreeNode left; // 左子节点指针 + TreeNode right; // 右子节点指针 TreeNode(int x) { val = x; } } ``` @@ -21,11 +21,11 @@ comments: true === "C++" ```cpp title="" - /* 二叉树结点结构体 */ + /* 二叉树节点结构体 */ struct TreeNode { - int val; // 结点值 - TreeNode *left; // 左子结点指针 - TreeNode *right; // 右子结点指针 + int val; // 节点值 + TreeNode *left; // 左子节点指针 + TreeNode *right; // 右子节点指针 TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; ``` @@ -33,24 +33,24 @@ comments: true === "Python" ```python title="" - """ 二叉树结点类 """ + """ 二叉树节点类 """ class TreeNode: def __init__(self, val: int): - self.val: int = val # 结点值 - self.left: Optional[TreeNode] = None # 左子结点指针 - self.right: Optional[TreeNode] = None # 右子结点指针 + self.val: int = val # 节点值 + self.left: Optional[TreeNode] = None # 左子节点指针 + self.right: Optional[TreeNode] = None # 右子节点指针 ``` === "Go" ```go title="" - /* 二叉树结点结构体 */ + /* 二叉树节点结构体 */ type TreeNode struct { Val int Left *TreeNode Right *TreeNode } - /* 结点初始化方法 */ + /* 节点初始化方法 */ func NewTreeNode(v int) *TreeNode { return &TreeNode{ Left: nil, @@ -63,27 +63,27 @@ comments: true === "JavaScript" ```javascript title="" - /* 二叉树结点类 */ + /* 二叉树节点类 */ function TreeNode(val, left, right) { - this.val = (val === undefined ? 0 : val); // 结点值 - this.left = (left === undefined ? null : left); // 左子结点指针 - this.right = (right === undefined ? null : right); // 右子结点指针 + this.val = (val === undefined ? 0 : val); // 节点值 + this.left = (left === undefined ? null : left); // 左子节点指针 + this.right = (right === undefined ? null : right); // 右子节点指针 } ``` === "TypeScript" ```typescript title="" - /* 二叉树结点类 */ + /* 二叉树节点类 */ class TreeNode { val: number; left: TreeNode | null; right: TreeNode | null; constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { - this.val = val === undefined ? 0 : val; // 结点值 - this.left = left === undefined ? null : left; // 左子结点指针 - this.right = right === undefined ? null : right; // 右子结点指针 + this.val = val === undefined ? 0 : val; // 节点值 + this.left = left === undefined ? null : left; // 左子节点指针 + this.right = right === undefined ? null : right; // 右子节点指针 } } ``` @@ -97,11 +97,11 @@ comments: true === "C#" ```csharp title="" - /* 二叉树结点类 */ + /* 二叉树节点类 */ class TreeNode { - int val; // 结点值 - TreeNode? left; // 左子结点指针 - TreeNode? right; // 右子结点指针 + int val; // 节点值 + TreeNode? left; // 左子节点指针 + TreeNode? right; // 右子节点指针 TreeNode(int x) { val = x; } } ``` @@ -109,11 +109,11 @@ comments: true === "Swift" ```swift title="" - /* 二叉树结点类 */ + /* 二叉树节点类 */ class TreeNode { - var val: Int // 结点值 - var left: TreeNode? // 左子结点指针 - var right: TreeNode? // 右子结点指针 + var val: Int // 节点值 + var left: TreeNode? // 左子节点指针 + var right: TreeNode? // 右子节点指针 init(x: Int) { val = x @@ -127,26 +127,26 @@ comments: true ``` -结点的两个指针分别指向「左子结点」和「右子结点」,并且称该结点为两个子结点的「父结点」。给定二叉树某结点,将“左子结点及其以下结点形成的树”称为该结点的「左子树」,右子树同理。 +节点的两个指针分别指向「左子节点」和「右子节点」,并且称该节点为两个子节点的「父节点」。给定二叉树某节点,将“左子节点及其以下节点形成的树”称为该节点的「左子树」,右子树同理。 -除了叶结点外,每个结点都有子结点和子树。例如,若将下图的“结点 2”看作父结点,那么其左子结点和右子结点分别为“结点 4”和“结点 5”,左子树和右子树分别为“结点 4 及其以下结点形成的树”和“结点 5 及其以下结点形成的树”。 +除了叶节点外,每个节点都有子节点和子树。例如,若将下图的“节点 2”看作父节点,那么其左子节点和右子节点分别为“节点 4”和“节点 5”,左子树和右子树分别为“节点 4 及其以下节点形成的树”和“节点 5 及其以下节点形成的树”。 - + -Fig. 父结点、子结点、子树
+Fig. 父节点、子节点、子树
## 7.1.1. 二叉树常见术语 二叉树的术语较多,建议尽量理解并记住。后续可能遗忘,可以在需要使用时回来查看确认。 -- 「根结点 Root Node」:二叉树最顶层的结点,其没有父结点; -- 「叶结点 Leaf Node」:没有子结点的结点,其两个指针都指向 $\text{null}$ ; -- 结点所处「层 Level」:从顶至底依次增加,根结点所处层为 1 ; -- 结点「度 Degree」:结点的子结点数量。二叉树中,度的范围是 0, 1, 2 ; -- 「边 Edge」:连接两个结点的边,即结点指针; -- 二叉树「高度」:二叉树中根结点到最远叶结点走过边的数量; -- 结点「深度 Depth」 :根结点到该结点走过边的数量; -- 结点「高度 Height」:最远叶结点到该结点走过边的数量; +- 「根节点 Root Node」:二叉树最顶层的节点,其没有父节点; +- 「叶节点 Leaf Node」:没有子节点的节点,其两个指针都指向 $\text{null}$ ; +- 节点所处「层 Level」:从顶至底依次增加,根节点所处层为 1 ; +- 节点「度 Degree」:节点的子节点数量。二叉树中,度的范围是 0, 1, 2 ; +- 「边 Edge」:连接两个节点的边,即节点指针; +- 二叉树「高度」:二叉树中根节点到最远叶节点走过边的数量; +- 节点「深度 Depth」 :根节点到该节点走过边的数量; +- 节点「高度 Height」:最远叶节点到该节点走过边的数量;  @@ -154,16 +154,16 @@ comments: true !!! tip "高度与深度的定义" - 值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。 + 值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过节点的数量”,此时高度或深度都需要 + 1 。 ## 7.1.2. 二叉树基本操作 -**初始化二叉树**。与链表类似,先初始化结点,再构建引用指向(即指针)。 +**初始化二叉树**。与链表类似,先初始化节点,再构建引用指向(即指针)。 === "Java" ```java title="binary_tree.java" - // 初始化结点 + // 初始化节点 TreeNode n1 = new TreeNode(1); TreeNode n2 = new TreeNode(2); TreeNode n3 = new TreeNode(3); @@ -180,7 +180,7 @@ comments: true ```cpp title="binary_tree.cpp" /* 初始化二叉树 */ - // 初始化结点 + // 初始化节点 TreeNode* n1 = new TreeNode(1); TreeNode* n2 = new TreeNode(2); TreeNode* n3 = new TreeNode(3); @@ -197,7 +197,7 @@ comments: true ```python title="binary_tree.py" """ 初始化二叉树 """ - # 初始化结点 + # 初始化节点 n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) @@ -214,7 +214,7 @@ comments: true ```go title="binary_tree.go" /* 初始化二叉树 */ - // 初始化结点 + // 初始化节点 n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) @@ -231,7 +231,7 @@ comments: true ```javascript title="binary_tree.js" /* 初始化二叉树 */ - // 初始化结点 + // 初始化节点 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), @@ -248,7 +248,7 @@ comments: true ```typescript title="binary_tree.ts" /* 初始化二叉树 */ - // 初始化结点 + // 初始化节点 let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), @@ -271,7 +271,7 @@ comments: true ```csharp title="binary_tree.cs" /* 初始化二叉树 */ - // 初始化结点 + // 初始化节点 TreeNode n1 = new TreeNode(1); TreeNode n2 = new TreeNode(2); TreeNode n3 = new TreeNode(3); @@ -287,7 +287,7 @@ comments: true === "Swift" ```swift title="binary_tree.swift" - // 初始化结点 + // 初始化节点 let n1 = TreeNode(x: 1) let n2 = TreeNode(x: 2) let n3 = TreeNode(x: 3) @@ -306,80 +306,80 @@ comments: true ``` -**插入与删除结点**。与链表类似,插入与删除结点都可以通过修改指针实现。 +**插入与删除节点**。与链表类似,插入与删除节点都可以通过修改指针实现。 - + -Fig. 在二叉树中插入与删除结点
+Fig. 在二叉树中插入与删除节点
=== "Java" ```java title="binary_tree.java" TreeNode P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P + // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; - // 删除结点 P + // 删除节点 P n1.left = n2; ``` === "C++" ```cpp title="binary_tree.cpp" - /* 插入与删除结点 */ + /* 插入与删除节点 */ TreeNode* P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P + // 在 n1 -> n2 中间插入节点 P n1->left = P; P->left = n2; - // 删除结点 P + // 删除节点 P n1->left = n2; ``` === "Python" ```python title="binary_tree.py" - """ 插入与删除结点 """ + """ 插入与删除节点 """ p = TreeNode(0) - # 在 n1 -> n2 中间插入结点 P + # 在 n1 -> n2 中间插入节点 P n1.left = p p.left = n2 - # 删除结点 P + # 删除节点 P n1.left = n2 ``` === "Go" ```go title="binary_tree.go" - /* 插入与删除结点 */ - // 在 n1 -> n2 中间插入结点 P + /* 插入与删除节点 */ + // 在 n1 -> n2 中间插入节点 P p := NewTreeNode(0) n1.Left = p p.Left = n2 - // 删除结点 P + // 删除节点 P n1.Left = n2 ``` === "JavaScript" ```javascript title="binary_tree.js" - /* 插入与删除结点 */ + /* 插入与删除节点 */ let P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P + // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; - // 删除结点 P + // 删除节点 P n1.left = n2; ``` === "TypeScript" ```typescript title="binary_tree.ts" - /* 插入与删除结点 */ + /* 插入与删除节点 */ const P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P + // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; - // 删除结点 P + // 删除节点 P n1.left = n2; ``` @@ -392,12 +392,12 @@ comments: true === "C#" ```csharp title="binary_tree.cs" - /* 插入与删除结点 */ + /* 插入与删除节点 */ TreeNode P = new TreeNode(0); - // 在 n1 -> n2 中间插入结点 P + // 在 n1 -> n2 中间插入节点 P n1.left = P; P.left = n2; - // 删除结点 P + // 删除节点 P n1.left = n2; ``` @@ -405,10 +405,10 @@ comments: true ```swift title="binary_tree.swift" let P = TreeNode(x: 0) - // 在 n1 -> n2 中间插入结点 P + // 在 n1 -> n2 中间插入节点 P n1.left = P P.left = n2 - // 删除结点 P + // 删除节点 P n1.left = n2 ``` @@ -420,13 +420,13 @@ comments: true !!! note - 插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。 + 插入节点会改变二叉树的原有逻辑结构,删除节点往往意味着删除了该节点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。 ## 7.1.3. 常见二叉树类型 ### 完美二叉树 -「完美二叉树 Perfect Binary Tree」的所有层的结点都被完全填满。在完美二叉树中,叶结点的度为 $0$ ,其余所有结点的度都为 $2$ ;若树高度 $= h$ ,则结点总数 $= 2^{h+1} - 1$ ,呈标准的指数级关系,反映着自然界中常见的细胞分裂。 +「完美二叉树 Perfect Binary Tree」的所有层的节点都被完全填满。在完美二叉树中,叶节点的度为 $0$ ,其余所有节点的度都为 $2$ ;若树高度 $= h$ ,则节点总数 $= 2^{h+1} - 1$ ,呈标准的指数级关系,反映着自然界中常见的细胞分裂。 !!! tip @@ -438,9 +438,9 @@ comments: true ### 完全二叉树 -「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满,且最底层结点尽量靠左填充。 +「完全二叉树 Complete Binary Tree」只有最底层的节点未被填满,且最底层节点尽量靠左填充。 -**完全二叉树非常适合用数组来表示**。如果按照层序遍历序列的顺序来存储,那么空结点 `null` 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。 +**完全二叉树非常适合用数组来表示**。如果按照层序遍历序列的顺序来存储,那么空节点 `null` 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。  @@ -448,7 +448,7 @@ comments: true ### 完满二叉树 -「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。 +「完满二叉树 Full Binary Tree」除了叶节点之外,其余所有节点都有两个子节点。  @@ -456,7 +456,7 @@ comments: true ### 平衡二叉树 -「平衡二叉树 Balanced Binary Tree」中任意结点的左子树和右子树的高度之差的绝对值 $\leq 1$ 。 +「平衡二叉树 Balanced Binary Tree」中任意节点的左子树和右子树的高度之差的绝对值 $\leq 1$ 。  @@ -464,7 +464,7 @@ comments: true ## 7.1.4. 二叉树的退化 -当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。 +当二叉树的每层的节点都被填满时,达到「完美二叉树」;而当所有节点都偏向一边时,二叉树退化为「链表」。 - 完美二叉树是一个二叉树的“最佳状态”,可以完全发挥出二叉树“分治”的优势; - 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $O(n)$ ; @@ -473,32 +473,32 @@ comments: trueFig. 二叉树的最佳与最二叉树的最佳和最差结构差情况
-如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。 +如下表所示,在最佳和最差结构下,二叉树的叶节点数量、节点总数、高度等达到极大或极小值。Fig. 完美二叉树的数组表示
-然而,完美二叉树只是个例,二叉树中间层往往存在许多空结点(即 `null` ),而层序遍历序列并不包含这些空结点,并且我们无法单凭序列来猜测空结点的数量和分布位置,**即理论上存在许多种二叉树都符合该层序遍历序列**。显然,这种情况无法使用数组来存储二叉树。 +然而,完美二叉树只是个例,二叉树中间层往往存在许多空节点(即 `null` ),而层序遍历序列并不包含这些空节点,并且我们无法单凭序列来猜测空节点的数量和分布位置,**即理论上存在许多种二叉树都符合该层序遍历序列**。显然,这种情况无法使用数组来存储二叉树。  @@ -519,7 +519,7 @@ comments: true ```cpp title="" /* 二叉树的数组表示 */ // 为了符合数据类型为 int ,使用 int 最大值标记空位 - // 该方法的使用前提是没有结点的值 = INT_MAX + // 该方法的使用前提是没有节点的值 = INT_MAX vectorFig. 任意类型二叉树的数组表示
-回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。 +回顾「完全二叉树」的定义,其只有最底层有空节点,并且最底层的节点尽量靠左,因而所有空节点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。 Fig. 完全二叉树的数组表示
-数组表示有两个优点: 一是不需要存储指针,节省空间;二是可以随机访问结点。然而,当二叉树中的“空位”很多时,数组中只包含很少结点的数据,空间利用率很低。 +数组表示有两个优点: 一是不需要存储指针,节省空间;二是可以随机访问节点。然而,当二叉树中的“空位”很多时,数组中只包含很少节点的数据,空间利用率很低。 diff --git a/chapter_tree/binary_tree_traversal.md b/chapter_tree/binary_tree_traversal.md index a91039cf2..585955226 100755 --- a/chapter_tree/binary_tree_traversal.md +++ b/chapter_tree/binary_tree_traversal.md @@ -4,13 +4,13 @@ comments: true # 7.2. 二叉树遍历 -从物理结构角度看,树是一种基于链表的数据结构,因此遍历方式也是通过指针(即引用)逐个遍历结点。同时,树还是一种非线性数据结构,这导致遍历树比遍历链表更加复杂,需要使用搜索算法来实现。 +从物理结构角度看,树是一种基于链表的数据结构,因此遍历方式也是通过指针(即引用)逐个遍历节点。同时,树还是一种非线性数据结构,这导致遍历树比遍历链表更加复杂,需要使用搜索算法来实现。 常见的二叉树遍历方式有层序遍历、前序遍历、中序遍历、后序遍历。 ## 7.2.1. 层序遍历 -「层序遍历 Level-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。 +「层序遍历 Level-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问节点。 层序遍历本质上是「广度优先搜索 Breadth-First Traversal」,其体现着一种“一圈一圈向外”的层进遍历方式。 @@ -27,17 +27,17 @@ comments: true ```java title="binary_tree_bfs.java" /* 层序遍历 */ List