继上次 递归实现列表赋值 的经历之后,今天又遇到了新的需要:给你一棵树的最深节点value值,让你找到从根到该节点的value列表。树的结构长下面这样

{
    value: 1,
    children: [
        {
            value: 2,
            children: [
                {
                    value: 3
                },
                {
                    value: 4
                }
            ]
        },
        {
            value: 5,
            children: [
                {
                    value: 6
                },
                {
                    value: 7
                }
            ]
        }
    ]
}

如果没听明白的话,你就把它想象成一个级联框,给定最后一级value值,返回从第一级到最后一级的列表。就拿上面的树结构举例,我简单画了个级联框

python中退出递归 如何退出递归_级联

如果 value是 3 或 4 的话,返回的 value列表 应该是 [1, 2, 3][1, 2, 4]

如果是 value 是 6 或 7 的话,返回的 value 列表应该是 [1, 5, 6][1, 5, 7]

当然,有的可能只有两级级联,所以 value 也可能是 2 或者 5 ,那么返回的 value 列表则是 [1, 2][1, 5]


好,问题分析完了,下面想办法解决。因为之前用过 递归实现列表赋值 ,为了图省事,顺便再练习一下遍历算法,那就敲定递归!直接上代码

// 1. 创建数组valueList作为栈
let valueList = []
let targetValue = 6

function traversalTree(tree) {
  tree.map((item) => {
    // 2. 当value === targetValue,返回栈
    if (item.value === targetValue) {
      valueList.push(item.value)
      return valueList
    }
    // 3. 如果有children属性,把key入栈,访问children
    if (item.children) {
      valueList.push(item.value)
      traversalTree(item.children)
    }
    // 4. 没有children属性,跳到上一级
    // 注意!这里不是清空栈! valueList = []
    valueList.pop()
  })
}

很顺利啊,那看看函数返回的结果是什么。我去,怎么拿到的值是 undefined ?查了查资料,发现里头的 return valueList 语句并没有真正退出 traversalTree 函数。当语句取到我们需要的 vlaue 时,可能已经在第二级或第三级级联框中,也就是在第二层或第三层堆栈中,return 只是返回到上一层堆栈(第一层或第二层),继续执行未完成的语句。


好家伙,这么一说还得多加个 return 才行。那万一嵌套太深了可咋整?嵌套多了那不得……(脑补一下地狱回调的代码

换个思路

既然简单的 return 行不通,那就换条路走。百度了一下发现还有种方法 —— try...catch。大体思路就是把遍历的操作放在 try 中,当遇到符合条件的情况时主动 throw 一个错误,用 catch 捕获,这样就能跳出嵌套了。因为整个递归算法都在 try 语句中,所以退出 try 语句后,也就退出了递归算法(真的是这样吗?🤔)

function traversalTree(tree) {
  try {
    tree.map((item) => {
      // 2. 当value === targetValue,返回栈
      if (item.value === targetValue) {
        valueList.push(item.value)
        throw valueList
      }
      // 3. 如果有children属性,把key入栈,访问children
      if (item.children) {
        valueList.push(item.value)
        traversalTree(item.children)
      }
      // 4. 没有children属性,跳到上一级
      valueList.pop()
    })
  } catch (valueList) {
    return valueList
  }
}

又跑了一遍代码,函数返回值竟然还是 undefined。纳闷了,咋又失败了呢?于是打了个断点去查了查堆栈

python中退出递归 如何退出递归_堆栈_02

在主动 throw (找到值)后,从堆栈中可以看到,如果处理完 catch 中的语句,代码还是会回到堆栈处理没调用完的函数。也就是说,简单地使用 try...catch 是不起作用的。

接着想办法

到这里我有点明白过来了,try...catch可以提前终止循环,但不能跳出堆栈,他只是申请回到上一次调用,和 return 功能一致。如果想要跳出这么多的堆栈,还得加一个判断,来一次一次地跳出去。对于我们的算法来说,需要有一个 flag 变量判断是否已经找到 targetValue ,并且在每一次递归调用去做判断。

function getValueList(targetValue) {
  // 1. 创建数组valueList作为栈
  var valueList = []
  let flag = false
  const that = this
  // 这里的that主要是传递this,还有一种this绑定的方法我忘了,就先凑合着吧...
  traversalTree(tree, that)
  return valueList

  function traversalTree(tree, that) {
    tree.map((item, index, arr) => {
      if (flag) return

      // 2. 当value === targetValue,返回栈
      if (item.value === targetValue) {
        valueList.push(item.value)
        flag = true
        return
      }

      // 3. 如果有children属性,把key入栈,访问children
      if (item.children) {
        valueList.push(item.value)
        traversalTree(item.children, that)
      }

      // 4. 读完了当前级的所有内容并且不是第一级
      if (arr.length - 1 === index && that.arrUnlike(arr, that.goodsTypeOptions)) {
        valueList.pop()
      }
    })
  }
}

function arrUnlike(arr1, arr2) {
  if (arr1.length !== arr2.length) return true
  for (const i in arr1) {
    if (arr1[i] !== arr2[i]) {
      return true
    }
  }
  return false
}

P.s. 递归是世界上最好的语言(逃