react
昨天面试被问到diff算法,将不出个所以然,所以今天就打算好好看看。
传统diff算法
先说说传统diff算法,一般看到react的diff算法的时候都会提到这个“传统diff算法的时间复杂度是O(n^3)”,但是一直不知道为什么是n ^ 3而不是n^2。
首先用旧的树的每一个结点对新的树的每一个结点进行遍历,这样的复杂度为O(n^2),然后查找对比差异后计算最小的转换方式,最终的时间复杂度是O(n ^ 3)。
react的diff算法
1.什么是调和?
将虚拟DOM树转化成真实DOM树的最少操作过程称为调和。
2.三大策略
(1)tree diff
React在新旧两颗虚拟DOM树之间进行比较,注意是两个虚拟DOM,并不是虚拟与真实DOM之间进行比较。
对树分层比较,只比较同一层次节点,如果该节点不存在,则该节点及其子结点将会被完全删除。
只需遍历一次就能完成整颗DOM树的比较。
(2)component diff
也有三种策略(也可以说是三种情况吧)
1.同一类型的两个组件,按原策略(层级比较)继续比较虚拟DOM即可。
2.同一类型的两个组件,组件A变化为组件B时,可能虚拟DOM没有任何变化,所以用户可以通过shouldComponentUpdate()来判断是否需要判断计算。(看介绍的时候这里我有个疑惑,一二两点难道不是同一个意思吗?我觉得应该是在比较同一类型的两个组件的情况下,可以选择是否调用shouldComponentUpdate()函数来进行判断。)
3.不同类型组件将一个(将被改变的)组件判断为脏组件,从而替换整个组件的所有节点。(就是直接替换嘛说的这么复杂。。)
注意,如果组件D和组件G结构相似,但是react判断是不同类型的组件,则不会比较其结构,而是删除组件D及其子结点,创建组件G及其子结点。
(3)element diff
当节点处于同一层级时(比如ListItem标签),diff提供三种节点操作:删除、插入、移动。
插入:组件C不在集合(A,B)中,需要插入
删除:1.组件不在集合中,但是已经被更改,不能复用和更新,需要删除旧组件,创建新组件。
2.集合中不再包含旧组件,旧组件需要被删除。
移动:组件已经在集合中,且组件未发生更新,只是位置改变。
下面再具体介绍一下移动的逻辑。
移动不再像传统diff算法一样,比较对应位置的节点。
以上面图片这种情况为例。
react先从新中取得B,然后判断旧中是否存在相同节点B,当发现存在节点B后,就判断是否移动B。
先说一个概念:lastIndex,有点像浮标,或者说一个map的索引,一开始是默认值0,他会与mao中的元素进行比较,比较完后,会改变自己的值(取index和lastindex的较大数),看了后面的解释就会明白。
B在旧 中的index=1,它的lastIndex=0,不满足 index < lastIndex 的条件,因此 B 不做移动操作。此时,lastIndex=(index,lastIndex)中的较大数=1.
看着 A,A在旧的index=0,此时的lastIndex=1(因为先前与新的B比较过了),满足index<lastIndex,因此,对A进行移动操作,此时lastIndex=max(index,lastIndex)=1。
看着D,同(1),不移动,由于D在旧的index=3,比较时,lastIndex=1,所以改变lastIndex=max(index,lastIndex)=3。
看着C,同(2),移动,C在旧的index=2,满足index<lastIndex(lastIndex=3),所以移动。
由于C已经是最后一个节点,所以diff操作结束。
diff的不足与有待优化的地方。
看图的 D,此时D不移动,但它的index是最大的,导致更新lastIndex=3,从而使得其他元素A,B,C的index<lastIndex,导致A,B,C都要去移动。
理想情况是只移动D,不移动A,B,C。因此,在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,会影响React的渲染性能。
(参考:https://www.jianshu.com/p/3ba0822018cf)
算法题
栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true 解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4, push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2之前弹出。
提示:
0 <= pushed.length == popped.length <= 1000
0 <= pushed[i], popped[i] < 1000
pushed 是 popped 的排列。
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
if(pushed.length == 0) return true;
Stack<Integer> stack = new Stack<Integer>();
int i = 0;
for(int num : pushed){
stack.push(num);
while(!stack.isEmpty() && stack.peek() == popped[i]){
stack.pop();
i++;
}
}
return stack.isEmpty();
}
}
再来回顾一下常见的几个排序算法和查找算法
冒泡排序
function bubbleSort(arr) {
varlen = arr.length;
for(vari = 0; i < len - 1; i++) {
for(varj = 0; j < len - 1 - i; j++) {
if(arr[j] > arr[j+1]) { // 相邻元素两两对比
vartemp = arr[j+1]; // 元素交换
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
returnarr;
}
选择排序
function selectionSort(arr) {
varlen = arr.length;
varminIndex, temp;
for(vari = 0; i < len - 1; i++) {
minIndex = i;
for(varj = i + 1; j < len; j++) {
if(arr[j] < arr[minIndex]) { // 寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
returnarr;
}
插入排序
function insertionSort(arr) {
varlen = arr.length;
varpreIndex, current;
for(vari = 1; i < len; i++) {
preIndex = i - 1;
current = arr[i];
while(preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
arr[preIndex + 1] = current;
}
returnarr;
}
希尔排序
function shellSort(arr) {
varlen = arr.length;
for(vargap = Math.floor(len / 2); gap > 0; gap = Math.floor(gap / 2)) {
// 注意:这里和动图演示的不一样,动图是分组执行,实际操作是多个分组交替执行
for(vari = gap; i < len; i++) {
varj = i;
varcurrent = arr[i];
while(j - gap >= 0 && current < arr[j - gap]) {
arr[j] = arr[j - gap];
j = j - gap;
}
arr[j] = current;
}
}
returnarr;
}
快速排序
function quickSort(arr, left, right) {
varlen = arr.length,
partitionIndex,
left = typeofleft != 'number'? 0 : left,
right = typeofright != 'number'? len - 1 : right;
if(left < right) {
partitionIndex = partition(arr, left, right);
quickSort(arr, left, partitionIndex-1);
quickSort(arr, partitionIndex+1, right);
}
returnarr;
}
function partition(arr, left ,right) { // 分区操作
varpivot = left, // 设定基准值(pivot)
index = pivot + 1;
for(vari = index; i <= right; i++) {
if(arr[i] < arr[pivot]) {
swap(arr, i, index);
index++;
}
}
swap(arr, pivot, index - 1);
returnindex-1;
}
function swap(arr, i, j) {
vartemp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
排序可以参考这篇文章https://www.cnblogs.com/onepixel/p/7674659.html
顺序查找
//顺序查找
int SequenceSearch(int a[], int value, int n)
{
int i;
for(i=0; i<n; i++)
if(a[i]==value)
return i;
return -1;
}
二分查找
//二分查找(折半查找),版本1
int BinarySearch1(int a[], int value, int n)
{
int low, high, mid;
low = 0;
high = n-1;
while(low<=high)
{
mid = (low+high)/2;
if(a[mid]==value)
return mid;
if(a[mid]>value)
high = mid-1;
if(a[mid]<value)
low = mid+1;
}
return -1;
}
//二分查找,递归版本
int BinarySearch2(int a[], int value, int low, int high)
{
int mid = low+(high-low)/2;
if(a[mid]==value)
return mid;
if(a[mid]>value)
return BinarySearch2(a, value, low, mid-1);
if(a[mid]<value)
return BinarySearch2(a, value, mid+1, high);
}