JavaScript 递归函数参数

JavaScript Recursive Function Parameters

提问人:bisky 提问时间:7/18/2023 最后编辑:bisky 更新时间:7/19/2023 访问量:86

问:

我正在构建一个二叉搜索树,并制作了一个递归插入新值的函数,我尝试通过两种方式放置其参数:

1- 通过赋值运算符将参数重新分配为新值

2-通过放置参数的新值而不使用赋值运算符重新分配它

对我来说,我看不出也不知道它们之间有什么区别,我认为每次函数调用自己时,它都会将节点参数重新分配给它们中的右侧或左侧。 我的问题是为什么这两种方式的工作方式不一样,这两种方式有什么区别?

这是两者的代码和我得到的输出:

class Node {
    constructor(key, left, right) {
        this.key = key || null;
        this.left = left || null;
        this.right = right || null;
    }
}

class Tree {
    constructor(arr) {
        this.tree = this.buildTree(arr);
    }
......
}
let tree = new Tree([3, 2, 4, 1, 6, 7, 5, 8, 10, 9, 12, 11])
tree.insert(100)

#1- 通过赋值运算符重新分配参数和新值

insert(value, node = this.tree) {
        if (node == null) return node = new Node(value)
        if (value == node.key) return node; // prevent dublicated values;
        if (value > node.key) node.right = this.insert(value, node = node.right);
        else if (value < node.key) node.left = this.insert(value, node = node.left);
        return node
    }

===>这种方式没有将新节点放在正确的位置,而是从树中删除了其他节点 这是它的输出: recursive function parameters with assignment operator

#2- 通过放置参数的新值,并在没有赋值运算符的情况下重新分配它

insert(value, node = this.tree) {
        if (node == null) return node = new Node(value)
        if (value == node.key) return node; // prevent dublicated values;
        if (value > node.key) node.right = this.insert(value, node.right);
        else if (value < node.key) node.left = this.insert(value, node.left);
        return node
    }

===>这种方式效果很好,可以在正确的位置插入新值而不会删除任何值 父节点 这是它的输出recursive function parameters without assignment operator

JavaScript 递归 参数传递 binary-search-tree

评论


答:

0赞 Scott Hunter 7/18/2023 #1

#1 在进行递归调用的过程中发生变化,从而改变它返回的内容;#2 没有。调试器将有助于明确这如何导致节点“丢失”。node

3赞 Scott Sauyet 7/18/2023 #2

也许这会有所帮助。以下内容可能看起来不正确:

insert(value, node = this.tree) {
    if (node == null) return node = new Node(value)
    if (value == node.key) return node; // prevent dublicated values;
    if (value > node.key) {
        node = node.right;
        node.right = this.insert(value, node);
    } else if (value < node.key) {
        node = node.left;
        node.left = this.insert(value, node);
    }
    return node;
}

当然不是。我们不想在返回之前更改其标识。但从本质上讲,这就是你的代码的作用。 此行的作用与上面的块相同:nodeif

if (value > node.key) node.right = this.insert(value, node = node.right);

首先,我们将变量的值重新赋值为 。然后我们调用 with new node 值。然后在函数的末尾,我们返回 ,并返回其新值nodenode.rightinsertnode

有一个强有力的论据,永远不要重新分配输入参数。这只是这个想法的一个证据。

评论

1赞 bisky 7/19/2023
现在更清楚了,非常感谢
1赞 Mulan 7/19/2023
关于重新分配和突变造成的痛苦的一个很好的教训!
3赞 Mulan 7/19/2023 #3

无法访问的代码

为了配合 Scott Sauyet 的回答,我建议你也简化你的条件。if..

insert(value, node = this.tree) {
  if (node == null)          // ..
  if (value == node.key)     // ..
  if (value > node.key)      // ..
  else if (value < node.key) // ..
  return node                // ???
}

想想看——如果值不小、不大、不等,那它是什么?撇开错误不谈(来自不正确的值),它是冗余的、无法访问的代码。如果该值既不大也不小,则它必须相等 -???

insert(value, node = this.tree) {
  if (node == null)      // node is empty
  if (value > node.key)  // value is greater (implicit: node not empty)
  if (value < node.key)  // value is lesser  (implicit: node not empty; value not greater)
  return node            // value is equal   (implicit: node not empty; value not greater, not lesser)
}

持久性数据结构

重新分配(或任何突变)也使我们的计划更难考虑。我们可以创建新节点,而不是改变旧节点。当一个数据结构保证它永远不会改变时,它被称为持久性数据结构——

insert(value, node = this.tree) {
  // is the node empty?
  if (node == null)
    return new Node(value)

  // the node is not empty. is the value greater?
  else if (value > node.key)
    return new Node(node.value, node.left, this.insert(value, node.right))

  // the node is not empty and the value is not greater. is it less?
  else if (value < node.key)
    return new Node(node.value, this.insert(value, node.left), node.right)

  // the node is not empty and the value is neither greater nor lesser
  else
    return node
}

面向模块的设计

最后,我建议将树函数与类解耦 -

// tree.js
const empty = Symbol("tree.empty")

const node = (value, left = empty, right = empty) =>
  ({ value, left, right })
  
const insert = (t, value) => {
  if (t === empty)
    return node(value)
  else if (value < t.value)
    return node(t.value, insert(t.left, value), t.right)
  else if (value > t.value)
    return node(t.value, t.left, insert(t.right, value))
  else
    return t
}

const fromArray = (arr) => arr.reduce(insert, empty)

const toString = (t) => ...

那么如果你想要一个面向对象的接口,就把它创建成一个围绕普通函数接口的薄包装器——

// tree.js (continued)
class tree {
  static fromArray(arr) { return new tree(fromArray(arr)) }
  constructor(t) { this.t = t }
  insert(value) { return new tree(insert(this.t, value)) }
  toString() { return toString(this.t) }
}

通过模块导出两个接口 -

// tree.js (continued)
export { empty, fromArray, insert, node, toString }
export default tree

现在,您的模块提供了双接口。您可以使用功能模块并利用摇树 -

import { insert, fromArray, toString } from "./tree.js"

const mytree = insert(
  fromArray([3, 2, 4, 1, 6, 7, 5, 8, 10, 9, 12, 11]),
  100
)

console.log(toString(mytree))

或者你可以通过类接口导入整个模块——

import tree from "./tree.js"

const mytree = tree
  .fromArray([3, 2, 4, 1, 6, 7, 5, 8, 10, 9, 12, 11])
  .insert(100)

console.log(mytree)

评论

0赞 Scott Sauyet 7/20/2023
一如既往的美丽!
1赞 bisky 7/20/2023
非常感谢你,我真的很感谢你,它真的很有帮助,设计得很好,关于工厂函数和模块化设计,我通常在构建页面时更多地使用这种模式,而不是面向对象的设计。只是这一次,我正在练习一般的数据结构,所以我决定使用类来练习这部分,而不仅仅是JS。