【问题标题】:Traversing tree with recursion works strange (JavaScript)递归遍历树的工作很奇怪(JavaScript)
【发布时间】:2022-01-19 18:50:53
【问题描述】:

我已经开始深入研究 Tree 的主题。而且我发现了奇怪的(对我而言)行为。

class tree_node {
  constructor(n_array) {
    this.n_array = n_array;
    this.descendants = [];
    this.child();
  }
  child() {
    if (this.n_array.length !=1){
      let m = Math.floor(this.n_array.length / 2);
      let l = this.n_array.slice(0,m);
      let r = this.n_array.slice(m);
      const left = new tree_node(l);
      const right = new tree_node(r);
      this.descendants.push(left, right);
    }
    else return 0
  }
}

所以,我在这里创建了一个节点结构,它获取一个整数数组,并将其分成两半,直到叶子不再包含数组的一个元素。

let n_array = [1,3,2,5];
root_node = new tree_node(n_array); 

现在我想遍历树并提醒每个节点的“n_array”。 我以为应该是这样的

function show_tree(root_node){
  if (root_node.descendants.lenght != 0){
    root_node.descendants.forEach(element => {
      return show_tree(element);
    });
  }
  else {
    alert(root_node.n_array)
    return 0;
  } 
}

但它不起作用。如果我使用网络浏览器控制台,那么 root.descendants.length 会给我长度。但在我的递归中,它没有。每次都是未定义

我从代码中删减了越来越多的内容,最后,我得到了这个。

function show_tree(root_node){
  alert(root_node.n_array)
  root_node.descendants.forEach(element => {
    return show_tree(element);
  });  
}

有效的解决方案有效,但我不明白为什么。 我预计它会遇到错误,因为在树的末尾 root_node.descendants 将是 undefined.

更糟糕的是 - 如果在每次调用 root_node.descendants !=0 中,我不明白为什么这个递归会停止并且不会进入无限循环。 .. 请帮我理解

  1. 为什么我的第一个版本 show_tree 不起作用
  2. 为什么最后一个有效。

【问题讨论】:

  • 你打错了.length(检查拼写!!!)

标签: javascript recursion tree


【解决方案1】:

你已经有效地构建了一个看起来像这样的树结构:

    [1, 3, 2, 5]
      /       \
  [1, 3]     [2, 5]
   /   \      /   \
  [1]  [3]   [2]   [5] 
   |    |     |     |
  [ ]  [ ]   [ ]   [ ]

在您的第一个函数中,您遇到了一些问题。直接的问题是您使用的是.lenght 而不是.length。关于您的函数,接下来要注意的是它何时执行其alert()s。在您的情况下,您仅在节点具有空数组作为其descendants 属性时才执行alert()。在上面的树形图中,您会注意到只有最后一个节点 [1][3][2][5] 会出现这种情况,因此这些节点会收到警报。您遍历的所有其他节点都具有非空后代,因此不会向屏幕发出警报/记录:

class tree_node {
  constructor(n_array) {
    this.n_array = n_array;
    this.descendants = [];
    this.child();
  }
  child() {
    if (this.n_array.length !=1){
      let m = Math.floor(this.n_array.length / 2);
      let l = this.n_array.slice(0,m);
      let r = this.n_array.slice(m);
      const left = new tree_node(l);
      const right = new tree_node(r);
      this.descendants.push(left, right);
    }
  }
}

let n_array = [1,3,2,5];
root_node = new tree_node(n_array); 

function show_tree(root_node){
  if (root_node.descendants.length != 0){
    root_node.descendants.forEach(element => {
      show_tree(element);
    });
  }
  else {
    console.log(root_node.n_array); // replaced with console.log (as this is a more preferred way of logging data)
  } 
}

show_tree(root_node);
// Logs:
// [1]
// [3]
// [2]
// [5]

与您的第一个函数不同,您的第二个函数会提醒它访问的每个节点,而不仅仅是那些没有后代的节点。那是因为你的 alert() 是函数的第一行,所以它总是会为调用 show_tree() 函数的每个节点运行警报。

我希望它会遇到错误,因为在结束时 树 root_node.descendants 将是未定义的

如果您查看您的 tree_node 类,您会注意到您创建的每个节点都有一个 descendants 属性,该属性被分配给您的构造函数中的一个空数组。因此,当您遍历/循环您的树并进行进一步的递归调用时,您传递给 show_tree() 函数的每个节点都将具有 descendants 属性 - 有时节点的 descendants 将是一个空数组.结果你使用的时候没有报错:

root_node.descendants.forEach(..)

因为在空数组 ([]) 上调用 .forEach() 是有效的。这导致你的另一个问题......:

我不明白为什么这个递归停止并且没有进入 无限循环

当您遍历树中的节点时,您最终会到达您的叶节点(没有后代的节点(.descendants 是一个空数组))。发生这种情况时,您的 .forEach() 循环将不会在其“回调”中运行代码,因此不会对 show_tree() 进行进一步的递归调用。相反,函数返回给它的调用者,它可以开始“解开”递归调用或继续循环其他兄弟节点。

【讨论】:

  • 真丢脸...是的,有一个错字...当然-长度。感谢您对 forEach 的解释,现在一切都清楚了。
【解决方案2】:

我相信您的问题的答案是:

  1. root_node.descendants.lenght != 0这行有错别字,我们可以改成root_node.descendants.length != 0

  2. root_node.descendants 不会是 undefined,而是 [] 或空数组,因此 forEach 根本不会运行,这可能是所需的行为。

我稍微修改了你的方法,包括一个 v1 和 v2 后缀来区分它们,并添加了一个 depth 参数,所以我们可以记录节点深度。另外,我们不使用 alert(),而是使用 console.log()。

我认为现在运行每个工作,v1 方法将只记录叶节点,而 v2 方法将记录所有节点。

class tree_node {
  constructor(n_array) {
    this.n_array = n_array;
    this.descendants = [];
    this.child();
  }
  child() {
    if (this.n_array.length !=1){
      let m = Math.floor(this.n_array.length / 2);
      let l = this.n_array.slice(0,m);
      let r = this.n_array.slice(m);
      const left = new tree_node(l);
      const right = new tree_node(r);
      this.descendants.push(left, right);
    }
    else return 0
  }
}

function show_tree_v1(root_node, depth = 0) {
  if (root_node.descendants.length != 0){
    root_node.descendants.forEach(element => {
      return show_tree_v1(element, depth + 1);
    });
  } else {
    console.log('show_tree_v1: [', root_node.n_array.join(","), '] , depth:', depth)
    return 0;
  } 
}

function show_tree_v2(root_node, depth = 0){
  console.log('show_tree_v2: n_array: [', root_node.n_array.join(","), '], depth:', depth)
  root_node.descendants.forEach(element => {
    return show_tree_v2(element, depth + 1);
  });  
}

let n_array = [1,3,2,5];
root_node = new tree_node(n_array);

console.log('Show tree (v1):')
show_tree_v1(root_node)

console.log('Show tree (v2):')
show_tree_v2(root_node)
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

  • 是的,有一个错字...当然 - 长度,谢谢。感谢您提供两个版本的 show_tree_v2!
  • 很高兴帮助@DonBobskiy,感谢您提出问题!
猜你喜欢
  • 1970-01-01
  • 2018-07-12
  • 2014-03-11
  • 1970-01-01
  • 2017-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多