This commit is contained in:
krahets
2023-06-02 02:38:24 +08:00
parent 874e75d92d
commit 2a85d796e6
35 changed files with 2354 additions and 0 deletions
+14
View File
@@ -162,6 +162,20 @@ comments: true
}
```
=== "Dart"
```dart title="my_heap.dart"
/* 构造方法,根据输入列表建堆 */
MaxHeap(List<int> nums) {
// 将列表元素原封不动添加进堆
_maxHeap = nums;
// 堆化除叶节点以外的其他所有节点
for (int i = _parent(size() - 1); i >= 0; i--) {
_siftDown(i);
}
}
```
## 8.2.3. &nbsp; 复杂度分析
为什么第二种建堆方法的时间复杂度是 $O(n)$ ?我们来展开推算一下。
+68
View File
@@ -307,6 +307,12 @@ comments: true
```
=== "Dart"
```dart title="heap.dart"
```
## 8.1.2. &nbsp; 堆的实现
下文实现的是大顶堆。若要将其转换为小顶堆,只需将所有大小逻辑判断取逆(例如,将 $\geq$ 替换为 $\leq$ )。感兴趣的读者可以自行实现。
@@ -514,6 +520,25 @@ comments: true
}
```
=== "Dart"
```dart title="my_heap.dart"
/* 获取左子节点索引 */
int _left(int i) {
return 2 * i + 1;
}
/* 获取右子节点索引 */
int _right(int i) {
return 2 * i + 2;
}
/* 获取父节点索引 */
int _parent(int i) {
return (i - 1) ~/ 2; // 向下整除
}
```
### 访问堆顶元素
堆顶元素即为二叉树的根节点,也就是列表的首个元素。
@@ -607,6 +632,15 @@ comments: true
}
```
=== "Dart"
```dart title="my_heap.dart"
/* 访问堆顶元素 */
int peek() {
return _maxHeap[0];
}
```
### 元素入堆
给定元素 `val` ,我们首先将其添加到堆底。添加之后,由于 val 可能大于堆中其他元素,堆的成立条件可能已被破坏。因此,**需要修复从插入节点到根节点的路径上的各个节点**,这个操作被称为「堆化 Heapify」。
@@ -918,6 +952,20 @@ comments: true
}
```
=== "Dart"
```dart title="my_heap.dart"
/* 元素入堆 */
void push(int val) {
// 添加节点
_maxHeap.add(val);
// 从底至顶堆化
_siftUp(size() - 1);
}
[class]{MaxHeap}-[func]{siftUp}
```
### 堆顶元素出堆
堆顶元素是二叉树的根节点,即列表首元素。如果我们直接从列表中删除首元素,那么二叉树中所有节点的索引都会发生变化,这将使得后续使用堆化修复变得困难。为了尽量减少元素索引的变动,我们采取以下操作步骤:
@@ -1354,6 +1402,26 @@ comments: true
}
```
=== "Dart"
```dart title="my_heap.dart"
/* 元素出堆 */
int pop() {
// 判空处理
if (isEmpty()) throw Exception('堆为空');
// 交换根节点与最右叶节点(即交换首元素与尾元素)
_swap(0, size() - 1);
// 删除节点
int val = _maxHeap.removeLast();
// 从顶至底堆化
_siftDown(0);
// 返回堆顶元素
return val;
}
[class]{MaxHeap}-[func]{siftDown}
```
## 8.1.3. &nbsp; 堆常见应用
- **优先队列**:堆通常作为实现优先队列的首选数据结构,其入队和出队操作的时间复杂度均为 $O(\log n)$ ,而建队操作为 $O(n)$ ,这些操作都非常高效。