vue和react的算法实现

1.前端算法

前端中的算法与数据结构

全排序(数组、链表、二叉树、堆)

偏排序(数组、链表、堆)

查找与搜索(二叉树、平衡二叉树(包括红黑树)、哈希表)

动态规划(数组、链表、堆、二叉树)

React中的算法与数据结构:深度优先搜索、递归、动态规划、散列表、 数组、链表、二叉树、、栈等

Vue中的算法与数据结构:动态规划、递归、二分查找、散列表、LRU(最近最少使用) 、数组等

Vue3 种 diff 使用了 最长递增子序列实现。

使用这个算法主要用处是,当diff 算法已经比较剩下两个数组时候,原来[1,2,3,4,5,6]. 调整后 变成 [1,3,4,2,6,5] 这时候要找到最优的移动方案。


image.png
  • 最差的方案就是每个元素都更新。
  • 最优的移动方案就是 只移动 2和5 ,2移动到4后,5 移动到6后。
    那么要只移动 2和5。就要确定 1,3,4,6 是固定不变的,也称为当前数组的最长的递增子序列。

因为原来就是使用数组为下标,所以已经是从小到大排列。只是局部乱了。所以找出局部错乱的是较优的策略。

https://leetcode.cn/problems/longest-increasing-subsequence/
leetcode只查询长度,当前vue3需要确定的数组 代码如下

var getSequence1 = function (nums) {
    let result = []
    for (let i = 0; i < nums.length; i++) {
      let last = nums[result[result.length - 1]],
        current = nums[i]
      if (current > last || last === undefined) {
        // 当前项大于最后一项
        result.push(i)
      } else {
        // 当前项小于最后一项,二分查找+替换
        let start = 0,
          end = result.length - 1,
          middle
        while (start < end) {
          middle = Math.floor((start + end) / 2)
          if (nums[result[middle]] > current) {
            end = middle
          } else {
            start = middle + 1
          }
        }
        result[start] = i
      }
    }
    return result
  }
  console.log( getSequence1([10,1,2,5,3,7,101,18]))

贪心算法

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。

二分查找

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

二分查找之所以快是因为它只需检查很少几个条目(相对于数组的大小)就能够找到目标元素,或者是确认目标元素不存在。

const searchInsert = (nums, target) => {
  let low = 0,
    high = nums.length - 1,
    mid;
  while (low <= high) {
    mid = (low + high) >> 1;
    if (target < nums[mid]) {
      high = mid - 1;
    } else if (target > nums[mid]) {
      low = mid + 1;
    } else {
      return mid;
    }
  }
};

实现VUE的VDOM DIFF

// vdom 虚拟dom
// old 老节点
// new 新节点
// old array  a b c d e f g
// new array  a b e c d h f g

// mountElement 新增元素 h
// patch  复用元素 a b c d e f g
// unmount 删除元素
// todo
// move 元素移动 ?

