使用Python进行快速排序


92

我对python完全陌生,我正在尝试在其中实现quicksort。有人可以帮我完成我的代码吗?

我不知道如何连接三个数组并打印它们。

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)

要合并列表,可以使用plus运算符my_list = list1 + list2 + ...。或将列表解压缩到新列表中my_list = [*list1, *list2]
Mark Mishyn

Answers:


254
def sort(array=[12,4,5,6,7,3,1,15]):
    """Sort the array by using quicksort."""

    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)
            elif 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 handle the part at the end of the recursion - when you only have one element in your array, just return the array.
        return array

8
您也可以if在for循环中与交换第二个,elifelse避免进行不必要的比较
SlimPDX

13
这听起来像合并排序不是快速排序
Emad Mokhtar 2015年

47
实际上,这是我在任何地方都可以找到quicksort 的最好,最易读的python代码。没有索引,没有辅助函数,清楚地显示了算法的要旨(分而治之)。(该数组的默认值是不必要的)
cmantas

19
@jsmedmar将比就地版本使用更多的内存,有关就地快速排序的信息,请参见suquant的答案。
约翰

14
可读性强,但这不会破坏快速排序的目的,因为这将无法实现“就地”排序吗?@RasmiRanjanNayak在这里排序是用户定义的函数(它是递归调用),而不是任何内置函数。
Maksood '16

161

快速排序,无需额外的内存(就位)

用法:

array = [97, 200, 100, 101, 211, 107]
quicksort(array)
# array -> [97, 100, 101, 107, 200, 211]
def partition(array, begin, end):
    pivot = begin
    for i in xrange(begin+1, end+1):
        if array[i] <= array[begin]:
            pivot += 1
            array[i], array[pivot] = array[pivot], array[i]
    array[pivot], array[begin] = array[begin], array[pivot]
    return pivot



def quicksort(array, begin=0, end=None):
    if end is None:
        end = len(array) - 1
    def _quicksort(array, begin, end):
        if begin >= end:
            return
        pivot = partition(array, begin, end)
        _quicksort(array, begin, pivot-1)
        _quicksort(array, pivot+1, end)
    return _quicksort(array, begin, end)

23
这应该是选择的答案,因为快速排序通常是选择的算法(例如,通过合并排序),因为它可以就地排序(并且仍然提供O(nlogn)运行时)。
BoltzmannBrain

3
if end is None:将被检查很多次,只会被检查一次True。您应该将其放在包装函数中,以便仅被调用一次。
Gillespie

顺便说一句,布鲁斯,@ mksteve是正确的,此行不正确。此外,array[pivot], array[begin] = array[begin], array[pivot]应替换beginend
瑞安·麦考

2
尽管就地是好的,但是这样做很慢,并且在有很多项目时会由于达到最大递归深度而出错。参见repl.it/@almenon/quicksorts?language=python3
Almenon

@mksteve和Ryan,我测试了这些更改并破坏了排序
aljgom

68

还有另一个简洁美观的版本

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]])

# this comment is just to improve readability due to horizontal scroll!!!

让我解释一下上面的代码以获取详细信息

  1. 选择数组的第一个元素arr[0]作为枢轴

    [arr[0]]

  2. qsort 小于数组的那些元素 List Comprehension

    qsort([x for x in arr[1:] if x < arr[0]])

  3. qsort 大于旋转的那些元素 List Comprehension

    qsort([x for x in arr[1:] if x >= arr[0]])


15
@zangw投票失败的可能原因:1)已排序或反转数组的二次运行时间2)解决方案不就地。因此,执行起来很糟糕,对不起。
alisianoi '16

16
根本不可读,您是否真的要尽量减少行数?代码由机器解释,但人类可以理解。
jsmedmar '16

4
@ AlfredoGallegos,quicksort的全部要点是它就位,如果您要执行合并排序,也可以实施。
Padraic Cunningham

13
这些评论是真的吗?如果您需要性能,请使用sorted,这显然是出于教育目的。而且它可读性强,比接受的答案更具可读性。
Nobilis

3
FWIW,我认为这是所有这些中最易读的实现。它比其他任何答案都更好地显示了算法的递归结构。当然,性能不会太好。
SolveIt '18

35

