漫画:什么是快速排序?

快速排序(Quick sort) 一、简介 快速排序是(Quick sort)是对冒泡排序的一种改进,是非常重要且应用比较广泛的一种高效率排序算法。 二、算法思路 快速排序是通过多次比较和交换来实现排序,在一趟排序中把将要排序的数据分成两个独立的部分,对这两部分进行排序使得其中一部分所有数据比另一部分都要小,然后继续递归排序这两部分,最终实现所有数据有序。 大致步骤如下: 首先设置一个分界值也就是基准值又是也称为监视哨,通过该分界值将数据分割成两部分。 将大于或等于分界值的数据集中到右边,小于分界值的数据集中到左边。一趟排序过后,左边部 阅读详情

 

 

—————  第二天  —————

 

 

 

 

 

 

 

 

 

 

 

 

 

 

————————————

 

 

 

 

 

 

 

 

 

 

 

 

同冒泡排序一样,快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。

 

不同的是,冒泡排序在每一轮只把一个元素冒泡到数列的一端,而快速排序在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列一边,比它小的元素移动到数列的另一边,从而把数列拆解成了两个部分。

 

 


 

 

这种思路就叫做分治法。

 

每次把数列分成两部分,究竟有什么好处呢?

 

假如给定8个元素的数列,一般情况下冒泡排序需要比较8轮,每轮把一个元素移动到数列一端,时间复杂度是O(n^2)。

 

而快速排序的流程是什么样子呢?

 

 

 

 

 

如图所示,在分治法的思想下,原数列在每一轮被拆分成两部分,每一部分在下一轮又分别被拆分成两部分,直到不可再分为止。

 

这样一共需要多少轮呢?平均情况下需要logn轮,因此快速排序算法的平均时间复杂度是 O(nlogn)。

 

 

 

 

 

 

基准元素的选择

 

基准元素,英文pivot,用于在分治过程中以此为中心,把其他元素移动到基准元素的左右两边。

 

那么基准元素如何选择呢?

 

最简单的方式是选择数列的第一个元素:

 

 

 

 

这种选择在绝大多数情况是没有问题的。但是,假如有一个原本逆序的数列,期望排序成顺序数列,那么会出现什么情况呢?

 

 

 

 

..........

 

 

 

 

 

 

 

 

我们该怎么避免这种情况发生呢?

 

其实很简单,我们可以不选择数列的第一个元素,而是随机选择一个元素作为基准元素。

 

 

 

 

这样一来,即使在数列完全逆序的情况下,也可以有效地将数列分成两部分。

 

当然,即使是随机选择基准元素,每一次也有极小的几率选到数列的最大值或最小值,同样会影响到分治的效果。

 

所以,快速排序的平均时间复杂度是 O(nlogn),最坏情况下的时间复杂度是 O(n^2)。

 

 

元素的移动

 

选定了基准元素以后,我们要做的就是把其他元素当中小于基准元素的都移动到基准元素一边,大于基准元素的都移动到基准元素另一边。

 

具体如何实现呢?有两种方法:

 

1.挖坑法

2.指针交换法

 

 

何谓挖坑法?我们来看一看详细过程。

 

给定原始数列如下,要求从小到大排序:

 

 

 

首先,我们选定基准元素Pivot,并记住这个位置index,这个位置相当于一个“坑”。并且设置两个指针left和right,指向数列的最左和最右两个元素:

 


 

接下来,从right指针开始,把指针所指向的元素和基准元素做比较。如果比pivot大,则right指针向左移动;如果比pivot小,则把right所指向的元素填入坑中。

 

在当前数列中,1<4,所以把1填入基准元素所在位置,也就是坑的位置。这时候,元素1本来所在的位置成为了新的坑。同时,left向右移动一位。

 

 

此时,left左边绿色的区域代表着小于基准元素的区域。

 

接下来,我们切换到left指针进行比较。如果left指向的元素小于pivot,则left指针向右移动;如果元素大于pivot,则把left指向的元素填入坑中。

 

在当前数列中,7>4,所以把7填入index的位置。这时候元素7本来的位置成为了新的坑。同时,right向左移动一位。

 

 

此时,right右边橙色的区域代表着大于基准元素的区域。

 

下面按照刚才的思路继续排序:

 

8>4,元素位置不变,right左移

 

 

 

2<4,用2来填坑,left右移,切换到left。

 

 

 

6>4,用6来填坑,right左移,切换到right。

 

 

 

3<4,用3来填坑,left右移,切换到left。

 

 

 

5>4,用5来填坑,right右移。这时候left和right重合在了同一位置。

 

 

 

