Week 3.2 | Quik sort | Princeton Algorithms

一、双路快排

1. idea

  1. 打乱数组
  2. 分割partition(让a[0]归位,其左边是比它小的,右边是比它大的)
  3. 递归sort

和mergesort显然不同的是,qsort是先进行本层的操作,再递归(从顶到底);而mergesort是先递归,再从底层开始逐层进行该层的操作(从底到顶)。

在这里插入图片描述
以上是qsort的主要思想。在此基础上,qsort也有一些不同的实现方式,如普通快排、双路快排、三路快排。普通快排似乎用到的不是太多,因为对于大量重复元素,它的表现是最差的,为n2。本节课主要学习的是双路快排和三路快排,其中三路快排对于大量重复元素的处理能够做到最好。
接下来要讲的主要是二路快排的思路和实现,三路快排在优化中讲:
在这里插入图片描述
在这里插入图片描述

2. 实现

  • 代码

在这里插入图片描述
在这里插入图片描述

  • 实现细节
    • in-place
      没有用到额外空间。可以使用额外空间,让快排更加简单并stable,但没必要。
    • not stable
      交换两个元素可能跨越相同的元素。
    • The (j == lo) test is redundant, but the (i == hi) test is not.
      因为当 i 指向的元素比low元素小时(不包括等于),i++,有可能超出high的限制(有可能所有的元素都比low元素小);当j指向的元素比low元素大时(不包括等于),j- -,但当j指向low元素时,j元素与low元素相等,则停止,不会超出low的限制。
    • shuffle打乱是有必要的,因为这样可以打乱数组本身的顺序。
      数组越有序,快排越慢。
      当快排的每一层,low元素归位后,正好从中间二分左右两部分是最快的,因为二分的话其树的深度最浅,深度为logN,时间为NlogN(每一层花费时间都为N)。但如果每次low元素归位后,左右两部分大小很不均衡,则很慢。考虑极端情况,数组本身是升序的,如果不打乱,每次low元素归位后,其左边没有任何元素比它小,右边是所有剩下的元素(都比它大),则树的深度最深,深度为N,时间为N2。为了避免这种情况,需要打乱,
    • equal keys
      当存在重复项时,最好的办法不是跳过,而是停止扫描,将其进行交换操作。在二路快排中的实现就是这么做的。i ++和j - -的条件分别是a[i] < a[low] 和 a[j] > a[low] ,而不是等于。也就是说,当 i 指针遇到重复项时,指针不再改变,而是与 j 元素进行交换。j 也同理。这样做的目的是让key元素归位后,其左右两边的元素都包含重复项,即图中所示,左边为<= v,右边为>= v。这样能够尽可能平分重复项,而不是让重复项集中在key元素的一边。这样能够尽可能让左右两区域均衡,减少树的深度。

3. 复杂度分析

  • 最好情况:全部二分

非灰色的元素是当前层访问到的元素。显然比最坏情况少许多。
在这里插入图片描述

  • 最坏情况:有序数组(可以通过打乱避免)

在这里插入图片描述

  • 总结

在这里插入图片描述
average case证明略,对我来说太复杂了…
最后一条的意思是,需要谨慎对待快排的一些实现方法,因为有些实现方法对于这两种情况会变成n2复杂度。当数组有序,如果不打乱,会n2;当有大量重复项,即使打乱了,如果在排序时指针直接跳过重复项而不进行交换,也可能是n2。具体见下文。

  • 该算法的其他性质:in-place, not stable。

4. 一些改进方法

  • 当前需要快排的区间如果非常小(小于10个),可以直接用插入排序对当前区间排序。因为快排对于很小的子数组有太多的多余开销。

在这里插入图片描述

  • 在low元素、high元素和当前区间的中间位置元素中,找到三个元素中排序为中间的元素,将该元素作为key元素(即与low元素交换)。这样能够更大可能地均分这一次分割后的两个区间。

在这里插入图片描述

二、双路快排的应用:找到第k小的元素(selection)

在这里插入图片描述
在这里插入图片描述
ps. 最坏情况为线性的selection算法还没有找到很完美的。现在就使用quik-select。

三、三路快排

为了解决大量重复项的问题。

当有大量重复项时,三种快排的表现:
在这里插入图片描述

  • 普通快排:当遇到重复项时,将其分配在key元素的同一侧。这样最慢。因为两侧不均衡。尤其是当数组中所有项都相同时,剩下的元素都在同一侧,树的深度最深。
  • 双路快排:当遇到重复项时,停止指针的扫描,将 i 指针与 j 指针交换,尽可能地均匀分配重复项在key元素两侧。但是这样在每次排序之后,后面的排序还需要对重复项进行操作。
  • 三路快排:最优方法,将数组分成三个区间,小于、等于和大于。每一层快排之后,都能将重复项直接归位,下一次排序只操作小于和大于部分,不再对重复项进行操作。

参考:
快速排序精讲——需要重点处理的三种特殊情况
快速排序 详解(快速排序 双路快排 三路快排)
快速排序之重复元素过多的改进

1. idea

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
例:当只有三个元素时,一次快排就可以排好序,不需要递归。该例子中key元素始终为R(即只是一层快排)。
在这里插入图片描述

2. 实现

在这里插入图片描述

3. 复杂度分析

Randomized quicksort with 3-way partitioning reduces running time from linearithmic to linear in broad class of applications

输入N个元素,任何排序方法的花费下限(也就是复杂度最低)是:
当N个元素都不同,是NlogN;
当只有常数数量的不同元素,剩下的都是相同的,是N。
(证明略)

三路快排已经能够做到最优。

四、系统的排序方法

  • Java的arrays.sort()
    对object使用的是mergesort,因为对于对象来说,可能对于空间的节省要求不那么高(对象本身就占不少空间),且mergesort是stable的。而对于原始数据类型,则使用quicksort,因为这样能够最大限度的节省空间(不需要额外空间,除了递归用到的栈),且不用担心不stable的问题(对于原始数据来说,排序就看当前元素的大小即可,元素没有其他的属性可以比较。而对象则不同,对象有好多属性可以比较,如果不stable就会很麻烦,排完这个属性,上一个属性又乱了)。

在这里插入图片描述

  • C, C++, Java 6等系统使用的quiksort,进行了许多微调。

在这里插入图片描述
在这里插入图片描述
即使如此, Java’s system sort is not solid。当输入数据过多,这些数据会使函数调用栈溢出,并让程序崩溃。

  • 如何选择一个排序算法,要考虑很多内容

在这里插入图片描述

几种基本排序方法的总结

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值