该答案是的就地QuickSort Python 2.x。我的答案是对Rosetta Code的就地解决方案的解释,该解决方案也适用于Python 3

import random

def qsort(xs, fst, lst):
    '''
    Sort the range xs[fst, lst] in-place with vanilla QuickSort

    :param xs:  the list of numbers to sort
    :param fst: the first index from xs to begin sorting from,
                must be in the range [0, len(xs))
    :param lst: the last index from xs to stop sorting at
                must be in the range [fst, len(xs))
    :return:    nothing, the side effect is that xs[fst, lst] is sorted
    '''
    if fst >= lst:
        return

    i, j = fst, lst
    pivot = xs[random.randint(fst, lst)]

    while i <= j:
        while xs[i] < pivot:
            i += 1
        while xs[j] > pivot:
            j -= 1

        if i <= j:
            xs[i], xs[j] = xs[j], xs[i]
            i, j = i + 1, j - 1
    qsort(xs, fst, j)
    qsort(xs, i, lst)

而且,如果您愿意放弃就地属性,则下面是另一个版本,可以更好地说明quicksort背后的基本思想。除了可读性之外,它的另一个优点是它很稳定(相等的元素以与以前在未排序列表中相同的顺序出现在排序列表中)。在上面介绍的较少占用内存的就地实现中,此稳定性属性不成立。

def qsort(xs):
    if not xs: return xs # empty sequence case
    pivot = xs[random.choice(range(0, len(xs)))]

    head = qsort([x for x in xs if x < pivot])
    tail = qsort([x for x in xs if x > pivot])
    return head + [x for x in xs if x == pivot] + tail

感谢您分享此解决方案。您能否帮助我们了解时间的复杂性?我看到递归将调用它15次。在这8个有效调用中。这是否意味着第一个解决方案的时间复杂度为O(n),而就位排序的空间复杂度为O(1)?
ForeverLearner 2015年

@Tammy似乎您误解了big-O表示法。而且,我不太了解你的问题。您能否将其作为一个单独的产品提出?最后,Quicksort作为一种算法在O(n logn)时间和O(n)空间中运行。
alisianoi 2015年