这时候,把之前的pivot元素,也就是4放到index的位置。此时数列左边的元素都小于4,数列右边的元素都大于4,这一轮交换终告结束。

 

 

 

 

 

 

 

public class QuickSort {


 
  1. public static void quickSort(int[] arr, int startIndex, int endIndex) {

  2.    // 递归结束条件:startIndex大等于endIndex的时候

  3.    if (startIndex >= endIndex) {

  4.        return;

  5.    }

  6.    // 得到基准元素位置

  7.    int pivotIndex = partition(arr, startIndex, endIndex);

  8.    // 用分治法递归数列的两部分

  9.    quickSort(arr, startIndex, pivotIndex - 1);

  10.    quickSort(arr, pivotIndex + 1, endIndex);

  11. }

  12.  

  13.  

  14. private static int partition(int[] arr, int startIndex, int endIndex) {

  15.    // 取第一个位置的元素作为基准元素

  16.    int pivot = arr[startIndex];

  17.    int left = startIndex;

  18.    int right = endIndex;

  19.    // 坑的位置,初始等于pivot的位置

  20.    int index = startIndex;

  21.  

  22.    //大循环在左右指针重合或者交错时结束

  23.    while ( right >= left  ){

  24.        //right指针从右向左进行比较

  25.        while ( right >= left ) {

  26.            if (arr[right] < pivot) {

  27.                arr[left] = arr[right];

  28.                index = right;

  29.                left++;

  30.                break;

  31.            }

  32.            right--;

  33.        }

  34.        //left指针从左向右进行比较

  35.        while ( right >= left ) {

  36.            if (arr[left] > pivot) {

  37.                arr[right] = arr[left];

  38.                index = left;

  39.                right--;

  40.                break;

  41.            }

  42.            left++;

  43.        }

  44.    }

  45.    arr[index] = pivot;

  46.    return index;

  47. }

  48.  

  49.  

  50. public static void main(String[] args) {

  51.    int[] arr = new int[] {4,7,6,5,3,2,8,1};

  52.    quickSort(arr, 0, arr.length-1);

  53.    System.out.println(Arrays.toString(arr));

  54. }

}

 

代码中,quickSort方法通过递归的方式,实现了分而治之的思想。

 

partition方法则实现元素的移动,让数列中的元素依据自身大小,分别移动到基准元素的左右两边。在这里,我们使用移动方式是挖坑法。

 

 

 

 

 

 

指针交换法

 

何谓指针交换法?我们来看一看详细过程。

 

给定原始数列如下,要求从小到大排序:

 

 

开局和挖坑法相似,我们首先选定基准元素Pivot,并且设置两个指针left和right,指向数列的最左和最右两个元素:

 

 

 

接下来是第一次循环,从right指针开始,把指针所指向的元素和基准元素做比较。如果大于等于pivot,则指针向左移动;如果小于pivot,则right指针停止移动,切换到left指针。

 

在当前数列中,1<4,所以right直接停止移动,换到left指针,进行下一步行动。

 

轮到left指针行动,把指针所指向的元素和基准元素做比较。如果小于等于pivot,则指针向右移动;如果大于pivot,则left指针停止移动。

 

由于left一开始指向的是基准元素,判断肯定相等,所以left右移一位。

 

 

 

由于7 > 4,left指针在元素7的位置停下。这时候,我们让left和right指向的元素进行交换。

 

 

 

 

接下来,我们进入第二次循环,重新切换到right向左移动。right先移动到8,8>4,继续左移。由于2<4,停止在2的位置。

 

 

 

切换到left,6>4,停止在6的位置。

 

 

 

元素6和2交换。

 

 

 

进入第三次循环,right移动到元素3停止,left移动到元素5停止。

 

 

 

元素5和3交换。

 

 

 

进入第四次循环,right移动到元素3停止,这时候请注意,left和right指针已经重合在了一起。

 

 

 

当left和right指针重合之时,我们让pivot元素和left与right重合点的元素进行交换。此时数列左边的元素都小于4,数列右边的元素都大于4,这一轮交换终告结束。

 

 

 

 

 

 

 

 

public class QuickSort {


 
  1. public static void quickSort(int[] arr, int startIndex, int endIndex) {

  2.    // 递归结束条件:startIndex大等于endIndex的时候

  3.    if (startIndex >= endIndex) {

  4.        return;

  5.    }

  6.    // 得到基准元素位置

  7.    int pivotIndex = partition(arr, startIndex, endIndex);

  8.    // 根据基准元素,分成两部分递归排序

  9.    quickSort(arr, startIndex, pivotIndex - 1);

  10.    quickSort(arr, pivotIndex + 1, endIndex);

  11. }

