用Python实现快速排序

114

我完全是Python的新手,正在尝试在其中实现快速排序算法。 请问是否有人能帮我完成我的代码呢?

我不知道如何将这三个数组连接起来并打印出它们。

def sort(array=[12,4,5,6,7,3,1,15]):
    less = []
    equal = []
    greater = []

    if len(array) > 1:
        pivot = array[0]
        for x in array:
            if x < pivot:
                less.append(x)
            if x == pivot:
                equal.append(x)
            if x > pivot:
                greater.append(x)
            sort(less)
            sort(pivot)
            sort(greater)

要合并列表,您可以使用加号运算符 my_list = list1 + list2 + ...。或者将列表解包到新列表中 my_list = [*list1, *list2] - Mark Mishyn
2
快速排序算法应该是一种原地算法,但你的代码并不是。此外,追加操作不一定在常数时间内执行。 - user1196549
37个回答

3
我认为这里的两个答案都对提供的列表(回答原问题)有效,但如果传递包含非唯一值的数组,则会中断。因此,为了完整起见,我将指出每个小错误并解释如何修复它们。
例如,尝试使用Brionius算法对以下数组[12,4,5,6,7,3,1,15,1]进行排序(注意1出现两次)...某个时刻将以空的较小数组和具有无法在下一次迭代中分离的一对值(1,1)的等于数组结束,并且len() > 1...因此你将陷入无限循环。
您可以通过返回数组(如果less为空)或更好地通过不在您的相等数组中调用排序来修复它,如zangw的答案所示。
def sort(array=[12,4,5,6,7,3,1,15]):
    less = []
    equal = []
    greater = []
 
    if len(array) > 1:
        pivot = array[0]
        for x in array:
            if x < pivot:
                less.append(x)
            elif x == pivot:
                equal.append(x)
            else: # if x > pivot
                greater.append(x)
        
        # Don't forget to return something!
        return sort(less) + equal + sort(greater)  # Just use the + operator to join lists
    # Note that you want equal ^^^^^ not pivot
    else:  # You need to hande the part at the end of the recursion - when you only have one element in your array, just return the array.
        return array

更高级的解决方案也会出现问题,但原因不同,在递归行中缺少了return语句,这将导致在某个时候返回None并尝试将其附加到列表中....为解决此问题,只需在该行添加一个return即可。
def qsort(arr): 
   if len(arr) <= 1:
      return arr
   else:
      return qsort([x for x in arr[1:] if x<arr[0]]) + [arr[0]] + qsort([x for x in arr[1:] if x>=arr[0]])

顺便说一下,简洁版本的性能不如长版本,因为它在列表推导中迭代数组两次。 - FedeN

3
分区 - 将数组按照一个枢轴(pivot)拆分成两部分,使得较小的元素移动到左边,较大的元素移动到右边或者反之亦然。枢轴可以是数组中的任意元素。为了实现这个算法,我们需要知道数组的起始和结束索引以及枢轴的位置。然后设置两个辅助指针L和R。
所以我们有一个数组user[...,begin,...,end,...]
L和R指针的起始位置 [...,begin,next,...,end,...]      R       L
当L < end时, 1. 如果user[pivot] > user[L],则将R向右移动一位并交换user[R]和user[L] 2. 将L向右移动一位
while循环结束后,用user[R]和user[pivot]进行交换。 快速排序 - 使用分区算法,直到由枢轴拆分出来的每个子数组的开始索引都大于或等于结束索引。
def qsort(user, begin, end):

    if begin >= end:
        return

    # partition
    # pivot = begin
    L = begin+1
    R = begin
    while L < end:
        if user[begin] > user[L]:
            R+=1
            user[R], user[L] = user[L], user[R]
        L+= 1
    user[R], user[begin] = user[begin], user[R]

    qsort(user, 0, R)
    qsort(user, R+1, end)

tests = [
    {'sample':[1],'answer':[1]},
    {'sample':[3,9],'answer':[3,9]},
    {'sample':[1,8,1],'answer':[1,1,8]},
    {'sample':[7,5,5,1],'answer':[1,5,5,7]},
    {'sample':[4,10,5,9,3],'answer':[3,4,5,9,10]},
    {'sample':[6,6,3,8,7,7],'answer':[3,6,6,7,7,8]},
    {'sample':[3,6,7,2,4,5,4],'answer':[2,3,4,4,5,6,7]},
    {'sample':[1,5,6,1,9,0,7,4],'answer':[0,1,1,4,5,6,7,9]},
    {'sample':[0,9,5,2,2,5,8,3,8],'answer':[0,2,2,3,5,5,8,8,9]},
    {'sample':[2,5,3,3,2,0,9,0,0,7],'answer':[0,0,0,2,2,3,3,5,7,9]}
]