3
我的错。为什么我到底在计算递归?:-)那么,15个递归是[1个调用(0级)+ 2个调用(1级分区)+ 4个调用(2级分区)+ 8个调用(3级分区或Leaf节点)。因此,我们仍然具有(lg8 +1)= lgn的高度。每个级别的总计算量为c1(某些成本)* n。因此,O(n lgn)。一个就地交换的空间复杂度= O(1)。因此,对于n个元素= O(n)。感谢您的指导。
ForeverLearner,2015年

3
这是远远地在互联网上最好的蟒蛇快速排序,它的伤心地看到它埋在这么多的O(n)的空间解决方案:(
萨沙Kondrashov

感谢@Timofey的客气话。您可能想看一下我的算法库,它有其他版本的排序,图形算法等。github.com
alisianoi/

23

使用Python进行快速排序

在现实生活中,我们应该始终使用Python提供的内置排序。但是,了解快速排序算法是有启发性的。

我的目标是分解主题,以便读者容易理解和复制,而无需返回参考资料。

快速排序算法本质上如下:

  1. 选择枢轴数据点。
  2. 将所有小于或低于枢轴的数据点移动到枢轴下方的位置-将大于或等于枢轴的数据点移动至枢轴下方的位置。
  3. 将算法应用于枢轴上方和下方的区域

如果数据是随机分布的,则选择第一个数据点作为枢轴等效于随机选择。

可读的示例:

首先,让我们看一个使用注释和变量名指向中间值的可读示例:

def quicksort(xs):
    """Given indexable and slicable iterable, return a sorted list"""
    if xs: # if given list (or tuple) with one ordered item or more: 
        pivot = xs[0]
        # below will be less than:
        below = [i for i in xs[1:] if i < pivot] 
        # above will be greater than or equal to:
        above = [i for i in xs[1:] if i >= pivot]
        return quicksort(below) + [pivot] + quicksort(above)
    else: 
        return xs # empty list

为了重述此处演示的算法和代码-我们将值移至枢轴上方的右侧,将值移至枢轴下方的左侧,然后将这些分区传递给同一函数以进行进一步排序。

打高尔夫球:

可以打成88个字符:

q=lambda x:x and q([i for i in x[1:]if i<=x[0]])+[x[0]]+q([i for i in x[1:]if i>x[0]])

要了解我们如何到达那里,请首先以我们易于理解的示例为例,删除注释和文档字符串,并找到适当的枢轴:

def quicksort(xs):
    if xs: 
        below = [i for i in xs[1:] if i < xs[0]] 
        above = [i for i in xs[1:] if i >= xs[0]]
        return quicksort(below) + [xs[0]] + quicksort(above)
    else: 
        return xs

现在就在上方和下方找到:

def quicksort(xs):
    if xs: 
        return (quicksort([i for i in xs[1:] if i < xs[0]] )
                + [xs[0]] 
                + quicksort([i for i in xs[1:] if i >= xs[0]]))
    else: 
        return xs

现在,知道and返回false的前一个元素,否则为true,它将求值并返回以下元素,我们有:

def quicksort(xs):
    return xs and (quicksort([i for i in xs[1:] if i < xs[0]] )
                   + [xs[0]] 
                   + quicksort([i for i in xs[1:] if i >= xs[0]]))

由于lambda返回单个表达式,并且我们已经简化为单个表达式(即使它变得越来越不可读),我们现在可以使用lambda:

quicksort = lambda xs: (quicksort([i for i in xs[1:] if i < xs[0]] )
                        + [xs[0]] 
                        + quicksort([i for i in xs[1:] if i >= xs[0]]))

为了简化我们的示例,请将函数和变量名称缩短为一个字母,并消除不需要的空格。

q=lambda x:x and q([i for i in x[1:]if i<=x[0]])+[x[0]]+q([i for i in x[1:]if i>x[0]])

请注意,与大多数代码打高尔夫球一样,此lambda样式很差。

就地快速排序,使用Hoare分区方案

先前的实现会创建很多不必要的额外列表。如果我们可以就地执行此操作,则将避免浪费空间。

以下实现使用Hoare分区方案,您可以在Wikipedia上了解更多信息(但显然,partition()通过使用while循环语义而不是do-while并将缩小步骤移至末尾,我们已经为每个调用最多删除了4个冗余计算。外部while循环)。

def quicksort(a_list):
    """Hoare partition scheme, see https://en.wikipedia.org/wiki/Quicksort"""
    def _quicksort(a_list, low, high):
        # must run partition on sections with 2 elements or more
        if low < high: 
            p = partition(a_list, low, high)
            _quicksort(a_list, low, p)
            _quicksort(a_list, p+1, high)
    def partition(a_list, low, high):
        pivot = a_list[low]
        while True:
            while a_list[low] < pivot:
                low += 1
            while a_list[high] > pivot:
                high -= 1
            if low >= high:
                return high
            a_list[low], a_list[high] = a_list[high], a_list[low]
            low += 1
            high -= 1
    _quicksort(a_list, 0, len(a_list)-1)
    return a_list

不知道我是否对它进行了充分的测试:

def main():
    assert quicksort([1]) == [1]
    assert quicksort([1,2]) == [1,2]
    assert quicksort([1,2,3]) == [1,2,3]
    assert quicksort([1,2,3,4]) == [1,2,3,4]
    assert quicksort([2,1,3,4]) == [1,2,3,4]
    assert quicksort([1,3,2,4]) == [1,2,3,4]
    assert quicksort([1,2,4,3]) == [1,2,3,4]
    assert quicksort([2,1,1,1]) == [1,1,1,2]
    assert quicksort([1,2,1,1]) == [1,1,1,2]
    assert quicksort([1,1,2,1]) == [1,1,1,2]
    assert quicksort([1,1,1,2]) == [1,1,1,2]

结论

该算法经常在计算机科学课程中教授,并要求在工作面试中提出。它可以帮助我们考虑递归和分而治之。

Quicksort在Python中不是很实用,因为我们内置的timsort算法非常有效,并且我们有递归限制。我们希望使用来对列表进行原位list.sort排序,或者使用来创建新的排序列表sorted-两者都带有keyreverse参数。


您的partition函数似乎不适用于以下项目:partition([5,4,3,2,1], 0, 4)。预期的回报指数是4,而它的回报是
3。– matino

@matino期望来自何处?我相信我简化了算法(如写此答案时在Wikipedia上所述),尽管我可能是错的,或者效率可能较低。如果您发现整个quicksort函数都失败的测试用例,那将很有帮助。
亚伦·霍尔

当我选择ivot = a_list [high]时,@ AaronHall却无法弄清楚如何使其工作。你能帮我吗 ?
秋朗

@matino我也有同样的困惑!分区函数很好,它满足的不变性比您的期望要弱-它不必找到将左右方向分开并小于枢轴的确切位置。它仅保证非平凡的分区,并且返回索引的所有左侧都小于返回索引的右侧。
Dror Speiser

21

已经有很多答案,但是我认为这种方法是最干净的实现:

def quicksort(arr):
    """ Quicksort a list

    :type arr: list
    :param arr: List to sort
    :returns: list -- Sorted list
    """
    if not arr:
        return []

    pivots = [x for x in arr if x == arr[0]]
    lesser = quicksort([x for x in arr if x < arr[0]])
    greater = quicksort([x for x in arr if x > arr[0]])

    return lesser + pivots + greater

当然,您可以跳过将所有内容存储在变量中的操作,并立即将它们返回,如下所示:

def quicksort(arr):
    """ Quicksort a list

    :type arr: list
    :param arr: List to sort
    :returns: list -- Sorted list
    """
    if not arr:
        return []

    return quicksort([x for x in arr if x < arr[0]]) \
        + [x for x in arr if x == arr[0]] \
        + quicksort([x for x in arr if x > arr[0]])

11
上!)?这是“ slowSort”吗?
Scott混合理论2014年

3
我相信在第一个代码示例中,它应该是“较小”和“较大”,而不是“ [较小]”和“ [较大]”,否则您将得到嵌套列表,而不是平面列表。
爱丽丝

@Scott混合理论我还在学习时间的复杂性,你能详细说明为什么这种实现O(N!)吗?假设嵌套列表[lesser][greater]错别字,不会是一般O(3N logN)这会减少预期平均O(N logN)?当然,这3个列表中的
伴奏

@Chrispy,如果您对倒序列表进行排序,例如5,4,3,2,1,该怎么办
Scott混合理论2015年

@Scott混合理论,您说得对,快速排序的最坏情况下的运行时间为慢Θ(n ^ 2),但是根据“算法简介”,平均情况下的运行时间为Θ(n lg n)。而且,更重要的是,在实践中,快速排序通常胜过堆排序
Lungang Fang

6

功能方法:

def qsort(list):
    if len(list) < 2:
        return list

    pivot = list.pop()
    left = filter(lambda x: x <= pivot, list)
    right = filter(lambda x: x > pivot, list)

    return qsort(left) + [pivot] + qsort(right)

列表是在python 3中保留的。请在此处查看代码的修改版本: gist.github.com/kunthar/9d8962e1438e93f50dc6dd94d503af3d
Kunthar

akarca和@Kunthar这两个python2或python3解决方案都将在每次运行时从列表中弹出一个元素,从而破坏列表。这是固定版本:gist.github.com/joshuatvernon/634e0d912401899af0fdc4e23c94192b
joshuatvernon

4

函数式编程方法

smaller = lambda xs, y: filter(lambda x: x <= y, xs)
larger = lambda xs, y: filter(lambda x: x > y, xs)
qsort = lambda xs: qsort(smaller(xs[1:],xs[0])) + [xs[0]] + qsort(larger(xs[1:],xs[0])) if xs != [] else []

print qsort([3,1,4,2,5]) == [1,2,3,4,5]

4

轻松的算法实现

def quicksort(arr):
    if len(arr) < 2:
        return arr #base case
    else:
        pivot = arr[0]
        less = [i for i in arr[1:] if i <= pivot] 
        more = [i for i in arr[1:] if i > pivot]
        return quicksort(less) + [pivot] + quicksort(more)

3

我认为这两个答案都可以对所提供的列表(可以回答原始问题)起作用,但是如果传递包含非唯一值的数组,则会中断。因此,为了完整起见,我只想指出每一个中的小错误并说明如何修复它们。

例如试图以下数组进行排序[12,4,5,6,7,3,1,15,1](需要注意的是两次1次出现)与Brionius算法..在某一时刻将结束与所述较少阵列空以及具有一对值(1,1)的等价数组,这些值在下一次迭代中无法分离,并且len()> 1 ...因此,您将遇到无限循环

您可以通过以下方式修复该问题:在less少为空的情况下返回数组,或者通过调用equal数组中的sort 更好地解决问题,例如 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)
            if x == pivot:
                equal.append(x)
            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并尝试将其附加到列表中。

