js 算法

37 阅读7分钟

递归

就是函数自己调用自己本身,或者在自己函数调用的下级函数中调用自己。

递归的步骤

  1. 假设递归函数已经写好
  2. 寻找递推关系
  3. 将递推关系的结构转换为递归体
  4. 将临界条件加入到递归体中

经典案例 1: 求和

求 1-100 的和

function sum(n) {
    if (n == 1) return 1;
    return sum(n - 1) + n;
}

经典案例 2: 斐波拉契数列

1,1,2,3,5,8,13,21,34,55,89...求第 n 项

// 递归方法
function fib(n) {
    if (n === 1 || n === 2) return n - 1;
    return fib(n - 1) + fib(n - 2);
}
console.log(fib(10)) // 34

//非递归方法 //
function fib(n) {
    let a = 0;
    let b = 1;
    let c = a + b;
    for (let i = 3; i < n; i++) {
        a = b;
        b = c;
        c = a + b;
    }
    return c;
}
console.log(fib(10)) // 34

经典案例 3: 深拷贝

原理: clone(o) = new Object; 返回一个对象

function clone(o) {
    var temp = {};
    for (var key in o) {
        if (typeof o[key] == 'object') {
            temp[key] = clone(o[key]);
        } else {
            temp[key] = o[key];
        }
    }
    return temp;
}

经典案例 4: 重新组合树形数据

// demo/index.html:
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>demo</title>
    </head>
    <body>
        <script type="text/javascript" src="./demo.js"></script>
    </body>
</html>

// demo/js:
var treeData = [
    {
        departmentName: "信息部",
        departmentId: 0,
        subset: [
            {
                departmentName: "数据中心",
                departmentId: 0,
                subset: [
                    {
                        departmentName: "ETL组",
                        departmentId: 0,
                        subset: null,
                    },
                    {
                        departmentName: "数据组",
                        departmentId: 1,
                        subset: null,
                    },
                    {
                        departmentName: "分析组",
                        departmentId: 2,
                        subset: null,
                    },
                ],
            },
        ],
    },
    {
        departmentName: "全域事业部",
        departmentId: 1,
        subset: [
            {
                departmentName: "战略发展中心",
                departmentId: 0,
                subset: [
                    {
                        departmentName: "华东大区",
                        departmentId: 0,
                        subset: null,
                    },
                    {
                        departmentName: "华南大区",
                        departmentId: 1,
                        subset: null,
                    },
                    {
                        departmentName: "华西大区",
                        departmentId: 2,
                        subset: null,
                    },
                    {
                        departmentName: "华北大区",
                        departmentId: 3,
                        subset: null,
                    },
                    {
                        departmentName: "华中大区",
                        departmentId: 4,
                        subset: null,
                    },
                ],
            },
            {
                departmentName: "业务拓展中心",
                departmentId: 1,
                subset: [
                    {
                        departmentName: "拓展一组",
                        departmentId: 0,
                        subset: null,
                    },
                    {
                        departmentName: "拓展二组",
                        departmentId: 1,
                        subset: null,
                    },
                    {
                        departmentName: "拓展三组",
                        departmentId: 2,
                        subset: null,
                    },
                ],
            },
        ],
    },
];

const handleTree = (treeList) => {
    return treeList.map((item) => {
        const { departmentName, departmentId, subset } = item;
        return {
            label: departmentName,
            value: departmentId,
            children: (() => {
                if (subset && subset.length > 0) {
                    return handleTree(subset);
                } else {
                    return null;
                }
            })(),
        };
    });
};

console.log("treeData:", handleTree(treeData));

经典案例 5:递归组件

递归组件: 组件在它的模板内可以递归的调用自己,只要给组件设置 name 组件就可以了。
不过需要注意的是,必须给一个条件来限制数量,否则会抛出错误: max stack size exceeded
组件递归用来开发一些具体有未知层级关系的独立组件。比如:联级选择器和树形控件

<template>
    <div v-for="(item,index) in treeArr"> {{index}} <br/>
        <tree :item="item.arr" v-if="item.flag"></tree>
    </div>
</template>
<script>
export default {
    // 必须定义name,组件内部才能递归调用
    name: 'tree',
    data(){
        return {}
    },
    // 接收外部传入的值
    props: {
        item: {
            type:Array,
            default: ()=>[]
        }
    }
}
</script>

拓展

操作数组

常用方法

join
把数组中所有元素放入一个字符串中,返回字符串,不改变原数组。

const arr = ['pr', 'is', 18];

console.log(arr.join(' ')); // pr is 18
console.log(arr); // [ 'pr', 'is', 18 ]

concat
连接多个(含两个)数组,两边的原始数组都不会变化,返回被连接数组的一个副本,可继续 concat

