第十二节:vue3 Diff 算法 最长递增子序列

448 阅读4分钟

最终的结果是索引

最长递增子序列 不需要连续只要是增加的就可以

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】以序列中最后一个值对应的索引向前追溯 最后一项肯定是正确的 image.png
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--; // 跳过不需要移动的元素, 为了减少移动操作 需要这个最长递增子序列算法  
        }
    }
}