forked from ascoders/weekly
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
258 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
掌握了不同数据结构的特点,可以让你在面对不同问题时,采用合适的数据结构处理,达到事半功倍的效果。 | ||
|
||
所以这次我们详细介绍各类数据结构的特点,希望你可以融会贯通。 | ||
|
||
## 精读 | ||
|
||
### 数组 | ||
|
||
<img width=200 src="https://img.alicdn.com/imgextra/i2/O1CN01noho9m1Vltg5ISaq2_!!6000000002694-2-tps-418-110.png"> | ||
|
||
数组非常常用,它是一块连续的内存空间,因此可以根据下标直接访问,其查找效率为 O(1)。 | ||
|
||
但数组的插入、删除效率较低,只有 O(n),原因是为了保持数组的连续性,必须在插入或删除后对数组进行一些操作:比如插入第 K 个元素,需要将后面元素后移;而删除第 K 个元素,需要将后面元素前移。 | ||
|
||
### 链表 | ||
|
||
<img width=280 src="https://img.alicdn.com/imgextra/i3/O1CN010JfUOo1b0A5muE4sE_!!6000000003402-2-tps-584-112.png"> | ||
|
||
链表是为了解决数组问题而发明出来的,它提升了插入、删除效率,而牺牲了查找效率。 | ||
|
||
链表的插入、删除效率是 O(1),因为只要将对应位置元素断链、重连就可以完成插入、删除,而无需关心其他节点。 | ||
|
||
相应的查找效率就低了,因为存储空间不是连续的,所以无法像数组一样通过下标直接查找,而需要通过指针不断搜索,所以查找效率为 O(n)。 | ||
|
||
顺带一提,链表可以通过增加 `.prev` 属性改造为双向链表,也可以通过定义两个 `.next` 形成二叉树(`.left` `.right`)或者多叉树(N 个 `.next`)。 | ||
|
||
<img width=160 src="https://img.alicdn.com/imgextra/i2/O1CN01IqNVQI1m5ABrZXV4i_!!6000000004902-2-tps-318-316.png"> | ||
|
||
### 栈 | ||
|
||
<img width=240 src="https://img.alicdn.com/imgextra/i3/O1CN01xSS8e21xbe3LP1khU_!!6000000006462-2-tps-466-122.png"> | ||
|
||
栈是一种先入后出的结构,可以用数组模拟。 | ||
|
||
```typescript | ||
const stack: number[] = [] | ||
|
||
// 入栈 | ||
stack.push(1) | ||
// 出栈 | ||
stack.pop() | ||
``` | ||
|
||
### 堆 | ||
|
||
<img width=500 src="https://img.alicdn.com/imgextra/i2/O1CN019O42yy1qE6NlA8w6V_!!6000000005463-2-tps-1154-952.png"> | ||
|
||
堆是一种特殊的完全二叉树,分为大顶堆与小顶堆。 | ||
|
||
大顶堆指二叉树根节点是最大的数,小顶堆指二叉树根节点是最小的数。为了方便说明,以下以大顶堆举例,小顶堆的逻辑与之相反即可。 | ||
|
||
大顶堆中,任意节点都比其叶子结点大,所以根节点是最大的节点。这种数据结构的优势是可以以 O(1) 效率找到最大值(小顶堆找最小值),因为直接取 `stack[0]` 就是根节点。 | ||
|
||
这里稍微提一下二叉树与数组结构的映射,因为采用数组方式操作二叉数,无论操作还是空间都有优势:第一项存储的是节点总数,对于下标为 K 的节点,其父节点下标是 `floor(K / 2)`,其子节点下标分别是 `K * 2`、`K * 2 + 1`,所以可以快速定位父子位置。 | ||
|
||
而利用这个特性,可以将插入、删除的效率达到 `O(logn)`,因为可以通过上下移动的方式调整其他节点顺序,而对于一个拥有 n 个节点的完全二叉树,树的深度为 `logn`。 | ||
|
||
### 哈希表 | ||
|
||
<img width=300 src="https://img.alicdn.com/imgextra/i4/O1CN01u3J1JF1Sl25HB6Q0I_!!6000000002286-2-tps-740-598.png"> | ||
|
||
哈希表就是所谓的 Map,不同 Map 实现方式不同,常见的有 HashMap、TreeMap、HashSet、TreeSet。 | ||
|
||
其中 Map 和 Set 实现类似,所以以 Map 为例讲解。 | ||
|
||
首先将要存储的字符求出其 ASCII 码值,再根据比如余数等方法,定位到一个数组的下标,同一个下标可能对应多个值,因此这个下标可能对应一个链表,根据链表进一步查找,这种方法称为拉链法。 | ||
|
||
如果存储的值超过一定数量,链表的查询效率就会降低,可能会升级为红黑树存储,总之这样的增、删、查效率为 `O(1)`,但缺点是其内容是无序的。 | ||
|
||
为了保证内容有序,可以使用树状结构存储,这种数据结构称为 HashTree,这样时间复杂度退化为 `O(logn)`,但好处是内容可以是有序的。 | ||
|
||
### 树 & 二叉搜索树 | ||
|
||
<img width=380 src="https://img.alicdn.com/imgextra/i4/O1CN01vOCoG91w82pzSITaQ_!!6000000006262-2-tps-800-504.png"> | ||
|
||
二叉搜索树是一种特殊二叉树,更复杂的还有红黑树,但这里就不深入了,只介绍二叉搜索树。 | ||
|
||
二叉搜索树满足对于任意节点,`left 的所有节点 < 根节点 < right 的所有节点`,注意这里是所有节点,因此在判断时需要递归考虑所有情况。 | ||
|
||
二叉搜索树的好处在于,访问、查找、插入、删除的时间复杂度均为 O(logn),因为无论何种操作都可以通过二分方式进行。但在最坏的情况会降级为 O(n),原因是多次操作后,二叉搜索树可能不再平衡,最后退化为一个链表,就变成了链表的时间复杂度。 | ||
|
||
更好的方案有 AVL 树、红黑树等,像 JAVA、C++ 标准库实现的二叉搜索树都是红黑树。 | ||
|
||
### 字典树 | ||
|
||
<img width=400 src="https://img.alicdn.com/imgextra/i2/O1CN01TqDeaL1ll0lTX75y3_!!6000000004858-2-tps-872-510.png"> | ||
|
||
字典树多用于单词搜索场景,只要给定一个单独开头,就可以快速查找到后面有几种推荐词。 | ||
|
||
比如上面的例子,输入 "o",就可以快速查找到后面有 "ok" 与 "ol" 两个单词。要注意的是,每个节点都要有一个属性 `isEndOfWord` 表示到当前为止是否为一个完整的单词:比如 `go` 与 `good` 两个都是完整的单词,但 `goo` 不是,因此第二个 `o` 与第四个 `d` 都有 `isEndOfWord` 标记,表示读到这里就查到一个完整的单词了,叶子结点的标记也可以省略。 | ||
|
||
### 并查集 | ||
|
||
<img width=300 src="https://img.alicdn.com/imgextra/i4/O1CN01B5xA5r21rSBj442z3_!!6000000007038-2-tps-622-172.png"> | ||
|
||
并查集用来解决团伙问题,或者岛屿问题,即判断多个元素之间是属于某个集合。并查集的英文是 Union and Find,即归并与查找,因此并查集数据结构可以写成一个类,提供两个最基础的方法 `union` 与 `find`。 | ||
|
||
其中 `union` 可以将任意两个元素放在一个集合,而 `find` 可以查找任意元素属于哪个根集合。 | ||
|
||
并查集使用数组的数据结构,只是有以下特殊含义,设下标为 k: | ||
|
||
- `nums[k]` 表示其所属的集合,如果 `nums[k] === k` 表示它是这个集合的根节点。 | ||
|
||
如果要数一共有几个集合,只要数有多少满足 `nums[k] === k` 条件的数目即可,就像数有几个团伙,只要数有几个老大即可。 | ||
|
||
并查集的实现不同,数据也会有微妙的不同,高效的并查集在插入时,会递归将元素的值尽量指向根老大,这样查找判断时计算的快一些,但即便指向的不是根老大,也可以通过递归的方式找到根老大。 | ||
|
||
### 布隆过滤器 | ||
|
||
<img width=300 src="https://img.alicdn.com/imgextra/i1/O1CN01CWabYX26RPkR0T3zs_!!6000000007658-2-tps-650-334.png"> | ||
|
||
Bloom Filter 只是一个过滤器,可以用远远超过其他算法的速度把未命中的数据排除掉,但未排除的也可能实际不存在,所以需要进一步查询。 | ||
|
||
布隆过滤器是如何做到这一点的呢?就是通过二进制判断。 | ||
|
||
如上图所示,我们先存储了 a、b 两个数据,将其转化为二进制,将对应为止改为 1,那么当我们再查询 a 或 b 时,因为映射关系相同,所以查到的结果肯定存在。 | ||
|
||
但查询 c 时,发现有一项是 0,说明 c 一定不存在;但查询 d 时,恰好两个都查到是 1,但实际 d 是不存在的,这就是其产生误差的原因。 | ||
|
||
布隆过滤器在比特币与分布式系统中使用广泛,比如比特币查询交易是否在某个节点上,就先利用布隆过滤器挡一下,以快速跳过不必要的搜索,而分布式系统计算比如 Map Reduce,也通过布隆过滤器快速过滤掉不在某个节点的计算。 | ||
|
||
## 总结 | ||
|
||
最后给出各数据结构 “访问、查询、插入、删除” 的平均、最差时间复杂度图: | ||
|
||
<img width=600 src="https://img.alicdn.com/imgextra/i1/O1CN01LV4sSl20vkHWdZ7nr_!!6000000006912-2-tps-2398-1272.png"> | ||
|
||
这个图来自 [bigocheatsheet](https://www.bigocheatsheet.com/#graphs),你也可以点开链接直接访问。 | ||
|
||
学习了这些基础数据结构之后,希望你可以融会贯通,善于组合这些数据结构解决实际的问题,同时还要意识到没有任何一个数据结构是万能的,否则就不会有这么多数据结构需要学习了,只用一个万能的数据结构就行了。 | ||
|
||
对于数据结构的组合,我举两个例子: | ||
|
||
第一个例子是如何以 O(1) 平均时间复杂度查询一个栈的最大或最小值。此时一个栈是不够的,需要另一个栈 B 辅助,遇到更大或更小值的时候才入栈 B,这样栈 B 的第一个数就是当前栈内最大或最小的值,查询效率是 O(1),而且只有在出栈时才需要更新,所以平均时间复杂度整体是 O(1)。 | ||
|
||
第二个例子是如何提升链表查找效率,可以通过哈希表与链表结合的思路,通过空间换时间的方式,用哈希表快速定位任意值在链表中的位置,就可以通过空间翻倍的牺牲换来插入、删除、查询时间复杂度均为 O(1)。虽然哈希表就能达到这个时间复杂度,但哈希表是无序的;虽然 HashTree 是有序的,但时间复杂度是 O(logn),所以只有通过组合 HashMap 与链表才能达到有序且时间复杂度更优,但牺牲了空间复杂度。 | ||
|
||
包括最后说的布隆过滤器也不是单独使用的,它只是一个防火墙,用极高的效率阻挡一些非法数据,但没有阻挡住的不一定就是合法的,需要进一步查询。 | ||
|
||
所以希望你能了解到各个数据结构的特征、局限以及组合的用法,相信你可以在实际场景中灵活使用不同的数据结构,以实现当前业务场景的最优解。 | ||
|
||
> 讨论地址是:[精读《React Server Component》· Issue #312 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/312) | ||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。** | ||
|
||
> 关注 **前端精读微信公众号** | ||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg"> | ||
|
||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
我站在前端角度梳理出数据领域技术专家应该学习的技术点,以下是能力要点: | ||
|
||
- 同时具备产品、数据、技术能力。 | ||
- 以前端为切入点,深入各技术领域,包括前后端、数据技术。 | ||
- 打牢基础知识,熟悉领域知识。 | ||
|
||
对于知识,可以分为通用、领域的知识,其中越通用、越基础的知识越保值,比如数学知识的保质期可能持续到地球毁灭,而 Webpack5 的 API 可能保质期只有三个月。 | ||
|
||
所以在学习前,要对各类知识的保质期有个大概的了解: | ||
|
||
- **通用基础知识**:100 年以上。 | ||
- **行业基础知识**,如产品、计算机基础知识:30 年以上。 | ||
- **行业通用领域知识**:10 年以内。 | ||
- **行业专用领域知识**:1 年左右。 | ||
|
||
### 能力模型 | ||
|
||
- **通用基础知识** | ||
- 数学 | ||
- 坐标系 | ||
- 参数方程 | ||
- 向量 | ||
- 点乘 | ||
- 叉乘 | ||
- 矩阵 | ||
- 矩阵乘法 | ||
- 线性变换 | ||
- 仿射变换 | ||
- 计算机 **行业基础知识** | ||
- 数据结构 | ||
- 数据 | ||
- 链表 | ||
- 栈 | ||
- 堆 | ||
- 哈希表 | ||
- 树 & 二叉树 & 二叉搜索树 | ||
- 字典树 | ||
- 并查集 | ||
- 布隆过滤器 | ||
- 算法 | ||
- 位运算 | ||
- 滑动窗口 | ||
- 双指针 | ||
- 贪心 | ||
- 回溯 | ||
- 递归 | ||
- 分治 | ||
- 动态规划 | ||
- 编译原理 | ||
- 设计模式 | ||
- 产品 | ||
- **行业基础知识** | ||
- 经济学 | ||
- 商学 | ||
- 心理学 | ||
- 数据 | ||
- **行业基础知识** | ||
- 数据明细与聚合 | ||
- SQL | ||
- **行业通用领域知识** | ||
- 计算字段 | ||
- **行业专用领域知识** | ||
- 数据分析表达式 | ||
- 前端 | ||
- **行业基础知识** | ||
- 编程范式 | ||
- 命令式 | ||
- 过程式 | ||
- 面向对象 | ||
- 声明式 | ||
- 逻辑式 | ||
- 函数式 | ||
- 数据修改 | ||
- 可变数据 | ||
- 不可变数据 | ||
- 图形学 | ||
- **行业通用领域知识** | ||
- javascript、typescript | ||
- css | ||
- dom、svg、canvas | ||
- webgl | ||
- **行业专用领域知识** | ||
- 前端框架 | ||
- React | ||
- Vue | ||
- 数据流框架 | ||
- Redux | ||
- Mobx | ||
- 构建工具 | ||
- Webpack | ||
- Snowpack | ||
- 脚手架 | ||
- Vite | ||
- 全栈框架 | ||
- Next.js | ||
- 后端 | ||
- **行业基础知识** | ||
- 架构模式 | ||
- 一主多从 | ||
- **行业专用领域知识** | ||
- 后端框架 | ||
- Spring | ||
|
||
### 前端精读与能力模型 | ||
|
||
前端精读不可能覆盖上述所有知识,有的知识是课本学的,有的知识是步入社会后,看书或者通过专门渠道学习的,所以本篇只对能力模型做一个指导,而学海无涯,前端精读即便持续创作一百年,也仍然挂一漏万。 | ||
|
||
不过这个能力模型会对前端精读选材起到主导作用,我会尽量挑选重要的,保质期长的基础知识解读,而保质期较短的行业专用领域知识则尽量少讲,希望前端精读能形成一个正金字塔,底座是厚厚的基础知识,金字塔尖是重要的行业知识,风沙会经常侵蚀金字塔尖,所以金字塔尖需要不断的更新迭代,但我相信,只要有一个稳定的底座,上层的修缮总不是难事。 |