vue diff 前后缀+最长递增子序列算法

文章目录

  • 查找相同前后缀
    • 通过前后缀位置信息新增节点
    • 通过前后缀位置信息删除节点
  • 中间部份 diff
    • 判断节点是否需要移动
    • 删除节点
      • 删除未查找到的节点
      • 删除多余节点
    • 移动和新增节点
      • 最长递增子序列
    • 求解最长递增子序列位置信息

查找相同前后缀

在这里插入图片描述

  • 如上图所示,新旧 children 拥有相同的前缀节点和后缀节点
  • 对于前缀节点,我们可以建立一个索引,指向新旧 children 中的第一个节点,并逐步向后遍历,直到遇到两个拥有不同 key 值的节点为止
// 更新相同的前缀节点
// j 为指向新旧 children 中第一个节点的索引
let j = 0
let prevVNode = prevChildren[j]
let nextVNode = nextChildren[j]
// while 循环向后遍历,直到遇到拥有不同 key 值的节点为止
while (prevVNode.key === nextVNode.key) {// 调用 patch 函数更新patch(prevVNode, nextVNode, container)j++prevVNode = prevChildren[j]nextVNode = nextChildren[j]
}
  • 对于相同的后缀节点,由于新旧 children 中节点的数量可能不同,所以我们需要两个索引分别指向新旧 children 的最后一个节点,并逐步向前遍历,直到遇到两个拥有不同 key 值的节点为止
// 更新相同的后缀节点// 指向旧 children 最后一个节点的索引
let prevEnd = prevChildren.length - 1
// 指向新 children 最后一个节点的索引
let nextEnd = nextChildren.length - 1prevVNode = prevChildren[prevEnd]
nextVNode = nextChildren[nextEnd]// while 循环向前遍历,直到遇到拥有不同 key 值的节点为止
while (prevVNode.key === nextVNode.key) {// 调用 patch 函数更新patch(prevVNode, nextVNode, container)prevEnd--nextEnd--prevVNode = prevChildren[prevEnd]nextVNode = nextChildren[nextEnd]
}

通过前后缀位置信息新增节点

// 前缀节点终止位置
j: 1
// 后缀节点终止位置
prevEnd: 0
nextEnd: 1
  • 发现 j > prevEnd 并且 j <= nextEnd,这说明当新旧 children 中相同的前缀和后缀被更新之后,旧 children 中的节点已经被更新完毕了,而新 children 中仍然有剩余节点,通过上图可以发现,新 children 中的 li-d 节点,就是这个剩余的节点。实际上新 children 中位于 j 到 nextEnd 之间的所有节点都应该是新插入的节点:
    在这里插入图片描述
// 满足条件,则说明从 j -> nextEnd 之间的节点应作为新节点插入
if (j > prevEnd && j <= nextEnd) {// 所有新节点应该插入到位于 nextPos 位置的节点的前面const nextPos = nextEnd + 1const refNode =nextPos < nextChildren.length ? nextChildren[nextPos].el : null// 采用 while 循环,调用 mount 函数挂载节点while (j <= nextEnd) {mount(nextChildren[j++], container, false, refNode)}
}

通过前后缀位置信息删除节点

在这里插入图片描述

  • 在这个案例中,当“去掉”相同的前缀和后缀之后,三个索引的值为:
j: 1
prevEnd: 1
nextEnd: 0
  • 这时条件 j > nextEnd 并且 j <= prevEnd 成立,通过上图可以很容的发现,旧 children 中的 li-b 节点应该被移除,实际上更加通用的规则应该是:在旧 children 中有位于索引 j 到 prevEnd 之间的节点,都应该被移除
if (j > prevEnd && j <= nextEnd) {// j -> nextEnd 之间的节点应该被添加const nextPos = nextEnd + 1const refNode =nextPos < nextChildren.length ? nextChildren[nextPos].el : nullwhile (j <= nextEnd) {mount(nextChildren[j++], container, false, refNode)}
} else if (j > nextEnd) {// j -> prevEnd 之间的节点应该被移除while (j <= prevEnd) {container.removeChild(prevChildren[j++].el)}
}
  • 假设在第一个 while 循环结束之后,索引 j 的值已经大于 prevEnd 或 nextEnd,那么还有必须执行第二个 while 循环吗?答案是没有必要,这是因为一旦索引 j 大于 prevEnd 则说明旧 children 中的所有节点都已经参与了 patch,类似的,如果索引 j 大于 nextEnd 则说明新 children 中的所有节点都已经参与了 patch,这时当然没有必要再执行后续的操作了。
