排序算法专题之快速排序

Posted Python算法之旅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序算法专题之快速排序相关的知识,希望对你有一定的参考价值。

说在前面  

    在各种基于关键码比较的内排序算法中,快速排序是实践中平均速度最快的算法之一。

    快速排序算法最基本的思想是划分,即按照某种标准将待排序列分割成独立的两个子序列(分别称为“小记录”和大记录“),然后分别对这两个子序列分别快速排序,以达到整个序列有序。根据划分算法的不同,快速排序有许多不同的实现方法,本文将给出一些常见版本并分析其优化过程,抓住问题的本质。

    说明:选取适当的枢纽元尽可能将数组划分为长度相当的两个子序列,是快速排序高效的关键。为了表达的方便,我们简单的选取a[left]作为枢纽元,并在之后的改进算法中选择更适合的枢纽元。


“Python算法之旅”微信群等着你
排序算法专题之快速排序
排序算法专题之快速排序
排序算法专题之快速排序
排序算法专题之快速排序

扫码加入“Python算法之旅”微信群,和斌哥面对面交流,更多资料和更有趣的话题等你一起来分享。

排序算法专题之快速排序

快速排序算法(填空版)

1.  非原地快速排序

例1.快速排序算法最基本的思想是划分,即按照某种标准将待排序列分割成独立的两个子序列(分别称为“小记录”和大记录“),然后分别对这两个子序列分别快速排序,以达到整个序列有序。

我们可以在列表中选择一个元素作为"基准"(枢纽元),将列表分成左右两个子序列,其中左序列的元素均小于枢纽元,而右序列的元素均不小于枢纽元。为了表达的方便,我们简单的选取a[0]作为枢纽元,并在之后的改进算法中选择更适合的枢纽元。

函数名:quick_sort(a)

参数表:a -- 待排序列表。

返回值:升序排序后的新列表。

def quick_sort(a):

    if len(a) <= 1: return a

   

    left, right = [], []

    p = a[0] #选取a[0]作为枢纽元

    for x in a[1:]:

        if x < p:

            left.append(x)

        else:

            ①             

return quick_sort(left) + [p] + quick_sort(right)


2.  原地快速排序(单向扫描)

在函数内嵌套定义快速排序子函数,不必另外分配空间。

方法一:单向扫描划分数组。使用两个下标i和j,分别指向最后一个小记录的下标和第一个未处理记录的下标。

优点:思路清晰,代码简洁易懂。

缺点:只向一个方向扫描,每遇到小记录元素就与下标i所指的大记录元素交换位置,不能将该大记录元素一步到位交换到右侧的正确位置,导致某些大记录元素需要交换多次才能到达正确位置。

一个极端的例子,a=[5,6,4,2,3,1],值为6的元素被交换了4次,才最终到达正确位置,都快赶上冒泡排序的低效了。

函数名:quick_sort_1(a)

参数表:a -- 待排序列表。

返回值:该方法没有返回值,但是会对列表的对象进行升序排序。

def quick_sort_1(a):

    #嵌套定义快速排序子函数

    def q_sort(left, right):

        if left >= right: return

    

        p = a[left] #选取a[left]作为枢纽元

        i = left

        for j in range(left+1, right+1):

            if a[j] < p: #确保小记录元素小于枢纽元

                i += 1

                if i != j: #将小记录元素交换到左侧

                    a[i], a[j] = a[j], a[i]

                   

        ①            #将枢纽元放到正确位置

        q_sort(left, i-1)

        ②                

    

q_sort(③         ) #调用快速排序子函数


3.  原地快速排序(双向扫描1)

方法二:双向扫描划分数组。方法一中被交换的大记录元素不能一步到位交换到右侧的正确位置,导致出现重复交换。

我们可以通过从数组的两端交替向中间扫描,每扫描一次就把“不合群”的元素交换到另一侧的方法,确保每个位置的元素最多只被交换一次,这样效率要高于方法一。

为了保证小记录元素小于枢纽元,我们需要选取a[right]作为枢纽元,这样向右扫描时才不会越界。

函数名:quick_sort_2(a)

参数表:a -- 待排序列表。

返回值:该方法没有返回值,但是会对列表的对象进行升序排序。

def quick_sort_2(a):

    #嵌套定义快速排序子函数

    def q_sort(left, right):

        if left >= right: return


        p = a[right] #选取a[right]作为枢纽元

        i, j = left, right

        while i < j:

            while a[i] < p: #确保小记录元素小于枢纽元

                ①               

            while i < j and a[j] >= p: #确保大记录元素不小于枢纽元

                ②               

            if i < j: #将“不合群”的元素交换到另一侧

                a[i], a[j] = a[j], a[i]

                   

        ③            #将枢纽元放到正确位置

        q_sort(left, i-1)

        q_sort(i+1, right)  

    

q_sort(0, len(a)-1) #调用快速排序子函数


4.  原地快速排序(双向扫描2)

方法三:双向扫描划分数组。方法二中采取先扫描,再交换的方法,每次交换前都要先判断是否满足i < j,交换完毕后,又要再次判断是否满足i < j,以决定是否进入循环。这样重复判断,效率较低。我们将交换操作放到扫描之前,可以少做一次判断。我们开始选取a[left]作为枢纽元,经过第一次交换操作后,枢纽元到达right位置。

函数名:quick_sort_3(a)

参数表:a -- 待排序列表。

返回值:该方法没有返回值,但是会对列表的对象进行升序排序。

def quick_sort_3(a):

    #嵌套定义快速排序子函数

    def q_sort(left, right):

        if left >= right: return

        p = a[left] #选取a[left]作为枢纽元

        i, j = left, right

        while i < j:

            ①             #第一次交换操作后,枢纽元到达right位置

            while a[i] < p: #确保小记录元素小于枢纽元

                 ②                  

            while i < j and a[j] >= p: #确保大记录元素不小于枢纽元

                 ③                  

                   

        ④                     #将枢纽元放到正确位置

        q_sort(left, i-1)

        q_sort(i+1, right)  

    

    q_sort(0, len(a)-1) #调用快速排序子函数

快速排序算法(完整版)

1.  非原地快速排序

例1.快速排序算法最基本的思想是划分,即按照某种标准将待排序列分割成独立的两个子序列(分别称为“小记录”和大记录“),然后分别对这两个子序列分别快速排序,以达到整个序列有序。

我们可以在列表中选择一个元素作为"基准"(枢纽元),将列表分成左右两个子序列,其中左序列的元素均小于枢纽元,而右序列的元素均不小于枢纽元。为了表达的方便,我们简单的选取a[0]作为枢纽元,并在之后的改进算法中选择更适合的枢纽元。

函数名:quick_sort(a)

参数表:a -- 待排序列表。

返回值:升序排序后的新列表。

def quick_sort(a):

    if len(a) <= 1: return a

   

    left, right = [], []

    p = a[0] #选取a[0]作为枢纽元

    for x in a[1:]:

        if x < p:

            left.append(x)

        else:

            right.append(x)

return quick_sort(left) + [p] + quick_sort(right)


2.  原地快速排序(单向扫描)

在函数内嵌套定义快速排序子函数,不必另外分配空间。

方法一:单向扫描划分数组。使用两个下标i和j,分别指向最后一个小记录的下标和第一个未处理记录的下标。

优点:思路清晰,代码简洁易懂。

缺点:只向一个方向扫描,每遇到小记录元素就与下标i所指的大记录元素交换位置,不能将该大记录元素一步到位交换到右侧的正确位置,导致某些大记录元素需要交换多次才能到达正确位置。

一个极端的例子,a=[5,6,4,2,3,1],值为6的元素被交换了4次,才最终到达正确位置,都快赶上冒泡排序的低效了。

函数名:quick_sort_1(a)

参数表:a -- 待排序列表。

返回值:该方法没有返回值,但是会对列表的对象进行升序排序。

def quick_sort_1(a):

    #嵌套定义快速排序子函数

    def q_sort(left, right):

        if left >= right: return

    

        p = a[left] #选取a[left]作为枢纽元

        i = left

        for j in range(left+1, right+1):

            if a[j] < p: #确保小记录元素小于枢纽元

                i += 1

                if i != j: #将小记录元素交换到左侧

                    a[i], a[j] = a[j], a[i]

                   

        a[i], a[left] = a[left], a[i] #将枢纽元放到正确位置

    

        q_sort(left, i-1)

        q_sort(i+1, right)  

    

q_sort(0, len(a)-1) #调用快速排序子函数


3.  原地快速排序(双向扫描1)

方法二:双向扫描划分数组。方法一中被交换的大记录元素不能一步到位交换到右侧的正确位置,导致出现重复交换。

我们可以通过从数组的两端交替向中间扫描,每扫描一次就把“不合群”的元素交换到另一侧的方法,确保每个位置的元素最多只被交换一次,这样效率要高于方法一。

为了保证小记录元素小于枢纽元,我们需要选取a[right]作为枢纽元,这样向右扫描时才不会越界。

函数名:quick_sort_2(a)

参数表:a -- 待排序列表。

返回值:该方法没有返回值,但是会对列表的对象进行升序排序。

def quick_sort_2(a):

    #嵌套定义快速排序子函数

    def q_sort(left, right):

        if left >= right: return


        p = a[right] #选取a[right]作为枢纽元

        i, j = left, right

        while i < j:

            while a[i] < p: #确保小记录元素小于枢纽元

                i += 1

            while i < j and a[j] >= p: #确保大记录元素不小于枢纽元

                j -= 1

            if i < j: #将“不合群”的元素交换到另一侧

                a[i], a[j] = a[j], a[i]

                   

        a[i], a[right] = a[right], a[i] #将枢纽元放到正确位置

       

        q_sort(left, i-1)

        q_sort(i+1, right)  

    

q_sort(0, len(a)-1) #调用快速排序子函数


4.  原地快速排序(双向扫描2)

方法三:双向扫描划分数组。方法二中采取先扫描,再交换的方法,每次交换前都要先判断是否满足i < j,交换完毕后,又要再次判断是否满足i < j,以决定是否进入循环。这样重复判断,效率较低。我们将交换操作放到扫描之前,可以少做一次判断。我们开始选取a[left]作为枢纽元,经过第一次交换操作后,枢纽元到达right位置。

函数名:quick_sort_3(a)

参数表:a -- 待排序列表。

返回值:该方法没有返回值,但是会对列表的对象进行升序排序。

def quick_sort_3(a):

    #嵌套定义快速排序子函数

    def q_sort(left, right):

        if left >= right: return


        p = a[left] #选取a[left]作为枢纽元

        i, j = left, right

        while i < j:

            a[i], a[j] = a[j], a[i] #第一次交换操作后,枢纽元到达right位置

            while a[i] < p: #确保小记录元素小于枢纽元

                i += 1

            while i < j and a[j] >= p: #确保大记录元素不小于枢纽元

                j -= 1

                   

        a[i],a[right] = a[right],a[i] #将枢纽元放到正确位置

       

        q_sort(left, i-1)

        q_sort(i+1, right)  

    

    q_sort(0, len(a)-1) #调用快速排序子函数
写在后面

Python排序算法系列文章是我在阅读了大量算法专著以后,尝试用浅陋的语言把自己的理解表达出来。由于本人水平有限,表述中难免出现疏漏甚至错误之处,敬请谅解。

无论是赞同还是反对我的看法,都请你给我留言。如果你有新的想法,千万不要憋在心里,请发出来大家一起讨论。让我们相互学习,共同进步!


需要本文word版的,可以加入“选考VB算法解析”知识星球参与讨论和下载文件,“选考VB算法解析”知识星球汇集了数量众多的同好,更多有趣的话题在这里讨论,更多有用的资料在这里分享。

我们专注选考VB算法,感兴趣就一起来!



相关优秀文章:


以上是关于排序算法专题之快速排序的主要内容,如果未能解决你的问题,请参考以下文章

常用排序算法专题—快速排序

排序算法专题之插入排序

排序专题之 各个内外排序算法的比较

排序算法专题之堆排序

LeetCode排序专题算法

LeetCode排序专题算法