exports.diffArray = (c1, c2, { mountElement, patch, unmount, move }) => {
  function isSameVnodeType(n1, n2) {
    return n1.key === n2.key; // && n1.type === n2.type;
  }

  let l1 = c1.length;
  let l2 = c2.length;
  let i = 0;
  let e1 = l1 - 1;
  let e2 = l2 - 1;

  // *1 从左边往右,如果元素可以复用就继续往右边,否则就停止循环
  while (i <= e1 && i <= e2) {
    const n1 = c1[i];
    const n2 = c2[i];
    if (isSameVnodeType(n1, n2)) {
      patch(n1.key);
    } else {
      break;
    }
    i++;
  }

  // *2 从右边往左,如果元素可以复用就继续往左边,否则就停止循环
  while (i <= e1 && i <= e2) {
    const n1 = c1[e1];
    const n2 = c2[e2];
    if (isSameVnodeType(n1, n2)) {
      patch(n1.key);
    } else {
      break;
    }
    e1--;
    e2--;
  }

  // *3.1 老节点没了,新节点还有
  if (i > e1) {
    if (i <= e2) {
      while (i <= e2) {
        const n2 = c2[i];
        mountElement(n2.key);
        i++;
      }
    }
  }
  // *3.2 老节点还有,新节点没了
  else if (i > e2) {
    if (i <= e1) {
      while (i <= e1) {
        const n1 = c1[i];
        unmount(n1.key);
        i++;
      }
    }
  } else {
    // *4 新老节点都还有,但是顺序不稳定,有点乱

    // * 4.1 把新元素做成Map,key:value(index)
    const s1 = i;
    const s2 = i;

    const keyToNewIndexMap = new Map();
    for (i = s2; i <= e2; i++) {
      const nextChild = c2[i];
      keyToNewIndexMap.set(nextChild.key, i);
    }

    // *4.2 记录一下新老元素的相对下标
    const toBePatched = e2 - s2 + 1;
    const newIndexToOldIndexMap = new Array(toBePatched);
    // 数组的下标记录的是新元素的相对下标,
    // value初始值是0
    // todo 在4.3中做一件事:一旦元素可以被复用,value值更新成老元素的下标+1
    // 数组的值如果还是0, 证明这个值在新元素中是要mount的
    for (i = 0; i < toBePatched; i++) {
      newIndexToOldIndexMap[i] = 0;
    }

    // * 4.3 去遍历老元素 (patch、unmount)
    // old sdasjkjkkklll
    // new ds
    // 记录新节点要多少个还没处理
    let patched = 0;

    let moved = false;
    let maxNewIndexSoFar = 0;

    for (i = s1; i <= e1; i++) {
      const prevChild = c1[i];

      if (patched >= toBePatched) {
        unmount(prevChild.key);
        continue;
      }

      const newIndex = keyToNewIndexMap.get(prevChild.key);

      if (newIndex === undefined) {
        // 没有找到要复用它的节点,只能删除
        unmount(prevChild.key);
      } else {
        // 节点要被复用
        //  1 2 3 5 10
        // maxNewIndexSoFar记录队伍最后一个元素的下标
        if (newIndex >= maxNewIndexSoFar) {
          maxNewIndexSoFar = newIndex;
        } else {
          // 插队
          moved = true;
        }

        // newIndex - s2是相对下标
        // i + 1老元素下标+1
        newIndexToOldIndexMap[newIndex - s2] = i + 1;

        patch(prevChild.key);
        patched++;
      }
    }

    // * 4.4 去遍新元素 mount、move
    // e2 -> i 下标遍历
    // toBePatched -> 0 相对下标
    // [1, 2];

    const increasingNewIndexSequence = moved
      ? getSequence(newIndexToOldIndexMap)
      : [];

    console.log("increasingNewIndexSequence", increasingNewIndexSequence); //sy-log
    let lastIndex = increasingNewIndexSequence.length - 1;
    for (i = toBePatched - 1; i >= 0; i--) {
      const nextChild = c2[s2 + i];

      // 判断节点是mount还是move
      if (newIndexToOldIndexMap[i] === 0) {
        // nextChild要新增
        mountElement(nextChild.key);
      } else {
        // 可能move
        // i 是新元素的相对下标
        // lastIndex是LIS的相对下标
        if (lastIndex < 0 || i !== increasingNewIndexSequence[lastIndex]) {
          console.log("ooo", nextChild.key); //sy-log
          move(nextChild.key);
        } else {
          lastIndex--;
        }
      }
    }
  }

  // function getSequence() {
  //   return [1, 2];
  // }

  //  1 2 5 [2]
  function getSequence(arr) {
    // return [1, 2];

    // 返回的是LIS的路径
    const lis = [0];

    const len = arr.length;

    const record = arr.slice();

    for (let i = 0; i < len; i++) {
      const arrI = arr[i];
      if (arrI !== 0) {
        const last = lis[lis.length - 1];
        if (arr[last] < arrI) {
          // 新来的元素比lis最后一个元素大,直接放到lis最后
          //  1 3 5 10
          record[i] = last;
          lis.push(i);
          continue;
        }

        // 二分替换
        let left = 0,
          right = lis.length - 1;
        while (left < right) {
          const mid = (left + right) >> 1;
          if (arr[lis[mid]] < arrI) {
            // 在右边
            left = mid + 1;
          } else {
            right = mid;
          }
        }
        // 从lis里找比arrI大的最小的元素,并且替换
        if (arrI < arr[lis[left]]) {
          if (left > 0) {
            record[i] = lis[left - 1];
          }
          lis[left] = i;
        }
      }
    }

    let i = lis.length;
    let last = lis[i - 1];

    while (i-- > 0) {
      lis[i] = last;
      last = record[last];
    }

    console.log("l-----is", lis); //sy-log

    return lis;
  }
};

