
大家好,我是何未来,本篇文章给大家讲解《AcWing算法基础课》785 题——快速排序。这篇文章介绍了使用快速排序算法对整数数列进行排序的方法,包括选择基准元素、分区操作和递归排序子数组。通过详细的步骤和示例,解释了快速排序的过程及其非稳定性特征,并提供了相应的 Java 代码实现。
❓题目描述
给定你一个长度为 n 的整数数列。
请你使用快速排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在 1∼10^9 范围内),表示整个数列。
输出格式
输出共一行,包含 n 个整数,表示排好序的数列。
数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
💡算法思路
- 选择基准元素:从数组中选择一个元素作为基准。这里我们选择的是中间位置的元素。
- 分区操作:重新排列数组,将所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作。
- 递归排序子数组:递归地将小于基准值元素的子数列和大于基准值元素的子数列排序。
具体实现步骤如下:
- 首先检查数组的起始索引
l是否大于或等于结束索引r,如果是,则直接返回,因为这意味着数组已经是有序的或者是空数组。 - 初始化两个指针
i和j,分别指向数组的起始位置的前一个位置和结束位置的后一个位置。选择数组的中间元素作为基准元素x。 - 使用两个指针
i和j从数组的两端向中间移动,直到i找到一个大于或等于基准的元素,j找到一个小于或等于基准的元素。如果i仍然小于j,则交换这两个元素。 - 重复上述过程,直到
i不再小于j。 - 递归地对基准元素左侧和右侧的子数组进行快速排序。
时间复杂度:O(n log n)
空间复杂度:O(n)
快速排序是非稳定排序:快速排序的分区过程中,当遇到与基准元素相等的元素时,通常会停止移动指针(在某些实现中,指针会继续移动),这可能导致相同元素的相对顺序被改变。例如,如果数组中有多个相同的元素,分区操作可能会将这些元素分散到基准的两侧,从而改变它们的相对顺序。
稳定排序的定义是:在排序过程中,具有相同键值的元素在排序前后的相对位置保持不变。然而,快速排序的分区操作可能会改变相同元素的相对顺序。
接下来,我给大家举个具体的例子来说明为什么快速排序是非稳定排序。
假设我们有一个包含学生信息的数组,每个学生由他们的姓名和成绩组成。我们希望按照成绩对学生进行排序。初始数组如下:
[ ("何未来", 85), ("乔布斯", 75), ("牛顿", 85), ("图灵", 75) ]
选择基准元素 ("牛顿", 85),进行第一次分区操作:
- 初始指针位置:
i = -1,j = 4 - 移动
i直到找到大于或等于基准的元素:i停在("何未来", 85) - 移动
j直到找到小于或等于基准的元素:j停在("图灵", 75) - 交换
i和j指向的元素:
[ ("图灵", 75), ("乔布斯", 75), ("牛顿", 85), ("何未来", 85) ]
第一次分区结束,基准元素 ("牛顿", 85) 的位置已经确定。现在我们有两个子数组:
- 左子数组:
[ ("图灵", 75), ("乔布斯", 75) ] - 右子数组:
[ ("何未来", 85) ]
我们先对左子数组进行递归排序:
选择基准元素 ("乔布斯", 75),进行第二次分区操作:
- 初始指针位置:
i = -1,j = 2 - 移动
i直到找到大于或等于基准的元素:i停在("图灵", 75) - 移动
j直到找到小于或等于基准的元素:j停在("乔布斯", 75) - 交换
i和j指向的元素:
[ ("乔布斯", 75), ("图灵", 75) ]
第二次分区结束,基准元素 ("乔布斯", 75) 的位置已经确定。现在我们有两个子数组:
- 左子数组:
[ ](空数组) - 右子数组:
[ ("图灵", 75) ]
由于左子数组为空,右子数组只有一个元素,这两个子数组都已经是有序的。
接下来对右子数组进行递归排序:
右子数组 [ ("何未来", 85) ] 只有一个元素,已经是有序的。
最终排序结果:
[ ("乔布斯", 75), ("图灵", 75), ("牛顿", 85), ("何未来", 85) ]
通过这个过程,我们可以看到,初始数组中相同成绩的学生 ("何未来", 85) 和 ("牛顿", 85) 的相对顺序在排序后发生了变化,从 ("何未来", 85) 在前变成了 ("牛顿", 85) 在前。这就是快速排序不稳定性的体现。

最低0.47元/天 解锁文章
843

被折叠的 条评论
为什么被折叠?



