refactor: Replace 结点 with 节点 (#452)
* Replace 结点 with 节点 Update the footnotes in the figures * Update mindmap * Reduce the size of the mindmap.png
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
@@ -2,19 +2,19 @@
|
||||
|
||||
内存空间是所有程序的公共资源,排除已被占用的内存空间,空闲内存空间通常散落在内存各处。在上一节中,我们提到存储数组的内存空间必须是连续的,而当我们需要申请一个非常大的数组时,空闲内存中可能没有这么大的连续空间。
|
||||
|
||||
与数组相比,链表更具灵活性,因为它可以存储在非连续的内存空间。「链表 Linked List」是一种线性数据结构,其每个元素都是一个结点对象,各个结点之间通过指针连接,从当前结点通过指针可以访问到下一个结点。由于指针记录了下个结点的内存地址,因此无需保证内存地址的连续性,从而可以将各个结点分散存储在内存各处。
|
||||
与数组相比,链表更具灵活性,因为它可以存储在非连续的内存空间。「链表 Linked List」是一种线性数据结构,其每个元素都是一个节点对象,各个节点之间通过指针连接,从当前节点通过指针可以访问到下一个节点。由于指针记录了下个节点的内存地址,因此无需保证内存地址的连续性,从而可以将各个节点分散存储在内存各处。
|
||||
|
||||
链表「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」,或称指向下一结点的「引用 Reference」。
|
||||
链表「节点 Node」包含两项数据,一是节点「值 Value」,二是指向下一节点的「指针 Pointer」,或称指向下一节点的「引用 Reference」。
|
||||
|
||||

|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title=""
|
||||
/* 链表结点类 */
|
||||
/* 链表节点类 */
|
||||
class ListNode {
|
||||
int val; // 结点值
|
||||
ListNode next; // 指向下一结点的指针(引用)
|
||||
int val; // 节点值
|
||||
ListNode next; // 指向下一节点的指针(引用)
|
||||
ListNode(int x) { val = x; } // 构造函数
|
||||
}
|
||||
```
|
||||
@@ -22,10 +22,10 @@
|
||||
=== "C++"
|
||||
|
||||
```cpp title=""
|
||||
/* 链表结点结构体 */
|
||||
/* 链表节点结构体 */
|
||||
struct ListNode {
|
||||
int val; // 结点值
|
||||
ListNode *next; // 指向下一结点的指针(引用)
|
||||
int val; // 节点值
|
||||
ListNode *next; // 指向下一节点的指针(引用)
|
||||
ListNode(int x) : val(x), next(nullptr) {} // 构造函数
|
||||
};
|
||||
```
|
||||
@@ -33,20 +33,20 @@
|
||||
=== "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 构造函数,创建一个新的链表
|
||||
@@ -61,13 +61,13 @@
|
||||
=== "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); // 指向下一节点的引用
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -75,13 +75,13 @@
|
||||
=== "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; // 指向下一节点的引用
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -89,16 +89,16 @@
|
||||
=== "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));
|
||||
@@ -111,11 +111,11 @@
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
/* 链表结点类 */
|
||||
/* 链表节点类 */
|
||||
class ListNode
|
||||
{
|
||||
int val; // 结点值
|
||||
ListNode next; // 指向下一结点的引用
|
||||
int val; // 节点值
|
||||
ListNode next; // 指向下一节点的引用
|
||||
ListNode(int x) => val = x; //构造函数
|
||||
}
|
||||
```
|
||||
@@ -123,10 +123,10 @@
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
/* 链表结点类 */
|
||||
/* 链表节点类 */
|
||||
class ListNode {
|
||||
var val: Int // 结点值
|
||||
var next: ListNode? // 指向下一结点的指针(引用)
|
||||
var val: Int // 节点值
|
||||
var next: ListNode? // 指向下一节点的指针(引用)
|
||||
|
||||
init(x: Int) { // 构造函数
|
||||
val = x
|
||||
@@ -137,13 +137,13 @@
|
||||
=== "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 {
|
||||
@@ -154,21 +154,21 @@
|
||||
}
|
||||
```
|
||||
|
||||
!!! 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);
|
||||
@@ -185,7 +185,7 @@
|
||||
|
||||
```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);
|
||||
@@ -202,7 +202,7 @@
|
||||
|
||||
```python title="linked_list.py"
|
||||
""" 初始化链表 1 -> 3 -> 2 -> 5 -> 4 """
|
||||
# 初始化各个结点
|
||||
# 初始化各个节点
|
||||
n0 = ListNode(1)
|
||||
n1 = ListNode(3)
|
||||
n2 = ListNode(2)
|
||||
@@ -219,7 +219,7 @@
|
||||
|
||||
```go title="linked_list.go"
|
||||
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
|
||||
// 初始化各个结点
|
||||
// 初始化各个节点
|
||||
n0 := NewListNode(1)
|
||||
n1 := NewListNode(3)
|
||||
n2 := NewListNode(2)
|
||||
@@ -236,7 +236,7 @@
|
||||
|
||||
```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);
|
||||
@@ -253,7 +253,7 @@
|
||||
|
||||
```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);
|
||||
@@ -270,7 +270,7 @@
|
||||
|
||||
```c title="linked_list.c"
|
||||
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
|
||||
// 初始化各个结点
|
||||
// 初始化各个节点
|
||||
ListNode* n0 = newListNode(1);
|
||||
ListNode* n1 = newListNode(3);
|
||||
ListNode* n2 = newListNode(2);
|
||||
@@ -287,7 +287,7 @@
|
||||
|
||||
```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);
|
||||
@@ -304,7 +304,7 @@
|
||||
|
||||
```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)
|
||||
@@ -321,7 +321,7 @@
|
||||
|
||||
```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};
|
||||
@@ -336,9 +336,9 @@
|
||||
|
||||
## 链表优点
|
||||
|
||||
**链表中插入与删除结点的操作效率高**。例如,如果我们想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。
|
||||
**链表中插入与删除节点的操作效率高**。例如,如果我们想在链表中间的两个节点 `A` , `B` 之间插入一个新节点 `P` ,我们只需要改变两个节点指针即可,时间复杂度为 $O(1)$ ;相比之下,数组的插入操作效率要低得多。
|
||||
|
||||