测试用例 vue-diff.spec.js

describe("数组Diff", () => {
    it("1. 左边查找", () => {
      const mountElement = jest.fn();
      const patch = jest.fn();
      const unmount = jest.fn();
      const move = jest.fn();
      const { diffArray } = require("../vue-diff");
      diffArray(
        [{ key: "a" }, { key: "b" }, { key: "c" }],
        [{ key: "a" }, { key: "b" }, { key: "d" }, { key: "e" }],
        {
          mountElement,
          patch,
          unmount,
          move,
        }
      );
      // 第一次调用次数
      expect(patch.mock.calls.length).toBe(2);
      // 第一次调用的第一个参数
      expect(patch.mock.calls[0][0]).toBe("a");
      expect(patch.mock.calls[1][0]).toBe("b");
    });
    it("2. 右边边查找", () => {
      const mountElement = jest.fn();
      const patch = jest.fn();
      const unmount = jest.fn();
      const move = jest.fn();
      const { diffArray } = require("../vue-diff");
      diffArray(
        [{ key: "a" }, { key: "b" }, { key: "c" }],
        [{ key: "d" }, { key: "e" }, { key: "b" }, { key: "c" }],
        {
          mountElement,
          patch,
          unmount,
          move,
        }
      );
      expect(patch.mock.calls.length).toBe(2);
      expect(patch.mock.calls[0][0]).toBe("c");
      expect(patch.mock.calls[1][0]).toBe("b");
    });
    it("3. 老节点没了,新节点还有", () => {
      const mountElement = jest.fn();
      const patch = jest.fn();
      const unmount = jest.fn();
      const move = jest.fn();
      const { diffArray } = require("../vue-diff");
      diffArray(
        [{ key: "a" }, { key: "b" }],
        [{ key: "a" }, { key: "b" }, { key: "c" }],
        {
          mountElement,
          patch,
          unmount,
          move,
        }
      );
      expect(patch.mock.calls.length).toBe(2);
      expect(patch.mock.calls[0][0]).toBe("a");
      expect(patch.mock.calls[1][0]).toBe("b");
      expect(mountElement.mock.calls[0][0]).toBe("c");
    });
    it("4. 老节点还有,新节点没了", () => {
      const mountElement = jest.fn();
      const patch = jest.fn();
      const unmount = jest.fn();
      const move = jest.fn();
      const { diffArray } = require("../vue-diff");
      diffArray(
        [{ key: "a" }, { key: "b" }, { key: "c" }],
        [{ key: "a" }, { key: "b" }],
        {
          mountElement,
          patch,
          unmount,
          move,
        }
      );
      // 第一次调用次数
      expect(patch.mock.calls.length).toBe(2);
      // 第一次调用的第一个参数
      expect(patch.mock.calls[0][0]).toBe("a");
      expect(patch.mock.calls[1][0]).toBe("b");
      expect(unmount.mock.calls[0][0]).toBe("c");
    });
    it("5. 新老节点都有,但是顺序不稳定", () => {
      const mountElement = jest.fn();
      const patch = jest.fn();
      const unmount = jest.fn();
      const move = jest.fn();
      const { diffArray } = require("../vue-diff");
      diffArray(
        [
          { key: "a" },
          { key: "b" },
          { key: "c" },
          { key: "d" },
          { key: "e" },
          { key: "f" },
          { key: "g" },
        ],
        [
          { key: "a" },
          { key: "b" },
          { key: "e" },
          { key: "d" },
          { key: "c" },
          { key: "h" },
          { key: "f" },
          { key: "g" },
        ],
        {
          mountElement,
          patch,
          unmount,
          move,
        }
      );
      // 第一次调用次数
      expect(patch.mock.calls.length).toBe(7);
      // 第一次调用的第一个参数
      expect(patch.mock.calls[0][0]).toBe("a");
      expect(patch.mock.calls[1][0]).toBe("b");
      expect(patch.mock.calls[2][0]).toBe("g");
      expect(patch.mock.calls[3][0]).toBe("f");
      expect(patch.mock.calls[4][0]).toBe("c");
      expect(patch.mock.calls[5][0]).toBe("d");
      expect(patch.mock.calls[6][0]).toBe("e");
      expect(unmount.mock.calls.length).toBe(0);
      //                 0 1  2 3 4  5 6
      // [i ... e1 + 1]: a b [c d e] f g
      // [i ... e2 + 1]: a b [e d c h] f g
      //                      4 3 2 0
      //                      [5 4 3 0]
      // e d c
      // e d c
      // todo
      // 1. mount
      expect(mountElement.mock.calls[0][0]).toBe("h");
      // // 2. move
      expect(move.mock.calls[0][0]).toBe("d");
      expect(move.mock.calls[1][0]).toBe("e");
    });
    it("6. 新老节点都有,但是顺序不稳定", () => {
      const mountElement = jest.fn();
      const patch = jest.fn();
      const unmount = jest.fn();
      const move = jest.fn();
      const { diffArray } = require("../vue-diff");
      diffArray(
        [
          { key: "a" },
          { key: "b" },
  
          { key: "c" },
          { key: "d" },
          { key: "e" },
  
          { key: "f" },
          { key: "g" },
        ],
        [
          { key: "a" },
          { key: "b" },
  
          { key: "d1" },
          { key: "e" },
          { key: "c" },
          { key: "d" },
          { key: "h" },
  
          { key: "f" },
          { key: "g" },
        ],
        {
          mountElement,
          patch,
          unmount,
          move,
        }
      );
      // 第一次调用次数
      expect(patch.mock.calls.length).toBe(7);
      // 第一次调用的第一个参数
      expect(patch.mock.calls[0][0]).toBe("a");
      expect(patch.mock.calls[1][0]).toBe("b");
  
      expect(patch.mock.calls[2][0]).toBe("g");
      expect(patch.mock.calls[3][0]).toBe("f");
  
      expect(patch.mock.calls[4][0]).toBe("c");
      expect(patch.mock.calls[5][0]).toBe("d");
      expect(patch.mock.calls[6][0]).toBe("e");
      expect(unmount.mock.calls.length).toBe(0);
      //                 0 1  2 3 4  5 6
      // [i ... e1 + 1]: a b [c d e] f g
      // [i ... e2 + 1]: a b [e c d h] f g
      // 真实下标         0 1  2 3 4 5  6 7
      // 相对下标              0 1 2 3
      // 下标是新元素的相对下标,value是老元素的下标+1
      //                     [5,3,4,0]
      // todo
      // 1. mount
      expect(mountElement.mock.calls[0][0]).toBe("h");
      expect(mountElement.mock.calls[1][0]).toBe("d1");
  
      // 2. move
      expect(move.mock.calls[0][0]).toBe("e");
    });
  });

