前言

上次分享的冒泡排序虽然比较简单、容易理解,但每一次冒泡的过程都需要依次比较相邻的元素,然后交换,可见性能还是有很大的优化空间,只要能减少比较次数,性能自然就上去啦;快速排序便是一个很不错的选择~~~

正文

1.1 快速排序算法思想

快速排序(Quicksort)是对上一次分享的冒泡排序算法的一种改进,主要是减少比较次数,以此来提高排序性能;也属于交换排序的一种。

算法思想

  • 在待排序列表中任取一个元素作为基准值
  • 将剩下的元素和基准值依次比较,小于的放左边,大于的放右边,最后通过一趟排序将待排序列表分为左右两部分,这个过程称为一次“划分”
  • 然后用同样的方法分别在左右两部分中取基准值进行排序,依次递归,直到划分的每部分内只有一个元素或空为止,则排序结束。

1.2 快速排序算法实现与解析

  • 算法实现

    划分代码实现,每一次取对应部分的首位元素作为基准值,然后通过依次比较,将对应数据又在细分为左右两部分,如下实现:

    快速排序的性能和名字一样优秀-LMLPHP

    使用递归进行划分排序,直到只有一个元素或为空为止:

    快速排序的性能和名字一样优秀-LMLPHP

  • 运行效果

    快速排序的性能和名字一样优秀-LMLPHP

  • 步骤解析(升序)

    快速排序的性能和名字一样优秀-LMLPHP

    上图步骤说明:

    上图演示是针对数组array进行升序排序,第一次以全部数据为一组,low为0,high为5,选择low位元素2为基准值(即第一个元素),开始进行比较:

    第1.1步:由于刚开始选择low位的元素为基准值(可以认为这个位置空了),所以接下来开始从high位开始遍历比较,high位元素3大于2,不用交换位置,high减1,继续比较;

    第1.2步:此时low为0,high为4,high位元素9大于2,不用交换位置,high减1,继续比较;

    第1.3步:此时low为0,high为3,high位元素1小于2,需要将high位元素1放到基准值的左边,则将元素1放到low指向的位置;

    第1.4步:此时low为0,high为3,由于high位已经交换过了(可以认为这个位置空了),所以这次开始到low位进行遍历比较,low位元素是刚交换过来的,所以不用交换位置;low加1继续比较,此时low为1,对应位置的元素5大于基准值2,所以需要将元素5放到基准值的右边,则将元素5放到high指向位置;

    第1.5步:此时low为1,high为3,由于low位已经交换过了(可以认为这个位置空了),所以这次又回到high位进行遍历比较,high位元素是刚交换过来的,所以不用交换位置;high减1继续比较,此时high为2,对应位置元素6大于基准值2,所以不用交换位置,high减1,继续比较;

    第1.6步:此时low为1,high为1,此时代表第一次划分排序完成,则将基准值放到这个位置;最终将原始数据分为左右两部分,左部分只有一个元素1,不用再划分了,右部分有6,5,9,3四个元素,继续对于右部分进行划分

    第2.1步:由于右部分是从索引位2开始,所以此时low为2,high为5,基准值为low为的元素6;

    第2.2步:由于刚开始选择low位的元素为基准值(可以认为这个位置空了),接下来从high为开始遍历比较,high为元素3小于基准元素6,需要将其放到基准元素的左边,则将元素3放到low指向的位置。

    第2.3步:此时low为2,high为5,由于high位已经交换过了(可以认为这个位置空了),所以这次开始到low位进行遍历比较,low位元素是刚交换过来的,所以不用交换位置;low加1继续比较,此时low为3,对应位置的元素5小于6,不需要交换元素,则low加1,继续比较;

    第2.4步:此时low为4,high为5,low位对应的元素9大于基准值6,所以需要将其放到基准元素的右边,则将元素9放到high指向的位置。

    第2.3步:此时low为4,high为5,由于low位已经交换过了(可以认为这个位置空了),所以这次开始到high位进行遍历比较,high位元素是刚交换过来的,所以不用交换位置;high减1继续比较,此时high为4,此时low和high都为4,找到此次划分基准值的位置,则将基准元素6的放到4位置; 到这又将原来的右部分6,5,9,3四个元素划分为左右两部分,右边只有一个元素9,不用继续划分;左边有元素3和5,继续划分排序;(这里就不重复演示)

    最终通过递归划分排序的方式,直到每个划分部分内只有一个元素或空为止,即可获得最后的排序结果。

1.3 快速排序算法分析

时间复杂度

从上面解析步骤得知,每一次排序都是只需要处理剩下未排序的元素,每一次排序时间复杂度不会超过O(n),但由于是通过递归进行划分排序,所以快速排序的整体时间复杂度和递归层数有关,即总的时间复杂度为O(n*递归层数)

通过上面演示得知,其实最终将待排序数据划分为一个二叉树结构,在这二叉树的高度就代表递归的层数(后续会专门分享这块内容),如下图:

快速排序的性能和名字一样优秀-LMLPHP

关于n个元素的二叉树的最小高度为(logn)+1,最大高度为n,如果待排序数据已经有序或逆序,如果每次都选择每部分的首个元素为基准值,这样就会导二叉树高度增加,即递归深度就会增加;所以快速排序的时间复杂度最好为O(nlogn),最坏为O(n)

这样以为快速排序就不行了吗?当然不是,可以随机选一个元素做为基准值,这样不管待排序数据为有序还是逆序,都不会导致递归深度太深。所以最后快速排序的平均时间复杂度为O(nlogn)

空间复杂度

空间复杂度在每次递归当中,用到的变量都是固定的(pivot,low,high),则最终影响空间复杂度的因素还是递归层数,则快速排序空间复杂度为O(递归层数), 最好为O(logn),最坏为O(n)。

稳定性

由于是用待排序数据和基准值进行比较,所以最终元素交换位置不是固定的,则不能保证两个相等元素原有顺序不变,则快速排序是不稳定的。如下图:

快速排序的性能和名字一样优秀-LMLPHP

如果取low位置的元素3作为基准值,最终会和元素2进行交换,最后就不能保证原来相等元素的前后顺序了。

综上所述,快速排序的时间复杂度为O(nlogn),空间复杂度为O(logn),是不稳定算法;

总结

快速排序有效的解决了冒泡排序的缺陷,减少了比较次数,提升了排序性能。但当待排序列表为有序或逆序时,如果单纯的取第一个元素或最后一个元素作为基准值,排序性能并没有提升。所以实现排序算法的关键是需要选个好的基准值,比如可以随机选择,也可以定义一个规则选择,看小伙伴的实现方式咯。

感谢小伙伴的:点赞收藏评论,下期继续~~~

一个被程序搞丑的帅小伙,关注"Code综艺圈",跟我一起学~~~

05-07 10:28