最终的结果是
索引
最长递增子序列
不需要连续
只要是增加的就可以Vue3 采用最长递增子序列,求解不需要移动的元素有哪些
newIndexToOldMapIndex = [5, 3, 4, 0]
说明 索引1到2 为最长递增子序列 子序列内的不需要动 只需要插入0和移动5最优情况:默认递增都不需要动
步骤
- 1、找到索引先放进去(按照默认最优的情况处理)
- 2、二分查找 在结果集中找到比当前值大的 用当前值的索引将其替换掉
- 3、前驱节点追溯 (核心点:记录 “要替换的值的 前一个的索引”)找到真正的最长递增子序列
找到更有潜力:
- 1、当前这一项比最后一项大则直接放到尾部
- 2、如果当前这一项比最后一项小。需要在序列中通过二分查找、找到比当前这一项大的一项 用当前这一项替换掉找到的
// 3 2 8 9 5 6 7 11 15 -> 个数
// 3
// 2
// 2 8 // 比前边的大
// 2 8 9
// 2 5 9 //在前边找到第一个比5大的8直接 替换掉
// 2 5 6 //在前边找到第一个比6大的9直接 替换掉
// 2 5 6 7 11 15 // 个数为6
1、找到索引先放进去(按照默认最优的情况)
// 【1】、找到索引先放进去
function getSequence(arr) { // 最终的结果是索引
const len = arr.length;
const result = [0]; // 以默认第0个位基准
let resultLastIndex;
for (let i = 0; i < len; i++) {
const arrI = arr[i]; // 获取数组中的每一项
// 但是 0 在我们diff算法中代表新增 所以要忽略
if (arrI !== 0) {
// 找到序列中的最后一项
resultLastIndex = result[result.length - 1];
// 取出序列中的最后一项对应的值 与当前项的值相比较
if (arr[resultLastIndex] < arrI) {
result.push(i); // 记录索引
continue
}
}
}
return result
}
// 第一步结束结果:console.log(getSequence([1,2,3,4,5,6,7,0])) // [0,1,2,3,4,5,6]
2、二分查找 在结果集中找到比当前值大的 用当前值的索引将其替换掉
预期result是递增序列 采用二分查找是最快的
找到的个数是对的但是 找到的值是不对的
// 【1】、找到索引先放进去
function getSequence(arr) { // 最终的结果是索引
const len = arr.length;
const result = [0]; // 以默认第0个位基准
let resultLastIndex;
for (let i = 0; i < len; i++) {
const arrI = arr[i]; // 获取数组中的每一项
// 但是 0 在我们diff算法中代表新增 所以要忽略
if (arrI !== 0) {
// 找到序列中的最后一项
resultLastIndex = result[result.length - 1];
// 取出序列中的最后一项对应的值 与当前项的值相比较
if (arr[resultLastIndex] < arrI) {
result.push(i); // 记录索引
continue
}
// 第一步结束结果:console.log(getSequence([1,2,3,4,5,6,7,0])) // [0,1,2,3,4,5,6]
// 【2】、二分查找 在结果集中找到比当前值大的 用当前值的索引将其替换掉
let start = 0;
let end = result.length - 1; // 二分查找 前后索引
while (start < end) { // 最终停止时 start = end
let middle = ((start + end) / 2) | 0; // 向下取整
// 拿result中间值 和 当前项 比较
if (arr[result[middle]] < arrI) { // 如果 比arrI小 就找后半段
start = middle + 1;
} else {// 如果 比arrI大 就找前半段
end = middle;
}
}
if (arrI < arr[result[end]]) { // 当前项小于中间值就替换掉大的那一项
result[end] = i; // 替换
}
// 第二步结束结果:console.log(getSequence([2,3,1,5,6,8,7,9,4] ))
// 找到的索引:[ 2, 1, 8, 4, 6, 7 ] 找到的值:[1,3,4,6,7,9] 个数: 6
// 个数是对的但是 找到的值是不对的应该是 [2,3,5,6,7,9]
}
}
return result
}
3、前驱节点追溯
核心点:记录 “要替换的值的 前一个的索引” 找到真正的最长递增子序列
- 【3.1】初始化, 里面内容无所谓 和 原本的数组长度相同就行 用来存放索引
- 【3.2】默认追加,标记当前一项(也就是最后一项)前一个对应的索引
- 假设有:[2,3,1,5,6,8,7,9,4] 为最新序列 -> 按照上述结果得出的结论为:[ 2, 1, 8, 4, 6, 7 ]
- 【3.3】替换并记录前驱节点,核心点:记录 “要替换的值的 前一个的索引”
- 【3.4】以序列中最后一个值对应的索引向前追溯 最后一项肯定是正确的
export function getSequence(arr) { // 最终的结果是索引
const len = arr.length;
const result = [0]; // 保存最长递增子序列的索引 以默认第0个位基准
// 【3.1】初始化, 里面内容无所谓 和 原本的数组长度相同就行 用来存放索引
const p = new Array(len).fill(0);
let resultLastIndex;
for (let i = 0; i < len; i++) {
// 【1】、找到索引先放进去
const arrI = arr[i]; // 获取数组中的每一项,
// 0 在我们diff算法中代表新增 所以要忽略
if (arrI !== 0) {
resultLastIndex = result[result.length - 1]; // 找到序列中的最后一项
if (arr[resultLastIndex] < arrI) { // 取出序列中的最后一项对应的值 与当前项的值相比较
// 【3.2】默认追加,标记当前一项(也就是最后一项)前一个对应的索引
// p[i]就是当前一项
p[i] = resultLastIndex;
result.push(i); // 记录索引
continue
}
// 第一步结束结果:console.log(getSequence([1,2,3,4,5,6,7,0])) // [0,1,2,3,4,5,6]
// 【2】、二分查找 在结果集中找到比当前值大的 用当前值的索引将其替换掉
// result是递增序列 采用二分查找是最快的
let start = 0;
let end = result.length - 1; // 二分查找 前后索引
while (start < end) { // 最终 start = end 就停止了
let middle = ((start + end) / 2) | 0; // 向下取整
// 拿result中间值 和 当前项 比较
if (arr[result[middle]] < arrI) { // 找比arrI大的值 或者等于arrI
start = middle + 1;
} else {
end = middle;
}
}
if (arrI < arr[result[end]]) { // 当前项小于中间值就替换掉大的那一项
if (end > 0) { // end > 0 才需要替换 p[i]就是当前一项
p[i] = result[end - 1]; // 【3.3】替换并记录前驱节点,核心点:记录 “要替换的值的 前一个的索引”
}
result[end] = i;
}
// 第二步结束结果:console.log(getSequence([2,3,1,5,6,8,7,9,4] ))
// 找到的索引:[ 2, 1, 8, 4, 6, 7 ] 找到的值:[1,3,4,6,7,9] 个数: 6
// 个数是对的但是 找到的值是不对的应该是 [2,3,5,6,7,9]
}
}
// 3.3得到的结果 [0,0,undefined,1,3,4,4,6,1]
// 【3.4】以序列中最后一个值对应的索引向前追溯 最后一项肯定是正确的
//找到的索引 [7,6,4,3,1,0]
// 找到的值 [9,7,6,5,3,2] 和要找到的刚好相反
let i = result.length // 总长度
let last = result[i - 1] // 找到了最后一项
while (i-- > 0) { // 倒序追溯 根据前驱节点一个个向前查找
result[i] = last // 最后一项肯定是正确的
last = p[last] // 重新赋值
}
// console.log(getSequence([2,3,1,5,6,8,7,9,4] )) [0,1,3,4,6,7]
return result
}
在diff算法中的运用
入口为 第十一节:vue3 Diff 算法 乱序比对 的
patchKeyedChildren
函数的插入节点部分
// 1、获取最长递增子序列
let increasingNewIndexSequence = getSequence(newIndexToOldMapIndex);
console.log(increasingNewIndexSequence) // [1,2]
let j = increasingNewIndexSequence.length - 1; // 取出最后一个人的索引
for (let i = toBePatched - 1; i >= 0; i--) {
let currentIndex = i + s2; // 找到h的索引
let child = c2[currentIndex]; // 找到h对应的节点
let anchor = currentIndex + 1 < c2.length ? c2[currentIndex + 1].el : null; // 第一次插入h 后 h是一个虚拟节点,同时插入后 虚拟节点会
if (newIndexToOldMapIndex[i] == 0) { // 如果自己是0说明没有被patch过
patch(null, child, container, anchor)
} else {)
if (i != increasingNewIndexSequence[j]) { // 不在最长递增子序列中
// 插入: 根据参照物 将节点直接移动过去
hostInsert(nextChild.el, el, anchor);
} else {
console.log('不做插入')
j--; // 跳过不需要移动的元素, 为了减少移动操作 需要这个最长递增子序列算法
}
}
}