大家好🙌我是你们的好朋友,大数据老虾😀。相遇是缘,既然来了就拎着小板凳坐下来一起唠会儿😎,如果在文中有所收获,请别忘了一键三连,动动你发财的小手👍,你的鼓励,是我创作的动力😁!废话不多说,直接😎 开干吧!
PS:文末干货,记得拎着小板凳离开的时候也给它顺走 🤣
座右铭:“懒”对一个人的毁灭性有多大,早起的重要性就多大。
【排序算法】快速排序
算法解析
快速排序算法有两个核心点,分别为哨兵划分和递归:
1、哨兵划分: 以数组某个元素(一般选取首元素)为基准数,将所有小于准基数的元素移动至其左边,大于基准数的元素移动至其右边
如下图所示:为哨兵划分操作流程。通过一轮哨兵划分,可将数组排序问题擦划分为两个较短数组的排序问题(称之为左(右)子数组)
递归: 对左子数组和右子数组分别递归执行哨兵划分,直至子数组长度为1时终止递归,即可完成对整个数组的排序。
如下图所示,为示例 数组[2, 4, 1, 0, 3, 5] 的快速排序流程。观察发现,快速排序和二分法的原来类似,都是以log时间复杂度实现搜索区间的缩小。
// java code
void quickSort(int[] nums, int l, int r) {
// 子数组长度为 1 时终止递归
if (l >= r) return;
// 哨兵划分操作
int i = partition(nums, l, r);
// 递归左(右)子数组执行哨兵划分
quickSort(nums, l, i - 1);
quickSort(nums, i + 1, r);
}
int partition(int[] nums, int l, int r) {
// 以 nums[l] 作为基准数
int i = l, j = r;
while (i < j) {
while (i < j && nums[j] >= nums[l]) j--;
while (i < j && nums[i] <= nums[l]) i++;
swap(nums, i, j);
}
swap(nums, i, l);
return i;
}
void swap(int[] nums, int i, int j) {
// 交换 nums[i] 和 nums[j]
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
// 调用
int[] nums = { 4, 1, 3, 2, 5 };
quickSort(nums, 0, nums.length - 1);
# python code
def quick_sort(nums, l, r):
# 子数组长度为 1 时终止递归
if l >= r: return
# 哨兵划分操作
i = partition(nums, l, r)
# 递归左(右)子数组执行哨兵划分
quick_sort(nums, l, i - 1)
quick_sort(nums, i + 1, r)
def partition(nums, l, r):
# 以 nums[l] 作为基准数
i, j = l, r
while i < j:
while i < j and nums[j] >= nums[l]: j -= 1
while i < j and nums[i] <= nums[l]: i += 1
nums[i], nums[j] = nums[j], nums[i]
nums[l], nums[i] = nums[i], nums[l]
return i
# 调用
nums = [3, 4, 1, 5, 2]
quick_sort(nums, 0, len(nums) - 1)
算法特性
时间复杂度
-
最佳 Ω(NlogN): 最佳情况下, 每轮哨兵划分操作将数组划分为等长度的两个子数组;哨兵划分操作为线性时间复杂度 O(N);递归轮数共 O(logN) 。
-
平均Θ(NlogN): 在随机输入数组下,哨兵划分操作的递归轮数也为O(logN) 。
-
最差 O(N^2): 在某些特殊输入数组下,每轮哨兵划分操作都将长度为 N 的数组划分为长度为 1 和 N−1 的两个子数组,此时递归轮数达到 N 。
空间复杂度
空间复杂度 O(N): 快速排序的递归深度最好与平均皆为logN;输入数组完全倒序下,达到最差递归深度 N。
虽然平均时间复杂度与归并排序和堆排序一致,但再实际使用中快速排序效率更高:
-
最差情况稀疏性: 虽然快速排序的最差时间复杂度为 O(N^2),差于归并排序和堆排序,但统计意义上看,这种情况出现的机率很低。大部分情况下,快速排序以O(NlogN) 复杂度运行。
-
缓存使用效率高: 哨兵划分操作时,将整个子数组加载入缓存中,访问元素效率很高;堆排序需要跳跃式访问元素,因此不具有此特性。
-
常数系数低: 在提及的三种算法中,快速排序的 比较、赋值、交换 三种操作的综合耗时最低(类似于插入排序快于冒泡排序的原理)。
-
原地: 不用借助辅助数组的额外空间,递归仅使用O(logN) 大小的栈帧空间。
-
非稳定: 哨兵划分操作可能改变相等元素的相对顺序。
-
自适应: 若每轮哨兵划分操作都将长度为 N 的数组划分为长度 1 和 N−1 两个子数组,则时间复杂度劣化至 O(N^2)。
算法优化
快排的常见优化手段:【Tail Call】和【随机基准数】两种
Tail Call:
1、由于普通快速排序每轮选取子数组最左元素作为基准数,因此在输入数组完全倒序时,partition()的递归深度会达到N,即最差空间复杂度为O(N)。
2、每轮递归时,仅对较短的子数组执行哨兵划分partition(),就可将最差的递归深度控制在O(logN)(每轮递归的子数组长度都 ≤ 当前数组长度),即实现最差空间复杂度 O(logN) 。
代码仅修改quick_sort()方法处:
// java code
void quickSort(int[] nums, int l, int r) {
// 子数组长度为 1 时终止递归
while (l < r) {
// 哨兵划分操作
int i = partition(nums, l, r);
// 仅递归至较短子数组,控制递归深度
if (i - l < r - i) {
quickSort(nums, l, i - 1);
l = i + 1;
} else {
quickSort(nums, i + 1, r);
r = i - 1;
}
}
}
# python code
def quick_sort(nums, l, r):
# 子数组长度为 1 时终止递归
while l < r:
# 哨兵划分操作
i = partition(nums, l, r)
# 仅递归至较短子数组,控制递归深度
if i - l < r - i:
quick_sort(nums, l, i - 1)
l = i + 1
else:
quick_sort(nums, i + 1, r)
r = i - 1
随机基准数:
1、由于快速排序每轮选取子数组最左元素作为基准数,因此在输入数组 完全有序 或 完全倒序 时, partition() 每轮只划分一个元素,达到最差时间复杂度 O(N^2)。
2、因此,可使用 随机函数 ,每轮在子数组中随机选择一个元素作为基准数,这样就可以极大概率避免以上劣化情况。
3、值得注意的是,由于仍然可能出现最差情况,因此快速排序的最差时间复杂度仍为 O(N^2)。
代码仅需修改 partition() 方法:
// java code
int partition(int[] nums, int l, int r) {
// 在闭区间 [l, r] 随机选取任意索引,并与 nums[l] 交换
int ra = (int)(l + Math.random() * (r - l + 1));
swap(nums, l, ra);
// 以 nums[l] 作为基准数
int i = l, j = r;
while (i < j) {
while (i < j && nums[j] >= nums[l]) j--;
while (i < j && nums[i] <= nums[l]) i++;
swap(nums, i, j);
}
swap(nums, i, l);
return i;
}
# python code
def partition(nums, l, r):
# 在闭区间 [l, r] 随机选取任意索引,并与 nums[l] 交换
ra = random.randrange(l, r + 1)
nums[l], nums[ra] = nums[ra], nums[l]
# 以 nums[l] 作为基准数
i, j = l, r
while i < j:
while i < j and nums[j] >= nums[l]: j -= 1
while i < j and nums[i] <= nums[l]: i += 1
nums[i], nums[j] = nums[j], nums[i]
nums[l], nums[i] = nums[i], nums[l]
return i
文末彩蛋🤩
🚗🤩😉💖🌹👀✨给各位朋友安利一下平时收集的各种学习资料!!!有需要的朋友点击一下⏩传送门,自行领取。程序员经典名言:“收藏了就等于学会啦”。 做人也要像蜡烛一样,在有限的一生中有一分热发一份光,给人以光明,给人以温暖!
图灵程序丛书300+
Linux实战100讲
Linux书籍
计算机基础硬核总结
计算机基础相关书籍
操作系统硬核总结
Java自学宝典
Java学习资料
Java硬核资料
Java面试必备
Java面试深度剖析
阿里巴巴Java开发手册
MySQL入门资料
MySQL进阶资料
深入浅出的SQL
Go语言书籍