React调度算法(最小堆)

最小堆参考:
https://juejin.cn/post/7048898549005680647/

排序(冒泡、快排、V8 sort)

代码

const increasingNewIndexSequece = moved
  ? getSequence(newIndexToOldIndexMap)
  : [];
let lastIndex = increasingNewIndexSequece.length - 1;
// 相对下标
for (i = toBePatched - 1; i >= 0; i--) {
  const nextChildIndex = s2 + i;
  const nextChild = c2[nextChildIndex];

  // 判断nextChild是mount还是move
  // 在老元素中出现的元素可能要move,没有出现过的要mount
  if (newIndexToOldIndexMap[i] === 0) {
    mountElement(nextChild.key);
  } else {
    // 可能move
    if (lastIndex < 0 || i !== increasingNewIndexSequece[lastIndex]) {
      move(nextChild.key);
    } else {
      lastIndex--;
    }
  }
}

排序场景

商品排序(销量、好评等)

实现:后端排序、前端排序

要求:全排序OR偏排序?稳定性?时间复杂度?空间复杂度?

偏排序

在计算机科学里,偏排序是排序算法的一个放宽的变种。全排序返回的列表中,每个元素都按一定顺序出现,而偏排序返回的列表中,仅有 k 个最小(或 k 个最大)的元素是有序的。其他元素(第 k 个最小之外) 也可能被就地排序后存储,也可能被舍弃。偏排序最普遍的实例是计算某个列表的 "Top 100"。