for test in tests:

    sample = test['sample'][:]
    answer = test['answer']

    qsort(sample,0,len(sample))

    print(sample == answer)

请解释你的代码/添加内容,以便OP和未来的观众可以更好地受益。 - Omar Einea

2

以下是一种真正的原地实现方式 [参考自Michael T. Goodrich和Roberto Tamassia所著《算法设计与应用》中的第8.9章和第8.11章]:

from random import randint

def partition (A, a, b):
    p = randint(a,b)
    # or mid point
    # p = (a + b) / 2

    piv = A[p]

    # swap the pivot with the end of the array
    A[p] = A[b]
    A[b] = piv

    i = a     # left index (right movement ->)
    j = b - 1 # right index (left movement <-)

    while i <= j:
        # move right if smaller/eq than/to piv
        while A[i] <= piv and i <= j:
            i += 1
        # move left if greater/eq than/to piv
        while A[j] >= piv and j >= i:
            j -= 1

        # indices stopped moving:
        if i < j:
            # swap
            t = A[i]
            A[i] = A[j]
            A[j] = t
    # place pivot back in the right place
    # all values < pivot are to its left and 
    # all values > pivot are to its right
    A[b] = A[i]
    A[i] = piv

    return i

def IpQuickSort (A, a, b):

    while a < b:
        p = partition(A, a, b) # p is pivot's location

        #sort the smaller partition
        if p - a < b - p:
            IpQuickSort(A,a,p-1)
            a = p + 1 # partition less than p is sorted
        else:
            IpQuickSort(A,p+1,b)
            b = p - 1 # partition greater than p is sorted


def main():
    A =  [12,3,5,4,7,3,1,3]
    print A
    IpQuickSort(A,0,len(A)-1)
    print A

if __name__ == "__main__": main()

2
def quick_sort(self, nums):
    def helper(arr):
        if len(arr) <= 1: return arr
        #lwall is the index of the first element euqal to pivot
        #rwall is the index of the first element greater than pivot
        #so arr[lwall:rwall] is exactly the middle part equal to pivot after one round
        lwall, rwall, pivot = 0, 0, 0
        #choose rightmost as pivot
        pivot = arr[-1]
        for i, e in enumerate(arr):
            if e < pivot:
                #when element is less than pivot, shift the whole middle part to the right by 1
                arr[i], arr[lwall] = arr[lwall], arr[i]
                lwall += 1
                arr[i], arr[rwall] = arr[rwall], arr[i]
                rwall += 1
            elif e == pivot:
                #when element equals to pivot, middle part should increase by 1
                arr[i], arr[rwall] = arr[rwall], arr[i]
                rwall += 1
            elif e > pivot: continue
        return helper(arr[:lwall]) + arr[lwall:rwall] + helper(arr[rwall:])
    return helper(nums)

2

或者,如果您更喜欢一行代码,该代码还演示了Python中等价于C/C++ varags、lambda表达式和if表达式的内容:

qsort = lambda x=None, *xs: [] if x is None else qsort(*[a for a in xs if a<x]) + [x] + qsort(*[a for a in xs if a>=x])

2
def quicksort(array):
 if len(array) < 2:
  return array
 else:
  pivot = array[0]

 less = [i for i in array[1:] if i <= pivot]
 greater = [i for i in array[1:] if i > pivot]
 return quicksort(less) + [pivot] + quicksort(greater)

虽然这段代码可能提供了解决问题的方案,但强烈建议您提供有关为什么和/或如何回答问题的其他上下文信息。仅提供代码答案通常在长期内变得无用,因为未来遇到类似问题的观众无法理解解决方案背后的推理。 - palaѕн

2

我知道许多人已经正确回答了这个问题,我很喜欢读他们的回答。我的答案与zangw几乎相同,但我认为之前的贡献者没有很好地解释事物实际上是如何工作的...所以这里是我尝试帮助其他可能在将来访问此问题/答案的人提供一个简单的快速排序实现。

