вопрос новичка по рекурсии в javascript с BST - PullRequest
0 голосов
/ 23 января 2020

Я пытаюсь расширить свое ограниченное понимание рекурсии. Я работал над деревом двоичного поиска и сейчас пытаюсь реализовать функцию обхода. Я начал с использования обхода по предварительному заказу, сделал это без проблем и перешел к заказу, который показался мне более хитрым. Я не мог найти рекурсивное решение самостоятельно, поэтому я нашел его в гугле и нашел много вариантов одного и того же ответа -

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

Очень простой код с еще более простыми объяснениями, ни один из которых не помог мне понять, что именно эта функция делает. Итак, поскольку вы, ребята, были очень полезны ранее, я надеялся, что вы поможете мне расширить мои знания здесь.

  1. Как программа узнает, что нужно остановить до завершения работы дерева? Мне кажется, что он должен продолжать go до левого узла бегуна, пока он не станет нулевым, после чего он пропустит console.log
  2. Как программа узнает, что узел уже напечатан ? Мне кажется, что он просто напечатал бы минимальное значение несколько раз или один раз, прежде чем перейти к максимальному значению, но, очевидно, узлы каким-то образом проверяются.
  3. Как печатаются все значения? Например, если второе наименьшее значение является правым узлом третьего наименьшего значения, как учитывается второе наименьшее значение?

Ответы [ 3 ]

2 голосов
/ 23 января 2020

Самый простой способ понять код - это попробовать его с помощью отладчика. Chrome имеет превосходный отладчик , который вы можете использовать для пошагового пошагового выполнения кода в реальном времени.

Следующий простой способ - использовать журналы консоли для распечатайте, что происходит, и именно так большинство программистов в определенном возрасте выяснили бы, что происходило до того, как отладчики сделали это проще.

Поскольку я не могу сидеть рядом с вами с открытым отладчиком, давайте сделаем следующее Лучше всего и добавьте несколько журналов консоли, чтобы мы могли видеть, что происходит:

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();
}

Итак, давайте попробуем пример BST:

enter image description here

Что может быть сконструировано так, чтобы выглядеть примерно так в JavaScript:

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

Вы можете запустить этот код в инструментах разработчика вашего браузера , вставив вышеуказанную функцию (и нажав Enter ), а затем вызвать его так:

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

Результат должен выглядеть примерно так:

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

Вы также можете использовать онлайн-инструменты, такие как BinaryTreeVisualizer , чтобы увидеть это с помощью анимации. * 10 30 *

Как программа узнает, что нужно остановить до завершения дерева? Мне кажется, что он должен продолжать go до левого узла бегуна, пока он не станет нулевым, и в этот момент он пропустит console.log

Обратите внимание, что когда функция возвращается влево сторона, когда рекурсивная функция возвращается, управление возвращается к родительской функции, которая продолжается вниз по правой стороне. Когда рекурсивная функция возвращается, она не сразу завершает родительскую функцию. Родительская функция обрабатывает рекурсивную функцию как любую другую функцию. Он вызывает его, а затем, когда возвращается, переходит к следующему:

Как программа узнает, что узел уже напечатан? Мне кажется, что он просто напечатал бы минимальное значение многократно или один раз, прежде чем перейти к максимальному значению, но, очевидно, узлы каким-то образом проверяются.

Это - то, где javascript получает немного запутанный. По сути, функция пытается go вниз по левой и правой стороне, но если значение root представляет собой строку, например "B", то root.left и root.right относятся к несуществующим свойствам. В javascript вместо выдачи ошибки просто возвращается undefined. Поэтому, когда мы возвращаемся к root.left и это значение равно undefined, мы ничего не делаем.

Итак, в нашем примере дерева:

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

Наш первый root равен { left: { ... }, value: 4, right: { value: 5 } }

Когда мы go влево, root теперь { left: { value: 1 }, value: 2, right: { value: 3 } }.

Когда мы go ушли снова, root теперь { value: 1 }.

Когда мы go снова ушли, root теперь undefined, поэтому мы ничего не делаем и возвращаемся к предыдущему вызову, где root равен { value: 1 }.

Мы print 1.

Затем мы go направо и root теперь undefined, поэтому мы ничего не делаем и возвращаемся к предыдущему вызову, где root равно { value: 1 }.

Мы закончили с { value: 1 }, поэтому мы возвращаемся к предыдущему вызову, где root равно { left: { value: 1 }, value: 2, right: { value: 3 } }

Мы печатаем 2.

Сейчас мы go вниз вправо, и процесс повторяется, как и для левой, печатая 3.

Затем мы go возвращаемся к предыдущему root, { left: { ... }, value: 4, right: { value: 5 } }

Мы печатаем 4.

Мы go вправо и, как и в предыдущем достаточно, напечатайте 5.

Мы возвращаемся и, поскольку мы достигли исходного вызова функции, мы возвращаем и завершаем программу.

В результате мы напечатали 1, 2, 3, 4, 5, в таком порядке.

Как печатаются все значения? Например, если второе наименьшее значение является правым узлом третьего наименьшего значения, как учитывается второе наименьшее значение?

Я не уверен, что вы спрашиваете, но это Важно отметить, что эта функция не сортирует дерево. Он просто сообщает значения. Так что, если BST не был построен должным образом (например, меньшее значение - это право большего значения), тогда оно будет также печатать эти значения не по порядку.

2 голосов
/ 23 января 2020

enter image description here

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

Строка 2 останавливает прогрессирование рекурсии навсегда.

На в левом нижнем углу графика выполняется оценка узла 2, затем вызывается inOrder с аргументом left, равным undefined. Строка 2 оценивается как true и сразу возвращается. Как только он вернулся в вызывающую точку, выполнение продолжается. Строка 4 оценивается, то есть 2 выводится на консоль. Затем строка 5 оценивается. Это происходит каждый раз, когда алгоритм достигает узла без левого или правого поддерева.

Q2

Он использует стековую природу самого языка программирования, чтобы отслеживать, где он находится на графике. Каждый раз, когда вызывается функция, в стек добавляется новый кадр стека, и каждый раз, когда функция завершает работу, этот кадр стека удаляется.

Q3

Узлы печатается в соответствии с их положением на графике, а не с их значением.

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)
0 голосов
/ 24 января 2020

Как уже говорили другие, использование отладчика Chrome для пошагового выполнения этого кода - отличный способ начать понимать, что происходит. Я предлагаю также нарисовать ваши собственные картинки, похожие на то, что показывали другие в своих ответах, чтобы помочь вам следить за тем, что делает отладчик.

Как программа узнает, что нужно остановить до завершения работы дерева? Мне кажется, что он должен продолжать go до левого узла бегуна, пока он не станет нулевым, после чего он пропустит console.log

Функция постоянно посещает левый узел, пока не будет достигает листа. Затем он постепенно возвращается, распечатывает данные и посещает каждый правый узел. Программа завершит работу после посещения каждого узла в дереве.

Как программа узнает, что узел уже напечатан? Мне кажется, что он просто напечатал бы минимальное значение несколько раз или один раз, прежде чем перейти к максимальному значению, но, очевидно, узлы каким-то образом проверяются.

Он не отслеживает посещенные узлы, так как вы предполагаете. Он проходит как можно дальше вниз по левой стороне дерева, прежде чем вернуться обратно к root и посетить правую сторону по пути.

Как печатаются все значения? Например, если второе наименьшее значение является правым узлом третьего наименьшего значения, как учитывается второе наименьшее значение?

Поскольку при каждом рекурсивном вызове оно печатает значение текущего "root".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...