齐姐漫画:排序算法(三)之「快排」

简介: 快排

image.png

算法


首先选一个基准 pivot,然后过一遍数组,


  • 把小于 pivot 的都挪到 pivot 的左边,


  • 把大于 pivot 的都挪到 pivot 的右边。


这样一来,这个 pivot 的位置就确定了,也就是排好了 1 个元素。


然后对 pivot 左边 ? 的数排序,


对 pivot 右边 ? 的数排序,


就完成了。


那怎么排左边和右边?


答:同样的方法。


所以快排也是用的分治法的思想。


「分」


选择一个 pivot,就把问题分成了


  • pivot 左边


  • pivot 右边


这两个问题。


「治」


就是最开始描述的方法,直到每个区间内没有元素或者只剩一个元素就可以返回了。


「合」


放在一起自然就是。


但是如何选择这个 pivot?


取中间的?


取第一个?


取最后一个?


举个例子:{5, 2, 1, 0, 3}.


比如选最后一个,就是 3.


然后我们就需要把除了 3 之外的数分成「比 3 大」和「比 3 小」的两部分,这个过程叫做 partition(划分)


这里我们仍然使用「挡板法」的思想,不用真的弄两个数组来存这两部分,而是用两个挡板,把区间划分好了。


我们用「两个指针」(就是挡板)把数组分成「三个区间」,那么


  • 左边的区间用来放小于 pivot 的元素;


  • 右边的区间用来放大于 pivot 的元素;


  • 中间是未排序区间。


image.png


那么初始化时,我们要保证「未排序区间」能够包含除了 3 之外的所有元素,所以


  • 未排序区间 = [i, j]


这样左边和右边的区间就成了:


  • [0, i):放比 3 小的数;


  • (j, array.length -2]:放比 3 大的数


注意 ⚠️ i, j 是不包含在左右区间里的呢。


那我们的目的是 check 未排序区间里的每一个数,然后把它归到正确的区间里,以此来缩小未排序区间,直到没有未排序的元素。


从左到右来 check:


Step1.


5 > 3, 所以 5 要放在右区间里,所以 5 和 j 指向的 0 交换一下:


image.png


这样 5 就排好了,指针 j --,这样我们的未排序区间就少了一个数;


Step2.


0 < 3,所以就应该在左边的区间,直接 i++;


image.png


Step3.


2 < 3,同理,i++;


image.png


Step4.


1 < 3,同理,i++;


image.png


所以当两个指针错位的时候,我们结束循环。


但是还差了一步,3 并不在正确的位置上呀。所以还要把它插入到两个区间中间,也就是和指针 i 交换一下。


image.png


image.png


齐姐声明:这里并不鼓励大家把 pivot 放最左边。


基本所有的书上都是放右边,既然放左右都是一样的,我们就按照大家默认的、达成共识的来,没必要去“标新立异”。


就比如围棋的四个星位,但是讲究棋道的就是先落自己这边的星位,而不是伸着胳膊去够对手那边的。


那当我们把 pivot 换回到正确的位置上来之后,整个 partition 就结束了。


之后就用递归的写法,对左右两边排序就好了。


最后还有两个问题想和大家讨论一下:


  1. 回到我们最初选择 pivot的问题,每次都取最后一个,这样做好不好?


答:并不好。


因为我们是想把数组分割的更均匀均匀的时间复杂度更低;但是如果这是一个有序的数组,那么总是取最后一个是最不均匀的取法。


所以应该随机取 pivot,这样就避免了因为数组本身的特点总是取到最值的情况。


  1. pivot 放在哪


随机选取之后,我们还是要把这个 pivot 放到整个数组的最右边,这样我们的未排序区间才是连续的,否则每次走到 pivot 这里还要想着跳过它,心好累哦。


class Solution {
  public void quickSort(int[] array) {
    if (array == null || array.length <= 1) {
      return;
    }
    quickSort(array, 0, array.length - 1);
  }
  private void quickSort(int[] array, int left, int right) {
    // base case
    if (left >= right) {
      return;
    }
    // partition
    Random random = new Random(); // java.util 中的随机数生成器
    int pivotIndex = left + random.nextInt(right - left + 1);
    swap(array, pivotIndex, right);
    int i = left;
    int j = right-1;
    while (i <= j) {
      if (array[i] <= array[right]) {
        i++;
      } else {
        swap(array, i, j);
        j--;
      }
    }
    swap(array, i, right);
    //「分」
    quickSort(array, left, i-1);
    quickSort(array, i+1, right);
  }
  private void swap(int[] array, int x, int y) {
    int tmp = array[x];
    array[x] = array[y];
    array[y] = tmp;
  }
}


这里的时空复杂度和分的是否均匀有很大关系,所以我们分情况来说:


1. 均分


时间复杂度


如果每次都能差不多均匀分,那么


  • 每次循环的耗时主要就在这个 while 循环里,也就是 O(right - left);


  • 均分的话那就是 logn 层;


  • 所以总的时间是 O(nlogn).


image.png


空间复杂度


  • 递归树的高度是 logn,


  • 每层的空间复杂度是 O(1),


  • 所以总共的空间复杂度是 O(logn).


2. 最不均匀


如果每次都能取到最大/最小值,那么递归树就变成了这个样子:


image.png


时间复杂度


如上图所示:O(n^2)


空间复杂度


这棵递归树的高度就变成了 O(n).


3. 总结


实际呢,大多数情况都会接近于均匀的情况,所以均匀的情况是一个 average case.

为什么看起来最好的情况实际上是一个平均的情况呢?