它是如何工作的?

  1. 我们基本上从我们的列表中选择第一个项目作为我们的枢轴,然后我们创建两个子列表。
  2. 我们的第一个子列表包含小于枢轴的项目
  3. 我们的第二个子列表包含大于或等于枢轴的项目
  4. 然后我们对它们进行快速排序,然后将它们组合成第一组+枢轴+第二组,以获得最终结果。

以下是一个示例及其相关视觉效果... (枢轴)9,11,2,0

平均:n log n

最坏情况:n ^ 2

enter image description here

代码:

def quicksort(data):
if (len(data) < 2):
    return data
else:
    pivot = data[0]  # pivot
    #starting from element 1 to the end
    rest = data[1:]
    low = [each for each in rest if each < pivot]
    high = [each for each in rest if each >= pivot]
    return quicksort(low) + [pivot] + quicksort(high)

items=[9,11,2,0] print(quicksort(items))


2
算法包含两个边界,一个是小于基准点的元素(由索引“j”跟踪),另一个是大于基准点的元素(由索引“i”跟踪)。
在每次迭代中,通过递增j来处理新元素。
不变量:-
1. 基准点和i之间的所有元素都小于基准点,并且 2. i和j之间的所有元素都大于基准点。
如果违反了不变量,则交换第ith和jth元素,并增加i。
在处理完所有元素并将基准点后面的所有内容分区后,基准点元素将与小于它的最后一个元素交换。
基准点元素现在将处于序列中的正确位置。它之前的元素将小于它,之后的元素将大于它,并且它们将是未排序的。
def quicksort(sequence, low, high):
    if low < high:    
        pivot = partition(sequence, low, high)
        quicksort(sequence, low, pivot - 1)
        quicksort(sequence, pivot + 1, high)

def partition(sequence, low, high):
    pivot = sequence[low]
    i = low + 1
    for j in range(low + 1, high + 1):
        if sequence[j] < pivot:
            sequence[j], sequence[i] = sequence[i], sequence[j]
            i += 1
    sequence[i-1], sequence[low] = sequence[low], sequence[i-1]
    return i - 1

def main(sequence):
    quicksort(sequence, 0, len(sequence) - 1)
    return sequence

if __name__ == '__main__':
    sequence = [-2, 0, 32, 1, 56, 99, -4]
    print(main(sequence))

选择枢轴

选择一个“好”的枢轴将会导致两个子序列大小大致相等。确定性地,可以通过一种朴素的方式来选择枢轴元素,也可以通过计算序列的中位数来选择。

选择枢轴的朴素实现是选择第一个或最后一个元素。在这种情况下,最坏情况运行时间为输入序列已经排序或反向排序的情况,因为一个子序列将为空,这将导致每个递归调用仅删除一个元素。

当枢轴是序列的中位数时,可以实现完全平衡的拆分。大于枢轴和小于枢轴的元素数量相同。这种方法保证了更好的总体运行时间,但是耗费的时间更多。

随机选择枢轴的一种非确定性/随机方法是均匀随机选择一个元素。这是一种简单轻量级的方法,可以将最坏情况的情况最小化,并且还可以实现大致平衡的拆分。这也提供了在选择枢轴的朴素方法和中位数方法之间取得平衡的方法。


1
这是一个简单的实现:-
def quicksort(array):
            if len(array) < 2:
                  return array
            else:
                  pivot= array[0]
                  less = [i for i in array[1:] if i <= pivot]

                  greater = [i for i in array[1:] if i > pivot]

                  return quicksort(less) + [pivot] + quicksort(greater)

print(quicksort([10, 5, 2, 3]))

1
def Partition(A,p,q):
    i=p
    x=A[i]
    for j in range(p+1,q+1):
        if A[j]<=x:
            i=i+1
            tmp=A[j]
            A[j]=A[i]
            A[i]=tmp
    l=A[p]
    A[p]=A[i]
    A[i]=l
    return i

def quickSort(A,p,q):
    if p<q:
        r=Partition(A,p,q)
        quickSort(A,p,r-1)
        quickSort(A,r+1,q)
    return A

1
请在您的代码中包含解释,说明它是如何回答问题的。特别是它与问题中发布的代码有什么关系。答案应该给予OP和未来的访问者指导,以便调试和修复他们的问题。指出您的代码背后的思想,有助于理解问题并应用或修改您的解决方案。[SO]不是一个代码编写服务,而是一个教学和学习的地方。 - Palec

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接