mirror of
https://github.com/krahets/hello-algo.git
synced 2026-08-22 00:07:13 +00:00
build
This commit is contained in:
@@ -8,7 +8,7 @@ In some cases, we want to build a heap using all elements of a list, and this pr
|
||||
|
||||
## 8.2.1 Implementing with Element Insertion
|
||||
|
||||
We first create an empty heap, then iterate through the list, performing the "element insertion operation" on each element in sequence. This means adding the element to the bottom of the heap and then performing "bottom-to-top" heapify on that element.
|
||||
We first create an empty heap, then iterate through the list, performing the "element insertion operation" on each element in sequence. This means appending the element to the end of the heap and then performing "bottom-to-top" heapify on that element.
|
||||
|
||||
Each time an element is inserted into the heap, the heap's length increases by one. Since nodes are added to the binary tree sequentially from top to bottom, the heap is constructed "from top to bottom."
|
||||
|
||||
@@ -23,9 +23,9 @@ In fact, we can implement a more efficient heap construction method in two steps
|
||||
|
||||
**After heapifying a node, the subtree rooted at that node becomes a valid sub-heap**. Since we traverse in reverse order, the heap is constructed "from bottom to top."
|
||||
|
||||
The reason for choosing reverse order traversal is that it ensures the subtree below the current node is already a valid sub-heap, making the heapification of the current node effective.
|
||||
The reason for choosing reverse-order traversal is that it ensures the subtrees beneath the current node are already valid sub-heaps, so heapifying the current node is effective.
|
||||
|
||||
It's worth noting that **since leaf nodes have no children, they are naturally valid sub-heaps and do not require heapification**. As shown in the code below, the last non-leaf node is the parent of the last node; we start from it and traverse in reverse order to perform heapification:
|
||||
It's worth noting that **since leaf nodes have no children, they are naturally valid sub-heaps and do not require heapification**. As shown in the code below, the last non-leaf node is the parent of the last node; we start from that node and heapify while traversing in reverse order:
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -327,23 +327,23 @@ It's worth noting that **since leaf nodes have no children, they are naturally v
|
||||
Next, let's attempt to derive the time complexity of this second heap construction method.
|
||||
|
||||
- Assuming the complete binary tree has $n$ nodes, then the number of leaf nodes is $(n + 1) / 2$, where $/$ is floor division. Therefore, the number of nodes that need heapification is $(n - 1) / 2$.
|
||||
- In the top-to-bottom heapify process, each node is heapified at most to the leaf nodes, so the maximum number of iterations is the binary tree height $\log n$.
|
||||
- In the top-to-bottom heapify process, each node can sink at most to a leaf node, so the maximum number of iterations is the height of the binary tree, $\log n$.
|
||||
|
||||
Multiplying these two together, we get a time complexity of $O(n \log n)$ for the heap construction process. **However, this estimate is not accurate because it doesn't account for the property that binary trees have far more nodes at lower levels than at upper levels**.
|
||||
|
||||
Let's perform a more accurate calculation. To reduce calculation difficulty, assume a "perfect binary tree" with $n$ nodes and height $h$; this assumption does not affect the correctness of the result.
|
||||
Let's perform a more accurate calculation. To simplify the analysis, assume a "perfect binary tree" with $n$ nodes and height $h$; this assumption does not affect the correctness of the result.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
<p align="center"> Figure 8-5 Node count at each level of a perfect binary tree </p>
|
||||
|
||||
As shown in Figure 8-5, the maximum number of iterations for a node's "top-to-bottom heapify" equals the distance from that node to the leaf nodes, which is precisely the "node height." Therefore, we can sum the "number of nodes $\times$ node height" at each level to **obtain the total number of heapify iterations for all nodes**.
|
||||
As shown in Figure 8-5, the maximum number of iterations for a node's "top-to-bottom heapify" equals the distance from that node to a leaf node, which is precisely the node's height. Therefore, we can sum the "number of nodes $\times$ node height" at each level to **obtain the total number of heapify iterations for all nodes**.
|
||||
|
||||
$$
|
||||
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1
|
||||
$$
|
||||
|
||||
To simplify the above expression, we need to use sequence knowledge from high school. First, multiply $T(h)$ by $2$ to get:
|
||||
Simplifying the expression above requires some high-school sequence algebra. First, multiply $T(h)$ by $2$ to get:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
@@ -352,7 +352,7 @@ T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
Using the method of differences, subtract the first equation $T(h)$ from the second equation $2 T(h)$ to get:
|
||||
Using subtraction of shifted sums, subtract the first equation $T(h)$ from the second equation $2 T(h)$ to get:
|
||||
|
||||
$$
|
||||
2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h
|
||||
|
||||
@@ -21,7 +21,7 @@ As a special case of a complete binary tree, heaps have the following characteri
|
||||
|
||||
## 8.1.1 Common Heap Operations
|
||||
|
||||
It should be noted that many programming languages provide a <u>priority queue</u>, which is an abstract data structure defined as a queue with priority sorting.
|
||||
It should be noted that many programming languages provide a <u>priority queue</u>, an abstract data structure defined as a queue whose elements are ordered by priority.
|
||||
|
||||
In fact, **heaps are typically used to implement priority queues, with max heaps corresponding to priority queues where elements are dequeued in descending order**. From a usage perspective, we can regard "priority queue" and "heap" as equivalent data structures. Therefore, this book does not make a special distinction between the two and uniformly refers to them as "heap."
|
||||
|
||||
@@ -425,13 +425,13 @@ Similar to "ascending order" and "descending order" in sorting algorithms, we ca
|
||||
|
||||
## 8.1.2 Implementation of the Heap
|
||||
|
||||
The following implementation is of a max heap. To convert it to a min heap, simply invert all size logic comparisons (for example, replace $\geq$ with $\leq$). Interested readers are encouraged to implement this on their own.
|
||||
The following implementation is for a max heap. To convert it to a min heap, simply reverse all comparison logic related to ordering (for example, replace $\geq$ with $\leq$). Interested readers are encouraged to implement this on their own.
|
||||
|
||||
### 1. Heap Storage and Representation
|
||||
|
||||
As mentioned in the "Binary Tree" chapter, complete binary trees are well-suited for array representation. Since heaps are a type of complete binary tree, **we will use arrays to store heaps**.
|
||||
|
||||
When representing a binary tree with an array, elements represent node values, and indexes represent node positions in the binary tree. **Node pointers are implemented through index mapping formulas**.
|
||||
When representing a binary tree with an array, elements represent node values, and indexes represent node positions in the binary tree. **Parent-child relationships are represented through index-mapping formulas**.
|
||||
|
||||
As shown in Figure 8-2, given an index $i$, the index of its left child is $2i + 1$, the index of its right child is $2i + 2$, and the index of its parent is $(i - 1) / 2$ (floor division). When an index is out of bounds, it indicates a null node or that the node does not exist.
|
||||
|
||||
@@ -808,9 +808,9 @@ The heap top element is the root node of the binary tree, which is also the firs
|
||||
|
||||
### 3. Inserting an Element Into the Heap
|
||||
|
||||
Given an element `val`, we first add it to the bottom of the heap. After addition, since `val` may be larger than other elements in the heap, the heap's property may be violated. **Therefore, it's necessary to repair the path from the inserted node to the root node**. This operation is called <u>heapify</u>.
|
||||
Given an element `val`, we first add it to the bottom of the heap. After insertion, because `val` may be larger than other elements in the heap, the heap property may be violated. **Therefore, we need to restore the heap property along the path from the inserted node to the root**. This operation is called <u>heapify</u>.
|
||||
|
||||
Starting from the inserted node, **perform heapify from bottom to top**. As shown in Figure 8-3, we compare the inserted node with its parent node, and if the inserted node is larger, swap them. Then continue this operation, repairing nodes in the heap from bottom to top until we pass the root node or encounter a node that does not need swapping.
|
||||
Starting from the inserted node, **perform heapify from bottom to top**. As shown in Figure 8-3, we compare the inserted node with its parent, and if the inserted node is larger, we swap them. We continue this process from bottom to top until we move past the root or reach a node that no longer needs to be swapped.
|
||||
|
||||
=== "<1>"
|
||||
{ class="animation-figure" }
|
||||
@@ -1760,6 +1760,6 @@ Similar to the element insertion operation, the time complexity of the heap top
|
||||
|
||||
## 8.1.3 Common Applications of Heaps
|
||||
|
||||
- **Priority queue**: Heaps are typically the preferred data structure for implementing priority queues, with both enqueue and dequeue operations having a time complexity of $O(\log n)$, and the heap construction operation having $O(n)$, all of which are highly efficient.
|
||||
- **Priority queue**: Heaps are typically the preferred data structure for implementing priority queues. The time complexity of both enqueue and dequeue operations is $O(\log n)$, and heap construction has a time complexity of $O(n)$, making these operations highly efficient.
|
||||
- **Heap sort**: Given a set of data, we can build a heap with them and then continuously perform element removal operations to obtain sorted data. However, we usually use a more elegant approach to implement heap sort, as detailed in the "Heap Sort" chapter.
|
||||
- **Getting the largest $k$ elements**: This is a classic algorithm problem and also a typical application, such as selecting the top 10 trending news for Weibo hot search, selecting the top 10 best-selling products, etc.
|
||||
- **Getting the largest $k$ elements**: This is a classic algorithm problem and also a typical application, such as selecting the top 10 trending news items for Weibo Hot Search or the top 10 best-selling products.
|
||||
|
||||
@@ -9,13 +9,13 @@ icon: material/family-tree
|
||||
|
||||
!!! abstract
|
||||
|
||||
Heaps are like mountain peaks, layered and undulating, each with its unique form.
|
||||
Heaps are like mountain peaks, rising layer upon layer, each with a distinct shape.
|
||||
|
||||
The peaks rise and fall at varying heights, yet the tallest peak always catches the eye first.
|
||||
|
||||
## Chapter contents
|
||||
|
||||
- [8.1 Heap](heap.md)
|
||||
- [8.2 Building a Heap](build_heap.md)
|
||||
- [8.3 Top-K Problem](top_k.md)
|
||||
- [8.2 Heap Construction Operation](build_heap.md)
|
||||
- [8.3 Top-k Problem](top_k.md)
|
||||
- [8.4 Summary](summary.md)
|
||||
|
||||
@@ -6,16 +6,16 @@ comments: true
|
||||
|
||||
### 1. Key Review
|
||||
|
||||
- A heap is a complete binary tree that can be categorized as a max heap or min heap based on its property. The heap top element of a max heap (min heap) is the largest (smallest).
|
||||
- A priority queue is defined as a queue with priority sorting, typically implemented using heaps.
|
||||
- Common heap operations and their corresponding time complexities include: element insertion $O(\log n)$, heap top element removal $O(\log n)$, and accessing the heap top element $O(1)$.
|
||||
- A heap is a complete binary tree. Depending on the property it satisfies, it can be classified as either a max heap or a min heap. The top element of a max heap (min heap) is the largest (smallest) element.
|
||||
- A priority queue is a queue in which elements are dequeued according to priority, and it is typically implemented using a heap.
|
||||
- Common heap operations and their corresponding time complexities include inserting an element $O(\log n)$, removing the top element $O(\log n)$, and accessing the top element $O(1)$.
|
||||
- Complete binary trees are well-suited for array representation, so we typically use arrays to store heaps.
|
||||
- Heapify operations are used to maintain the heap property and are employed in both element insertion and removal operations.
|
||||
- The time complexity of building a heap with $n$ input elements can be optimized to $O(n)$, which is highly efficient.
|
||||
- Top-k is a classic algorithm problem that can be efficiently solved using the heap data structure, with a time complexity of $O(n \log k)$.
|
||||
- Building a heap from $n$ input elements can be optimized to $O(n)$, which is highly efficient.
|
||||
- Top-k is a classic algorithmic problem that can be solved efficiently using a heap, with a time complexity of $O(n \log k)$.
|
||||
|
||||
### 2. Q & A
|
||||
|
||||
**Q**: Are the "heap" in data structures and the "heap" in memory management the same concept?
|
||||
**Q**: Does the term "heap" in data structures mean the same thing as "heap" in memory management?
|
||||
|
||||
The two are not the same concept; they just happen to share the name "heap." The heap in computer system memory is part of dynamic memory allocation, where programs can use it to store data during runtime. Programs can request a certain amount of heap memory to store complex structures such as objects and arrays. When this data is no longer needed, the program needs to release this memory to prevent memory leaks. Compared to stack memory, heap memory management and usage require more caution, as improper use can lead to issues such as memory leaks and dangling pointers.
|
||||
They are not the same concept; they simply share the same name. In computer systems, the heap is part of dynamic memory allocation, and programs can use it to store data at runtime. A program can request a certain amount of heap memory to store complex structures such as objects and arrays. When the data is no longer needed, the program must release that memory to prevent memory leaks. Compared with stack memory, heap memory requires more careful management and use; improper handling can lead to problems such as memory leaks and dangling pointers.
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 8.3 Top-K Problem
|
||||
# 8.3 Top-k Problem
|
||||
|
||||
!!! question
|
||||
|
||||
Given an unordered array `nums` of length $n$, return the largest $k$ elements in the array.
|
||||
|
||||
For this problem, we'll first introduce two solutions with relatively straightforward approaches, then introduce a more efficient heap-based solution.
|
||||
For this problem, we will first introduce two relatively straightforward solutions, followed by a more efficient heap-based solution.
|
||||
|
||||
## 8.3.1 Method 1: Iterative Selection
|
||||
|
||||
We can perform $k$ rounds of traversal as shown in Figure 8-6, extracting the $1^{st}$, $2^{nd}$, $\dots$, $k^{th}$ largest elements in each round, with a time complexity of $O(nk)$.
|
||||
|
||||
This method is only suitable when $k \ll n$, because when $k$ is close to $n$, the time complexity approaches $O(n^2)$, which is very time-consuming.
|
||||
This method is only suitable when $k \ll n$, because when $k$ is close to $n$, the time complexity approaches $O(n^2)$, making it very inefficient.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -28,7 +28,7 @@ This method is only suitable when $k \ll n$, because when $k$ is close to $n$, t
|
||||
|
||||
As shown in Figure 8-7, we can first sort the array `nums`, then return the rightmost $k$ elements, with a time complexity of $O(n \log n)$.
|
||||
|
||||
Clearly, this method "overachieves" the task, as we only need to find the largest $k$ elements, without needing to sort the other elements.
|
||||
Clearly, this method does more work than necessary, because we only need to find the largest $k$ elements rather than sort the other elements.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -36,7 +36,7 @@ Clearly, this method "overachieves" the task, as we only need to find the larges
|
||||
|
||||
## 8.3.3 Method 3: Heap
|
||||
|
||||
We can solve the Top-k problem more efficiently using heaps, with the process shown in Figure 8-8.
|
||||
We can solve the Top-k problem more efficiently with a heap, as shown in Figure 8-8.
|
||||
|
||||
1. Initialize a min heap, where the heap top element is the smallest.
|
||||
2. First, insert the first $k$ elements of the array into the heap in sequence.
|
||||
@@ -463,4 +463,4 @@ Example code is as follows:
|
||||
|
||||
A total of $n$ rounds of heap insertions and removals are performed, with the heap's maximum length being $k$, so the time complexity is $O(n \log k)$. This method is very efficient; when $k$ is small, the time complexity approaches $O(n)$; when $k$ is large, the time complexity does not exceed $O(n \log n)$.
|
||||
|
||||
Additionally, this method is suitable for dynamic data stream scenarios. By continuously adding data, we can maintain the elements in the heap, thus achieving dynamic updates of the largest $k$ elements.
|
||||
Additionally, this method is well suited to dynamic data streams. As new data arrives, we can continuously maintain the elements in the heap, enabling dynamic updates to the largest $k$ elements.
|
||||
|
||||
Reference in New Issue
Block a user