|
||||

|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -400,9 +400,9 @@
|
||||
[class]{}-[func]{insert}
|
||||
```
|
||||
|
||||
在链表中删除结点也非常方便,只需改变一个结点的指针即可。如下图所示,尽管在删除操作完成后,结点 `P` 仍然指向 `n1`,但实际上 `P` 已经不再属于此链表,因为遍历此链表时无法访问到 `P`。
|
||||
在链表中删除节点也非常方便,只需改变一个节点的指针即可。如下图所示,尽管在删除操作完成后,节点 `P` 仍然指向 `n1`,但实际上 `P` 已经不再属于此链表,因为遍历此链表时无法访问到 `P`。
|
||||
|
||||

|
||||

|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -466,7 +466,7 @@
|
||||
|
||||
## 链表缺点
|
||||
|
||||
**链表访问结点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而,链表无法直接访问任意结点,这是因为系统需要从头结点出发,逐个向后遍历直至找到目标结点。例如,若要访问链表索引为 `index`(即第 `index + 1` 个)的结点,则需要向后遍历 `index` 轮。
|
||||
**链表访问节点效率较低**。如上节所述,数组可以在 $O(1)$ 时间下访问任意元素。然而,链表无法直接访问任意节点,这是因为系统需要从头节点出发,逐个向后遍历直至找到目标节点。例如,若要访问链表索引为 `index`(即第 `index + 1` 个)的节点,则需要向后遍历 `index` 轮。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -528,11 +528,11 @@
|
||||
[class]{}-[func]{access}
|
||||
```
|
||||
|
||||
**链表的内存占用较大**。链表以结点为单位,每个结点除了保存值之外,还需额外保存指针(引用)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。
|
||||
**链表的内存占用较大**。链表以节点为单位,每个节点除了保存值之外,还需额外保存指针(引用)。这意味着在相同数据量的情况下,链表比数组需要占用更多的内存空间。
|
||||
|
||||
## 链表常用操作
|
||||
|
||||
**遍历链表查找**。遍历链表,查找链表内值为 `target` 的结点,输出结点在链表中的索引。
|
||||
**遍历链表查找**。遍历链表,查找链表内值为 `target` 的节点,输出节点在链表中的索引。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -596,20 +596,20 @@
|
||||
|
||||
## 常见链表类型
|
||||
|
||||
**单向链表**。即上述介绍的普通链表。单向链表的结点包含值和指向下一结点的指针(引用)两项数据。我们将首个结点称为头结点,将最后一个结点成为尾结点,尾结点指向 `null` 。
|
||||
**单向链表**。即上述介绍的普通链表。单向链表的节点包含值和指向下一节点的指针(引用)两项数据。我们将首个节点称为头节点,将最后一个节点成为尾节点,尾节点指向 `null` 。
|
||||
|
||||
**环形链表**。如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,任意结点都可以视作头结点。
|
||||
**环形链表**。如果我们令单向链表的尾节点指向头节点(即首尾相接),则得到一个环形链表。在环形链表中,任意节点都可以视作头节点。
|
||||
|
||||
**双向链表**。与单向链表相比,双向链表记录了两个方向的指针(引用)。双向链表的结点定义同时包含指向后继结点(下一结点)和前驱结点(上一结点)的指针。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。
|
||||
**双向链表**。与单向链表相比,双向链表记录了两个方向的指针(引用)。双向链表的节点定义同时包含指向后继节点(下一节点)和前驱节点(上一节点)的指针。相较于单向链表,双向链表更具灵活性,可以朝两个方向遍历链表,但相应地也需要占用更多的内存空间。
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title=""
|
||||
/* 双向链表结点类 */
|
||||
/* 双向链表节点类 */
|
||||
class ListNode {
|
||||
int val; // 结点值
|
||||
ListNode next; // 指向后继结点的指针(引用)
|
||||
ListNode prev; // 指向前驱结点的指针(引用)
|
||||
int val; // 节点值
|
||||
ListNode next; // 指向后继节点的指针(引用)
|
||||
ListNode prev; // 指向前驱节点的指针(引用)
|
||||
ListNode(int x) { val = x; } // 构造函数
|
||||
}
|
||||
```
|
||||
@@ -617,11 +617,11 @@
|
||||
=== "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) {} // 构造函数
|
||||
};
|
||||
```
|
||||
@@ -629,22 +629,22 @@
|
||||
=== "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 初始化
|
||||
@@ -660,15 +660,15 @@
|
||||
=== "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; // 指向前驱节点的指针(引用)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -676,15 +676,15 @@
|
||||
=== "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; // 指向前驱节点的指针(引用)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -698,11 +698,11 @@
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
/* 双向链表结点类 */
|
||||
/* 双向链表节点类 */
|
||||
class ListNode {
|
||||
int val; // 结点值
|
||||
ListNode next; // 指向后继结点的指针(引用)
|
||||
ListNode prev; // 指向前驱结点的指针(引用)
|
||||
int val; // 节点值
|
||||
ListNode next; // 指向后继节点的指针(引用)
|
||||
ListNode prev; // 指向前驱节点的指针(引用)
|
||||
ListNode(int x) => val = x; // 构造函数
|
||||
}
|
||||
```
|
||||
@@ -710,11 +710,11 @@
|
||||
=== "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
|
||||
@@ -725,14 +725,14 @@
|
||||
=== "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 {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
- 数组和链表是两种基本数据结构,分别代表数据在计算机内存中的连续空间存储和离散空间存储方式。两者的优缺点呈现出互补的特性。
|
||||
- 数组支持随机访问、占用内存较少;但插入和删除元素效率低,且初始化后长度不可变。
|
||||
- 链表通过更改指针实现高效的结点插入与删除,且可以灵活调整长度;但结点访问效率低、占用内存较多。常见的链表类型包括单向链表、循环链表、双向链表。
|
||||
- 链表通过更改指针实现高效的节点插入与删除,且可以灵活调整长度;但节点访问效率低、占用内存较多。常见的链表类型包括单向链表、循环链表、双向链表。
|
||||
- 动态数组,又称列表,是基于数组实现的一种数据结构。它保留了数组的优势,同时可以灵活调整长度。列表的出现极大地提高了数组的易用性,但可能导致部分内存空间浪费。
|
||||
- 下表总结并对比了数组与链表的各项特性。
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
!!! note "缓存局部性"
|
||||
|
||||
在计算机中,数据读写速度排序是“硬盘 < 内存 < CPU 缓存”。当我们访问数组元素时,计算机不仅会加载它,还会缓存其周围的其它数据,从而借助高速缓存来提升后续操作的执行速度。链表则不然,计算机只能挨个地缓存各个结点,这样的多次“搬运”降低了整体效率。
|
||||
在计算机中,数据读写速度排序是“硬盘 < 内存 < CPU 缓存”。当我们访问数组元素时,计算机不仅会加载它,还会缓存其周围的其它数据,从而借助高速缓存来提升后续操作的执行速度。链表则不然,计算机只能挨个地缓存各个节点,这样的多次“搬运”降低了整体效率。
|
||||
|
||||
- 下表对比了数组与链表在各种操作上的效率。
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
@@ -78,8 +78,8 @@
|
||||
""" 类 """
|
||||
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:
|
||||
@@ -131,8 +131,8 @@
|
||||
val;
|
||||
next;
|
||||
constructor(val) {
|
||||
this.val = val === undefined ? 0 : val; // 结点值
|
||||
this.next = null; // 指向下一结点的引用
|
||||
this.val = val === undefined ? 0 : val; // 节点值
|
||||
this.next = null; // 指向下一节点的引用
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,8 +159,8 @@
|
||||
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; // 指向下一节点的引用
|
||||
}
|
||||
}
|
||||
|
||||
@@ -890,7 +890,7 @@ $$
|
||||
|
||||
### 指数阶 $O(2^n)$
|
||||
|
||||
指数阶常见于二叉树。高度为 $n$ 的「满二叉树」的结点数量为 $2^n - 1$ ,占用 $O(2^n)$ 空间。
|
||||
指数阶常见于二叉树。高度为 $n$ 的「满二叉树」的节点数量为 $2^n - 1$ ,占用 $O(2^n)$ 空间。
|
||||
|
||||
=== "Java"
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 72 KiB |
@@ -4,7 +4,7 @@
|
||||
|
||||
## 逻辑结构:线性与非线性
|
||||
|
||||
**「逻辑结构」揭示了数据元素之间的逻辑关系**。在数组和链表中,数据按照顺序依次排列,体现了数据之间的线性关系;而在树中,数据从顶部向下按层次排列,表现出祖先与后代之间的派生关系;图则由结点和边构成,反映了复杂的网络关系。
|
||||
**「逻辑结构」揭示了数据元素之间的逻辑关系**。在数组和链表中,数据按照顺序依次排列,体现了数据之间的线性关系;而在树中,数据从顶部向下按层次排列,表现出祖先与后代之间的派生关系;图则由节点和边构成,反映了复杂的网络关系。
|
||||
|
||||
逻辑结构通常分为「线性」和「非线性」两类。线性结构比较直观,指数据在逻辑关系上呈线性排列;非线性结构则相反,呈非线性排列,例如网状或树状结构。
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 213 KiB After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
@@ -12,7 +12,7 @@ $$
|
||||
|
||||

|
||||
|
||||
那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作结点,把「边」看作连接各个结点的指针,则可将「图」看作是一种从「链表」拓展而来的数据结构。**相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,从而更为复杂**。
|
||||
那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作节点,把「边」看作连接各个节点的指针,则可将「图」看作是一种从「链表」拓展而来的数据结构。**相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,从而更为复杂**。
|
||||
|
||||
## 图常见类型
|
||||
|
||||
@@ -62,7 +62,7 @@ $$
|
||||
|
||||
### 邻接表
|
||||
|
||||
「邻接表 Adjacency List」使用 $n$ 个链表来表示图,链表结点表示顶点。第 $i$ 条链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点(即与该顶点相连的顶点)。
|
||||
「邻接表 Adjacency List」使用 $n$ 个链表来表示图,链表节点表示顶点。第 $i$ 条链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点(即与该顶点相连的顶点)。
|
||||
|
||||

|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
@@ -94,7 +94,7 @@
|
||||
|
||||
- **添加边**:在顶点对应链表的末尾添加边即可,使用 $O(1)$ 时间。因为是无向图,所以需要同时添加两个方向的边。
|
||||
- **删除边**:在顶点对应链表中查找并删除指定边,使用 $O(m)$ 时间。在无向图中,需要同时删除两个方向的边。
|
||||
- **添加顶点**:在邻接表中添加一个链表,并将新增顶点作为链表头结点,使用 $O(1)$ 时间。
|
||||
- **添加顶点**:在邻接表中添加一个链表,并将新增顶点作为链表头节点,使用 $O(1)$ 时间。
|
||||
- **删除顶点**:需遍历整个邻接表,删除包含指定顶点的所有边,使用 $O(1)$ 时间。
|
||||
- **初始化**:在邻接表中创建 $n$ 个顶点和 $2m$ 条边,使用 $O(n + m)$ 时间。
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
=== "删除顶点"
|
||||

|
||||
|
||||
以下是基于邻接表实现图的代码示例。细心的同学可能注意到,**我们在邻接表中使用 `Vertex` 结点类来表示顶点**,这样做的原因有:
|
||||
以下是基于邻接表实现图的代码示例。细心的同学可能注意到,**我们在邻接表中使用 `Vertex` 节点类来表示顶点**,这样做的原因有:
|
||||
|
||||
- 如果我们选择通过顶点值来区分不同顶点,那么值重复的顶点将无法被区分。
|
||||
- 如果类似邻接矩阵那样,使用顶点列表索引来区分不同顶点。那么,假设我们想要删除索引为 $i$ 的顶点,则需要遍历整个邻接表,将其中 $> i$ 的索引全部减 $1$,这样操作效率较低。
|
||||
|
||||
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
@@ -22,7 +22,7 @@ BFS 通常借助「队列」来实现。队列具有“先入先出”的性质
|
||||
2. 在循环的每轮迭代中,弹出队首顶点并记录访问,然后将该顶点的所有邻接顶点加入到队列尾部;
|
||||
3. 循环步骤 `2.` ,直到所有顶点被访问完成后结束;
|
||||
|
||||
为了防止重复遍历顶点,我们需要借助一个哈希表 `visited` 来记录哪些结点已被访问。
|
||||
为了防止重复遍历顶点,我们需要借助一个哈希表 `visited` 来记录哪些节点已被访问。
|
||||
|
||||
=== "Java"
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
@@ -24,13 +24,13 @@
|
||||
|
||||
链式地址下,哈希表的操作方法包括:
|
||||
|
||||
- **查询元素**:输入 key ,经过哈希函数得到数组索引,即可访问链表头结点,然后遍历链表并对比 key 以查找目标键值对。
|
||||
- **添加元素**:先通过哈希函数访问链表头结点,然后将结点(即键值对)添加到链表中。
|
||||
- **删除元素**:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标结点,并将其删除。
|
||||
- **查询元素**:输入 key ,经过哈希函数得到数组索引,即可访问链表头节点,然后遍历链表并对比 key 以查找目标键值对。
|
||||
- **添加元素**:先通过哈希函数访问链表头节点,然后将节点(即键值对)添加到链表中。
|
||||
- **删除元素**:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标节点,并将其删除。
|
||||
|
||||
尽管链式地址法解决了哈希冲突问题,但仍存在一些局限性,包括:
|
||||
|
||||
- **占用空间增大**,由于链表或二叉树包含结点指针,相比数组更加耗费内存空间;
|
||||
- **占用空间增大**,由于链表或二叉树包含节点指针,相比数组更加耗费内存空间;
|
||||
- **查询效率降低**,因为需要线性遍历链表来查找对应元素;
|
||||
|
||||
为了提高操作效率,**可以将链表转换为「AVL 树」或「红黑树」**,将查询操作的时间复杂度优化至 $O(\log n)$ 。
|
||||
|
||||
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
@@ -12,8 +12,8 @@
|
||||
|
||||
1. **无序数组**:每个元素为 `[学号, 姓名]` ;
|
||||
2. **有序数组**:将 `1.` 中的数组按照学号从小到大排序;
|
||||
3. **链表**:每个结点的值为 `[学号, 姓名]` ;
|
||||
4. **二叉搜索树**:每个结点的值为 `[学号, 姓名]` ,根据学号大小来构建树;
|
||||
3. **链表**:每个节点的值为 `[学号, 姓名]` ;
|
||||
4. **二叉搜索树**:每个节点的值为 `[学号, 姓名]` ,根据学号大小来构建树;
|
||||
|
||||
各项操作的时间复杂度如下表所示(详解可见[二叉搜索树章节](https://www.hello-algo.com/chapter_tree/binary_search_tree/))。无论是查找元素还是增删元素,哈希表的时间复杂度都是 $O(1)$,全面胜出!
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 57 KiB |
@@ -1,18 +1,18 @@
|
||||
# 建堆操作 *
|
||||
|
||||
如果我们想要根据输入列表来生成一个堆,这样的操作被称为「建堆」。
|
||||
如果我们想要根据输入列表生成一个堆,这个过程被称为「建堆」。
|
||||
|
||||
## 两种建堆方法
|
||||
|
||||
### 借助入堆方法实现
|
||||
|
||||
最直接地,考虑借助「元素入堆」方法,先建立一个空堆,**再将列表元素依次入堆即可**。
|
||||
最直接的方法是借助“元素入堆操作”实现,首先创建一个空堆,然后将列表元素依次添加到堆中。
|
||||
|
||||
设元素数量为 $n$ ,则最后一个元素入堆的时间复杂度为 $O(\log n)$ ,在依次入堆时,堆的平均长度为 $\frac{n}{2}$ ,因此该方法的总体时间复杂度为 $O(n \log n)$ 。
|
||||
设元素数量为 $n$ ,则最后一个元素入堆的时间复杂度为 $O(\log n)$ 。在依次添加元素时,堆的平均长度为 $\frac{n}{2}$ ,因此该方法的总体时间复杂度为 $O(n \log n)$ 。
|
||||
|
||||
### 基于堆化操作实现
|
||||
|
||||
有趣的是,存在一种更加高效的建堆方法,时间复杂度可以达到 $O(n)$ 。我们先将列表所有元素原封不动添加进堆,**然后迭代地对各个结点执行「从顶至底堆化」**。当然,**无需对叶结点执行堆化**,因为其没有子结点。
|
||||
有趣的是,存在一种更高效的建堆方法,其时间复杂度仅为 $O(n)$ 。我们先将列表所有元素原封不动添加到堆中,**然后迭代地对各个节点执行“从顶至底堆化”**。当然,**我们不需要对叶节点执行堆化操作**,因为它们没有子节点。
|
||||
|
||||
=== "Java"
|
||||
|
||||
@@ -76,24 +76,24 @@
|
||||
|
||||
## 复杂度分析
|
||||
|
||||
第二种建堆方法的时间复杂度为什么是 $O(n)$ 呢?我们来展开推算一下。
|
||||
为什么第二种建堆方法的时间复杂度是 $O(n)$ ?我们来展开推算一下。
|
||||
|
||||
- 完全二叉树中,设结点总数为 $n$ ,则叶结点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此在排除叶结点后,需要堆化结点数量为 $(n - 1)/2$ ,即为 $O(n)$ ;
|
||||
- 从顶至底堆化中,每个结点最多堆化至叶结点,因此最大迭代次数为二叉树高度 $O(\log n)$ ;
|
||||
- 完全二叉树中,设节点总数为 $n$ ,则叶节点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此,在排除叶节点后,需要堆化的节点数量为 $(n - 1)/2$ ,复杂度为 $O(n)$ ;
|
||||
- 在从顶至底堆化的过程中,每个节点最多堆化到叶节点,因此最大迭代次数为二叉树高度 $O(\log n)$ ;
|
||||
|
||||
将上述两者相乘,可得时间复杂度为 $O(n \log n)$ 。这个估算结果不够准确,因为我们没有考虑到 **二叉树底层结点远多于顶层结点** 的性质。
|
||||
将上述两者相乘,可得到建堆过程的时间复杂度为 $O(n \log n)$。**然而,这个估算结果并不准确,因为我们没有考虑到二叉树底层节点数量远多于顶层节点的特性**。
|
||||
|
||||
下面我们来展开计算。为了减小计算难度,我们假设树是一个「完美二叉树」,该假设不会影响计算结果的正确性。设二叉树(即堆)结点数量为 $n$ ,树高度为 $h$ 。上文提到,**结点堆化最大迭代次数等于该结点到叶结点的距离,而这正是“结点高度”**。
|
||||
接下来我们来进行更为详细的计算。为了减小计算难度,我们假设树是一个“完美二叉树”,该假设不会影响计算结果的正确性。设二叉树(即堆)节点数量为 $n$ ,树高度为 $h$ 。上文提到,**节点堆化最大迭代次数等于该节点到叶节点的距离,而该距离正是“节点高度”**。
|
||||
|
||||

|
||||

|
||||
|
||||
因此,我们将各层的“结点数量 $\times$ 结点高度”求和,即可得到 **所有结点的堆化的迭代次数总和**。
|
||||
因此,我们可以将各层的“节点数量 $\times$ 节点高度”求和,**从而得到所有节点的堆化迭代次数的总和**。
|
||||
|
||||
$$
|
||||
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1
|
||||
$$
|
||||
|
||||
化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,易得
|
||||
化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,得到
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
@@ -108,7 +108,7 @@ $$
|
||||
2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \cdots + 2^{h-1} + 2^h
|
||||
$$
|
||||
|
||||
观察上式,$T(h)$ 是一个等比数列,可直接使用求和公式,得到时间复杂度为
|
||||
观察上式,发现 $T(h)$ 是一个等比数列,可直接使用求和公式,得到时间复杂度为
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
@@ -118,4 +118,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)$ ,非常高效**。
|
||||
|
||||
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 108 KiB |