前端不懂算法(四)--快速排序

JavaScript实现快速排序算法 JavaScript实现经典排序算法—快排 快速排序算法的性能比之前的冒泡、选择排序都要好,和归并排序一样,是一个可以用于实战的算法,听说chrome用的就是快排 又听说前端面试会考,那你实现一个快排吧! 一、快速排序的算法思路 还是在这里看一下快排是啥意思 快速排序算法动图 1、快速排序的特点就是随机设置一个基准点,比如是数组的第一个元素,然后数组的其他元素就跟这个基准线进行对比,比基准线大的放在左边,比基准线小的放在右边 2、再设置一个基准线,再这样小的放左边,大的放右边,递归。 二、编码实现 详细分析 阅读详情

本篇文章参考借鉴了以下文章和极客时间的数据结构与算法一文,若涉及侵权,请联系我删除。

前言

  上一篇博客我们一块学习了, 归并排序,它的时间复杂度是 O(nlogn),是一种排序效率很高的算法,本篇文章,我们一块来学习一种效率高,并且名气很大的排序算法。快速排序。

没看过之前文章的小伙伴可以先了解一下

核心思想

  快速排序的核心思想也是 分治思想 ,我们习惯叫它“快排”。乍看起来快排很像归并排序,但是实现原理完全不同。稍后我们讨论一下它们之间的区别

快速排序原理

  如果要排序数组中下标从 p 到 r 之间的一组数据,我们选择 p 到 r 之间的任意一个数据作为 pivot (区分点)。

  我们遍历 p 到 r 之间的数据,把大于 pivot 的放到右边,小于 pivot 的放到左边,将 pivot 放到中间。经过这一步之后,数组 p 到 r 之间的数据就被分成了三部分,前面的 p 到 q - 1 之间的都是小于 pivot ,后面的 q + 1 到 r 之间的都是大于 pivot 的。
在这里插入图片描述

  根据分治、递归的思想,我们可以用递归排序下标从 p 到 q - 1 之间的数据和下标从 q + 1 到 r 之间的数据,直到区间缩小为 1 ,就说明所有的数据都有序了。

  用递推公式写出上面的过程如下:

  递推公式:

  quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q + 1…r)

  终止条件:

  p >= r

  将递推公式转化成代码如下:

//快速排序
function quickSort(arr) {
    quickSortFn(arr, 0, arr.length-1)
}

//快速排序递归函数,p r为下标
function quickSortFn(arr, p, r) {
    if (p >= r) return;
    
    //获得区分点    
    q = partition(arr, p, r)
    quickSortFn(arr, p, q-1);
    quickSortFn(arr, q+1, r);
}

  归并排序有一个 merge() 合并函数,我们这里有一个 partition() 分区函数。partition()分区函数实际上我们已经讲过原理了,就是随机选择一个元素作为 pivot (一般情况下可以选择 p 到 r 的最后一个元素),然后对 arr[p…r] 分区,函数返回 pivot 的下标。

  如果不考虑空间消耗的话,partition() 分区函数可以写的非常简单。我们申请两个临时数组 X 和 Y ,遍历 arr[p…r] 将小于 pivot 的元素拷贝到临时数组 X ,将大于 pivot 的元素拷贝到临沭数组 Y ,最后再将数组 X,Y 中的数据按顺序拷贝到 arr[p…r] 。
在这里插入图片描述
  但是如果按照这种思路实现的话, partition() 函数就需要很多额外的内存空间,这样快排就不是原地排序算法了。如果我们希望快排是原地排序算法,那它的空间复杂度就得是O(1) ,那 partition() 函数就不能占用太多空间,我们就需要在 arr[p…r] 的原地完成分区操作。

  原地分区函数的实现非常巧妙,代码如下:

//快速排序
function quickSort(arr) {
    quickSortFn(arr, 0, arr.length-1)
}

//快速排序递归函数,p r为下标
function quickSortFn(arr, p, r) {
    if (p >= r) return;
    
    //获得区分点    
    q = partition(arr, p, r)
    
    quickSortFn(arr, p, q-1);
    quickSortFn(arr, q+1, r);
}