  12.  

  13.  

  14. private static int partition(int[] arr, int startIndex, int endIndex) {

  15.    // 取第一个位置的元素作为基准元素

  16.    int pivot = arr[startIndex];

  17.    int left = startIndex;

  18.    int right = endIndex;

  19.  

  20.    while( left != right) {

  21.        //控制right指针比较并左移

  22.        while(left<right && arr[right] > pivot){

  23.            right--;

  24.        }

  25.        //控制right指针比较并右移

  26.        while( left<right && arr[left] <= pivot) {

  27.            left++;

  28.        }

  29.        //交换left和right指向的元素

  30.        if(left<right) {

  31.            int p = arr[left];

  32.            arr[left] = arr[right];

  33.            arr[right] = p;

  34.        }

  35.    }

  36.  

  37.    //pivot和指针重合点交换

  38.    int p = arr[left];

  39.    arr[left] = arr[startIndex];

  40.    arr[startIndex] = p;

  41.  

  42.    return left;

  43. }

  44.  

  45.  

  46. public static void main(String[] args) {

  47.    int[] arr = new int[] {4,7,6,5,3,2,8,1};

  48.    quickSort(arr, 0, arr.length-1);

  49.    System.out.println(Arrays.toString(arr));

  50. }

}

 

和挖坑法相比,指针交换法在partition方法中进行的元素交换次数更少。

 

 

非递归实现

 

 

 

 

 

 

为什么这样说呢?

 

因为我们代码中一层一层的方法调用,本身就是一个函数栈。每次进入一个新方法,就相当于入栈;每次有方法返回,就相当于出栈。

 

所以,我们可以把原本的递归实现转化成一个栈的实现,在栈当中存储每一次方法调用的参数:

 

 

 

下面我们来看一下代码:

 

public class QuickSortWithStack {


 
  1. public static void quickSort(int[] arr, int startIndex, int endIndex) {

  2.    // 用一个集合栈来代替递归的函数栈

  3.    Stack<Map<String, Integer>> quickSortStack = new Stack<Map<String, Integer>>();

  4.    // 整个数列的起止下标,以哈希的形式入栈

  5.    Map rootParam = new HashMap();

  6.    rootParam.put("startIndex", startIndex);

  7.    rootParam.put("endIndex", endIndex);

  8.    quickSortStack.push(rootParam);

  9.  

  10.    // 循环结束条件:栈为空时结束

  11.    while (!quickSortStack.isEmpty()) {

  12.        // 栈顶元素出栈,得到起止下标

  13.        Map<String, Integer> param = quickSortStack.pop();

  14.        // 得到基准元素位置

  15.        int pivotIndex = partition(arr, param.get("startIndex"), param.get("endIndex"));

  16.        // 根据基准元素分成两部分, 把每一部分的起止下标入栈

  17.        if(param.get("startIndex") <  pivotIndex -1){

  18.            Map<String, Integer> leftParam = new HashMap<String, Integer>();

  19.            leftParam.put("startIndex",  param.get("startIndex"));

  20.            leftParam.put("endIndex", pivotIndex -1);

  21.            quickSortStack.push(leftParam);

  22.        }

  23.        if(pivotIndex + 1 < param.get("endIndex")){

  24.            Map<String, Integer> rightParam = new HashMap<String, Integer>();

  25.            rightParam.put("startIndex", pivotIndex + 1);

  26.            rightParam.put("endIndex", param.get("endIndex"));

  27.            quickSortStack.push(rightParam);

  28.        }

  29.    }

  30. }

  31.  

  32. private static int partition(int[] arr, int startIndex, int endIndex) {

  33.    // 取第一个位置的元素作为基准元素

  34.    int pivot = arr[startIndex];

  35.    int left = startIndex;

  36.    int right = endIndex;

  37.  

  38.    while( left != right) {

  39.        //控制right指针比较并左移

  40.        while(left<right && arr[right] > pivot){

  41.            right--;

  42.        }

  43.        //控制right指针比较并右移

  44.        while( left<right && arr[left] <= pivot) {

  45.            left++;

  46.        }

  47.        //交换left和right指向的元素

  48.        if(left<right) {

  49.            int p = arr[left];

  50.            arr[left] = arr[right];

  51.            arr[right] = p;

  52.        }

  53.    }

  54.  

  55.    //pivot和指针重合点交换

  56.    int p = arr[left];

  57.    arr[left] = arr[startIndex];

  58.    arr[startIndex] = p;

  59.  

  60.    return left;

  61. }

  62.  

