查找二叉搜索树的深度时超过最大递归深度

Maximum recursion depth exceeded when finding the depth of binary-search-tree

这是我的代码:

# increase the limit of recursion
import sys
sys.setrecursionlimit(100000)
import numpy as np

# make binary search tree from list
def bst_build(seq):
    binary_search_tree = [seq[0], None, None]
    seq.pop(0)

    def add_child(main, sub):
        if len(sub) != 0:
            if main[0] > sub[0]:
                if main[1] == None:
                    main.pop(1)
                    main.insert(1, [sub[0], None, None])
                    sub.pop(0)
                    add_child(binary_search_tree, sub)
                else:
                    add_child(main[1], sub)
            
            elif main[0] < sub[0]:
                if main[2] == None:
                    main.pop(2)
                    main.insert(2, [sub[0], None, None])
                    sub.pop(0)
                    add_child(binary_search_tree, sub)        
                else:
                    add_child(main[2], sub)
            else:
                sub.pop(0)         
        else:
            pass

    add_child(binary_search_tree, seq)

    return binary_search_tree

# check the correctness
def bst_check(b):
    if b is None: return True
    if b[1] is not None:
        if b[1][0] >= b[0]: return False
        if bst_check(b[1]) == False: return False
    if b[2] is not None:
        if b[2][0] <= b[0]: return False
        if bst_check(b[2]) == False: return False
    return True

# find depth of binary search tree
def bst_depth(b):
    maximum_depth = 0
    current_depth = 0

    def find_depth(tree):
        nonlocal maximum_depth
        nonlocal current_depth

        if len(tree) != 1:
            if tree[1] != None and len(tree[1]) != 1:
                current_depth += 1
                find_depth(tree[1])
            else:
                tree.pop(1)
                if maximum_depth <= current_depth:
                    maximum_depth = current_depth
                current_depth = 0
                find_depth(b)
        else:
            pass  
    find_depth(b)      

    return maximum_depth

unsorted = list(np.random.randint(0,100,10))
sorted = unsorted.copy()
sorted.sort()

t1 = bst_build(unsorted)
t2 = bst_build(sorted)

print(t1)
print(t2)

print(bst_check(t1), bst_check(t2))
print( bst_depth(t1), bst_depth(t2) )

首先,我从列表中创建一个二叉搜索树,并检查它是否是一个二叉搜索树。

给定列表的第一个元素是根节点,后面的元素成为子节点。 None 附加到叶节点。

例如调用bst_build([4, 2, 1, 3, 6, 5, 7])的结果是:

[4, 
  [2, 
    [1, None, None], 
    [3, None, None]
  ], 
  [6, 
    [5, None, None],
    [7, None, None]]
  ]
]

结果是二叉搜索树,因为左子节点小于父节点,右子节点大于父节点。因此调用 bst_child 的结果是 True.

然后我添加了查找二叉搜索树深度的代码。 我通过对第一个列表进行排序,制作了两个不同的二叉搜索树。

在这种情况下,第一个列表是[4,2,1,3,6,5,7],第二个列表是[1,2,3,4,5,6,7],已排序。

从第一个列表构建的二叉搜索树的深度为2,而从排序列表构建的二叉搜索树的深度为6。

unsorted = [4,2,1,3,6,5,7]
sorted = unsorted.copy()
sorted.sort()

t1 = bst_build(unsorted)
t2 = bst_build(sorted)

print(t1)
print(t2)
print(bst_check(t1), bst_check(t2))
print( bst_depth(t1), bst_depth(t2) )

输出是(在漂亮地打印嵌套列表之后):

[4, 
  [2, 
    [1, None, None], 
    [3, None, None]
  ], 
  [6, 
    [5, None, None],
    [7, None, None]]
  ]
]

[1, None, 
  [2, None, 
    [3, None,
      [4, None,
        [5, None,
          [6, None,
            [7, None, None]
          ]
        ]
      ]
    ]
  ]
]

True True
2 6

我认为我把二叉搜索树和代码查找深度做得很好。 但是当我尝试使用长列表时,我的 vscode 无法打印正确的结果。

当我尝试使用长度为 20 的列表时,它成功了。

unsorted = list(np.random.randint(0,1000,20))
sorted = unsorted.copy()
sorted.sort()

t1 = bst_build(unsorted)
t2 = bst_build(sorted)

print(bst_check(t1), bst_check(t2))
print( bst_depth(t1), bst_depth(t2) )

输出:

True True
5 19

但是,当list的长度在30左右时,是不行的。 排序后的列表长度为30,但结果不是29而是其他一些数字,比如3,或者出现递归错误:

maximum recursion depth exceeded while calling a Python object

所以我通过添加 sys.setrecursionlimit.

增加了递归限制

然后我的代码一直工作到列表的长度大约为 40~50。

但是当列表长度超过50时,这种方法仍然会出现同样的问题。我也尝试过 sys.setrecursionlimit(1000000) 或超过数百万,但它没有帮助。

甚至有时什么都不打印。

我该如何解决这个问题?

在你的二叉树比较平衡的情况下,递归深度不要太大。尽管如此,即使您的二叉树是 退化 ,就像您从排序序列 (sorted) 创建它时发生的情况一样,您仍然应该只需要 O(n) 堆栈space.

但是 bst_buildfind_depth 的代码需要更多堆栈 space。这是因为它们在到达叶子时不会原路返回。相反,他们都从 root 重新开始遍历,而没有首先离开 current 递归树:

  • bst_build 通过在想要插入来自 seq 的下一个输入时再次调用 bst_build 来实现这一点。这应该发生在一个循环中(在递归之外),而不是当你已经深入递归树时。
  • find_depth 通过在到达叶子时再次调用 find_depth 来实现。这应该通过首先回溯一个步骤然后再次出现在同级节点中来实现。相反,它会从根部重新开始搜索(这是浪费时间),并且不会先退出递归。

这使得此代码需要大量堆栈 space,在最坏(退化)的情况下就像 O(n²)。

与您的问题无关,但遗憾的是 - 完全没有必要 - find_depth 在遍历树时破坏了树。

另一个与你的问题无关的问题:你的bst_check不正确。对于以下树,它将 return 为真:

            2
           /
          1
           \
            3

但是,这棵树不是有效的 BST。有效 BST 的要求不仅是左 child 小于其 parent,而且 左子树中的所有节点 都小于 parent.

以下是所有这些问题的修复方法:

def bst_build(seq):
    if not seq:
        return None

    def add_child(main, value):
        if value == main[0]:
            return
        childindex = 1 if main[0] > value else 2
        if not main[childindex]:
            main[childindex] = [value, None, None]
        else:
            add_child(main[childindex], value)

    binary_search_tree = [seq[0], None, None]
    for value in seq[1:]:
        add_child(binary_search_tree, value)

    return binary_search_tree


def bst_depth(b):
    return 1 + max(bst_depth(b[1]) if b[1] else -1, bst_depth(b[2]) if b[2] else -1)


def bst_check(b, low=float("-inf"), high=float("inf")):
    return not b or (low <= b[0] <= high and bst_check(b[1], low, b[0]) 
                                         and bst_check(b[2], b[0], high))