function partition(arr, p, r) {
    let pivot = arr[r],
        i = p,
        tmp = '';
    for (let j = p; j <= r-1; j++) {
        if (arr[j] < pivot) {
            tmp = arr[i];
            arr[i] = arr[j];
            arr[j] = tmp;
            i++
        }
    }
    tmp = arr[i];
    arr[i] = arr[r];
    arr[r] = tmp;
    debugger
    return i;
}
let arr = [10, 8, 6, 4, 9, 7, 5];
quickSort(arr);
console.log(arr) //[4, 5, 6, 7, 8, 9, 10]

  这里处理有点类似选择排序。我们通过游标 i 把 arr[p…r-1] 分成两部分。arr[p…i-1] 的元素都是小于 pivot 的,我们暂且叫它“已处理区”,arr[i…r-1] 是“未处理区”。我们每次都从“未处理区间” arr[i…r-1] 中取一个元素 arr[j] 与 pivot 对比,如果小于 pivot ,则将其加入到已处理区间,也就是 arr[i] 的位置。

  数组的插入操作还记得吗?在数组某个位置插入元素,需要搬移数据,非常耗时。这时需要一种处理技巧,就是交换,在 O[1] 的时间复杂度内完成插入操作。借助这个思想,只需要将 arr[i] 与 arr[j] 的数据交换,就可以在 O[1] 的时间复杂度内将 arr[j] 放入到下标为 i 的位置。

  文字不如图直观,上图来看
在这里插入图片描述
  因为分区的过程涉及交换操作,如果数组中有两个相同的元素,比如序列 6, 8,7,6,3,5,9,4,在经过第一次分区操作之后,两个 6 的相对先后顺序就会改变,所以快排并不是一个稳定排序算法。

快排与归并的区别

  现在明白了快排的原理,我们分析一下快排与上篇文章的归并有什么区别,这两个都是用的分治思想,递推公式和递推代码也很相似,那他们的区别在哪?
在这里插入图片描述

  由图中可以看出,归并排序的处理过程是 由下到上 的,先处理子问题,然后再合并。而快排正好相反,它的处理过程是 由上到下 的,先分区然后再处理子问题。归并排序虽然是稳定的,时间复杂度为 O(nlogn) 的排序算法,但它是非原地排序算法。上篇文章讲过,非原地排序算法,主要原因是合并函数无法在原地执行。 快速排序通过设计巧妙的原地分区函数,可以实现原地排序,解决了归并排序占用太多内存的问题。

快排性能分析

  上面已经说明了稳定性和原地排序,接下来重点分析一下时间复杂度。

  快排是递归实现的,对于递归代码的时间复杂度,前面总结的公式依然适用。不记得的小伙伴可以查看以下文章。

  如果每次分区操作都能把数组分成大小接近相等的两个小区间,那快排的时间复杂度递推求解公式和归并是相同的。所以时间复杂度也是 O(nlogn) 。

  • T(1) = C; n = 1 时,只需要常量级的执行时间。
  • T(n) = 2*T(n) + n; n > 1

  但是公式成立的前提条件是每次分区操作,我们选择的 pivot 都很合适,正好能将大区间对等的一分为二。但实际上这种情况很难实现。

  举一个极端的栗子,如果排序一个已经有序的数组,[1,3,5,6,8]。如果每次选择最后一个元素作为 pivot ,那每次分区得到的两个区间都是不均等的。我们需要进行大约 n 次分区,才能完成快排整个过程。每次分区平均要扫描 n/2 个元素,这种情况下,快排的时间复杂度就从 O(nlogn) 退化到了 O(n2) 。

  上面是两个极端例子,分别对应最好时间复杂度和最坏时间复杂度,那平均时间复杂度是多少呢?

  假设每次分区操作都将区间分成 9 : 1 两个小区间。我们继续套用递归时间复杂度的递推公式,就会变成下面这样。

  • T(1) = C; n = 1 时,只需要常量级的执行时间。
  • T(n) = T(n/10) + T(9*n/10) + n; n > 1

  这个过程比较复杂,不推荐使用,还有一种方法求解递归时间复杂度, 递归树 这个请关注我后续的博客内容,有兴趣的小伙伴可以自己推导下。

  接下来我们直接给出结果, T(n) 在大部分情况下都可以做到 O(nlogn) ,只有在极端情况下,才会出现 O(n2) 也有很多方法降低这个概率,请关注我的后续博客。

解答开篇

   快排核心思想是 分治 和 分区 ,我们可以利用分区的思想,解答开篇的问题:

  • O(n) 时间复杂度内求无序数组中的第 K 大元素。比如[4, 2, 5, 12, 3] 这样一组数据,第 3 大元素就是 4 。

   我们选择数组区间 A [0…n-1] 的最后一个元素 A [n-1] 作为 pivot ,对数组 A [0…n-1] 原地分区,这样数组就分成了三个部分, A [0…p-1] , A [p] , A [p+1…n-1] 。

   如果 p + 1 = K, 那 A [p] 就是要求解的元素;如果 K > p + 1 说明 K 大元素在 A [p+1…n-1] 区间上,在按照上面思路递归的在 A [p+1…n-1] 区间上查找。

