简体   繁体   English

关于使用 BST 在 javascript 中递归的初学者问题

[英]beginner question on recursion in javascript with BST

I am trying to broaden my limited understanding of recursion.我试图扩大我对递归的有限理解。 I have been working on a Binary Search Tree and am currently attempting to implement a traversal function.我一直在研究二叉搜索树,目前正在尝试实现遍历功能。 I started off with using a Pre-order Traversal, did that no problem and moved on to In-order which seemed much trickier to me.我开始使用 Pre-order Traversal,没有问题,然后转向 In-order,这对我来说似乎更棘手。 I couldn't figure out a recursive solution on my own so I googled it and found many variations of the same answer-我无法自己找出递归解决方案,所以我用谷歌搜索它并发现了相同答案的许多变体 -

function inOrderHelper(root) {
   if (root !== null) {
      inOrderHelper(root.left);
      console.log(root.data);
      inOrderHelper(root.right);
   }
}

Very simple code with even simpler explanations, none of which helped me undestand what exactly this function is doing.非常简单的代码和更简单的解释,没有一个能帮助我理解这个函数到底在做什么。 So, since you guys have been so helpful previously, I was hoping you could help me expand my knowledge here.所以,既然你们以前一直很有帮助,我希望你们能在这里帮助我扩展我的知识。

  1. How does the program know to stop before the tree is finished?程序如何知道在树完成之前停止? It seems to me that it should continue to go to the runner's left node until it is null, at which point it will skip the console.log在我看来它应该继续去跑步者的左节点,直到它为空,此时它会跳过console.log
  2. How does the program know that a node has already been printed?程序如何知道一个节点已经被打印? It seems to me that it would just print the minimum value repeatedly or once before traversing to the maximum value but apparently the nodes are being checked off somehow.在我看来,它只会在遍历最大值之前重复或一次打印最小值,但显然节点正在以某种方式被检查。
  3. How are all the values printed?如何打印所有值? For example, if the second smallest value is the right node of the third smallest, value, how is the second smallest value account for?例如,如果第二个最小值是第三个最小值的右节点,那么第二个最小值如何解释?

在此处输入图片说明

1  function inOrder(root) {
2    if (!root) return;
3    inOrderHelper(root.left);
4    console.log(root.data);
5    inOrderHelper(root.right);
6  }
7
8  inOrder(root) // 2 4 6 7 9

Q1第一季度

Line 2 stops the recursion progressing forever.第 2 行永远停止递归进行。

At the bottom left of the graph, node 2 is evaluated, then inOrder is invoked with left as an argument, which is undefined .在图的左下角,计算节点 2,然后使用left作为参数调用inOrder ,这是undefined Line 2 evaluates to true and immediately returns.第 2 行计算结果为true并立即返回。 Once it has returned to the calling point, execution continues.一旦它返回到调用点,继续执行。 Line 4 is evaluated, meaning 2 is printed to the console.第 4 行被评估,这意味着2被打印到控制台。 Then line 5 is evaluated.然后评估第 5 行。 This happens every time the algorithm hits a node without a left or a right subtree.每次算法遇到没有左子树或右子树的节点时都会发生这种情况。

Q2 Q2

It uses the stack-based nature of the programming language itself, to keep track of where it is in the graph.它使用编程语言本身的基于堆栈的特性来跟踪它在图中的位置。 Every time the function is called a new stack frame is added to the stack, and every time a function completes, that stack frame is removed.每次调用该函数时,都会将一个新的堆栈帧添加到堆栈中,并且每次函数完成时,都会删除该堆栈帧。

Q3 Q3

Nodes are printed according to their position in the graph, not their value.节点是根据它们在图中的位置而不是它们的值来打印的。

 const root = { data: 7, left: { data: 4, left: { data: 2 }, right: { data: 6 } }, right: { data: 9 } } function inOrder(root) { if (!root) return; inOrder(root.left); console.log(root.data); inOrder(root.right); } inOrder(root)

The easiest way to understand code is usually to try it out with a debugger.理解代码的最简单方法通常是使用调试器进行尝试。 Chrome has an excellent debugger which you can use to step through the code, line by line, as it runs in real time. Chrome 有一个出色的调试器,您可以使用它逐行逐行调试代码,因为它是实时运行的。

The next easiest way is to use console logs to print out what's happening, which is how most programmers over a certain age would figure out what was happening before debuggers made it easier.下一个最简单的方法是使用控制台日志打印出正在发生的事情,这是大多数超过一定年龄的程序员在调试器变得更容易之前弄清楚发生了什么的方式。

Since I can't sit next to you with a debugger open, let's do the next best thing and add some console logs so we can see what's happening:由于我不能坐在你旁边打开调试器,让我们做下一个最好的事情并添加一些控制台日志,以便我们可以看到发生了什么:

