快速排序

89 阅读7分钟

1、基础知识:

快速排序是霍尔(Hoare)于1962年提出的一种二叉树结构的交换排序方法

2、基本思想(二分+分治):

1、先从数列中取出一个元素作为基准数

2、扫描数列,将比基准数小的元素全部放到它的左边,大于或者等于基准数的元素全部放到它的右边,得到左右两个区间

3、在对左右两个区间重复第二部操作,直到各区间少于两个元素。

3、快速排序的单趟排序:

快速排序将待排序序列逐步划分为较小的子序列,并对每个子序列再进行单趟排序。被嵌套调用的一系列单趟排序构成了完成的快速排序算法。

在快速排序的单趟排序中,我们通过选择一个基准元素(代码中用Key表示,其下标用KeyIndex表示),并根据其值将序列进行划分。

我们可以使用霍尔法、挖坑法、前后指针法三种方式来实现单趟排序。

一、挖坑法实现步骤:

1、选择第一个元素作为基准元素,然后把基准元素用Key存放起来。

图片1.png

我们选择将55作为基准元素。

2、HoleIndex是坑的位置,初始位置为基准元素的位置。left和right分别指向待排数组的最左侧和最右侧,right找比key基准值小的数,left找比key基准值大的数。 图片2.png

3、right从右侧开始,寻找第一个小于基准元素的值。

图片3.png

4、将该值移动到当前的坑中,该值原来位置成为新的坑位,因此更新 HoleIndex 为右侧坑的位置。

图片4.png

5、left从左侧开始,寻找第一个大于基准元素的值。

image.png

6、将该值填充到右侧的坑中,该值原来位置成为新的坑位,因此更新 HoleIndex 为左侧坑的位置。(此时应该从右边开始新一轮的遍历)

image.png

7、重复步骤 3-6,直到左侧指针 left 和右侧指针 right 相遇。

image.png

8、将基准元素放置到最后一个坑中,即填充到最终的位置。

image.png

9、返回坑的位置 HoleIndex 作为划分子序列的分界点。然后分别对左右两边的子序列进行排序(也是利用挖坑法)。因为方法相同,所以是用递归的方法,这就是分治的思想

二、霍尔法实现步骤:

1、选择基准元素,通常选择序列的第一个元素作为基准元素。

image.png

2、利用两个下标left和right分别指向待排数组的最左侧和最右侧,right指针找比key基准值小的数,left找比key基准值大的数。

image.png

3、right 先向左移动,直到找到第一个小于基准元素的值。

image.png

4、left 向右移动,直到找到第一个大于基准元素的值。

image.png

5、找到之后交换这两个下标对应的数值,即交换a[left]和a[right]。

image.png

6、重复步骤 3-5,直到 left 与 right相遇。

image.png

7、最后,交换基准元素 a[KeyIndex] 和 a[left],此时,基准元素所在位置就是它最终的位置。

image.png

8、函数返回 left(left标记了子序列的分界点)。然后分别对左右两边序列进行排序(使用霍尔法)。

为什么能够保证相遇位置比key小?

因为right先走,使得结局为以下两种:

1、right停下之后,left向右靠近并与right相遇,由于right位置定在比key小的值上,所以最终left和right都在比key小的位置处。

2、left停下之后,right与left进行交换,交换后left指向的值比key小,此时right遇到left的位置一定比key小。

4、快速排序的问题与改进方法:

优化快速排序的基准数选择对算法的性能和稳定性有很大的影响。如果基数选择不好会非常影响算法的时间复杂度。为了避免最坏情况的发生,我们在选择基准数时更加谨慎,确保快速排序的平均性能得到保证。

最坏情况发生在以下两种情景下:

1、数组已经有序(升序或降序):当输入的数组是有序的时候,如果每次选择第一个或最后一个元素作为基准数,快速排序每次只能将数组划分为一个元素和剩余的元素,导致递归深度为 n(数组长度),时间复杂度为 O(n^2)。

2、数组具有相同元素:当输入的数组中所有元素都相同(例如,数组元素全部为2),无论选择哪个元素作为基准数,每次划分都只能将数组分为一个元素和一坨剩余的元素,导致递归深度为 n(数组长度),时间复杂度为 O(n^2)。

 

 

改进策略:

1、三值取中法是一种常用的优化策略,它通过选取子序列中某三个元素的中位数作为基准数(一般选择首、中、尾三数),从而尽可能防止选择的基准数为待排数组的最大、最小值,而导致最坏情况出现。

2、(当序列长度小于某个阈值时进行使用。)为了解决递归到较深处时,调用大量栈帧来实现短小的排序的“小题大做”问题,可以采用小区间优化的方法,即当待排序的子序列长度小于某个阈值时,不再使用递归方式进行排序,而是采用其他排序算法(如插入排序)对该子序列进行排序。通过直接对较短的子序列使用简单而高效的排序算法,避免了递归调用带来的开销,从而提高了排序的效率。我们通常在排序中进行判断,如果序列长度小于某个固定值时就会选择使用插入排序进行改进。

问题:

1、待排数组较短时,此时再使用递归方式对短数组进行快速排序为什么会导致效率下降?(小区间优化的必要性)

当待排序的数组长度较短时,使用递归方式进行快速排序可能会导致效率下降的原因是递归的函数调用本身会带来一定的开销。每次递归调用都需要在内存中创建函数栈帧(包括参数、局部变量、返回地址等),并进行函数调用和返回的操作。数组越短,栈帧创建越多,导致效率下降,这是很不划算的事情。

 

1、为什么我们选择了“插入排序”进行“小区间优化”?

为了实现小区间优化,我们选择了插入排序作为替代算法。插入排序算法的特点是对几乎有序的序列具有较好的性能。在较短的子序列中,由于经过快速排序的初步划分,可能已经接近有序或完全有序。这种情况下,插入排序能够利用序列的局部有序性,通过较少的比较和交换操作来完成排序。

相比于其他排序算法(如冒泡排序或选择排序),插入排序在局部有序性较好的情况下,具有更好的性能。它的时间复杂度为 O(n^2),但在较短的子序列中,由于交换的次数较少,实际的运行时间较快。因此,选择插入排序作为小区间优化的替代算法,能够有效地提高快速排序在短数组上的性能,进一步优化算法的效率。

声明:本文章用作个人总结,文章中内容有也参考了其他博主的内容,请各位原谅!!!