  63. public static void main(String[] args) {

  64.    int[] arr = new int[] {4,7,6,5,3,2,8,1};

  65.    quickSort(arr, 0, arr.length-1);

  66.    System.out.println(Arrays.toString(arr));

  67. }

}

 

和刚才的递归实现相比,代码的变动仅仅在quickSort方法当中。该方法中引入了一个存储Map类型元素的栈,用于存储每一次交换时的起始下标和结束下标。

 

每一次循环,都会让栈顶元素出栈,进行排序,并且按照基准元素的位置分成左右两部分,左右两部分再分别入栈。当栈为空时,说明排序已经完毕,退出循环。

 

 

 

 

 

 

 

 

 

几点补充:

 

本漫画纯属娱乐,还请大家尽量珍惜当下的工作,切勿模仿小灰的行为哦。

算法介绍——快速排序 快速排序 快速排序是最常用的排序算法。 举例说明 假设我们要对 “6 1 2 7 9 3 4 5 10 8” 这 10 个数进行排序。 C代码实现 # 复杂度分析 阅读详情

相关推荐

从Zemax到Speos:HUD设计数据无缝迁移避坑指南(含STEP文件精度验证)

本文详细介绍了从Zemax到Speos的HUD设计数据无缝迁移方法,重点解决Zernike曲面参数保真、虚拟厚度设置和坐标系对齐等核心问题。通过STEP文件精度验证和工程化解决方案,帮助工程师避免数据迁移中的常见陷阱,提升HUD系统设计效率。适用于Ansys Zemax和Ansys Speos用户,优化光学性能验证流程。

h6i7j8的博客 1063

快速排序详解

快速排序(英语:Quicksort),又称分区交换排序(英语:partition-exchange sort),简称「快排」,是一种被广泛运用的排序算法。

感觉画质不如...原神的博客 1万+

yolov7模型部署流程

yolov7部署流程

算法的猫 1395

什么是快速排序?

文章目录快速排序快速排序简介:基本思想原理快速排序法示意图排序演示Java代码 快速排序 快速排序简介: 快速排序是对冒泡排序的一种改进。 基本思想 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都小于或者等于另外一部分的所有数据,然后再按照此方法对这两部分数据分别进行快速排序,整个排序的过程可以递归进行,以此达到整个数据变成有序序列。 原理 设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选 用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它左边,所

热爱编程的菜鸟 5595

自适应陷波器的原理与实现

x(t)是叠加有两个频率干扰信号的输入信号,s(t)是需要保留的有用信号,在输入信号中,我们只知道频率w1和w2,但不知道信号的幅度A和相位θ,自适应陷波器可以估计两个未知的幅度和相位值,这需要我们提供两路相互正交的单频信号,如cos(w1t)、sin(w1t),通过调整其权值,从而合成出与干扰信号完全相同的信号A1cos(w1t+θ1),从而实现干扰信号的滤除。采用LMS算法,在保证不损失其他有用信号的情况下,有效地抑制输入信号中某一频率的干扰,如需滤除多个频率,只需要增加相应的输入信号。

Melkiades2333的博客 6883

NumPy 与 Pandas 的详细应用(含实例)

本文系统介绍了Python数据分析中NumPy和Pandas两大核心库的应用。NumPy专注于高效数值计算,提供多维数组和向量化运算功能;Pandas则擅长结构化数据处理,支持数据清洗、分组统计等操作。文章详细讲解了库的安装、基础操作(数组/DataFrame创建、索引切片)、进阶功能(缺失值处理、分组聚合),并通过电商销售分析案例演示了实际应用场景。最后总结了二者的协同优势:NumPy负责底层数值计算,Pandas处理高级数据操作,配合使用可显著提升数据处理效率。建议读者从基础操作入手,结合实际案例练习,

weixin_42030577的博客 2297

漫画:什么是快速排序算法?

这篇文章,以对话的方式,详细着讲解了快速排序以及排序排序的一些优化。一禅:归并排序是一种基于分治思想的排序,处理的时候可以采取递归的方式来处理。我弄个例子吧,好理解点。例如对于这个数组arr[] = { 4,1,3,2,7,5,8,0}。我们把它切割成两部分。把左半部分和右半部分分别排序好。之后再用一个,把这两个有序的子数组汇总成一个有序的大数组排好之后在复制这时,就排序完毕了一禅:左半部分和右半部分的排序相当于一个原问题的一个的,也是采取同样的方式,把左半部分分成两部分,然后…

帅地 3675

Mid-360与FAST-LIO2实战:从点云采集到高精度建图全流程解析