const arr = [1, 2, 3, 4];
const arr1 = ['pr', 'is', 'a', 'boy'];
const arr2 = [5, 6, 7];

console.log(arr.concat(arr1, arr2).concat(8, 9)); // [1, 2, 3, 4, 'pr', 'is', 'a', 'boy', 5, 6, 7, 8, 9 ]
console.log(arr); // [ 1, 2, 3, 4 ]

slice
从开始到结束([)左闭右开,即不包括结束)选择数组的一部分浅拷贝到一个新数组,不改变原始数组。
slice(1, 5) 可见里面最多含 4(5 - 1) 个元素,而且从第 1 位开始取。

const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

console.log(arr.slice(1, 5)); // [ 1, 2, 3, 4 ]
console.log(arr); // [ 0,1,2,3,4,5,6,7,8,9 ]

map
创建一个新数组并返回,新数组的每个元素由原数组中的每一个元素执行提供的函数而来,其中原始数组不会发生改变。

const arr = [1, 2, 3, 4];

console.log(arr.map(i => i * 10 - 5)); // [ 5, 15, 25, 35 ]
console.log(arr); // [ 1, 2, 3, 4 ]

every
检测数组所有元素是否都符合指定条件,不改变原始数组。

  • 如果数组中检测到有一个元素不满足,则整个表达式返回 false,且剩余的元素不会再进行检测;
  • 如果所有元素都满足条件,则返回 true
const arr = [1, 2, 3, 4];

console.log(arr.every(i => i > 2)); // false
console.log(arr.every(i => i > 0)); // true
console.log([].every(i => i === 'pr')); // true
console.log(arr); // [ 1, 2, 3, 4 ]

some
用于检测数组中的元素是否满足指定条件,不改变原始数组。

  • 如果有一个元素满足条件,则表达式返回 true,剩余的元素不会再执行检测;
  • 如果没有满足条件的元素,则返回 false
const arr = [1, 2, 3, 4];

console.log(arr.some(i => i > 4)); // false
console.log(arr.some(i => i > 0)); // true
console.log([].some(i => i === 'pr')); // false
console.log(arr); // [ 1, 2, 3, 4 ]

filter
创建一个新的数组,新数组中的元素是通过检查符合条件的所有元素,不改变原始数组。

const arr = [1, 2, 3, 4];

console.log(arr.filter(i => i > 2)); // [3, 4]
console.log([].filter(i => i === 'pr')); // []
console.log(arr); // [ 1, 2, 3, 4 ]

forEach
用于调用数组的每个元素,并将元素传递给回调函数,返回 undefiend,不改变原始数组。

const arr = [1, 2, 3, 4];
const copy = [];

console.log(arr.forEach(i => {
    copy.push(i * 2);
}));
console.log(copy); // [ 2, 4, 6, 8 ]
console.log(arr); // [ 1, 2, 3, 4 ]

reduce
接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。若是空数组是不会执行回调函数的,不改变原始数组。

  • pre 提供的初始值,或者是上次调用返回的结果值;
  • cur当前项;
  • index 当前的索引值;
  • list 源数据;
  • 100 reduce 操作的初始值。
let list = [1, 2, 3, 41, 5, 33, 67, 44, 8, 88, 8, 55, 79, 89, 89, 99]; 
list.reduce((pre, cur, index, list) => { 
    console.log(pre, cur, index, list); 
    return pre + cur; 
}, 100);
const arr = [1, 2, 3, 4];

console.log(arr.reduce((prev, cur) => prev + cur, 0)); // 10
console.log(arr); // [ 1, 2, 3, 4 ]

pop
删除数组的最后一个元素,并返回这个元素(即被删除的元素),原始数组被改变。

  • 如果数组为空,则不改变数组,返 undefined
const arr = [1, 2, 3, 4];
const arr1 = [];

console.log(arr.pop()); // 4
console.log(arr1.pop()); // undefined
console.log(arr); // [ 1, 2, 3 ]
console.log(arr1); // []

push
将一个或多个元素添加到数组的末尾,返回值是改变后的数组的长度,原始数组被改变。

const arr = [1, 2, 3, 4];

console.log(arr.push(5)); // 5
console.log(arr.push([1, 2])); // 6
console.log(arr); // [ 1, 2, 3, 4, 5, [ 1, 2 ] ]

shift
删除数组的第一个元素,并返回这个元素,原始数组被改变。

const arr = [1, 2, 3, 4];

console.log(arr.shift()); // 1
console.log(arr); // [ 2, 3, 4 ]

unshift
将一个或多个元素添加到数组的开头,返回值是改变后的数组的长度,原始数组被改变。

const arr = [1, 2, 3, 4];

