mirror of
https://github.com/krahets/hello-algo.git
synced 2024-12-26 12:26:29 +08:00
build
This commit is contained in:
parent
a9ff8a9e91
commit
2c74c6e3f4
3 changed files with 29 additions and 11 deletions
|
@ -12,9 +12,11 @@ comments: true
|
|||
|
||||
最直接地,考虑借助「元素入堆」方法,先建立一个空堆,**再将列表元素依次入堆即可**。
|
||||
|
||||
设元素数量为 $n$ ,则最后一个元素入堆的时间复杂度为 $O(\log n)$ ,在依次入堆时,堆的平均长度为 $\frac{n}{2}$ ,因此该方法的总体时间复杂度为 $O(n \log n)$ 。
|
||||
|
||||
### 基于堆化操作实现
|
||||
|
||||
然而,**存在一种更加高效的建堆方法**。设元素数量为 $n$ ,我们先将列表所有元素原封不动添加进堆,**然后迭代地对各个结点执行「从顶至底堆化」**。当然,**无需对叶结点执行堆化**,因为其没有子结点。
|
||||
有趣的是,存在一种更加高效的建堆方法,时间复杂度可以达到 $O(n)$ 。我们先将列表所有元素原封不动添加进堆,**然后迭代地对各个结点执行「从顶至底堆化」**。当然,**无需对叶结点执行堆化**,因为其没有子结点。
|
||||
|
||||
=== "Java"
|
||||
|
||||
|
@ -155,25 +157,25 @@ comments: true
|
|||
|
||||
## 8.2.2. 复杂度分析
|
||||
|
||||
对于第一种建堆方法,元素入堆的时间复杂度为 $O(\log n)$ ,而平均长度为 $\frac{n}{2}$ ,因此该方法的总体时间复杂度为 $O(n \log n)$ 。
|
||||
|
||||
那么,第二种建堆方法的时间复杂度是多少呢?我们来展开推算一下。
|
||||
第二种建堆方法的时间复杂度为什么是 $O(n)$ 呢?我们来展开推算一下。
|
||||
|
||||
- 完全二叉树中,设结点总数为 $n$ ,则叶结点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此在排除叶结点后,需要堆化结点数量为 $(n - 1)/2$ ,即为 $O(n)$ ;
|
||||
- 从顶至底堆化中,每个结点最多堆化至叶结点,因此最大迭代次数为二叉树高度 $O(\log n)$ ;
|
||||
|
||||
将上述两者相乘,可得时间复杂度为 $O(n \log n)$ 。然而,该估算结果仍不够准确,因为我们没有考虑到 **二叉树底层结点远多于顶层结点** 的性质。
|
||||
将上述两者相乘,可得时间复杂度为 $O(n \log n)$ 。这个估算结果不够准确,因为我们没有考虑到 **二叉树底层结点远多于顶层结点** 的性质。
|
||||
|
||||
下面我们来尝试展开计算。为了减小计算难度,我们假设树是一个「完美二叉树」,该假设不会影响计算结果的正确性。设二叉树(即堆)结点数量为 $n$ ,树高度为 $h$ 。上文提到,**结点堆化最大迭代次数等于该结点到叶结点的距离,而这正是“结点高度”**。因此,我们将各层的“结点数量 $\times$ 结点高度”求和,即可得到所有结点的堆化的迭代次数总和。
|
||||
|
||||
$$
|
||||
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1
|
||||
$$
|
||||
下面我们来展开计算。为了减小计算难度,我们假设树是一个「完美二叉树」,该假设不会影响计算结果的正确性。设二叉树(即堆)结点数量为 $n$ ,树高度为 $h$ 。上文提到,**结点堆化最大迭代次数等于该结点到叶结点的距离,而这正是“结点高度”**。
|
||||
|
||||
![完美二叉树的各层结点数量](build_heap.assets/heapify_operations_count.png)
|
||||
|
||||
<p align="center"> Fig. 完美二叉树的各层结点数量 </p>
|
||||
|
||||
因此,我们将各层的“结点数量 $\times$ 结点高度”求和,即可得到 **所有结点的堆化的迭代次数总和**。
|
||||
|
||||
$$
|
||||
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1
|
||||
$$
|
||||
|
||||
化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,易得
|
||||
|
||||
$$
|
||||
|
|
16
chapter_sorting/bucket_sort.md
Normal file
16
chapter_sorting/bucket_sort.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
## 拓展到桶排序
|
||||
|
||||
如果我们把上述 `bucket` 中的每个索引想象成一个桶,那么可以将计数排序理解为把 $n$ 个元素分配到对应的桶中,再根据桶与桶之间天然的有序性来实现排序。
|
||||
|
||||
以上解读便是「桶排序 Bucket Sort」的核心思想。具体地,桶排序考虑将 $n$ 个元素根据大小范围均匀地分配到 $k$ 个桶中,由于桶之间是有序的,**因此仅需在每个桶内部执行排序**,最终按照桶之间的大小关系将元素依次排列,即可得到排序结果。
|
||||
|
||||
假设使用「快速排序」来排序各个桶内的元素,每个桶内元素数量为 $\frac{n}{k}$ ,则排序单个桶使用 $O(\frac{n}{k} \log\frac{n}{k})$ 时间,排序所有桶使用 $O(n \log\frac{n}{k})$ 时间。**当桶数量 $k$ 接近 $n$ 时,时间复杂度则趋向于 $O(n)$** 。
|
||||
|
||||
(图)
|
||||
|
||||
理论上桶排序的时间复杂度是 $O(n)$ ,**但前提是需要将元素均匀分配到各个桶中**,而这是不太容易做到的。假设我们要把淘宝中的 $100$ 万件商品根据价格范围平均分配到 $100$ 个桶中,由于商品价格不是均匀分布的,比如 $1$ ~ $100$ 元的商品非常多、$1$ 万元以上的商品非常少等,因此难以简单地设定各个桶的价格分界线。解决方案有:
|
||||
|
||||
- 先初步设置一个分界线,将元素分配完后,**把元素较多的桶继续划分为多个桶**,直至每个桶内元素数量合理为止;该做法一般使用递归实现;
|
||||
- 如果我们提前知道商品价格的概率分布,**则可以根据已知分布来设置每个桶的价格分界线**;值得说明的是,数据分布不一定需要 case-by-case 地统计,有时可以采用一些常见分布来近似,例如自然界的正态分布;
|
||||
|
||||
(图)
|
|
@ -294,7 +294,7 @@ comments: true
|
|||
|
||||
举个例子,给定数组 `[0, 0, 0, 0, 1]` ,如果先“从左向右查找”,哨兵划分后数组为 `[1, 0, 0, 0, 0]` ,这个结果是不对的。
|
||||
|
||||
再往深想一步,如果我们选择 `nums[right]` 为基准数,那么正好反过来,必须先“从左往右查找”。
|
||||
再深想一步,如果我们选择 `nums[right]` 为基准数,那么正好反过来,必须先“从左往右查找”。
|
||||
|
||||
## 11.4.1. 算法流程
|
||||
|
||||
|
|
Loading…
Reference in a new issue