Python 中的递归合并排序和堆栈帧

Recursive Merge sort and Stack Frames in Python

以下代码(不是我的,只是研究它)在递归原始列表(即 list_)和合并例程之间(正确地)反弹。堆栈帧的流程(即,即使在 Python Tutor 中观看时,它们如何以及为何返回它们的方式也不清楚,这就是我在下面叙述的内容)。代码 returns 和问题如何遵循程序的描述。

def merge(left, right):
    if not len(left) or not len(right):
        return left or right

    result = []
    i, j = 0, 0
    while (len(result) < len(left) + len(right)):
        if left[i] < right[j]:
            result.append(left[i])
            i+= 1
        else:
            result.append(right[j])
            j+= 1
        if i == len(left) or j == len(right):
            result.extend(left[i:] or right[j:])
            break 

    return result

def mergesort(list_):
    if len(list_) < 2:
        return list_

    middle = len(list_)//2
    left = mergesort(list_[:middle])
    right = mergesort(list_[middle:])

    return merge(left, right)


list_ = [7,5,2,1,3]

print(mergesort(list_))

我们命中的第一个递归调用是left(我们要对列表的左半部分进行排序,然后对左半部分的左半部分进行排序,等等,直到我们得到一个列表的大小一个并击中基本情况)。这很好用。我们从 [7,5,2,1,3] 到 [7,5] 再到 [7] 并达到基本情况。到目前为止,一切都很好。 我希望堆栈帧开始返回,但事实并非如此。我认为是 returns 7,但随后我们跳转到下一组递归调用 right 5 再次出现。 5 出现在 7 之前的堆栈帧中,因此返回的顺序相反(这很好)。新参数将 5 分开并创建一个列表,从而引发基本情况并返回 5。

这就是它变得奇怪的地方:程序进行到合并步骤,这是正确的。 如何 "know" 跳过对 rightleft 的进一步递归(我给了它一个完整的列表,其中大部分是未触及的)并跳转到合并?此外,它如何知道在没有明确指示的情况下从合并返回到合并排序函数,并确切知道从哪里开始? 任何人都可以阐明这一点吗?传统算法文本和大多数视频的帮助为零,因为它们不涉及堆栈框架。

我认为您的困惑与不了解每个堆栈帧都有其自己的执行点有关。每次调用函数时,框架的执行都会暂停,并为函数调用创建一个新框架。当它 returns 时,前一帧从它停止的地方开始。 "knows" 代码下一步应该放在哪个中心位置,每个级别都自行处理。

在您描述的示例场景中,mergesort[7, 5] 的调用首先将列表拆分为 [7][5],然后在每个列表中递归转得到leftright。当这些递归调用中的第二个返回时,它继续执行代码的下一部分,即 merge 调用,因为这就是代码中的下一个部分。

你可以很清楚地看到逻辑,就在这里:

left = mergesort(list_[:middle])
right = mergesort(list_[middle:])

return merge(left, right)

这告诉你在这个 运行 上(以及每个没有达到基本情况的 运行),它将递归两次然后 merge.