while (prevVNode.key === nextVNode.key) {patch(prevVNode, nextVNode, container)j++if (j > prevEnd || j > nextEnd) {break;}prevVNode = prevChildren[j]nextVNode = nextChildren[j]
}
// 更新相同的后缀节点
prevVNode = prevChildren[prevEnd]
nextVNode = nextChildren[nextEnd]
while (prevVNode.key === nextVNode.key) {patch(prevVNode, nextVNode, container)prevEnd--nextEnd--if (j > prevEnd || j > nextEnd) {break outer}prevVNode = prevChildren[prevEnd]nextVNode = nextChildren[nextEnd]
}if(!(j > prevEnd && j>prevEnd)){// 满足条件,则说明从 j -> nextEnd 之间的节点应作为新节点插入if (j > prevEnd && j <= nextEnd) {// j -> nextEnd 之间的节点应该被添加// 省略...} else if (j > nextEnd) {// j -> prevEnd 之间的节点应该被移除// 省略...}
}

中间部份 diff

在这里插入图片描述

  • 首先,我们需要构造一个数组 source,该数组的长度等于新 children 在经过预处理之后剩余未处理节点的数量,初始化该数组中每个元素的初始值为 -1
  • 实际上 source 数组将用来存储新 children 中的节点在旧 children 中的位置,后面将会使用它计算出一个最长递增子序列,并用于 DOM 移动
    在这里插入图片描述
  • 再建立一个 Index Map 中的键是节点的 key,值是节点在新 children 中的位置索引,用空间来换取时间上的优化
    在这里插入图片描述
if (j > prevEnd && j <= nextEnd) {// 省略...
} else if (j > nextEnd) {// 省略...
} else {// 构造 source 数组const nextLeft = nextEnd - j + 1  // 新 children 中剩余未处理节点的数量const source = []for (let i = 0; i < nextLeft; i++) {source.push(-1)}const prevStart = jconst nextStart = jlet moved = falselet pos = 0// 构建索引表const keyIndex = {}for(let i = nextStart; i <= nextEnd; i++) {keyIndex[nextChildren[i].key] = i}// 遍历旧 children 的剩余未处理节点for(let i = prevStart; i <= prevEnd; i++) {prevVNode = prevChildren[i]// 通过索引表快速找到新 children 中具有相同 key 的节点的位置const k = keyIndex[prevVNode.key]if (typeof k !== 'undefined') {nextVNode = nextChildren[k]// patch 更新patch(prevVNode, nextVNode, container)// 更新 source 数组source[k - nextStart] = i// 判断是否需要移动if (k < pos) {moved = true} else {pos = k}} else {// 没找到}}}

判断节点是否需要移动

  • 在上一步代码中,遍历旧 children 的剩余未处理节点,通过索引表快速找到新 children 中具有相同 key 的节点的位置
  • 如果新旧节点位置没有变化,那么位置信息应该是相同的,否则,新节点索引表信息为[1,2,3,4],如果映射成旧节点为[1,2,4,3],说明旧节点的最后一个位置和前面的位置互换了,说明节点移动了
const prevStart = j
const nextStart = j
let moved = false
let pos = 0
// 构建索引表
const keyIndex = {}
for(let i = nextStart; i <= nextEnd; i++) {keyIndex[nextChildren[i].key] = i
}
// 遍历旧 children 的剩余未处理节点
for(let i = prevStart; i <= prevEnd; i++) {prevVNode = prevChildren[i]// 通过索引表快速找到新 children 中具有相同 key 的节点的位置const k = keyIndex[prevVNode.key]if (typeof k !== 'undefined') {nextVNode = nextChildren[k]// patch 更新patch(prevVNode, nextVNode, container)// 更新 source 数组source[k - nextStart] = i// 判断是否需要移动if (k < pos) {moved = true} else {pos = k}} else {// 没找到}
}

删除节点

删除未查找到的节点

  • 拿旧 children 中的节点尝试去新 children 中寻找具有相同 key 值的节点,但并非总是能够找得到,当 k === ‘undefined’ 时,说明该节点在新 children 中已经不存在了,这时我们应该将其移除
// 遍历旧 children 的剩余未处理节点
for(let i = prevStart; i <= prevEnd; i++) {prevVNode = prevChildren[i]// 通过索引表快速找到新 children 中具有相同 key 的节点的位置const k = keyIndex[prevVNode.key]if (typeof k !== 'undefined') {// 省略...} else {// 没找到,说明旧节点在新 children 中已经不存在了,应该移除container.removeChild(prevVNode.el)}
}

删除多余节点

  • 旧 children 中已经更新过的节点数量应该小于新 children 中需要更新的节点数量,一旦更新过的节点数量超过了新 children 中需要更新的节点数量,则说明该节点是多余的节点,我们也应该将其移除
const nextLeft = nextEnd - j + 1  // 新 children 中剩余未处理节点的数量
let patched = 0
// 遍历旧 children 的剩余未处理节点
for (let i = prevStart; i <= prevEnd; i++) {prevVNode = prevChildren[i]if (patched < nextLeft) {// 通过索引表快速找到新 children 中具有相同 key 的节点的位置const k = keyIndex[prevVNode.key]if (typeof k !== 'undefined') {nextVNode = nextChildren[k]// patch 更新patch(prevVNode, nextVNode, container)patched++// 更新 source 数组source[k - nextStart] = i// 判断是否需要移动if (k < pos) {moved = true} else {pos = k}} else {// 没找到,说明旧节点在新 children 中已经不存在了,应该移除container.removeChild(prevVNode.el)}} else {// 多余的节点,应该移除container.removeChild(prevVNode.el)}
}

移动和新增节点

在这里插入图片描述

最长递增子序列

  • source 数组的值为 [2, 3, 1, -1],很显然最长递增子序列应该是 [ 2, 3 ],换算成位置信息是 [ 0, 1 ]
  • 而最长递增子序列是 [ 0, 1 ] 这告诉我们:新 children 的剩余未处理节点中,位于位置 0 和位置 1 的节点的先后关系与他们在旧 children 中的先后关系相同。或者我们可以理解为位于位置 0 和位置 1 的节点是不需要被移动的节点
  • 只有 li-b 节点和 li-g 节点是可能被移动的节点,但是我们发现与 li-g 节点位置对应的 source 数组元素的值为 -1,这说明 li-g 节点应该作为全新的节点被挂载,所以只有 li-b 节点需要被移动
  • 使用 for 循环从后向前遍历新 children 中剩余未处理的子节点
  • 这里的技巧在于 i 的值的范围是 0 到 nextLeft - 1,这实际上就等价于我们对剩余节点进行了重新编号。接着判断当前节点的位置索引值 i 是否与子序列中位于 j 位置的值相等,如果不相等,则说明该节点需要被移动;如果相等则说明该节点不需要被移动,并且会让 j 指向下一个位置
  • 节点需要被怎么移动呢?找到 li-b 节点的后一个节点(li-g),将其插入到 li-g 节点的前面即可
if (moved || source.indexOf(-1)!==-1) {// 根据 source 数组计算一个最长递增子序列:const seq = lis(sources) // [ 0, 1 ],代表的是最长递增子序列中的各个元素在 source 数组中的位置索引let j = seq.length - 1// 从后向前遍历新 children 中的剩余未处理节点for (let i = nextLeft - 1; i >= 0; i--) {if (source[i] === -1) {// 作为全新的节点挂载// 该节点在新 children 中的真实位置索引const pos = i + nextStartconst nextVNode = nextChildren[pos]// 该节点下一个节点的位置索引const nextPos = pos + 1// 挂载mount(nextVNode,container,false,nextPos < nextChildren.length? nextChildren[nextPos].el: null)} else if (i !== seq[j]) {// 说明该节点需要移动// 该节点在新 children 中的真实位置索引const pos = i + nextStartconst nextVNode = nextChildren[pos]// 该节点下一个节点的位置索引const nextPos = pos + 1// 移动container.insertBefore(nextVNode.el,nextPos < nextChildren.length? nextChildren[nextPos].el: null)} else {// 当 i === seq[j] 时,说明该位置的节点不需要移动// 并让 j 指向下一个位置j--}}
}}

求解最长递增子序列位置信息

[ 0, 8, 4, 12, 2, 10]
// 最长递增子序列长度
[ 3, 2, 2, 1, 2, 1]
  • 如上可知最长子序列长度为 3,从右往左遍历查找子序列长度为2位置,接着查找为1的位置,这样就能找到所有的位置信息
// 所有的最长递增子序列
[ 0, 8, 12 ]
[ 0, 8, 10 ]
[ 0, 4, 12 ]
[ 0, 4, 10 ]
[ 0, 2, 10 ]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/78401.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023年08月在线IDE流行度最新排名

点击查看最新在线IDE流行度最新排名&#xff08;每月更新&#xff09; 2023年08月在线IDE流行度最新排名 TOP 在线IDE排名是通过分析在线ide名称在谷歌上被搜索的频率而创建的 在线IDE被搜索的次数越多&#xff0c;人们就会认为它越受欢迎。原始数据来自谷歌Trends 如果您相…

LeetCode257. 二叉树的所有路径

257. 二叉树的所有路径 文章目录 257. 二叉树的所有路径一、题目二、题解方法一&#xff1a;深度优先搜索递归方法二&#xff1a;迭代 一、题目 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点…

【逗老师的PMP学习笔记】5、项目范围管理

目录 一、规划范围管理二、收集需求1、【关键工具】头脑风暴2、【关键工具】访谈3、【关键工具】问卷调查4、【关键工具】标杆对照&#xff08;对标&#xff09;5、【关键工具】亲和图和思维导图6、【关键工具】质量功能展开7、【关键工具】用户故事8、【关键工具】原型法9、【…

python制作小程序制作流程,用python编写一个小程序

这篇文章主要介绍了python制作小程序代码宠物运输&#xff0c;具有一定借鉴价值&#xff0c;需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获&#xff0c;下面让小编带着大家一起了解一下。 1 importtkinter2 importtkinter.messagebox3 importmath4 classJSQ:5 6 7 d…

Pytest简介及jenkins集成

一、pytest介绍 pytest介绍 - unittest\nose pytest&#xff1a;基于unittest之上的单元测试框架 自动发现测试模块和测试方法 断言使用assert表达式即可 可以设置测试会话级、模块级、类级、函数级的fixtures 数据准备 清理工作 unittest&#xff1a;setUp、teardown、…

6.6.tensorRT高级(1)-mmdetection框架下yolox模型导出并推理

目录 前言1. yolox导出2. yolox推理3. 补充知识3.1 知识点3.2 mmdetection 总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习…

从 GPU 到 ChatGPT,一文带你理清GPU/CPU/AI/NLP/GPT之间的千丝万缕【建议收藏】

目录 硬件 GPU 什么是 GPU&#xff1f; GPU 是如何工作的&#xff1f; GPU 和 CPU 的区别 GPU 厂商 海外头部 GPU 厂商&#xff1a; 国内 GPU 厂商&#xff1a; nvidia 的产品矩阵 AI 什么是人工智能 (Artificial Intelligence-AI)&#xff1f; 人工智能细分领域 …

ROS添加发布者和订阅者机制实现

一. ROS的节点和包 ✨Node&#xff1a; ROS的基本单位&#xff0c;实现某个功能的节点。比如实现超声波传感器就是一个节点&#xff0c;雷达传感器就可以是一个节点 ✨Package&#xff1a; 多个有联系的节点组成的单位&#xff0c;比如你要控制无人机姿态&#xff0c;可能需要…

Crowd-Robot Interaction 论文阅读

论文信息 题目&#xff1a;Crowd-Robot Interaction:Crowd-aware Robot Navigation with Attention-based Deep Reinforcement Learning 作者&#xff1a;Changan Chen, Y uejiang Liu 代码地址&#xff1a;https://github.com/vita-epfl/CrowdNav 来源&#xff1a;arXiv 时间…

ES新特性部分

文章目录 Symbol创建使用拓展对象的方法直接添加 控制对象控制类型检查控制是否展开 遍历迭代器自定义遍历 生成器函数&#xff08;实现异步编程&#xff09;解决回调地狱 Promise连续读文件 SetMap类静态属性继承ES5ES6 GET与SET 数值Object方法模块化导入另一种导入 babel ES…

2023华数杯数学建模竞赛选题建议

提示&#xff1a;DS C君认为的难度&#xff1a;C<B<A&#xff0c;开放度&#xff1a;B<A<C 。 A题&#xff1a;隔热材料的结构优化控制研究 A题是数模类赛事很常见的物理类赛题&#xff0c;需要学习不少相关知识。 其中第一问需要建立平纹织物整体热导率与单根纤…

力扣 -- 467. 环绕字符串中唯一的子字符串

一、题目 二、解题步骤 下面是用动态规划的思想解决这道题的过程&#xff0c;相信各位小伙伴都能看懂并且掌握这道经典的动规题目滴。 三、参考代码 class Solution { public:int findSubstringInWraproundString(string s) {int ns.size();vector<int> dp(n,1);int re…

Android 刷新与显示

目录 屏幕显示原理&#xff1a; 显示刷新的过程 VSYNC机制具体实现 小结&#xff1a; 屏幕显示原理&#xff1a; 过程描述&#xff1a; 应用向系统服务申请buffer 系统服务返回一个buffer给应用 应用开始绘制&#xff0c;绘制完成就提交buffer&#xff0c;系统服务把buffer数据…

移动开发最佳实践:为 Android 和 iOS 构建成功应用的策略

您可以将本文作为指南&#xff0c;确保您的应用程序符合可行的最重要标准。请注意&#xff0c;这份清单远非详尽无遗&#xff1b;您可以加以利用&#xff0c;并添加一些自己的见解。 了解您的目标受众 要制作一个成功的应用程序&#xff0c;你需要了解你是为谁制作的。从创建…

接口自动化测试Mock Get和Post请求

Mock可以模拟一个http接口的后台响应&#xff0c;可以模拟request&#xff0c;response 下载 moco-runner-0.11.0-standalone.jar 下载链接: https://pan.baidu.com/s/1bmFzvJPRnDlQ-cmuJ_3iRg 提取码: kpjv 确保安装了jdk,cmd下可以运行java -version 一、模拟不带参的get请求…

AQL品质抽样标准

AQL抽样标准 - 百度文库 Acceptance Quality Limit 接收质量限的缩写&#xff0c;即当一个连续系列批被提交验收时&#xff0c;可允许的最差过程平均质量水平。 AQL普遍应用于各行业产品的质量检验&#xff0c;不同的AQL标准应用于不同物质的检验上。在AQL 抽样时&#xff0c;…

VUE框架:vue2转vue3全面细节总结(3)路由组件传参

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人_python人工智能视觉&#xff08;opencv&#xff09;从入门到实战,前端,微信小程序-CSDN博客 最新的uniapp毕业设计专栏也放在下方了&#xff1a; https://blog.csdn.net/lbcy…

idea添加翻译插件并配置有道翻译

1、安装Translation插件 2、 创建有道云应用 有道智云控制台 3、设置idea 4、效果&#xff08;选中文本右键翻译&#xff0c;默认快捷键CtrlShiftY&#xff09;

图文演示:如何三分钟极速搭建一个元宇宙3D虚拟展厅

引言&#xff1a; 元宇宙3D虚拟展厅时代已经来临。元宇宙是一个虚拟的、立体的数字空间&#xff0c;可以让用户沉浸在其中进行交互操作&#xff0c;并体验无限可能。如何快速搭建一个属于自己的虚拟展厅则受到越来越多人的关注。 一&#xff0e;虚拟展厅类型 1.党建展馆 实现…

Linux 匿名页的生命周期

目录 匿名页的生成 匿名页生成时的状态 do_anonymous_page缺页中断源码 从匿名页加入Inactive lru引出 一个非常重要内核patch 匿名页何时回收 本文以Linux5.9源码讲述 匿名页的生成 用户空间malloc/mmap(非映射文件时&#xff09;来分配内存&#xff0c;在内核空间发生…