簡體   English   中英

返回在 Python 中遞歸創建的不同結果的列表

[英]Returning list of different results that are created recursively in Python

最近我一直在處理 Python 中的一些遞歸問題,我必須使用遞歸生成可能的配置列表(即給定字符串的排列列表、子字符串列表等)。 我很難找到最佳實踐,也很難理解如何在遞歸中管理這種變量。

我將給出 生成二叉樹問題的示例。 我或多或少知道我必須在遞歸中實現什么:

  1. 如果 n=1,則只返回一個節點。
  2. 如果 n=3,則返回唯一可能的二叉樹。
  3. 對於 n>3,創建一個節點然后探索可能性:左節點是無子節點,右節點是無子節點,沒有一個節點是無子節點。 遞歸地探索這些可能性。

現在,我最難以想象的事情是我將如何准確地到達樹列表。 Currently the practice I do is pass along a list in the function call (as an argument) and the function would return this list, but then the problem is in case 3 when calling the recursive function to explore the possibilites for the nodes it would be返回一個列表,而不是將節點附加到我正在構建的樹上。 當我在腦海中描繪遞歸樹時,我想象一個“樹”變量,它對每個樹葉都是唯一的,這些樹被添加到由“根”(即第一個)調用返回的列表中。 但我不知道這是否可能。 我想到了一個全局列表,遞歸 function 沒有返回任何東西(只是附加到它上面),但我認為問題是在每次調用時 function 都會收到變量的副本。

在遞歸的這些情況下,我該如何處理生成組合和返回配置列表? 雖然我舉了一個例子,但答案越籠統越好。 我還想知道在這方面是否有“最佳實踐”。

目前我所做的做法是在 function 調用(作為參數)中傳遞一個列表,並且 function 將返回此列表

這不是解決遞歸問題的最純粹的方法。 如果您可以制作遞歸 function 以便解決子問題而無需必須使用額外的參數變量,那就更好了。 因此,遞歸 function 應該只返回一個結果,就好像它是唯一的調用(由測試框架)。 所以在這個例子中,遞歸調用應該返回一個帶有樹的列表。

或者,遞歸 function 可以是不返回列表但產生單個值(在本例中為樹)的子函數。 然后調用者可以決定是否將其打包到列表中。 這更像是pythonic。

對於示例問題,識別一些不變量也很重要。 例如,很明顯當n為偶數時沒有解。 至於遞歸方面:一旦你決定創建一個根,那么它的左右子樹都會有奇數個節點。 當然,這是針對此問題的特定觀察,但尋找此類問題的屬性很重要。

最后,同樣重要的是查看相同的子問題是否可以重復出現多次。 在示例問題中肯定就是這種情況:例如,左子樹有時可能與右子樹具有相同數量的節點。 在這種情況下,記憶將提高效率(動態編程)。

當遞歸 function 返回一個列表時,調用者可以迭代該列表以檢索其元素(示例中的樹),並使用它們構建滿足調用者任務的擴展結果。 在示例情況下,這意味着從遞歸檢索的列表中獲取的樹作為子節點附加到新的根節點。 然后這個新樹被附加到一個列表中(與遞歸調用返回的無關)。 這個新列表在許多情況下會更長,盡管這取決於問題的類型。

為了進一步說明解決這些問題的方法,這里是示例問題的解決方案:使用主要 function 進行遞歸調用,並使用記憶:

class Solution:
    memo = { 1: [TreeNode()] }
    
    def allPossibleFBT(self, n: int) -> List[Optional[TreeNode]]:
        # If we didn't solve this problem before...
        if n not in self.memo:
            # Create a list for storing the results (the trees)
            results = []
            # Before creating any root node, 
            #    decide the size of the left subtree.
            #    It must be odd
            for num_left in range(1, n, 2):
                # Make the recursive call to get all shapes of the
                # left subtree
                left_shapes = self.allPossibleFBT(num_left)
                # The remainder of the nodes must be in the right subtree
                num_right = n - 1 - num_left  # The root also counts as 1
                right_shapes = self.allPossibleFBT(num_right)
                # Now iterate the results we got from recursion and 
                #    combine them in all possible ways to create new trees
                for left in left_shapes:
                    for right in right_shapes:
                        # We have a combination. Now create a new tree from it
                        # by putting a root node on top of the two subtrees:
                        tree = TreeNode(0, left, right)
                        # Append this possible shape to our results
                        results.append(tree)
            # All done. Save this for later re-use
            self.memo[n] = results
        return self.memo[n]

使用列表推導可以使這段代碼更緊湊,但它可能會降低代碼的可讀性。

不要將信息傳遞給遞歸調用,除非他們需要該信息來計算其本地結果。 當你在沒有副作用的情況下編寫時,推理遞歸要容易得多。 因此,不要讓遞歸調用將其自己的結果放入列表中,而是編寫代碼以便使用遞歸調用的結果來創建返回值。

讓我們舉一個簡單的例子,將一個簡單的循環轉換為遞歸,並使用它來累積一個遞增的整數序列。

def recursive_range(n):
    if n == 0:
        return []
    return recursive_range(n - 1) + [n]

我們以自然的方式使用函數:我們使用 arguments 輸入信息,並使用返回值(而不是參數的突變)獲取信息

在你的情況下:

現在,我最難以想象的事情是我將如何准確地到達樹列表。

所以你知道你想在過程結束時返回一個樹列表 因此,自然的方式是,您希望每個遞歸調用也這樣做。

在遞歸的這些情況下,我該如何處理生成組合和返回配置列表? 雖然我舉了一個例子,但答案越籠統越好。

遞歸調用返回子問題的結果列表。 您使用這些結果來創建當前問題的結果列表。

為了編寫遞歸算法,您無需考慮如何實現遞歸。 您無需考慮調用堆棧。 確實需要考慮兩件事:

  • 基本情況是什么?
  • 問題如何遞歸分解? (或者:為什么遞歸非常適合這個問題?)

問題是,遞歸並不特殊 進行遞歸調用就像調用任何其他 function碰巧會為您提供子問題的正確答案一樣。 因此,您需要做的就是了解解決子問題如何幫助您解決當前問題。

暫無
暫無

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

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