function inOrderHelper(root) {
   console.group("Entering inOrderHelper with ", root);
   if (root !== null && root !== undefined) {
      console.log("Root is not null, so continue");

      console.group("Traversing down the left node");
      inOrderHelper(root.left);
      console.groupEnd();

      console.log("The root value is ", root.value);

      console.group("Traversing down the right node");
      inOrderHelper(root.right);
      console.groupEnd();
   } else {
      console.log("Root is null, so back up");
   }
   console.log("Exiting inOrderHelper");
   console.groupEnd();
}

So let's try an example BST:因此,让我们尝试一个示例 BST:

在此处输入图片说明

Which could be constructed to look something like this in JavaScript:在 JavaScript 中可以构造成如下所示的样子:

{
  left: {
    left: {
      value: 1,
    },
    value: 2,
    right: {
      value: 3,
    },
  },
  value: 4,
  right: {
    value: 5,
  },
}

You can run this code in your browser's dev tools by pasting in the above function (and hitting enter) and then calling it like so:您可以通过粘贴上述函数(并按 Enter)然后像这样调用它,在浏览器的开发工具中运行此代码

inOrderHelper({
  left: {
    left: {
      value: 1,
    },
    value: 2,
    right: {
      value: 3,
    },
  },
  value: 4,
  right: {
    value: 5,
  },
})

The result should look something like this:结果应该是这样的:

Entering inOrderHelper with  {left: {…}, value: 4, right: {…} }
  Root is not null, so continue
  Traversing down the left node
  Entering inOrderHelper with  {left: {…}, value: 2, right: {…}}
    Root is not null, so continue
    Traversing down the left node
    Entering inOrderHelper with  { value: 1 }
      Root is not null, so continue
      Traversing down the left node
      Entering inOrderHelper with  undefined
        Root is null, so back up
        Exiting inOrderHelper

      The root value is  1

      Traversing down the right node
      Entering inOrderHelper with  undefined
        Root is null, so back up
        Exiting inOrderHelper
      Exiting inOrderHelper

    The root value is 2

    Traversing down the right node
    Entering inOrderHelper with { value: 3 }
      Root is not null, so continue
      Traversing down the left node
      Entering inOrderHelper with  undefined
        Root is null, so back up
        Exiting inOrderHelper

      The root value is  3

      Traversing down the right node
      Entering inOrderHelper with  undefined
        Root is null, so back up
        Exiting inOrderHelper
      Exiting inOrderHelper
    Exiting inOrderHelper

  The root value is 4

  Traversing down the right node
  Entering inOrderHelper with { value: 5 }
    Root is not null, so continue
    Traversing down the left node
    Entering inOrderHelper with  undefined
      Root is null, so back up
      Exiting inOrderHelper

    The root value is 5

    Traversing down the right node
    Entering inOrderHelper with  undefined
      Root is null, so back up
      Exiting inOrderHelper
    Exiting inOrderHelper
  Exiting inOrderHelper

You can also use online tools, like BinaryTreeVisualizer , to see this demonstrated with animations.您还可以使用在线工具(如BinaryTreeVisualizer )来查看动画演示。

How does the program know to stop before the tree is finished?程序如何知道在树完成之前停止? It seems to me that it should continue to go to the runner's left node until it is null, at which point it will skip the console.log在我看来它应该继续去跑步者的左节点,直到它为空,此时它会跳过console.log

Notice that when the function recurses down the left side, when the recursive function returns, control returns to the parent function, which continues on down the right side.请注意,当函数在左侧向下递归时,当递归函数返回时,控制权返回到父函数,父函数在右侧继续向下。 When a recursive function returns, it doesn't immediately end the parent function.当递归函数返回时,它不会立即结束父函数。 The parent function treats the recursive function like any other function.父函数像对待任何其他函数一样对待递归函数。 It calls it and then, when it returns, goes on to the next thing.它调用它,然后当它返回时,继续进行下一件事。

How does the program know that a node has already been printed?程序如何知道一个节点已经被打印? It seems to me that it would just print the minimum value repeatedly or once before traversing to the maximum value but apparently the nodes are being checked off somehow.在我看来,它只会在遍历最大值之前重复或一次打印最小值,但显然节点正在以某种方式被检查。

This is where javascript gets a little bit confusing.这就是 javascript 有点令人困惑的地方。 Essentially, the function is trying to go down the left and right side, but if the root value is a string, like "B" , then root.left and root.right refer to properties that don't exist.本质上,该函数试图在左侧和右侧向下移动,但如果根值是一个字符串,如"B" ,则root.leftroot.right指的是不存在的属性。 In javascript, rather than throwing an error, it just returns undefined .在 javascript 中,它不会抛出错误,而是返回undefined So when we recurse on root.left and that value is undefined then we do nothing.因此,当我们在root.left递归并且该值undefined我们什么都不做。