LeetCode最小的k个数

Array.sort

下面排序结果是?

[30, 4, 1, 2, 5].sort()

默认情况下,sort会按照升序重新排列数组元素。为此,sort会在每一项上调用String()转型函数,然后比较字符串来决定顺序。即使数组的元素都是数值,也会先把数组转换为字符串再比较、排序。

因此,升序修改如下:

[30, 4, 1, 2, 5].sort((a, b) => a - b)

2.常见算法排序

插入排序 VS 快速排序 VS 归并排序

快速排序 相比较于 归并排序,在整体性能上表现更好:

  • 更高的计算效率。快速排序 在实际计算机执行环境中比同等时间复杂度的其他排序算法更快(不命中最差组合的情况下)
  • 更低的空间成本。前者仅有O(㏒n)的空间复杂度,相比较后者O(n)的空间复杂度在运行时的内存消耗更少

实际应用:

如果数组很短,优先插入排序。(插入排序常数项低,长度小于60,选择插入排序)

如果数组很长,则检查元素是否是基础类型,如数字、字符串,因为这个时候和稳定性无关,可以选择快速排序。如果元素是对象,则稳定性一般情况下比较重要,这个时候可以选择归并排序。

冒泡排序

Bubble Sort,是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“”到数列的顶端。

冒泡排序算法的运作如下:

  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
var bubble_sort = function(nums) {
    let len = nums.length
    if(len<=1) {
        return nums
    }
    for(let i=0; inums[j+1]) {
                [nums[j], nums[j+1]] = [nums[j+1], nums[j]]
            }
        }
    }
    return nums
};

选择排序

Selection Sort, 它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

