【问题标题】:Mergesort implementation in PythonPython中的合并排序实现
【发布时间】:2015-07-22 23:02:39
【问题描述】:

我一直在 Python/C++ 中实现合并排序(来自 interactivepython)。代码完全有效,但我的问题是我似乎无法弄清楚为什么代码的特定部分确实有效。

代码是:

def mergeSort(alist):
    if len(alist)>1:
        mid = len(alist)//2
        lefthalf = alist[:mid]
        righthalf = alist[mid:]

        mergeSort(lefthalf)
        mergeSort(righthalf)

        i=0
        j=0
        k=0
        while i<len(lefthalf) and j<len(righthalf):
            if lefthalf[i]<righthalf[j]:
                alist[k]=lefthalf[i]
                i=i+1
            else:
                alist[k]=righthalf[j]
                j=j+1
            k=k+1

        while i<len(lefthalf):
            alist[k]=lefthalf[i]
            i=i+1
            k=k+1

        while j<len(righthalf):
            alist[k]=righthalf[j]
            j=j+1
            k=k+1

plist = [54,26,93,17]
mergeSort(plist)
print(plist)

在 lefthalf = [54 26] 之后,进一步的子程序拆分为 lefthalf = [54] 和 righthalf = [26],合并代码提供 alist = [26, 54](这是排序的左半部分)。

现在我在调试窗口中执行的下一步是 lefthalf = [26, 54]。这怎么可能,因为第一个调试显示它之前定义为 [54, 26]。 [26, 54] 的更新发生在哪里?

任何帮助将不胜感激!

【问题讨论】:

  • 您的代码没有问题。我怀疑这里发生的事情是您的调试控制台在递归调用之后暂停,以便 lefthalfrighthalf 的内容已经排序。
  • 在运行和单步执行之前使用 Shell 调试

标签: python mergesort


【解决方案1】:

您的mergesort 函数会修改您传递给它的列表。也就是说,它会更改内容以使项目按顺序排列,而不是返回新列表。

这是您在递归调用期间在调试器中看到的内容。在递归的第一级中,lefthalf 是通过从原始列表中复制一些值来创建的(使用切片语法)。它开始包含[54, 26]。然后将该列表传递给mergesort 的另一个调用。请注意,命名可能会令人困惑,因为在内部调用中,它将列表称为alist(并且它有自己单独的lefthalf 列表)。当内部调用返回时,外部调用的lefthalf 的内容竟然被修改为[26, 54](它们是按顺序排列的,这就是我们想要的!)。

可能是您的调试器在返回发生时没有明确说明。由于都是同一个函数(由于递归),内部调用何时结束,外部调用的控制流恢复时可能并不明显。

这是您的代码演练,在您对示例列表进行排序时,我会在其中显示不同递归级别中不同变量的值。请注意,这不是可运行的 Python 代码,我正在缩进以指示递归级别,而不是用于控制流。为了使示例相对简短,我还省略了一些步骤,例如比较两个子列表中的值并在合并过程中更新 i jk 索引:

plist = [54,26,93,17]
mergesort(plist)
    # alist is a referece to plist which contains [54,26,93,17]
    lefthalf = alist[:mid]  # new list which initially contains [54,26]
    righthalf = alist[mid:] # new list which initially contains [93,17]
    mergesort(lefthalf)
        # alist is a reference to the outer lefthalf list, which contains [54,26]
        lefthalf = alist[:mid]  # new list, initially contains [54]
        righthalf = alist[mid:] # new list, initially contains [26]
        mergesort(lefthalf)
            # alist is a reference to the previous level's lefthalf, [54]
            # the if statement doesn't pass its test, so we do nothing here (base case)
        # lefthalf was not changed by the recursive call
        mergesort(righthalf)
            # alist is a reference to the previous level's righthalf, [26]
            # base case again
        # righthalf was not changed
        alist[k]=righthalf[j] # now we merge lefthalf and righthalf back into alist
        alist[k]=lefthalf[i] # these statements change the contents of alist
    # lefthalf's contents changed, it is now sorted, [26,54]
    mergesort(righthalf)
        # alist is a reference to the outer righthalf list, which contains [93,17]
        lefthalf = alist[:mid]  # new list, initially contains [93]
        righthalf = alist[mid:] # new list, initially contains [17]
        mergesort(lefthalf) # base case, nothing happens (I'll skip the details)
        mergesort(righthalf) # base case, nothing happens
        alist[k]=righthalf[j] # merge lefthalf and righthalf back into alist
        alist[k]=lefthalf[i]  # we change the contents of alist to [17,93]
    # righthalf's contents changed, it is now sorted, [17,93]
    alist[k]=righthalf[j] # merge lefthalf and righthalf back into alist (more steps)
    alist[k]=lefthalf[i]
    alist[k]=lefthalf[i]
    alist[k]=righthalf[j] # after these statements, alist is [17,26,54,93]
# plists's contents are changed so it contains [17,26,54,93]

这可能会帮助您从这种复杂的递归情况中退后一步,看看一个更简单的示例,以确保您了解列表是如何变异的:

a = [1, 2] # create a list object with some initial contents

b = a      # b refers to the same list object as a, nothing is copied
b[1] = 3   # this modifies the list object itself, replacing the 2 with a 3

print(a)   # prints [1, 3] even through we're looking at a rather than b

def func(lst): # lst will be a new name bound to whatever is passed to the function
    lst[1] = 4 # we can modify that passed-in object (assuming it's the right type)

func(a)    # lst in the function will become another name for the list object
print(a)   # prints [1, 4]

【讨论】:

  • 我在答案中添加了一个额外的位,更多地解释了可变对象在绑定到其他名称时如何被修改(无论新名称是通过函数调用还是只是一个简单的任务)。我希望整件事有所帮助!
【解决方案2】:

python 中的列表在函数内部是可变的(即它们的内容可以改变)。当列表在函数内部发生变化时,变化发生在你调用函数的列表上,因为它是同一个列表。

因此,每次在第一次调用或任何递归调用中操作alistalist 的一部分时,都会更新您的列表。

编辑:删除误导性位

【讨论】:

  • 您关于切片不是新列表的评论具有误导性。在处理常规 Python 列表(而不是 numpy 数组或类似的)时,当您对现有列表进行切片时,您确实会得到一个新列表。
  • 如果列表更新了(即我们上面讨论的迭代后alist = [26, 54, 93, 17]),那么为什么我的调试窗口在下一步之后显示本地alist = [ 54, 26, 93, 17] 和 lefthalf = [26, 54]?抱歉,需要更多信息的家伙!
猜你喜欢
  • 2013-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多