本文详细解析了使用Livox Mid-360激光雷达与FAST-LIO2算法实现高精度实时建图的全流程。内容涵盖硬件准备、软件环境搭建、算法部署配置、数据采集实战,并重点探讨了多楼层场景下的参数调优与误差抑制策略,为机器人及无人机三维感知应用提供了实用指南。

prometheus9mon的博客 411

漫画:什么才是快速排序?

点击上方“前端小苑”,选择“置顶公众号”精品技术文章,热门资讯第一时间本文由程序员小灰授权转载,版权归原作者所有————— 第二天 —————————————————同冒泡排序一样,...

yuqing1008的博客 142

基于eBPF和LSM的强制文件访问控制系统架构设计和实现

本文介绍了一种基于 eBPF 与 Linux 安全模块(LSM)实现的强制文件访问控制系统。该系统利用 eBPF 的高性能和动态加载能力以及 LSM 的系统调用拦截功能,提供了灵活且强大的文件访问控制策略。文章详细描述了系统的设计思路、架构组成、关键代码实现及错误处理方式。通过用户空间程序与内核态 eBPF 程序的协作,实现了对文件操作的实时监控和控制,支持动态更新安全策略而无需重启系统。此外,文中还讨论了未来可能的扩展方向,如增加更多 hook 点、集成其他安全模块以及性能优化等。

Linux内核研究者 1838

R语言中的Gamma分布函数

在R语言中,我们可以使用最大似然估计(Maximum Likelihood Estimation,MLE)或最小二乘估计(Least Squares Estimation,LSE)等方法来估计Gamma分布的参数。本文介绍了在R语言中使用Gamma分布函数的基本操作,包括生成随机变量、计算概率密度函数和累积分布函数,以及参数估计和拟合。函数来生成Gamma分布的随机变量,计算概率密度函数(PDF)和累积分布函数(CDF),以及进行参数估计和拟合。要计算Gamma分布的累积分布函数(CDF),我们可以使用。

ByteWhiz的博客 1629

【漫画算法】指挥官的排序战术:快速排序算法解密

揭示快速排序的策略,本文通过军队排序的故事引入,详细讲解算法步骤与代码实现,并探索性能优化策略,旨在提升排序效率和理解深度。

10年数据\经营分析经验,现任大厂数据分析负责人 2045

Taro React组件使用(5) —— RuiPasswordInput 密码输入框显示隐藏和清空

初始化时,需要将传入的 value 赋值给当前组件的 value, 不能直接修改 props 的 value;在组件内输出组件内的变量时,最好先判断输出函数是否定义,再输出,否则很容易引起报错。

止于至善 891

漫画:什么是快速排序?(完整版)

————— 第二天 ————— ———————————— 同冒泡排序一样,快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。 不同的是,冒泡排序在每一轮只把一个元素冒泡到数列的一端,而快速排序在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列一边,比它小的元素移动到数列的另一边,从而把数列拆解成了两个部分。 这种思路就叫做分治法。 每次把数列分成两部分,究竟有什么好处呢? 假如给定8个元素的数列,一般情况下冒泡排序需要比较8...

little_spice的博客 553

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

漫画:以后有面试官问你快速排序,你就干脆把我这篇熬夜写的文章扔给他!

这篇文章,以对话的方式,详细着讲解了快速排序以及排序排序的一些优化。 一禅:归并排序是一种基于分治思想的排序,处理的时候可以采取递归的方式来处理子问题。我弄个例子吧,好理解点。例如对于这个数组arr[] = { 4,1,3,2,7,5,8,0}。 我们把它切割成两部分。 把左半部分和右半部分分别排序好。 之后再用一个临时数组,把这两个有序的子数组汇总成一个有序的大数组 排好之后...

帅地 6347

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

【漫画】MoSCoW排序法:1000个需求快速排序法

需求管理不规范,项目交付两行泪!快去试试用MoSCoW来进行需求管理吧!

weixin_45228755的博客 164

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

python快速排序最简单写法_漫画:最最最最最简单的选择排序

public static int[] selectionSort(int[] array){ if (array.length == 0) return array; for(int i =0;i<array.length;i++){ int min = i; int temp; for(int j = i;j<a...

weixin_39743357的博客 126

漫画:什么是快速排序?(完整版)--请关注微信公众号“程序员小灰”

点击上方“程序员小灰”,选择关注公众号 有趣有内涵的文章第一时间送达!   —————  第二天  ————— public class QuickSort { public static void quickSort(int[] arr, int startIndex, int endIndex) {     /...

$firecat利白的代码足迹$ 4350
上一篇: 漫画:什么是归并排序?
下一篇: 漫画:“排序算法” 大总结
废言Pro
博客等级 码龄16年 1092粉丝 142原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值