簡體   English   中英

如何通過遞歸和輔助函數理解作用域/閉包?

[英]How to understand scope/closure with recursion and helper function?

以下是帶有簡單問題的示例:

例 1:求二叉樹的最大深度。

我得到了正確的答案,但不知道為什么我原來的錯誤答案是錯誤的。

正確答案:

var maxDepth = function(root) {
    if (root === null) return 0;
    var maxDepth = 1;
    maxDepth = maxDepthHelper(root, 1, maxDepth);
    return maxDepth;
};

function maxDepthHelper(tree, depth, maxDepth) {
    if (tree.left === null && tree.right === null) {
        maxDepth = depth > maxDepth ? depth : maxDepth;
        return maxDepth;
    }
    if (tree.left) {
    maxDepth = maxDepthHelper(tree.left, depth + 1, maxDepth);
    }
    if (tree.right) {
    maxDepth = maxDepthHelper(tree.right, depth + 1, maxDepth);
    }
    return maxDepth;
}

錯誤的答案:

var maxDepth = function(root) {
    if (root === null) return 0;
    var maxDepth = 1;
    maxDepthHelper(root, 1, maxDepth);
    return maxDepth;
};

function maxDepthHelper(tree, depth, maxDepth) {
    if (tree.left === null && tree.right === null) {
        maxDepth = depth > maxDepth ? depth : maxDepth;
        return;
    }
    if (tree.left) {
        maxDepthHelper(tree.left, depth + 1, maxDepth);
    }
    if (tree.right) {
        maxDepthHelper(tree.right, depth + 1, maxDepth);
    }
}

這與我認為 maxDepth 應該由輔助函數更改有關,最終當我返回時它應該返回更改但它沒有。 它只返回 1,這是我分配給它的原始值。 但是在下面的示例中,我可以從輔助函數中的父級更改變量,那么我在這里遺漏了什么?

示例 2:給定一棵二叉搜索樹,編寫一個函數 kthSmallest 來查找其中的第 k 個最小元素。

解決方案:

var kthSmallest = function(root, k) {
    let smallestArr = [];
    kthSmallestHelper(root, k, smallestArr);
    return smallestArr.pop()
};

function kthSmallestHelper(bst, k, array) {
    if (bst === null) return;
    kthSmallestHelper(bst.left, k, array);
    if (array.length === k) return;
    array.push(bst.val);
    kthSmallestHelper(bst.right, k, array);
}

在第二個程序中, maxDepth是一個數字,通過值(復制)而不是通過引用傳遞。 遞歸調用實際上是空操作,它們的返回值會立即被丟棄。 對於正在學習如何將不同的變量類型從一個函數傳遞到另一個函數的初學者來說,這是一個常見的錯誤。


也就是說,遞歸是一種函數式遺產,因此將它與函數式風格結合使用會產生最好的結果。 這意味着避免諸如突變和變量重新分配之類的副作用。 你可以大大簡化你的depth計划——

function depth(tree)
{ if (tree == null)
    return 0
  else
    return 1 + max(depth(tree.left), depth(tree.right))
}

function max (a, b)
{ if (a > b)
    return a
  else
    return b
}

基於表達式的語法通常是首選,因為表達式的計算結果為值,而語句(如ifreturn )則不是 -

const depth = tree =>
  tree == null
    ? 0
    : 1 + max(depth(tree.left), depth(tree.right))

const max = (a, b) =>
  a > b
    ? a
    : b

您的kthSmallest程序更難,但 JavaScript 的命令式生成器可以快速解決問題。 突變k--已使用,但無法從函數外部觀察到 -

function *inorder (tree)
{ if (tree == null) return
  yield* inorder(tree.left)
  yield tree.val
  yield* inorder(tree.right)
}

function kthSmallest (tree, k)
{ for (const v of inorder(tree))
    if (k-- == 0)
      return v
}

這個程序的純表達形式略有不同——

const inorder = tree =>
  tree == null
    ? []
    : [ ...inorder(tree.left), tree.val, ...inorder(tree.right) ]

const kthSmallest = (tree, k) =>
  inorder(tree)[k]

這是一個功能演示 -

import { depth, fromArray, inorder, kthSmallest } from "./Tree"