要修复它,只需在该行添加返回

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]])

顺便说一句,精简版的性能要比长版精简的版本低,这是因为它将数组遍历两次到列表理解中。
2014年

3

这是使用Hoare分区方案的快速排序版本,具有较少的交换和局部变量

def quicksort(array):
    qsort(array, 0, len(array)-1)

def qsort(A, lo, hi):
    if lo < hi:
        p = partition(A, lo, hi)
        qsort(A, lo, p)
        qsort(A, p + 1, hi)

def partition(A, lo, hi):
    pivot = A[lo]
    i, j = lo-1, hi+1
    while True:
      i += 1
      j -= 1
      while(A[i] < pivot): i+= 1
      while(A[j] > pivot ): j-= 1

      if i >= j: 
          return j

      A[i], A[j] = A[j], A[i]


test = [21, 4, 1, 3, 9, 20, 25, 6, 21, 14]
print quicksort(test)

3

分区 -通过枢轴拆分数组,较小的元素向左移动,较大的元素向右移动,反之亦然。枢轴可以是数组中的随机元素。要制定此算法,我们需要知道什么是数组的开始索引和结束索引以及枢轴在哪里。然后设置两个辅助指针L,R。

所以我们有一个数组用户[...,开始,...,结束,...]

L和R指针的起始位置
[...,begin,next,...,end,...]
     R L

