Python:Quicksort,中位数为3

时间:2018-06-18 15:29:28

标签: python sorting pivot quicksort median

我正在尝试更改此快速排序代码,以使用一个取“中位数为三”的数据透视图。

def quickSort(L, ascending = True): 
    quicksorthelp(L, 0, len(L), ascending)


def quicksorthelp(L, low, high, ascending = True): 
    result = 0
    if low < high: 
        pivot_location, result = Partition(L, low, high, ascending)  
        result += quicksorthelp(L, low, pivot_location, ascending)  
        result += quicksorthelp(L, pivot_location + 1, high, ascending)
    return result


def Partition(L, low, high, ascending = True):
    print('Quicksort, Parameter L:')
    print(L)
    result = 0 
    pivot, pidx = median_of_three(L, low, high)
    L[low], L[pidx] = L[pidx], L[low]
    i = low + 1
    for j in range(low+1, high, 1):
        result += 1
        if (ascending and L[j] < pivot) or (not ascending and L[j] > pivot):
            L[i], L[j] = L[j], L[i]  
            i += 1
    L[low], L[i-1] = L[i-1], L[low] 
    return i - 1, result

liste1 = list([3.14159, 1./127, 2.718, 1.618, -23., 3.14159])

quickSort(liste1, False)  # descending order
print('sorted:')
print(liste1)

但我不确定该怎么做。中位数必须是列表的第一个,中间和最后一个元素的中位数。如果列表具有偶数个元素,则middle将成为上半部分的最后一个元素。

这是我的中位数函数:

def median_of_three(L, low, high):
    mid = (low+high-1)//2
    a = L[low]
    b = L[mid]
    c = L[high-1]
    if a <= b <= c:
        return b, mid
    if c <= b <= a:
        return b, mid
    if a <= c <= b:
        return c, high-1
    if b <= c <= a:
        return c, high-1
    return a, low

2 个答案:

答案 0 :(得分:2)

让我们首先实现三个数字的中位数,这是一个独立的函数。我们可以通过对三个元素的列表进行排序,然后返回第二个元素,如:

def median_of_three(a, b, c):
    return sorted([a, b, c])[1]

现在,对于范围low .. high(包括low,并排除high),我们应该确定哪些元素应该构造三个中位数:

  1. 第一个元素:L[low]
  2. 最后一个元素L[high-1]
  3. 中间元素(如果有两个这样的话,请选择第一个)L[(low+high-1)//2]
  4. 所以现在我们只需要将分区功能修补为:

    def Partition(L, low, high, ascending = True):
        print('Quicksort, Parameter L:')
        print(L)
        result = 0 
        pivot = median_of_three(L[low], L[(low+high-1)//2], L[high-1])
        i = low + 1  
        for j in range(low + 1, high, 1): 
            result += 1
            if (ascending and L[j] < pivot) or (not ascending and L[j] > pivot):
                L[i], L[j] = L[j], L[i]  
                i += 1  
        L[low], L[i-1] = L[i-1], L[low] 
        return i - 1, result

    编辑:确定三个元素的中位数。

    三个元素的中位数是位于另外两个值中间的元素。因此,如果a <= b <= c,则b是中位数。

    因此我们需要确定元素的顺序,以便我们可以确定中间的元素。像:

    def median_of_three(a, b, c):
        if a <= b and b <= c:
            return b
        if c <= b and b <= a:
            return b
        if a <= c and c <= b:
            return c
        if b <= c and c <= a:
            return c
        return a
    

    所以现在我们已经用四个if个案例定义了三个中位数。

    EDIT2 :这仍然存在问题。执行数据透视后,在原始代码中将元素L[i-1]L[low]交换(数据透视图的位置)。但这当然不再起作用了:因为枢轴现在可以位于三个维度中的任何一个。因此,我们需要使median_of_three(..)变得更聪明:它不仅应该返回枢轴元素,还要返回该枢轴的位置:

    def median_of_three(L, low, high):
        mid = (low+high-1)//2
        a = L[low]
        b = L[mid]
        c = L[high-1]
        if a <= b <= c:
            return b, mid
        if c <= b <= a:
            return b, mid
        if a <= c <= b:
            return c, high-1
        if b <= c <= a:
            return c, high-1
        return a, low
    

    现在我们可以用以下方法解决这个问题:

    def Partition(L, low, high, ascending = True):
        print('Quicksort, Parameter L:')
        print(L)
        result = 0 
        pivot, pidx = median_of_three(L, low, high)
        i = low + (low == pidx)
        for j in range(low, high, 1):
            if j == pidx:
                continue
            result += 1
            if (ascending and L[j] < pivot) or (not ascending and L[j] > pivot):
                L[i], L[j] = L[j], L[i]  
                i += 1 + (i+1 == pidx)
        L[pidx], L[i-1] = L[i-1], L[pidx] 
        return i - 1, result

    EDIT3 :清理它。

    虽然上述内容似乎有效,但它非常复杂:我们需要让ij“跳过”枢轴的位置。

    如果我们先将枢轴移动到子列表的前面(对low索引),则可能更简单:

    def Partition(L, low, high, ascending = True):
        print('Quicksort, Parameter L:')
        print(L)
        result = 0 
        pivot, pidx = median_of_three(L, low, high)
        L[low], L[pidx] = L[pidx], L[low]
        i = low + 1
        for j in range(low+1, high, 1):
            result += 1
            if (ascending and L[j] < pivot) or (not ascending and L[j] > pivot):
                L[i], L[j] = L[j], L[i]  
                i += 1
        L[low], L[i-1] = L[i-1], L[low] 
        return i - 1, result

答案 1 :(得分:1)

在快速排序的“三个中位数”版本中,您不仅要查找使用它作为枢轴的中位数,还要将最大值和最小值放在它们的位置,这样一些旋转是已经完成了。换句话说,您想要在这三个地方对这三个项目进行排序。 (有些变体不希望它们以通常的方式排序,但我会在这里坚持一个更简单易懂的版本。)

您可能不希望在函数中执行此操作,因为函数调用在Python中相当昂贵,并且此特定功能并不广泛有用。所以你可以做这样的代码。假设您要排序的三个值位于索引ijk中,并带有i < j < k。在实践中,您可能会使用lowlow + 1high,但您可以根据需要进行更改。

if L(i) > L(j):
    L(i), L(j) = L(j), L(i)
if L(i) > L(k):
    L(i), L(k) = L(k), L(i)
if L(j) > L(k):
    L(j), L(k) = L(k), L(j)

可以做一些优化。例如,您可能希望在数据透视过程中使用中值,因此您可以更改代码以将最终值L(j)存储在一个简单变量中,从而减少数组查找。请注意,一般情况下,您不能在少于三次比较中执行此操作 - 您无法将其减少为两次比较,但在某些特殊情况下您可以这样做。