代码

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function (nums) {
    let len = nums.length;
    if (len === 1) {
        return nums;
    }
    for(let i=0; i

插入排序

Insertion Sort,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5

代码实现

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function (nums) {
    let len = nums.length;
    if (len === 1) {
        return nums;
    }

    for(let i=1; i0 && (nums[j]< nums[j-1]); j--) {
            [ nums[j], nums[j-1] ] = [ nums[j-1], nums[j] ]
        }
  }
    
    return nums

};

希尔排序

Shell Sort,也称递减增量排序算法,是插入排序的一种更高效的改进版本。

h?(步长,gap)

h = 3

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位

代码

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function (nums) {
    let len = nums.length;
    if (len === 1) {
        return nums;
    }
  
    let h = 1
    while(h=1) {
        for(let i=h; i=h) && (nums[j]

快速排序

Quick Sort,又称分区交换排序(partition-exchange sort),最早由东尼·霍尔提出。

快速排序使用分治法(Divide and conquer)策略来把一个序列分为较小和较大的2个子序列,然后递归地排序两个子序列。(分而治之) [less] pivot [greater]

步骤为:

  1. 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot)
  2. 分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成,
  3. 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。

递归到最底部的判断条件是数列的大小是零或一,此时该数列显然已经有序。

举例:

[6, 2, 1, 5, 4, 3]


quicksort(nums, left, right)

[6, 2, 1, 5, 4, 3]


i=0, j = 6

pivot = 6

i 加加, 至 5

3 2 1 5 4 6

return 5


quick(nums, 0, 4)

—quick(nums, 6, 5)

pivot = 3

i=0, j=5

i=3, j = 2

1 2 3 5 4 6

return 2


quick(nums, 0, 1)

i=0, j = 2

pivot=1

i=1, j =0

1 2 3 5 4 6

return 0

quick(nums, 3, 5)

i=3, j=6

pivot=5

i=4, j = 4

1 2 3 4 5 6

伪代码

简版

缺点:需要额外O(n)空间

function quicksort(q)
{
    var list less, pivotList, greater
    if length(q) ≤ 1 
        return q
    else 
    {
        select a pivot value pivot from q
        for each x in q except the pivot element
        {
            if x < pivot then add x to less
            if x ≥ pivot then add x to greater
        }
        add pivot to pivotList
        return concatenate(quicksort(less), pivotList, quicksort(greater))
    }
}

代码实现

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function(nums) {
    let len = nums.length
    if(len===1) {
        return nums
    }
    
    quickSort(nums, 0, len-1)

    function quickSort(nums, left, right) {
        if(left=j) {
                break
            }
            [ nums[i], nums[j] ] = [ nums[j], nums[i] ]
        }
        [ nums[left], nums[j] ] = [ nums[j], nums[left] ]
        return j
    }

    return nums
};

计数排序

Counting Sort,适合少量、数值集中的非负数排序。可实现优化以支持负数。

非比较排序。

由于用来计数的数组count的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。

c语言步骤如下:

  1. 遍历数组nums,获取最大值max
  2. 定义数组count,长度为max+1,初始值均为0
  3. 遍历数组count,令count[ nums[i] ] ++ ,即把nums[i]当做count的数组下标,记录nums[i]出现的次数
  4. 定义数组res存放待排序数组,定义初始值index=0
  5. 遍历count数组,把count[i]不是0的下标值逐个放入res数组中。注意:鉴于nums数组中可能会有相等的元素,因此count[i]可能会大于1

代码实现

var sortArray = function (nums) {
    let len = nums.length
    if(len<2) {
        return nums
    }
    // 5 5 2 3 1
    const count = []
    for(let i=0; i0) {
                res.push(j)
                count[j] -- 
            }
        }
    }
    return res
};

如果数组的数字是10到99的数字,那么前10个空间会被浪费,因此可以优化如下:

var sortArray = function (nums) {
    let len = nums.length
    if(len<2) {
        return nums
    }
    // 5 5 2 3 1
    
    let min = nums[0]
    let max = nums[0]
    for(let i=1; imax) {
            max = nums[i]
        } else if(nums[i]0) {
            res.push(j+min) 
                count[j] --
        }
    }
    // 1 2 3 5 5
    return res
};

不获取max

var sortArray = function (nums) {
    let len = nums.length
    if(len<2) {
        return nums
    }
   // 100-999   
   // count.length = max+1
   const min = Math.min.apply(null, nums)
    const count = []
    for(let i=0; i0) {
                res.push(j+min)
                count[j]--
            }
        }
    }

    return res
};

基数排序