而L <结束
  。1.如果user [pivot]> user [L],则将R移一,并将user [R]与user [L]交换
  2.将L移一

片刻之后,将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和将来的视图受益更多。
奥马尔·埃尼纳

2

或者,如果您更喜欢单行代码,也可以说明Python等效的C / C ++变量,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 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

我知道很多人都正确回答了这个问题,我很喜欢阅读它们。我的答案与zangw几乎相同,但是我认为以前的贡献者没有很好地直观地解释事物的实际工作原理...所以这是我的尝试,以帮助其他将来可能会访问此问题的人快速排序实施的简单解决方案。

它是如何工作的 ?

  1. 我们基本上从列表中选择第一个项目作为枢轴,然后创建两个子列表。
  2. 我们的第一个子列表包含小于枢纽的项目
  3. 我们的第二个子列表包含大于或等于数据透视的项目
  4. 然后,我们快速对每个分类,然后将它们合并为第一组+数据透视+第二组以得到最终结果。

这是一个带有视觉效果的示例...(枢轴)9,11,2,0

平均值:n的对数

最坏的情况:n ^ 2

在此处输入图片说明

代码:

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]打印(快速排序(项目))


1
def quick_sort(array):
    return quick_sort([x for x in array[1:] if x < array[0]]) + [array[0]] \
        + quick_sort([x for x in array[1:] if x >= array[0]]) if array else []

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和未来的访问者提供有关如何调试和解决其问题的指南。指出,代码背后的思想是什么,将极大地帮助您理解问题并应用或修改解决方案。Stack Overflow不是代码编写服务,而是一个教学场所。
Palec 2015年

1