内容小结

   归并排序和快速排序是两种稍微复杂的排序算法,用的都是分治的思想,代码都是通过递归实现的,过程很相似。理解归并排序的重点是理解 递推公式 和 merge()函数 ,同理理解快速排序的中点是 递推公式 和 partition()分区函数。

   归并排序算法是一种在任何情况下时间复杂度都稳定的算法,但是空间复杂度较高,这个致命缺点导致它没有快排应用广泛。

   快速排序最坏的情况下时间复杂度是 O(n2) ,而且可以通过合理的选择 pivot 来避免这种情况。

算法与前端的可访问性 可访问性(Accessibility, a11y)是现代 Web 开发的核心,确保所有用户,包括残障人士,都能无障碍地使用应用。算法在优化前端性能的同时,也能通过高效的数据处理和交互逻辑提升可访问性体验。例如,排序算法可优化屏幕阅读器的内容导航,搜索算法可加速辅助技术的响应。结合 WCAG 2.1 标准,算法与前端框架的集成能够打造高效且包容的用户体验。 阅读详情

相关推荐

全网最详细 解决戴维南定理的详细推导

替代定理,戴维南定理详细证明

m0_52555753的博客 3万+

前端必会算法——标准快速排序

标准快速排序 定义一个数组var arr = [6,9,8,7,2,5]; 它有两个指针,一个是左指针(L)一个是右指针(R) 我们希望左侧的东西都要比6小,指针L所指位 9 比 6 大,按道理应该放到指针L的右边,具体放到哪里呢? 此时要看指针R的所指位,可以看出 5 比 6 小,应该把 5 放到左边。所以把 5 和 9 交换位置即可。 指针L继续右移一位,当前位置8 比 6 大,不动。此时,指针R左移一位,指向 2 ,2 比 6 小,交换 8 和 2 的位置。 8 和 2 交换

qq_63664183的博客 999

炉石传说游戏系统拆解和部分系统分析

一.游戏综述 《炉石传说:魔兽英雄传》是一款由暴雪娱乐公司出品的策略类卡牌游戏。中国大陆地区的独家运营由网易公司代理。2014年3月13日全球同步正式运营。游戏背景设定于暴雪的魔兽系列,共十位魔兽中的角色作为十种不同的职业。而玩家要做的,就是根据己方现有的卡牌组建合适的卡组,指挥英雄,驱动随从,施展法术,与游戏好友或素不相识的对手一决高下。 二.游戏系统拆解图 三.游戏系统部分分析 1. 对战系统 (1)战斗准备 炉石传说开始前都需要配置至少30张卡牌作为出战卡组 .....

Jimmy72236的博客 4923

前端算法之快速排序

重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。从数列中挑出一个元素,称为 “基准”(pivot);

xiaohua0708day的博客 356

Proteus快速入门(最详细教程)

Proteus 是一款用于嵌入式单片机仿真的软件,用于在打PCB板之前对软件程序和硬件电路的正确性进行仿真验证。1.所以Proteus一般会和Keil软件进行联合仿真,在Proteus中绘制系统的电路图,在Keil软件中编写对应的程序后生成Hex文件。2.Proteus的仿真结果仅供参考,只能在一定程度上验证系统的可能性。实际实物系统还需要进行进一步的调试。以上即为本次分享的Proteus入门教程的全部内容,希望可以能够帮助到各位道友。

ediykk的博客 15万+

【问题解析】Matlab Function的易错点(Variable Size)

Matlab Function的易错点

CrazyRabbit的博客 9865

前端必备数据结构:快速排序,两种不同思路解答,必会

实现思路: 将数组的最后一个数字作为基准,最后使得基准数字位于数组中间某个位置,它的左边的数字都比它小,它的右边的数字都比它大。 细节:设置两个分别指向数组头部和尾部的指针i和j,首先向左移动j,使得array[j] 小于基准。然后向右移动i,使得array[i] 大于基准,交换这两个元素。当i 和j 的值相等时,交换基准与位置i上的元素,然后递归对i左边以及右边的元素分别进行快速排序。 具体代码实现: var quickSort = function(arr) {   if (arr.length &lt

喜欢做一些demo,和分享遇到的 BUG 985

医学图像处理实战:利用OpenSlide解析病理SVS切片的完整解决方案

本文介绍了使用openslide处理病理SVS切片的完整流程,从基础安装到科研应用。主要内容包括:1)SVS切片特点及openslide工具简介;2)环境配置与软件安装;3)基础操作:读取切片、查看信息、可视化不同层级图像;4)进阶操作:坐标裁剪与批量处理;5)科研方向拓展:结合AI模型进行病理分割分类、量化分析及可视化报告。教程提供了完整的代码示例,帮助毕业设计学生快速掌握病理图像分析的核心技能。