Radix Sort,非比较型整数排序,将所有待比较数值(非负整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

非比较排序因为不涉及比较,其基本操作的代价较小,所以在一定情况下,基数排序一般要快过基于比较的排序,比如快速排序。

代码实现

var sortArray = function (nums) {
    let len = nums.length
    if(len<2) {
        return nums
    }
   // nums的最大值
   let max = Math.max.apply(null, nums)
   // max的位数,如131就是3位
   let maxDigit = 1
   while(max = Math.floor(max/10)){
       maxDigit ++
   }
    let count = []
    let mod = 10
    let dev = 1
    for(let i=0; i

TimSort

V8引擎

V8 是 Google 发布的开源 JavaScript 引擎,采用 C++ 编写,在 Google 的 Chrome 浏览器中被使用。

如果想要查看chome的版本,可以再浏览器直接输入chrome://version/

Array.prototype.sort

v8文档关于排序的原文链接:https://v8.dev/features/stable-sort。

Array.prototype.sort是 V8 中用 JavaScript 实现的一个内置函数之一。

以前V8的排序算法是,对于数组长度小于或者等于10的时候,采用插入排序,否则采用快速排序。但是从 V8 v7.0 / Chrome 70 之后采用。

很长一段时间以来,JavaScript 规范并不要求排序稳定性Array#sort,而是将其留给了实现。换句话说,JavaScript 开发人员不能依赖排序稳定性。在实践中,因为一些 JavaScript 引擎会对短数组使用稳定排序,对大数组使用不稳定排序。这会造成比较困惑的结果,因为开发人员会测试他们的代码,看到稳定的结果,但是当数组稍大时,在生产中突然得到不稳定的结果。

好消息是,现在的V8已经接受了一个使Array#sort稳定规范更改,所有主要的 JavaScript 引擎现在都实现了一个稳定的Array#sort。作为 JavaScript 开发人员,就少了一件需要担心的事情。Nice!

TimSort

时间复杂度:O(n log n)

稳定性:稳定

TimSort主体是归并排序,但小片段的合并中 用了插入排序。

用上了二分搜索等算法

利用待排序数据可能部分有序的事实,

依据待排序数据内容,动态改变排序策略——选择性进行归并以及galloping

1. 分区(run)

在实际场景中,大部分的数组都是部分有序的,而TimSort就很好地利用了这一点:分区,分到的每个区都是有序的。如果分到的区是严格降序,那么就翻转(reverse)这个分区。最终得到若干个升序的分区。

如,对[1, 3, 5, 2, 4, 8, 7, 6]分区:

1, 3, 5

2, 4, 8

7, 6 --翻转--> 6, 7

2. 合并分区的顺序

下一步我们要两两合并分区,也就意味着要比较两个分区中的元素大小,但是如果长度为1000的分区和长度为1的分区合并,把长度为1001的分区和长度为2的分区合并,最后还需要把前面合并得到的长度为1001和长度为1003的分区合并,这显然不如先给分区长度排序,然后run1和run2合并,run1000和run1001合并。

TimSort维护了一个stack,在这个栈里,分区是按照分区长度升序存储的。

3. 合并分区

既然我们得到的每个分区都是升序的,那合并两个分区的时候可以去逐个去比较这两个分区中的元素,以此得到一个有序的分区。但是对于元素量较大的分区来说,这种做法显然是性能耗费过大。

这个时候,可以了解下Galloping(倍增搜寻法),即以2^n的递增,最终得到

run1[2^n-1] < run0[i] < run1[2^(n +1) - 1]。那么这样我们就知道了,run0[i]在run1中的顺序我们就锁定了run1[2n-1]与run1[2(n +1) - 1]之间,这个时候我们可以再使用二分查找高效定位run0[i]在run1中的位置。

4. 二分排序

当分区的长度较短时,相对来说二分插入排序会较快。

v8源码中的sort实现

Vue DOM DIFF、React

你可能感兴趣的:(vue和react的算法实现)