一个“真正的”就地实现[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()

1

该算法有4个简单步骤:

  1. 将数组分为3个不同的部分:左,枢轴和右,其中枢轴将只有一个元素。让我们选择此枢轴元素作为数组的第一个元素
  2. 通过将元素与枢轴元素进行比较,将元素附加到各个部分。(注释中的解释)
  3. 递归该算法,直到对数组中的所有元素进行排序
  4. 最后,连接左+枢轴+右部分

python中算法的代码:

def my_sort(A):

      p=A[0]                                       #determine pivot element. 
      left=[]                                      #create left array
      right=[]                                     #create right array
      for i in range(1,len(A)):
        #if cur elem is less than pivot, add elem in left array
        if A[i]< p:
          left.append(A[i])         
          #the recurssion will occur only if the left array is atleast half the size of original array
          if len(left)>1 and len(left)>=len(A)//2:          
              left=my_sort(left)                            #recursive call
        elif A[i]>p: 
          right.append(A[i])                                #if elem is greater than pivot, append it to right array
          if len(right)>1 and len(right)>=len(A)//2:        # recurssion will occur only if length of right array is atleast the size of original array
              right=my_sort(right)
     A=left+[p]+right                                        #append all three part of the array into one and return it
     return A

my_sort([12,4,5,6,7,3,1,15])

左右部分递归地执行此算法。


1

另一个快速排序实现:

# A = Array 
# s = start index
# e = end index
# p = pivot index
# g = greater than pivot boundary index

def swap(A,i1,i2):
  A[i1], A[i2] = A[i2], A[i1]

def partition(A,g,p):
    # O(n) - just one for loop that visits each element once
    for j in range(g,p):
      if A[j] <= A[p]:
        swap(A,j,g)
        g += 1

    swap(A,p,g)
    return g

def _quicksort(A,s,e):
    # Base case - we are sorting an array of size 1
    if s >= e:
      return

    # Partition current array
    p = partition(A,s,e)
    _quicksort(A,s,p-1) # Left side of pivot
    _quicksort(A,p+1,e) # Right side of pivot

# Wrapper function for the recursive one
def quicksort(A):
    _quicksort(A,0,len(A)-1)

A = [3,1,4,1,5,9,2,6,5,3,5,8,9,7,9,3,2,3,-1]

print(A)
quicksort(A)
print(A)

1

对于Python 3.x版本:一种功能样式的使用operator模块,主要用于提高可读性。

from operator import ge as greater, lt as lesser

def qsort(L): 
    if len(L) <= 1: return L
    pivot   = L[0]
    sublist = lambda op: [*filter(lambda num: op(num, pivot), L[1:])]

    return qsort(sublist(lesser))+ [pivot] + qsort(sublist(greater))

并测试为

print (qsort([3,1,4,2,5]) == [1,2,3,4,5])

如果与acarcaArnaldo P. Figueira FigueiraBirger从过去的岁月得到答案相似,则很好(就未记录的代码而言)。读取问题时不确定是否是答案。使用定义看起来并不是严格必要的。… complete my code?lambdasublist()
greybeard

@greybeard实际上,Arnaldo的代码未在Python 3中编译。此外,如何sublist仅使用可以定义filter?那有可能吗?
lifebalance

1
(临时评论:思考def-尚未开始进行修补,因为我试图弄清是否有一种更有效的方法来“分发”可迭代的元素以分隔列表(或者,一个包含一个“类别”))
灰胡子

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

该算法包含两个边界,一个边界的元素小于枢轴(由索引“ j”跟踪),另一个边界的元素大于枢轴(由索引“ i”跟踪)。

在每次迭代中,通过递增j来处理新元素。

不变式:-

  1. 枢轴和i之间的所有元素都小于枢轴,并且
  2. i和j之间的所有元素都大于轴。

如果违反了不变量,则第ith个元素和第j个元素交换,并且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))

选择枢轴

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

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

当支点是序列的中间元素时,可以实现完美的平衡拆分。大于等于小于小于等于的元素数量相等。这种方法可以保证更好的总体运行时间,但是要花很多时间。

选择枢轴的一种非确定性/随机方式是随机地均匀拾取一个元素。这是一种简单轻巧的方法,可以最大程度地减少最坏的情况,并可以实现大致平衡的拆分。这还将在幼稚方法和选择支点的中位数方法之间取得平衡。


0
  1. 首先,我们将数组中的第一个值声明为pivot_value,并设置左右标记
  2. 我们创建第一个while循环,该while循环会告诉分区过程如果不满足必要条件则再次运行
  3. 然后我们应用分区过程
  4. 在两个分区进程都运行之后,我们检查其是否满足适当条件。如果是这样,我们将其标记为完成,否则,我们将左右值切换并再次应用
  5. 完成后,切换左右值并返回split_point

我附上下面的代码!由于枢轴值位置,这种快速排序是一种很好的学习工具。由于它处于固定位置,因此您可以多次浏览它,并真正掌握所有工作原理。在实践中,最好将数据透视点随机化以避免O(N ^ 2)运行时。

def quicksort10(alist):
    quicksort_helper10(alist, 0, len(alist)-1)