So, in our example tree:因此,在我们的示例树中:

{
  left: {
    left: {
      value: 1,
    },
    value: 2,
    right: {
      value: 3,
    },
  },
  value: 4,
  right: {
    value: 5,
  },
}

Our first root is { left: { ... }, value: 4, right: { value: 5 } }我们的第一个根是{ left: { ... }, value: 4, right: { value: 5 } }

When we go to the left, root is now { left: { value: 1 }, value: 2, right: { value: 3 } } .当我们向左走时,root 现在是{ left: { value: 1 }, value: 2, right: { value: 3 } }

When we go left again, the root is now { value: 1 } .当我们再次向左走时,根现在是{ value: 1 }

When we go left again, the root is now undefined , so we do nothing and return to the previous call where the root is { value: 1 } .当我们再次向左走时,根现在是undefined ,所以我们什么都不做并返回到先前的调用,其中根是{ value: 1 }

We print 1 .我们打印1

Then we go to the right and the root is now undefined , so we do nothing and return to the previous call where the root is { value: 1 } .然后我们向右走,现在根是undefined ,所以我们什么都不做并返回到前一个调用,其中根是{ value: 1 }

We're done with { value: 1 } , so we return to the previous call where the root is { left: { value: 1 }, value: 2, right: { value: 3 } }我们完成了{ value: 1 } ,所以我们返回到前一个调用,其中根是{ left: { value: 1 }, value: 2, right: { value: 3 } }

We print 2 .我们打印2

Now we go down to the right, and the process repeats as it did for the left, printing 3 .现在我们往右走,这个过程和左边一样重复,打印3

We then go back up to the previous root, { left: { ... }, value: 4, right: { value: 5 } }然后我们回到上一个根, { left: { ... }, value: 4, right: { value: 5 } }

We print 4 .我们打印4

The we go to the right and, as with the previous examples, print 5 .我们向右移动,与前面的示例一样,打印5

We return and, since we've arrived at the original function call, we return and end the program.我们返回,因为我们已经到达了最初的函数调用,我们返回并结束程序。

The end result is that we printed 1 , 2 , 3 , 4 , 5 , in that order.最终结果是我们按顺序打印了12345

How are all the values printed?如何打印所有值? For example, if the second smallest value is the right node of the third smallest, value, how is the second smallest value account for?例如,如果第二个最小值是第三个最小值的右节点,那么第二个最小值如何解释?

I'm not sure what you're asking, but it's important to note that this function does not sort the tree.我不确定你在问什么,但重要的是要注意这个函数不会对树进行排序。 It just reports the values.它只是报告值。 So if the BST was not constructed properly (eg a smaller value is the right of a larger value), then it will print those values out of order as well.因此,如果 BST 构造不正确(例如,较小的值是较大值的右侧),那么它也会无序打印这些值。

As other's have stated, using the Chrome debugger to step through this code is a great way to start to understand what is going on.正如其他人所说,使用 Chrome 调试器逐步执行此代码是开始了解正在发生的事情的好方法。 I suggest also drawing your own pictures similar to what other's have shown in their answers to help you follow along with what the debugger is doing.我还建议您绘制自己的图片,类似于其他人在他们的答案中显示的图片,以帮助您了解调试器正在做什么。

How does the program know to stop before the tree is finished?程序如何知道在树完成之前停止? It seems to me that it should continue to go to the runner's left node until it is null, at which point it will skip the console.log在我看来它应该继续去跑步者的左节点,直到它为空,此时它会跳过console.log

The function continually visits the left node until it reaches a leaf.该函数不断访问左节点,直到它到达一个叶子节点。 Then it gradually returns, prints out the data, and visits each right node.然后它逐渐返回,打印出数据,并访问每个正确的节点。 The program will terminate after it has visited every node in the tree.程序将在访问完树中的每个节点后终止。

How does the program know that a node has already been printed?程序如何知道一个节点已经被打印? It seems to me that it would just print the minimum value repeatedly or once before traversing to the maximum value but apparently the nodes are being checked off somehow.在我看来,它只会在遍历最大值之前重复或一次打印最小值,但显然节点正在以某种方式被检查。

It doesn't keep track of visited nodes as you assume.它不会像您假设的那样跟踪访问过的节点。 It traverses as far as it can down the left side of the tree before working its way back to the root and visiting the right side along the way.它尽可能地沿着树的左侧向下遍历,然后返回根部并沿途访问右侧。

How are all the values printed?如何打印所有值? For example, if the second smallest value is the right node of the third smallest, value, how is the second smallest value account for?例如,如果第二个最小值是第三个最小值的右节点,那么第二个最小值如何解释?

Because at each recursive call, it prints the value of the current "root".因为在每次递归调用时,它都会打印当前“根”的值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM