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;
};
相关推荐
周全全10 分钟前
Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现
java·vue.js·spring boot·安全·php
ZwaterZ35 分钟前
vue el-table表格点击某行触发事件&&操作栏点击和row-click冲突问题
前端·vue.js·elementui·c#·vue
码农六六35 分钟前
vue3封装Element Plus table表格组件
javascript·vue.js·elementui
徐同保40 分钟前
el-table 多选改成单选
javascript·vue.js·elementui
快乐小土豆~~40 分钟前
el-input绑定点击回车事件意外触发页面刷新
javascript·vue.js·elementui
周三有雨1 小时前
【面试题系列Vue07】Vuex是什么?使用Vuex的好处有哪些?
前端·vue.js·面试·typescript
大霞上仙2 小时前
element ui table 每行不同状态
vue.js·ui·elementui
lv程序媛3 小时前
el-table表头前几列固定,后面几列根据接口返回的值不同展示不同
javascript·vue.js·elementui
蒟蒻的贤3 小时前
vue学习11.21
javascript·vue.js·学习
麻辣_水煮鱼5 小时前
vue数据变化但页面不变
前端·javascript·vue.js