const rand = _ =>
  Math.random() * 100 >> 0

const t =
  fromArray(Array.from(Array(10), rand))

console.log("inorder:", Array.from(inorder(t)))
console.log("depth:", depth(t))
console.log("0th:", kthSmallest(t, 0))
console.log("1st:", kthSmallest(t, 1))
console.log("2nd:", kthSmallest(t, 2))
console.log("99th:", kthSmallest(t, 99))

輸出 -

inorder: [ 12, 14, 25, 44, 47, 53, 67, 70, 85, 91 ]
depth: 5
0th: 12
1st: 14
2nd: 25
99th: undefined

編寫像下面的Tree這樣的模塊是分離關注點和組織代碼的好習慣 -

// Tree.js

const empty =
  null

const node = (val, left = empty, right = empty) =>
  ({ val, left, right })

const fromArray = (a = []) =>
  a.length < 1
    ? empty
    : insert(fromArray(a.slice(1)), a[0])

const insert = (t = empty, v = null) =>
  t === empty
    ? node(v)
: v < t.val
    ? node(t.val, insert(t.left, v), t.right)
: v > t.val
    ? node(t.val, t.left, insert(t.right, v))
: t

const depth = (tree = empty) => ...

const inorder = (tree = empty) => ...

const kthSmallest = (tree = empty, k = 0) => ...

export { depth, empty, fromArray, inorder, kthSmallest, node }

展開下面的代碼段以在您自己的瀏覽器中驗證結果 -

 const empty = null const node = (val, left = empty, right = empty) => ({ val, left, right }) const fromArray = (a = []) => a.length < 1 ? empty : insert(fromArray(a.slice(1)), a[0]) const insert = (t = empty, v) => t === empty ? node(v) : v < t.val ? node(t.val, insert(t.left, v), t.right) : v > t.val ? node(t.val, t.left, insert(t.right, v)) : t const inorder = (tree = empty) => tree === empty ? [] : [ ...inorder(tree.left), tree.val, ...inorder(tree.right) ] const kthSmallest = (tree = empty, k = 0) => inorder(tree)[k] const depth = (tree = empty) => tree == null ? 0 : 1 + Math.max(depth(tree.left), depth(tree.right)) const rand = _ => Math.random() * 100 >> 0 const t = fromArray(Array.from(Array(10), rand)) console.log("inorder:", JSON.stringify(Array.from(inorder(t)))) console.log("depth:", depth(t)) console.log("0th:", kthSmallest(t, 0)) console.log("1st:", kthSmallest(t, 1)) console.log("2nd:", kthSmallest(t, 2)) console.log("99th:", kthSmallest(t, 99))

函數maxDepth (不幸的命名)中的變量maxDepth (我們稱之為外部maxDepth )存儲一個值(數字 1)。 當您調用maxDepthHelper(root, 1, maxDepth) ,值 1 被傳入並存儲在maxDepth內的局部變量maxDepthHelper 我們可以將任何內容分配給本地maxDepth ,但它不會影響存儲在外部maxDepth的值,因為它們是兩個不同的變量。

可變smallestArr在函數kthSmallest存儲的值; 該值是指向空數組的指針(內存位置)。 kthSmallestHelper(root, k, smallestArr) ,就像以前一樣,該值(指針)被傳入並存儲在kthSmallestHelper的局部變量array中。 實際上,現在arraysmallestArr都存儲指向同一個空數組的指針(內存位置)。 如果我們現在對array進行任何賦值,比如array=['some new arr'] ,變量smallestArr不會受到影響。 但是當你調用一個array.push(bst)方法時,比如array.push(bst) ,發生的事情是 Javascript 引擎查看存儲在array中的指針,並修改存儲在該內存位置的數組。 因為smallestArr存儲了指向這個修改數組的指針,如果你調用smallestArr.pop() ,Javascript 引擎將彈出修改數組的最后一項。

要記住的重要一點是,每當您編寫像let x = /* some array or object */這樣的表達式時,就會創建一個數組/對象,然后將指向該數組/對象的指針存儲在變量中。 如果你寫let x = /* some primitive value (like 3)*/ ,值 3 直接存儲在變量中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM