排序算法——关于快速排序的一些思考

快速排序

快速排序有多个版本,这里记录在看《啊哈!算法》中快速排序时遇到的问题与思考。

《啊哈!算法》:http://developer.51cto.com/art/201403/430986.htm

Python实现

import random
A = [random.randint(0,30) for i in range(10)] #生成10个0~30以内的随机数
print(A)

#从小到大排序
def quicksort(A,l,r):
    if l<r:
        c = partition(A,l,r) 
        quicksort(A,l,c-1)
        quicksort(A,c+1,r)
    return A

def partition(A,l,r):
    x = A[l] #选择A[l]作为基准数
    y = l #基准数下标
    while r>l:
        while A[r]>=x and r>l: #先从右边找比基准数小的数字
            r -= 1
        while A[l]<=x and r>l: #从左边找比基准数大的数字
            l += 1
        A[l],A[r] = A[r],A[l] #找到之后进行交换
    A[l],A[y] = x,A[l] #基准数交换
    return l #交换后基准数下标

print(quicksort(A,0,len(A)-1))

看了快速排序之后虽然已经会用,但是依然感觉有一团迷雾在脑海里面挥之不去,到底是哪些问题让人感觉不够清晰呢?

    一、为什么必须要先从右边开始进行比较呢?先从左边开始可以吗?这取决于什么?

    二、进行比较的时候,应该找大于基准数的数字还是找小于基准数的数字呢?如何判断?

    三快速排序中,基准数一定要选择最左边(或最右边)的第一个数吗?如果基准数选择中间的某个数可以吗?

答:

    1.从哪边先比较,取决于“基准数初始位置在归位位置的左边还是右边”。

    2.是找大于基准数的数字,还是找小于基准数的数字取决于“排序是从小到大,还是从大到小”。

    3.基准数最好选择最左边或者最右边的数字,选择中间的某个数字可能会降低效率。

    注意:下面的排序都是指从左到右的排序

为什么:

    首先有四个重要的点:

        一、排列顺序(从小到大或从大到小)

        二、基准数初始位置在归位位置的左边还是右边

        三、先从哪边开始比较

        四、应该选择大于基准数还是小于基准数的数字进行比较

    这四个点之间的关系是这样的:一决定四,二决定三,三和四决定排序是否正确高效

    第一点决定第四点:这点想想快速排序的原理就清楚了,回顾快速排序的原理是“通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列”。在排列顺序为从小到大的一趟排序过程中,最后一次交换是某个数和基准数的交换,最后基准数左边的数字都比基准数右边的数字小,也就是说从小到大的排序中,左边的大于基准数的数字被交换到右边,右边小于基准数的数字被交换到左边,且每个数字只交换了一次。因此在从小到大的排列中,左边往右找比基准数大的数字,右边 往左找比基准数小的数字。从大到小的排列中,左边往右找比基准数小的数字,右边往左找比基准数大的数字。

    第二点决定第三点:首先“基准数归位位置”在哪里?在一趟排序中最后一次交换就是基准数和某个数位置的交换,交换过后的基准数的位置就是“基准数归位位置”。整个排序过程中每次排序的基准数一旦交换,这次排序就结束了,基准数的位置就不再变动。因此“第二点决定第四点”讲的就是“基准数的初始位置是在归位位置的左边还是右边”将影响排序时“应该先从左边还是右边开始进行与基准数做比较”。为什么要选择先从左边还是先从右边呢?因为先从哪边开始将影响的排序是否正确,如果选错了方向可能会导致增加排序的交换次数降低排序的效率或者导致无限的循环交换(可以看下面的例子)。那么第二点是如何影响第三点的呢?例如一趟从小到大的排序中,假设知道基准数的归位位置(事实上并不知道,如果知道了就无需排序了),由于基准数初始位置是由自己指定的,所以便知道基准数初始位置在归位位置的左边还是右边了,假设在左边,那么最后一次交换会将基准数从初始位置交换到归位位置,也就是交换到右边。因为是从小到大排序,所以如果基准数交换到了右边,就说明与基准数交换的数字比基准数小,进而我们判断应该先从右边开始比较(为什么?看上面第一点决定第四点)。可是有一个问题就是,在排序之前我们并不知道“基准数归位位置”,那如何判断初始位置是它左边还是右边呢?有一个办法就是把基准数定为数列的最左边(或最右边)的数字,这样的话基准数归位位置一定在初始位置的右边(或左边)或者归位位置就是初始位置,进而我们能判断从哪边先开始比较。如果把基准数定为最左和最右之间的某个数字的话,我们将难以确定基准数初始位置是在归位位置的左边还是右边,进而可能会出现下面例子里不合理的排序。这就是为什么基准数最好选择最左边或者最右边。

例子:

初始无序数列:[ 6, 1, 2, 7, 9 ]

最终目标数列:[ 1, 2, 6, 7, 9 ]

选取基准数为[6],根据目标数列可知是从小到大排列。因此正确的应该是先从右边开始找比基准数小的数。

但是如果先从左开始会出现什么情况呢?

在第一趟排序中:

1.先从左边找比基准数[6]大的数字(由于是从小到大排列,所以不可能从左边找比基准数小的数字),找到[7]停下来

即:[ 6, 1, 2, 7, 9 ]

2.此时右边找比基准数[6]小的数字,由于在数字[7]与左边指针相遇停了下来,于是基准数[6][7]进行交换,第一趟排序结束。

即:[ 7, 1, 2, 6, 9 ]

这样反而将大的数字交换到了前面。

接下来[7]又跟[2]交换,然后[7]再跟[6]交换才得到一趟排序中正确的序列[ 2, 1, 6, 7, 9 ]

如果先从右边找比基准数小的数字,第一次交换就得到[ 2, 1, 6, 7, 9 ]

所以选错方向会增加交换的次数(好像也可能出现两个数字无限循环交换的情况)。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值