因为即使如果没有取到最中间的那个点,比如分成了 10% 和 90% 两边的数,那其实每层的时间还是 O(n),只不过层数变成了以 9 为底的 log,那总的时间还是 O(nlogn).

所以快排的平均时间复杂度是 O(nlogn)。


稳定性


那你应该能看出来了,在 swap 的时候,已经破坏了元素之间的相对顺序,所以快排并不具有稳定性。


这也回答了我们开头提出的问题,就是


  • 为什么对于primitive type 使用快排


  • 因为它速度最快;


  • 为什么对于object 使用归并


  • 因为它具有稳定性且快。


以上就是快排的所有内容了,也是很常考的内容哦!那下一篇文章我会讲几道从快排引申出来的题目,猜猜是什么??


image.png

目录
相关文章
|
搜索推荐 算法 IDE
齐姐漫画:排序算法(二)之「 归并排序」和「外排序」
那我们借用 cs50 里的例子,比如要把一摞卷子排好序,那用并归排序的思想是怎么做的呢?
189 0
齐姐漫画:排序算法(二)之「 归并排序」和「外排序」
|
搜索推荐 算法
齐姐漫画:排序算法(一)
借用《算法导论》里的例子,就是我们打牌的时候,每新拿一张牌都会把它按顺序插入,这,其实就是插入排序。
162 0
齐姐漫画:排序算法(一)
|
5天前
|
算法 数据安全/隐私保护 计算机视觉
基于FPGA的图像双线性插值算法verilog实现,包括tb测试文件和MATLAB辅助验证
本项目展示了256×256图像通过双线性插值放大至512×512的效果,无水印展示。使用Matlab 2022a和Vivado 2019.2开发,提供完整代码及详细中文注释、操作视频。核心程序实现图像缩放,并在Matlab中验证效果。双线性插值算法通过FPGA高效实现图像缩放,确保质量。
|
1月前
|
算法 数据安全/隐私保护 计算机视觉
基于Retinex算法的图像去雾matlab仿真
本项目展示了基于Retinex算法的图像去雾技术。完整程序运行效果无水印,使用Matlab2022a开发。核心代码包含详细中文注释和操作步骤视频。Retinex理论由Edwin Land提出,旨在分离图像的光照和反射分量,增强图像对比度、颜色和细节,尤其在雾天条件下表现优异,有效解决图像去雾问题。
|
1天前
|
算法
基于遗传优化算法的风力机位置布局matlab仿真
本项目基于遗传优化算法(GA)进行风力机位置布局的MATLAB仿真,旨在最大化风场发电效率。使用MATLAB2022A版本运行,核心代码通过迭代选择、交叉、变异等操作优化风力机布局。输出包括优化收敛曲线和最佳布局图。遗传算法模拟生物进化机制,通过初始化、选择、交叉、变异和精英保留等步骤,在复杂约束条件下找到最优布局方案,提升风场整体能源产出效率。
|
1月前
|
算法 数据可视化 安全
基于DWA优化算法的机器人路径规划matlab仿真
本项目基于DWA优化算法实现机器人路径规划的MATLAB仿真,适用于动态环境下的自主导航。使用MATLAB2022A版本运行,展示路径规划和预测结果。核心代码通过散点图和轨迹图可视化路径点及预测路径。DWA算法通过定义速度空间、采样候选动作并评估其优劣(目标方向性、障碍物距离、速度一致性),实时调整机器人运动参数,确保安全避障并接近目标。
149 68
|
1天前
|
算法 安全 机器人
基于包围盒的机械臂防碰撞算法matlab仿真
基于包围盒的机械臂防碰撞算法通过构建包围盒来近似表示机械臂及其环境中各实体的空间占用,检测包围盒是否相交以预判并规避潜在碰撞风险。该算法适用于复杂结构对象,通过细分目标对象并逐级检测,确保操作安全。系统采用MATLAB2022a开发,仿真结果显示其有效性。此技术广泛应用于机器人运动规划与控制领域,确保机器人在复杂环境中的安全作业。
|
1天前
|
机器学习/深度学习 数据采集 算法
基于WOA鲸鱼优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
本项目基于MATLAB 2022a实现时间序列预测,采用CNN-GRU-SAM网络结构,结合鲸鱼优化算法(WOA)优化网络参数。核心代码含操作视频,运行效果无水印。算法通过卷积层提取局部特征,GRU层处理长期依赖,自注意力机制捕捉全局特征,全连接层整合输出。数据预处理后,使用WOA迭代优化,最终输出最优预测结果。
|
4天前
|
算法
基于SOA海鸥优化算法的三维曲面最高点搜索matlab仿真
本程序基于海鸥优化算法(SOA)进行三维曲面最高点搜索的MATLAB仿真,输出收敛曲线和搜索结果。使用MATLAB2022A版本运行,核心代码实现种群初始化、适应度计算、交叉变异等操作。SOA模拟海鸥觅食行为,通过搜索飞行、跟随飞行和掠食飞行三种策略高效探索解空间,找到全局最优解。
|
1月前
|
算法 数据安全/隐私保护
室内障碍物射线追踪算法matlab模拟仿真
### 简介 本项目展示了室内障碍物射线追踪算法在无线通信中的应用。通过Matlab 2022a实现,包含完整程序运行效果(无水印),支持增加发射点和室内墙壁设置。核心代码配有详细中文注释及操作视频。该算法基于几何光学原理,模拟信号在复杂室内环境中的传播路径与强度,涵盖场景建模、射线发射、传播及接收点场强计算等步骤,为无线网络规划提供重要依据。

热门文章

最新文章