大家好我是 132,昨天为了面试看了一下动态规划(虽然面试没考到),但还是有很多收获
反正不管怎么样,我只得出了一个结论,那就是,动态规划是用来解决最xx
的问题的
很明显,diff 算法就是很典型的……最短编辑距离
diff 算法其实非常多,比较典型的主要有三种,一种是 preact 的试探下一个算法,一种是 snabbdom 的两端遍历的方法,再就是 inferno 的最长递增子序列的算法
前两种用于链表是在太过于困难,注意链表的特点,它不是数组,不能很方便地取到首尾节点,也不是递归,和递归时候的参数完全不同,所以两端遍历这种严重依赖首尾,索引的算法,用不到链表中
所以没办法只能用 inferno 的算法了,不过幸好,inferno 的算法似乎更简单
最长递增子序列(lis)
inferno 的算法,核心思想就是寻找一个最长递增子序列,然后这部分节点保持不动,其他的节点进行往前插
举个例子
- A: [1 2 3 4] // 旧的
- B: [4 1 2 3] // 新的
- P: [3 0 1 2] // 索引队列
- LIS: [0 1 2] // 最长递增子序列
我们只需要用一个数组记录变化后的索引队列,然后寻找这个索引队列的最长递增子序列
完美,然后就变成一个动态规划的问题了……
然后最终我们找到了最长递增子序列之后,这些就保持位置不动,然后其他的进行插入
即可
最长公共子序列(lcs)
虽然使用最长递增子序列是没毛病的,但是这个实现的代码量简直不忍直视
vue3 甚至都达到了 2000 多行:https://github.com/vuejs/vue-next/blob/master/packages/runtime-core/src/renderer.ts
同样是抄袭,俺一定要抄成 200 行!
所以复制粘贴是不行了,我们得继续抽象
答案是最长公共子序列,这样我们可以直接比对两个数组序列,而不需要维护 p 数组了
但是即便是 lcs,要想实现 nlogn 的复杂度,还是要将其转化为 lis 问题才行
同时还有一点,因为 vue,nerv,inferno,ivi 是基本雷同的
fre 不想雷同,虽然我也不去自创算法,但是我至少代码架构不去复制粘贴
时间复杂度
大家可能会对时间复杂度有疑问,然后动态规划不是 O(n*) 吗,那 react 说的 O(n) 是咋回事
时间复杂度,是针对 js 代码的,也就是你写的代码是 O(n),虽然 react 的代码时间复杂度低,但是它根本无法做到最短编辑距离呀
要想真正做到最短编辑距离,是不可能 O(n) 的,撑死就是优化动态规划,比如使用二分查找,让它变成 O(nlogn)
当然这些都不是重点,我们既然决定动这个算法,肯定要换个追求:
编辑距离 > 时间复杂度
实现思路
之后再说
总结
算法都是有目的性的,比如我们为了实现最短,那么它的时间复杂度就不可能最低
相反,如果我们放弃目标,求个不短的,自然实现简单,复杂度也很低
性能是一个很难量化的东西,大多数时候我们要的不是理论极限值而是一个刚刚好的值
当然,由于你们都不用 fre,那还谈什么刚刚好,作为一个没人用的框架,就应该只空谈理论极限!
未来很长一段时间,基于 lcs 的 diff 算法将会是 fre 的研究方向,大家有兴趣的可以一起来玩