多图讲解Vue3的diff算法最长递增子序列实现原理

vue3的diff的算法用到了最长递增子序列 那么到底vue3是如何实现的呐? 本文将通过一系列图文的方式,给大家分析下,最后会附上Vue3的源码和注释。多图警告

ini 复制代码
let i = 0;
const source = [2, 5, 6, 1, 7, 3, 4, 10]; // 原数组
const resIndex = [] // 存储下标索引的结果

接下来我们一步一步通过图文的方式看vue是如何求出source的最长递增子序列的。

i = 0

resIndex为空数组,所以source[0] = 2的下标 直接放入resIndex结果中。 此时resIndex中最大值为2.

i =1

source[i] = 5 大于resIndex最后一项代表的值2,所以将5对象的下标 1 加入resIndex中。

resIndex中下标1的前一项索引为0 添加1指向0的前指针 如图箭头。

i =2

source[i] = 6 大于resIndex最后一项代表的值5,所以将6对象的下标 2 加入resIndex中。

resIndex中下标2的前一项索引为1 添加2指向1的前指针 如图箭头。

i =3

source[i] = 1 小于resIndex最后一项代表的值6,所以通过二分查找找到比对象1小的,然后用1替换掉下一位。

例如这个例子中 resIndex=0,1,2 resValue=2,5,6 因为序列是递增的,1和2相比 大于2的数字一定大于1 所以这里根据贪心算法 使用1替换掉2 同时更新resIndex=3,1,2

resIndex中下标3的前一项索引为空 所以不需要添加前指针 如图箭头。

i = 4

source[i] = 7 大于resIndex最后一项代表的值6,所以将7对象的下标 4 加入resIndex中

resIndex中下标4的前一项索引为2 添加4指向2的前指针 如图箭头。

i = 5

source[i] = 3 小于resIndex最后一项代表的值7,所以通过二分查找找到比对象3小的,然后用3替换掉下一位。

例如这个例子中 resIndex=3,1,2,4 resValue=1,5,6,7 因为序列是递增的,3和5相比 大于5的数字一定大于3 所以这里根据贪心算法 使用3替换掉5 同时更新resIndex=3,5,2,4

resIndex中下标5的前一项索引为3 添加5指向3的前指针 如图箭头。

i = 6

source[i] = 4 小于resIndex最后一项代表的值7,所以通过二分查找找到比对象4小的,然后用4替换掉下一位。

例如这个例子中 resIndex=3,5,2,4 resValue=1,3,6,7 因为序列是递增的,4和6相比 大于6的数字一定大于4 所以这里根据贪心算法 使用4替换掉6 同时更新resIndex=3,5, 6,4

resIndex中下标6的前一项索引为5 添加6指向5的前指针 如图箭头。

i = 7

source[i] = 10 大于resIndex最后一项代表的值7,所以将 10 对象的下标 7 加入resIndex中

resIndex中下标7的前一项索引为4 添加7指向4的前指针 如图箭头。

产生的问题

目前我们已经得到了resIndex=3,5,6,4,7 代表的值value =1,3,4,7,10

我们已知最终结果为:2,5,6,7,10

显然目前长度是和结果一致的但是数据明显不对。

这是因为贪心算法在做替换的时 只关注当前的局部最优解而导致了最终结果的偏差。

所以我们需要对结果进行修正,此时就会用到我们之前设置的前指针.

回溯修正

我们从resIndex的最后一项开始 从后向前根据前指针 重新生成一个新的resIndex和新的结果。

  • 第一步: resIndex = 7 value=10

  • 第二步: 下标7的前指针为下标4 所以将下标4加入 resIndex = 4,7 value=7,10

  • 第三步: 下标4的前指针为下标2 所以将下标2加入 resIndex = 2,4,7 value=6,7,10

  • 第四步: 下标2的前指针为下标1 所以将下标1加入 resIndex = 1,2,4,7 value=5,6,7,10

  • 第五步: 下标1的前指针为下标0 所以将下标0加入 resIndex = 0,1,2,4,7 value=2,5,6,7,10

得到最终的递增序列:resIndex = 0,1,2,4,7 value=2,5,6,7,10

vue源码解析: getSequence

ini 复制代码
function getSequence(arr) {
  const p = arr.slice(); // 这个是用于标识前指针的数组 和文章中的箭头等价
  const result = [0]; // result 这个是存储结果索引的数组 和文章中的resIndex等价
  let i, j, u, v, c;
  const len = arr.length;
  for (i = 0; i < len; i++) {
    const arrI = arr[i];
    if (arrI !== 0) { // arrI !== 0 这个判断可以先忽略和最长递增子序列算法无关 是dom操作用于新增节点的
      j = result[result.length - 1];
      if (arr[j] < arrI) { // 这里是判断数组的item是否大于结果的最大值 大于最大值说明递增 
        p[i] = j; // 这里是更新前指针
        result.push(i); // 递增的情况 下标直接加入到结果索引
        continue;
      }
      u = 0;
      v = result.length - 1;
      while (u < v) { // 不大于最大值 通过二分查找 找到正确位置 插入替换
        c = (u + v) >> 1;
        if (arr[result[c]] < arrI) {
          u = c + 1;
        } else {
          v = c;
        }
      }
      if (arrI < arr[result[u]]) {
        if (u > 0) {
          p[i] = result[u - 1]; // 插入替换时 同样更新前指针
        }
        result[u] = i; // 插入替换
      }
    }
  }
  u = result.length;
  v = result[u - 1];
  while (u-- > 0) { // 这里从后向前 回溯修正结果 
    result[u] = v;
    v = p[v];
  }
  return result; // 返回正确的结果索引 注意并不是返回结果数值 算法从头到尾都是对索引操作
}
相关推荐
Frostnova丶2 小时前
【算法笔记】数学知识
笔记·算法
吴可可1233 小时前
AutoCAD 2016与2014二次开发关键差异
算法
老毛肚3 小时前
jeecgboot vue TS & 模板化 04
前端·javascript·vue.js
雨白4 小时前
哈希:以时间换空间的算法实战
算法
Cosolar4 小时前
LlamaIndex 文档解析与分块策略深度解析
人工智能·面试·架构
San813_LDD6 小时前
[数据结构]LeetCode学习
数据结构·算法·图论
x138702859576 小时前
c语言排雷游戏(基础版9*9)
c语言·算法·游戏
kyriewen6 小时前
我读了一遍 Babel 编译后的 async/await,终于搞懂了它的原理(附 20 行手写实现)
前端·javascript·面试
sheeta19987 小时前
LeetCode 每日一题笔记 日期:2026.06.06 题目:2196. 根据描述创建二叉树
笔记·算法·leetcode