m0_71589036的博客 309

前端算法之快排

之前数据结构课学过一些排序算法,如冒泡 希尔 插入等等,可是都忘了,而前端最基本的算法就是快排,这里赶紧复习一下。快排的原理快排的基本原理是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,再通过此方法去对这两部分进行排序,直到不可再分为止。 算法步骤(懒得写,百科抄的): 1)设置两个变量i、j,排序开始的时候:i=0,j=N-1; 2)以第一

yushenxin的博客 1475

【电子通识】TINA-TI怎么使用受控源向导创建受控源

TINA-TI怎么使用受控源向导创建受控源

一点硬件 357

物联网Node-Red开发教程-第6章 HTTP与Web服务

本章摘要(147字): 本章系统讲解了Node-RED中的HTTP节点应用,重点包括: 三大核心HTTP节点功能:http in(接收请求)、http response(返回响应)、http request(调用外部API) 详细演示了REST API开发流程,包括路径参数(req.params)处理和响应构造 提供了创建Hello World API、传感器数据接口等实用示例 涵盖HTTP方法特性、状态码设置及响应头配置等关键知识点 通过流程图和JSON配置直观展示节点连接方式,帮助快速掌握Web服务开发

shihui2003的博客 264

前端必会的十个排序算法

具体来说,就是将数列分别按个位,十位,百位... 的大小顺序排序,最后组合在一起。快速排序是一种分治算法,它的思想是通过选定一个基准值,将数组分成两个部分,左边部分的元素都小于等于基准值,右边部分的元素都大于基准值,然后再递归地对左右两个部分进行快速排序。桶排序是一种非比较的排序算法,通过分配数据到不同的桶中,最后对每个桶内的数据进行单独的排序或计数,最后将所有桶内的数据按顺序连接,即实现了排序的过程。在冒泡排序中,每次比较两个相邻的元素,并交换它们的位置,如果左边的元素比右边的元素大,则交换它们的位置。

weixin_46489412的博客 2228

MCAL知识点(十):ERU驱动配置详解

ERS:外部请求单元ETL:中断逻辑单元OGU:输出逻辑单元MCU里面配置要使用的功能和选择输入的管脚,注意上述模块关注点讲述的ERU必须和边沿检测模块相关联。

梦想技术家 1222

前端快速排序算法详解及代码案例

快速排序(QuickSort)是一种高效且广泛应用的排序算法,特别适用于大规模数据的排序。其平均时间复杂度为O(n log n),尽管在最坏情况下可能达到O(n^2),但通过使用随机选择或“三数取中”等策略,可以极大地避免最坏情况的发生。作为参数,并返回排序后的数组。它首先检查数组的长度,如果长度小于或等于1,则直接返回数组。快速排序是递归的,因此需要使用递归栈来保存中间状态。快速排序的核心思想是通过多次比较和交换来实现排序。进行排序,并将结果合并在一起,最后返回排序后的数组。

qq_63447955的博客 669

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

前端快速排序算法【JavaScript 与vue通用】

前端快速排序算法 🎖

AI布道师丨波仔椿 525

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

前端算法之堆--桶排序和快速排序

leetcode链接:https://leetcode.cn/problems/top-k-frequent-elements/给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。示例 1: 示例 2: 提示: 进阶: 你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。更多详细内容,请微信搜索““, 戳我 查看 。 使用桶排序 桶排序适用 top k中 频次题,计数排序适用 top k中 值的题 数组中的第K个最

BradenHan的专栏 1047

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

前端必会算法——简单快速排序

