如何用循环取代递归

如何用循环取代递归

1. 引子

在实际开发中,我们经常会用到一种写法,那就是递归。只要是遍历一个有层级的结构,毫无疑问,你第一方法就是递归去处理。但是我在开发中,常常不想问了一个小功能,就去写一个方法处理递归,毕竟给方法命名是极其痛苦的,原谅的词汇量的稀少。以前大学时,听老师说过:凡是递归,必定可以用循环解决。所以就花了点时间思考了下如何用循环取代递归。

2. 递归和循环比较

先说下递归和循环各自的优缺点:

递归:

优点:简单,易于理解,不用关心嵌套了多少层

缺点:需要把递归的业务单独提取,开一个新的方法;如果递归层数较深,容易发生栈溢出;调试极其不友好;效率不太好,需要频繁进入方法

循环:

优点:效率高,不需要担心栈溢出问题

缺点:逻辑复杂,难理解,难维护(尤其是你写的又长又臭的时候)

3. 递归与循环的转换

一下所有讲解的代码为了方便我都使用JavaScript,请自己转换成自己用的语言

对于递归,最好的例子就是遍历树,所以我们先构建一棵树:

{
    "id": 1,
    "name": "根节点",
    "children": [
        {
            "id": 2,
            "name": "节点2",
            "children": [
                {
                    "id": 4,
                    "name": "节点4",
                    "children": [
                        {
                            "id": 6,
                            "name": "节点6",
                            "children": []
                        }
                    ]
                },
                {
                    "id": 5,
                    "name": "节点5",
                    "children": []
                }
            ]
        },
        {
            "id": 3,
            "name": "节点3",
            "children": []
        }
    ]
}

递归实现:

let json = JSON.parse(`{"id":1,"name":"根节点","children":[
{"id":2,"name":"节点2","children":
[{"id":4,"name":"节点4","children":[{"id":6,"name":"节点6","children":[]}
]}
,{"id":5,"name":"节点5","children":[]}]},{"id":3,"name":"节点3","children":[]}
]}`);

recursion(json);

function recursion( tree ){
    // 如果不存在,直接返回,作为递归结束
    if(!tree){
        return;
    }
    // 业务处理
    console.info(`${tree.id} -- ${tree.name}`);
    // 递归处理子节点
    if( tree.children ){
        for (const item of tree.children) {
            recursion(item);
        }
    }
}

循环实现(广度优先):

先给个提示:队列

let json = JSON.parse(`{"id":1,"name":"根节点","children":[
{"id":2,"name":"节点2","children":
[{"id":4,"name":"节点4","children":[{"id":6,"name":"节点6","children":[]}
]}
,{"id":5,"name":"节点5","children":[]}]},{"id":3,"name":"节点3","children":[]}
]}`);

// 创建一个数组作为队列
let queue = [];

// 顶层节点入队
queue.push(json);

while( queue.length > 0 ){
    // 出队一个元素
    let item = queue.shift();
    // 业务处理
    console.info(`${item.id} -- ${item.name}`);
    // 子节点入队
    if( item.children ){
        for (const childItem of item.children) {
        	queue.push(childItem);
        }
    }
}

循环实现(深度优先):

先给个提示:堆栈

let json = JSON.parse(`{"id":1,"name":"根节点","children":[
{"id":2,"name":"节点2","children":
[{"id":4,"name":"节点4","children":[{"id":6,"name":"节点6","children":[]}
]}
,{"id":5,"name":"节点5","children":[]}]},{"id":3,"name":"节点3","children":[]}
]}`);


// 创建一个数组作为栈
let stack = [];

// 顶层节点入栈
stack.push(json);

while( stack.length > 0 ){
    // 出栈一个元素
    let item = stack.pop();
    // 业务处理
    console.info(`${item.id} -- ${item.name}`);
    // 子节点入栈
    if( item.children ){
        for (const childItem of item.children.reverse()) {
            stack.push(childItem);
        }
    }
}

4. 总结

​ 用循环实现递归其实不难,借助队列和栈这两种数据结构就可以很简单地实现。但是我们需要将原本递归处理的数据封装成一个新的数据结构,作为元素传入队列/栈中。例如我们上个例子中,每个节点对象就是一个递归处理数据,因为节点对象本身就是一个对象,所以我们才没必要在封装了。但如果相对应的递归函数:recursion( x , y ),这样子,那我们就需要封装一下了:{ x: "", y: "" },封装成一个对象

​ 如果可以,其实还是递归更简单,也推荐用递归,除非你像我一样,不喜欢创建多一个函数,或者栈溢出。

posted @ 2021-07-07 10:54  _ME  阅读(2713)  评论(0编辑  收藏  举报