def quicksort_helper10(alist, first, last):
    """  """
    if first < last:
        split_point = partition10(alist, first, last)
        quicksort_helper10(alist, first, split_point - 1)
        quicksort_helper10(alist, split_point + 1, last)

def partition10(alist, first, last):
    done = False
    pivot_value = alist[first]
    leftmark = first + 1
    rightmark = last
    while not done:
        while leftmark <= rightmark and alist[leftmark] <= pivot_value:
            leftmark = leftmark + 1
        while leftmark <= rightmark and alist[rightmark] >= pivot_value:
            rightmark = rightmark - 1

        if leftmark > rightmark:
            done = True
        else:
            temp = alist[leftmark]
            alist[leftmark] = alist[rightmark]
            alist[rightmark] = temp
    temp = alist[first]
    alist[first] = alist[rightmark]
    alist[rightmark] = temp
    return rightmark

0
def quick_sort(l):
    if len(l) == 0:
        return l
    pivot = l[0]
    pivots = [x for x in l if x == pivot]
    smaller = quick_sort([x for x in l if x < pivot])
    larger = quick_sort([x for x in l if x > pivot])
    return smaller + pivots + larger

其他18个答案,其中一半以上回答OP的原始问题“如何连接三个数组”。您的答案是否添加了新内容?
Teepeemm '16

0

分区步骤中带有打印变量的完整示例:

def partition(data, p, right):
    print("\n==> Enter partition: p={}, right={}".format(p, right))
    pivot = data[right]
    print("pivot = data[{}] = {}".format(right, pivot))

    i = p - 1  # this is a dangerous line

    for j in range(p, right):
        print("j: {}".format(j))
        if data[j] <= pivot:
            i = i + 1
            print("new i: {}".format(i))
            print("swap: {} <-> {}".format(data[i], data[j]))
            data[i], data[j] = data[j], data[i]

    print("swap2: {} <-> {}".format(data[i + 1], data[right]))
    data[i + 1], data[right] = data[right], data[i + 1]
    return i + 1


def quick_sort(data, left, right):
    if left < right:
        pivot = partition(data, left, right)
        quick_sort(data, left, pivot - 1)
        quick_sort(data, pivot + 1, right)

data = [2, 8, 7, 1, 3, 5, 6, 4]

print("Input array: {}".format(data))
quick_sort(data, 0, len(data) - 1)
print("Output array: {}".format(data))

0
def is_sorted(arr): #check if array is sorted
    for i in range(len(arr) - 2):
        if arr[i] > arr[i + 1]:
            return False
    return True

def qsort_in_place(arr, left, right): #arr - given array, #left - first element index, #right - last element index
    if right - left < 1: #if we have empty or one element array - nothing to do
        return
    else:
        left_point = left #set left pointer that points on element that is candidate to swap with element under right pointer or pivot element
        right_point = right - 1 #set right pointer that is candidate to swap with element under left pointer

        while left_point <= right_point: #while we have not checked all elements in the given array
            swap_left = arr[left_point] >= arr[right] #True if we have to move that element after pivot
            swap_right = arr[right_point] < arr[right] #True if we have to move that element before pivot

            if swap_left and swap_right: #if both True we can swap elements under left and right pointers
                arr[right_point], arr[left_point] = arr[left_point], arr[right_point]
                left_point += 1
                right_point -= 1
            else: #if only one True we don`t have place for to swap it
                if not swap_left: #if we dont need to swap it we move to next element
                    left_point += 1
                if not swap_right: #if we dont need to swap it we move to prev element
                    right_point -= 1

        arr[left_point], arr[right] = arr[right], arr[left_point] #swap left element with pivot

        qsort_in_place(arr, left, left_point - 1) #execute qsort for left part of array (elements less than pivot)
        qsort_in_place(arr, left_point + 1, right) #execute qsort for right part of array (elements most than pivot)

def main():
    import random
    arr = random.sample(range(1, 4000), 10) #generate random array
    print(arr)
    print(is_sorted(arr))
    qsort_in_place(arr, 0, len(arr) - 1)
    print(arr)
    print(is_sorted(arr))

if __name__ == "__main__":
    main()

1
请提供一些解释
Rai

0

该算法不使用递归函数。

N与数字的任何名单len(N) > 0。组K = [N]并执行以下程序。