简单快速排序 快排思路,小的站左边,大的站右边。 代码演示: var arr = [4, 2, 3, 1, 5, 6]; function quickSort(arr) { if (arr === null || arr.length === 0) return []; //严谨性判断 // 1. 选队长 var captain = arr[0]; // 2. 小的站队长左边,大的站队长右边 var left = []; var right = []; for (var i =

qq_63664183的博客 832

前端算法:快速排序算法

快速排序算法思想: /** 1.选择基准值base:pivotIndex:Math.floor(arr.length/2); 2.循环原数组,小于基准值的放在left[];大于基准值的放在right[]; 3.使用递归,对left[];right[];做相同的操作; 3.递归结束条件是arr.len<=1; return arr; */ 复制代码 talk i...

weixin_34405332的博客 232

前端基础算法--快速排序

算法之排序 快速排序 在数组中,找一个基准点,然后splice出去; 将数组中小于改基准点的数据放在一个数组,大于基准点的数据放在一个数组; 对左边的数组进行递归操作,对右边的数组递归操作,最后concat起来,并返回 function quickSort(arr) { arr = arr.concat(); if(arr.length<=1){ retur...

duanshilong的博客 609

【金三银四】关于Web前端开发中常见的经典算法题 阿里、字节跳动真题

引言 金三银四,特地整理一份面试题,现介绍本文特色: 1、适合前端,需要面试找工作 2、即将毕业面临实习,积累经验 3、从务实基础到彻底弄懂 4、探索框架源码,研究前端必备算法 5、直击阿里、腾讯、美团、今日头条等大厂原题,逐步引入 6、学完即准备投简历 BAT/TMD这样的大公司是如何面试的 注意嗷,在这里TMD可不是骂人的话哦,可能你知道BAT,但TMD你知道么?(不知道赶紧去百度!) ...

超逸の学习技术博客 2483

【21天打卡】前端攻城狮重学算法之-快速排序

快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)

happy921212的博客 318

排序算法之——快速排序

排序算法之——快速排序 1、序 作为一名前端开发工程师,算法也是不可以不略的一part,特别是排序,在项目实际开发中,也会经常用到。今天我们就一起重温一下,什么是快速排序,它是如何进行排序的,最后我们再手写一个快速排序。 2、什么是快速排序,如何进行排序呢? ​ 快速排序( Quicksort )是对冒泡排序算法的一种改进,排序规则如下: 选择一个中心点Pivot(在这里我选择的是最左边的第一个值为中心点); 选定两个指针,分别是 L 和 R 分别指向数组最左边和最右边的位置; 将R指向的数据和Pivo

Linzsong的博客 944

前端排序算法实现——JS快速排序

其实快速排序很简单,只是对冒泡排序的一种改进。 快速排序的步骤: 1、找到数组的中间项,并把它移除 2、准备左右两个新数组,循环数组剩下的每一项,比中间数小的,放左边的数组中,比中间数大的放右边的数组中 3、以递归的方式将左右两边的数组持续这样处理,一直到左右两个数组都排好序为止,然后拼接(concat) 简单画了张图可能会更好理解一些: 话不多说,放代码: //创建一个快速排序的数组 ...

JTz666666的博客 914

程序员必须掌握哪些算法?——前端开发工程师需要掌握的算法

一个程序员一生中可能会邂逅各种各样的算法,但总有那么几种,是作为一个程序员一定会遇见且大概率需要掌握的算法。作为一名前端开发工程师,今天就通过这个话题和文章来聊聊前端开发工程师需要掌握的算法有哪些呢。

黛琳ghz的博客,期待你的关注。 1694

前端JavaScript实现冒泡,插入及快速排序

前端三大经典排序算法冒泡排序插入排序快速排序 冒泡排序 冒泡排序算法描述:使用冒泡排序把一个无序的数组按照从小到大的顺序排列的,思路是:首先需要双重遍历这个数组,首先拿出来第一个和后面的第二个做对比,如果前面的比后面的值大,这两个值就做交换,否则就不执行任何操作,然后再对比第二个和第三个。。。依次类推,直到比较到最后一个。这样外层循环每循环一次都会冒出一个当前最大的值,这个值确定之后,我们就不用再管这个位置的值了,然后再对比前面的即可。 算法如下所示: function bubbleSort(arr){

qq_39454432的博客 289

【校招VIP】前端排序算法之快速排序

考点介绍: 快速排序是大厂和三四线公司校招的必考点。需要在理解原理的前提下,尽量把代码记忆下来。一方便理解双指标头尾双向进行的原因,另一方面对循环和递归的终止要准确掌握。 本期分享的前端排序算法之快速排序,分为试题、文章以及视频三部分。 答案详情解析和文章内容可扫下方二维码或链接即可查看! 一、考点题目 1.有字符序列 {Q,H,C,Y,P,A,M,S,R,D,F,X} ,新序列{F,H,C,D,P,A,M,Q,R,S,Y,X},是下列____排序算法一趟扫描的结果。 A.二路归并排序 B.快速

shuize123的博客 246
上一篇: 前端不懂算法(三)--归并排序
下一篇: 前端不懂算法(五)--桶排序,计数排序,基数排序
不见浅诗~
博客等级 码龄7年 506粉丝 129原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值