console.log(arr.unshift(5, 6)); // 6
console.log(arr.unshift([1, 2])); // 7
console.log(arr); // [ [ 1, 2 ], 5, 6, 1, 2, 3, 4 ]

reverse
颠倒数组中元素的位置,返回该数组的引用,原始数组被改变。

const arr = [1, 2, 3, 4];
const hello = 'hello';
const helloArray = hello.split('');

console.log(helloArray.reverse().join('')); // olleh
console.log(arr.reverse()); // [ 4, 3, 2, 1 ]
console.log(arr); // [ 4, 3, 2, 1 ]
console.log(helloArray); // [ 'o', 'l', 'l', 'e', 'h' ]

sort
对数组的元素进行排序,并返回数组。排序不一定是稳定的。默认排序顺序是根据字符串 Unicode 码点,原始数组被改变。

const arr = [1, 2, 3, 4, 10, 12, 22];

console.log(arr.sort()); // [ 1, 10, 12, 2, 22, 3, 4];
console.log(arr); // [ 1, 10, 12, 2, 22, 3, 4];

splice
向数组中添加/删除项目,然后返回被删除项目,原始数组被改变。

const arr = [1, 2, 3, 4];

console.log(arr.splice(1, 2, 10, 12)); // [ 2, 3 ]
console.log(arr); // [ 1, 10, 12, 4 ]

数组去重

filter 方法实现

const arr = [1, 2, 11, 22, 11, 1, 11, 22, 1, 2];
const unique2 = arr => arr.filter((element, index, self) => self.indexOf(element) === index); 
console.log(unique2(arr)); // [1, 2, 11, 22]

find 方法实现

const arr3 = [ 
    { id: 1, name: '张三' }, 
    { id: 2, name: '李四' }, 
    { id: 11, name: '王五' }, 
    { id: 1, name: '张三' }, 
    { id: 11, name: '王五' }, 
    { id: 3, name: '李四' } 
]; 
const unique3 = (arr, id) => { 
    let cache = []; 
    for(let item of arr){ 
        if(cache.find(v => v[id] === item[id])) { 
            continue; 
        } 
        cache.push(item); 
    } 
    return cache; 
} 
console.log(unique3(arr3, 'id')); 
// [{id: 1, name: "张三"}, {id: 2, name: "李四"}, {id: 11, name: "王五"}, {id: 3, name: "李四"}]

new Set( ) 方式实现

function noRepeat(arr){
    var newArr = [...new Set(arr)]; //利用了Set结构不能接收重复数据的特点
    return newArr;
}

var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22];
console.log(arr); // [1, 9, 8, 8, 7, 2, 5, 3, 3, 3, 2, 3, 1, 4, 5, 444, 55, 22]   
var arr2 = noRepeat(arr)
console.log(arr2); // [1, 9, 8, 7, 2, 5, 3, 4, 444, 55, 22] 

reduce 方法实现

let list = [ "张三", "李四", "王五", "赵六", "田七", "张三", "李四" ]; 
let result = list.reduce((pre, cur) => { 
    if (!pre.includes(cur)) { 
        return pre.concat(cur); 
    } else { 
        return pre; 
    } 
}, []); 
console.log("result", result) // ['张三', '李四', '王五', '赵六', '田七']

数组降维

reduce 方法实现

const arr = [1, [ 1, 2, [2, 3]], [3, 4], 5];
const arrayDimensionalityReduction = arr => Array.isArray(arr) ? 
    arr.reduce((prev, cur) => [...prev, ...arrayDimensionalityReduction(cur)], []) :
    [arr];

console.log(arrayDimensionalityReduction(arr)); // [1, 1, 2, 2, 3, 3, 4, 5]
let list = [ [1, 2, [55, 99, 33], 3, 4], [43, 6, 5], [0, 4234, 44] ]; 
const resultList = function (list) { 
    return list.reduce((pre, cur) => { 
        return pre.concat(Array.isArray(cur) ? resultList(cur) : cur); 
    }, []); 
} 
console.log(resultList(list)) // [1, 2, 55, 99, 33, 3, 4, 43, 6, 5, 0, 4234, 44]

flat 方法实现

var arr=['red',[1,2,[3,4,[5,6,[7,8,[9,10],11,12]]]]];
console.log(arr.flat()); //  ['red', 1, 2, Array(3)]
console.log(arr.flat(2)); // ['red', 1, 2, 3, 4, Array(3)]
console.log(arr.flat(3)); // ['red', 1, 2, 3, 4, 5, 6, Array(5)]
console.log(arr.flat(4)); // ['red', 1, 2, 3, 4, 5, 6, 7, 8, Array(2), 11, 12]
console.log(arr.flat(Infinity)); // ['red', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

拓展
Javascript 数组常用算法
JS中flat---提取嵌套数组元素