注意:这是一种稳定的排序算法。

def BinaryRip2Singletons(K, S):
    K_L = []
    K_P = [ [K[0][0]] ] 
    K_R = []
    for i in range(1, len(K[0])):
        if   K[0][i] < K[0][0]:
            K_L.append(K[0][i])
        elif K[0][i] > K[0][0]:
            K_R.append(K[0][i])
        else:
            K_P.append( [K[0][i]] )
    K_new = [K_L]*bool(len(K_L)) + K_P + [K_R]*bool(len(K_R)) + K[1:]
    while len(K_new) > 0:
        if len(K_new[0]) == 1:
            S.append(K_new[0][0])
            K_new = K_new[1:]
        else: 
            break
    return K_new, S

N = [16, 19, 11, 15, 16, 10, 12, 14, 4, 10, 5, 2, 3, 4, 7, 1]
K = [ N ]
S = []

print('K =', K, 'S =', S)
while len(K) > 0:
    K, S = BinaryRip2Singletons(K, S)
    print('K =', K, 'S =', S)

程序输出:

K = [[16, 19, 11, 15, 16, 10, 12, 14, 4, 10, 5, 2, 3, 4, 7, 1]] S = []
K = [[11, 15, 10, 12, 14, 4, 10, 5, 2, 3, 4, 7, 1], [16], [16], [19]] S = []
K = [[10, 4, 10, 5, 2, 3, 4, 7, 1], [11], [15, 12, 14], [16], [16], [19]] S = []
K = [[4, 5, 2, 3, 4, 7, 1], [10], [10], [11], [15, 12, 14], [16], [16], [19]] S = []
K = [[2, 3, 1], [4], [4], [5, 7], [10], [10], [11], [15, 12, 14], [16], [16], [19]] S = []
K = [[5, 7], [10], [10], [11], [15, 12, 14], [16], [16], [19]] S = [1, 2, 3, 4, 4]
K = [[15, 12, 14], [16], [16], [19]] S = [1, 2, 3, 4, 4, 5, 7, 10, 10, 11]
K = [[12, 14], [15], [16], [16], [19]] S = [1, 2, 3, 4, 4, 5, 7, 10, 10, 11]
K = [] S = [1, 2, 3, 4, 4, 5, 7, 10, 10, 11, 12, 14, 15, 16, 16, 19]

0

可能只是一个偏爱,但我想在函数顶部添加验证。

def quicksort(arr):
  if len(arr) <= 1:
    return arr

  left  = []
  right = []
  equal = []
  pivot = arr[-1]
  for num in arr:
    if num < pivot:
      left.append(num)
    elif num == pivot:
      equal.append(num)
    else:
      right.append(num)

  return quicksort(left) + equal + quicksort(right)

0

我的答案与@alisianoi中的最佳答案非常相似。但是,我认为他的代码有一点点效率低下(请参阅我的评论),我已将其删除。此外,我添加了更多的解释,并且对重复(枢轴)值的问题更加具体。

def quicksort(nums, begin=0, end=None):
    # Only at the beginning end=None. In this case set to len(nums)-1
    if end is None: end = len(nums) - 1

    # If list part is invalid or has only 1 element, do nothing
    if begin>=end: return

    # Pick random pivot
    pivot = nums[random.randint(begin, end)]

    # Initialize left and right pointers
    left, right = begin, end
    while left < right:
        # Find first "wrong" value from left hand side, i.e. first value >= pivot
        # Find first "wrong" value from right hand side, i.e. first value <= pivot
        # Note: In the LAST while loop, both left and right will point to pivot!
        while nums[left] < pivot: left += 1
        while nums[right] > pivot: right -= 1

        # Swap the "wrong" values
        if left != right:
            nums[left], nums[right] = nums[right], nums[left]
            # Problem:  loop can get stuck if pivot value exists more than once. Simply solve with...
            if nums[left] == nums[right]:
                assert nums[left]==pivot
                left += 1

    # Now, left and right both point to a pivot value.
    # All values to its left are smaller (or equal in case of duplicate pivot values)
    # All values to its right are larger.
    assert left == right and nums[left] == pivot
    quicksort(nums, begin, left - 1)
    quicksort(nums, left + 1, end)
    return
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.