Python递归合并按索引排序

问题描述

我对递归合并排序的Python版本有疑问。我完成了仅由数组引用的基本版本,现在正在使用索引版本。我将陷入无尽的循环,但是我不确定我做错了什么。您愿意分享一些想法吗?预先谢谢你。

成功且无索引的版本:

def mergesort(x):
    # The base case is when the array contains less than 1 observation. 
    length = len(x)
    if length < 2:
        return x
    else:
        # Recursive case:merge sort on the lower subarray,and the upper subarray. 
        mid = (length) // 2
        lower = mergesort(x[:mid])
        upper = mergesort(x[mid:])
        # merge two subarrays.
        x_sorted = merge(lower,upper)
        return (x_sorted)

def merge(lower,upper):
    nlower = len(lower)
    nupper = len(upper)
    i,j,k = 0,0
    # create a temp array to store the sorted results
    temp = [0] * (nlower + nupper)
    # as the lower and upper are sorted,since the base case is the single observation. 
    # Now we compare the smallest element in each sorted array,and store the smaller one to the temp array
    # repeat this process until one array is completed moved to the temp array 
    # store the other array to the end of the temp array 
    while i < nlower and j < nupper:
        if lower[i] <= upper[j]:
            temp[k] = lower[i]
            i += 1
            k += 1
        else:
            temp[k] = upper[j]
            j += 1
            k += 1
    if i == nlower:
        temp[i+j:] = upper[j:]
    else:
        temp[i+j:] = lower[i:]
    return temp 

预期结果:

x = random.sample(range(0,30),15)
mergesort(x)
[0,1,3,6,9,10,11,13,14,17,18,20,25,27,29]

但是我将陷入索引版本的无限循环:

def ms(x,left,right):
    # the base case: right == left as a single-element array
    if left < right:
        mid = (left + right) // 2
        ms(x,mid)
        ms(x,mid,right + 1)
        m(x,right)
    return m
def m(x,right):
    nlower = mid - left
    nupper = right - mid + 1
    temp = [0] * (nlower + nupper)
    ilower,iupper,k = left,0
    
    while ilower < mid and iupper < right + 1:
        if x[ilower] <= x[iupper]:
            temp[k] = x[ilower]
            ilower += 1
            k += 1
        else:
            temp[k] = x[iupper]
            iupper += 1
            k += 1
    if ilower == mid:
        temp[k:] = x[iupper:right+1]
    else:
        temp[k:] = x[ilower:mid]
    x[left:right+1] = temp
    return x

测试数据为:

x = random.sample(range(0,15)
ms(x,14)
---------------------------------------------------------------------------
RecursionError                            Traceback (most recent call last)
<ipython-input-59-39859c9eae4a> in <module>
      1 x = random.sample(range(0,15)
----> 2 ms(x,14)

... last 2 frames repeated,from the frame below ...

<ipython-input-57-854342dcdefb> in ms(x,right)
      3     if left < right:
      4         mid = (left + right)//2
----> 5         ms(x,mid)
      6         ms(x,right+1)
      7         m(x,right)

RecursionError: maximum recursion depth exceeded in comparison

您介意提供一些见解吗?谢谢。

解决方法

您的索引版本使用一个令人困惑的约定,其中left是切片中第一个元素的索引,而right是最后一个元素的索引。此约定要求容易出错的+1 / -1调整。您的问题是这样的:mid的计算结果是左半部分最后一个元素的索引,但是您认为mid是右半部分的第一个元素:将2个元素切片分成一个为0,另一个为2,因此无限递归。您可以通过将ms(x,mid,right+1)更改为ms(x,mid+1,right)等来解决此问题。

此外,从功能m重新调整ms毫无意义。如果有的话,您应该返回x

right成为最后一个元素之后的索引的可能性要小得多,就像Python中的范围说明符一样。这样,就不会出现令人困惑的+1 / -1调整。

此处是修改版本:

def ms(x,left,right):
    # the base case: right - left as a single-element array
    if right - left >= 2:
        mid = (left + right) // 2  # index of the first element of the right half
        ms(x,mid)
        ms(x,right)
        m(x,right)
    return x

def m(x,right):
    nlower = mid - left
    nupper = right - mid
    temp = [0] * (nlower + nupper)
    ilower,iupper,k = left,0
    
    while ilower < mid and iupper < right:
        if x[ilower] <= x[iupper]:
            temp[k] = x[ilower]
            ilower += 1
            k += 1
        else:
            temp[k] = x[iupper]
            iupper += 1
            k += 1
    if ilower == mid:
        temp[k:] = x[iupper:right]
    else:
        temp[k:] = x[ilower:mid]
    x[left:right] = temp
    return x

您可以按以下方式调用

x = random.sample(range(0,30),15)
ms(x,len(x))