Vue3快速diff算法中的最长递增子序列个人理解

本篇比较适合对Vue3的快速diff算法有了解的,但是不明白Vue3中最长递增子序列实现的朋友阅读。本篇只阐述个人对求最长递增子序列部分的理解。

为什么求最长递增子序列?

减少dom的移动,从而减少重排。如果更新节点时,只是先卸载旧dom,挂载新dom,会导致下面的dom全部进行重排。

怎么求最长递增子序列?

快速diff要求拿到的最长递增子序列是旧节点在旧children中的索引,首先要知道这一点。 其实求最长递增子序列的方法好几种,比如动态规划贪心加二分等,这里为了说清楚快速diff,先介绍另一种方法,快速diff其实就是对它的优化。

快速diff前的小插曲:

可以去b站看渡一讲的最长递增子序列的视频!有动画,比看这段文字好容易理解多了! 假如要求一个数组[2, 4, 5, 3, 1, 1, 8, 123, 99]的最长递增子序列。

这个方法的核心是 保存最长递增子序列长度为1,2,3.....时的最优解。 这里的最优解是,当最长递增子序列长度为1,2,3......时序列最有可能增长的解。比如这个例子中当遍历到1时,长度为1的最优解就是1了,而不是2。这样最后一种情况就是最终的结果!!!

那为什么要保存长度的这么小的情况呢?因为不知道后面有多少节点,由于长度比较小的情况,它们最后一个节点的值也比较小,它的增长潜力更大。长度长的虽然最后一个节点值比较大,增长潜力小,但是它们长,也更可能成为结果,所以也应该保存。

遍历的过程中就是要维护这些情况。那么怎么维护呢?其实维护操作就分两种情况:

  1. 遍历存储的这些情况,如果当前节点比最后一个节点大,那么它可以增长。若这个情况目前是最长的,则新增一个情况;不是的话,用目前节点和下一个情况(就是长度+1的情况)的最后一个节点比较,若是小的话,就可以更新它了。
  2. 如果它比长度为1的情况小,那么直接更新长度为1的情况。
js 复制代码
const getLIS = (nums) => {
    const result = [0, [nums[0]]];
    for (let i = 1; i < nums.length; i++) {
        for (let j = 1; j < result.length; j++) {
            const curEnd = result[j][j - 1];
            if (nums[i] > curEnd) {
                const nextEnd = j + 1 < result.length ? result[j + 1][j] : null;
                if (nextEnd && nextEnd > nums[i]) {
                        result[j + 1][j] = nums[i];
                } else if (!nextEnd) {
                        result[j + 1] = [...result[j], nums[i]];
                }
            } else if (nums[i] < curEnd && j === 1) {
                result[1][0] = nums[i];
            }
        }
    }
    console.log(result);
};

getLIS([2, 4, 5, 3, 1, 1, 8, 123, 99]);

快速diff实现:

快速diff其实上面的一个优化。上面保存了长度为所有可能情况下的结果,占用内存比较大,而且当最长递增子序列比较长的时候,两层for循环比较慢。 快速diff实际上只保存了长度为1,2,3......情况下最后一个节点的索引。 假如这个容器为result吧。

但是需要注意的是,遍历完后,result最后一个节点的含义是不是 长度为result.length时,最长递增子序列最后一个节点的索引,它就是最终要求的最长递增子序列的最后一索引。

那么有没有一种方法可以通过这个确定索引将最终结果还原?结合上面条件,是不是通过确定的最后一个节点找到前一个节点,再找前一个节点......就可以完成了?!

我们知道,以当前节点结尾的最长递增子序列 = 前面最后一个节点的值比它小的 最长递增子序列 + 当前节点,这就是一个递推公式,最终的最长递增子序列也是逐渐递推出来的!

我们的result最后一个节点还有一层含义:它是以source[result[result.length - 1]]结尾的最长递增子序列的最后一个节点。它是不是可以通过前一个节点为结尾的最长递增子序列推出来 + 1给推出来?前一个结节点是不是也可以以同样的方法推出来?
所以prevs只要记录 在以某个节点为结尾的最长递增子序列中,这个节点的前一个节点就可以了。

那么怎么求prevs呢?在遍历时,我们先使用二分找到第一个比它大的节点进行替换,那么它和前面所有节点就构成了以它为结尾的最长递增子序列 ,那么prevs[i]就是被替换节点前面那个节点。

基本的理论搞完了,下面贴上我的代码,仅供参考!

js 复制代码
const getLis = (source) => {
    const result = [];
    const prevs = Array.from(source); // 记录以source[i]结尾的最长递增子序列,source[i]前一个节点的索引
    for (let i = 0; i < source.length; i++) {
        const replacedIdxInResult = bs(result, source, 0, result.length - 1, source[i]);
        if (replacedIdxInResult !== -1) {
            prevs[i] = result[replacedIdxInResult - 1] || -1;

            result[replacedIdxInResult] = i;
        } else {
            prevs[i] = result.length === 0 ? -1 : result[result.length - 1];
            result.push(i);
        }
    }

    const finalRes = [];
    const endIdxInSource = result[result.length - 1];
    finalRes.push(endIdxInSource);
    let prevIdxInSource = prevs[endIdxInSource];
    while (prevIdxInSource >= 0) {
        fs.push(pinalRerevIdxInSource);
        prevIdxInSource = prevs[prevIdxInSource];
    }
    return finalRes.reverse();
};

const bs = (result, source, l, r, num) => {
    while (l < r) {
        let mid = Math.floor((l + r) >> 1);
        if (source[result[mid]] > num) {
                r = mid;
        } else {
                l = mid + 1;
        }
    }
    if (source[result[l]] > num) return l;
    else return -1;
};
相关推荐
Dread_lxy44 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
龙猫蓝图3 小时前
vue el-date-picker 日期选择器禁用失效问题
前端·javascript·vue.js
peachSoda73 小时前
随手记:简单实现纯前端文件导出(XLSX)
前端·javascript·vue.js
Tttian6223 小时前
Vue全栈开发旅游网项目(11)-用户管理前端接口联调
前端·vue.js·django
龙猫蓝图5 小时前
vue el-date-picker 日期选择 回显后成功后无法改变的解决办法
前端·javascript·vue.js
刘志辉5 小时前
Pure Adminrelease(水滴框架配置)
vue.js
工业互联网专业6 小时前
Python毕业设计选题:基于Django+uniapp的公司订餐系统小程序
vue.js·python·小程序·django·uni-app·源码·课程设计
黄景圣6 小时前
CURD低代码程序设计
前端·vue.js·后端
lin-lins6 小时前
Vue 模板编译原理
前端·javascript·vue.js
customer087 小时前
【开源免费】基于SpringBoot+Vue.JS课程答疑系统(JAVA毕业设计)
java·jvm·vue.js·spring boot·spring cloud·kafka·开源