首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

为什么Quicksort有很深的函数调用堆栈,在输入中有很多重复?

Quicksort是一种高效的排序算法,其平均时间复杂度为O(n log n)。然而,在某些情况下,特别是当输入数据中存在大量重复元素时,Quicksort的性能可能会下降,导致函数调用堆栈变深。

基础概念

Quicksort通过选择一个“基准”元素,将数组分成两个子数组,一个包含小于基准的元素,另一个包含大于基准的元素,然后递归地对这两个子数组进行排序。

问题原因

  1. 重复元素:当输入数据中有很多重复元素时,Quicksort的性能会受到影响。这是因为传统的Quicksort在处理重复元素时,可能会导致不平衡的分割,从而增加递归调用的深度。
  2. 基准选择:如果基准元素的选择不当,可能会导致分割极不平衡,特别是在数据已经部分有序或完全有序的情况下。

解决方案

为了解决这些问题,可以采用以下几种优化方法:

  1. 三路划分(Three-way Partitioning)
    • 这种方法将数组划分为三个部分:小于基准、等于基准和大于基准。这样可以避免对重复元素进行不必要的递归调用。
    • 示例代码:
    • 示例代码:
  • 随机基准选择
    • 通过随机选择基准元素,可以减少最坏情况发生的概率,特别是在输入数据已经有序或接近有序的情况下。
    • 示例代码:
    • 示例代码:

应用场景

  • 大数据排序:Quicksort在处理大规模数据时表现出色,尤其是在数据分布均匀的情况下。
  • 实时系统:由于其高效的平均时间复杂度,Quicksort适用于需要快速响应的实时系统。

参考链接

通过上述优化方法,可以有效减少Quicksort在处理包含大量重复元素的输入数据时的函数调用堆栈深度,提高算法的性能和稳定性。

页面内容是否对你有帮助?
有帮助
没帮助

相关·内容

递归

2.递归代码要警惕堆栈溢出 我们在栈那一节有讲过,函数调用会使用栈来保存临时变量。...每调用一个函数,都会将临时变量封装为帧栈压入内存栈,等函数执行完成时,才出栈。 而系统栈或者虚拟机栈空间一般都不大。 如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。...那么,要怎么避免出现堆栈溢出呢? 我们可以通过在代码中限制递归调用的最大深度的方式来解决。 就是递归调用超过一定深度之后,我们就不继续往下递归了,直接返回报错。...在时间效率上,递归代码里多了很多的函数调用, 当这些函数调用的数量较大时,就会积聚成一个可观的时间成本。...4.把递归代码改写为非递归代码 递归有利有弊;利是递归代码表达能力很强,写起来简洁; 而弊就是空间复杂度高,有堆栈溢出风险, 存在重复计算,过多的函数调用会耗时过多等问题。

82440

递归的递归之书:第五章到第九章

输入quicksort,这是由计算机科学家 Tony Hoare 在 1959 年开发的递归排序算法。 快速排序使用一种称为分区的分而治之技术。想象一下分区:想象你有一大堆未按字母顺序排列的书。...quicksort()函数有多少递归调用? 数组[0, 3, 1, 2, 5, 4, 7, 6]在以4为中轴值时没有正确分区? 归并排序的基本情况是什么?...Python 的标准库有一个functools模块,其中有一个名为@lru_cache()的函数装饰器,它可以自动记忆它装饰的函数。...要理解尾调用优化的工作原理,记住第一章中函数调用时发生了什么。首先,创建一个帧对象并将其存储在调用堆栈上。如果函数调用另一个函数,将创建另一个帧对象并将其放在调用堆栈的第一个帧对象的顶部。...尾递归函数将递归函数调用的返回值作为递归情况中的最后一个操作返回。这允许函数删除当前帧对象,并防止调用堆栈在进行新的递归函数调用时增长。如果调用堆栈不增长,递归函数不可能导致堆栈溢出。

37210
  • 【数据结构与算法】深入浅出递归和迭代的通用转换思想

    int sum(int n ) { if(n==1) return 1; else return n+sum(n-1); } 同样是求0~n的和,这段代码是每次在函数体中调用自身函数,...在函数调用过程中,系统会分配一个堆栈,当递归深度越深,堆栈的占用就越大,造成的后果就是会产生堆栈溢出。 所以,在能够用迭代的地方就不要用递归。这里又有问题呢?...(四)递归转成迭代的通用方式 尾递归转换成迭代 尾递归:递归的特殊情况,函数调用出现在函数尾部的递归方式。上述两个例子都输入尾递归。 尾递归可以轻松的转换成迭代方式。这里就不在具体说明了。...非尾递归转换成迭代 非尾递归转换成迭代就必须用到堆栈,简而言之,就是模拟函数调用的堆栈。...,减少了函数调用带来的额外开销,也避免了系统堆栈的溢出。

    1.5K10

    聊聊面试必考-递归思想与实战

    递归算法是什么 维基百科: 递归是在一个函数定义的内部用到自身。...如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。 这么写代码,对于这种假设的 无敌长队肯定会出现爆栈的情况,修改代码 // 全局变量,表示递归的深度。...,可能这么说大家还不明白,画了一个重复调用函数的图,应该就懂了。...看图中的函数调用,你会发现好多函数被调用多次,比如 f(3) ,计算 f(5) 时候需先计算 f(4) 和 f(3),到了计算 f(4) 的时候还要计算 f(3) 和 f(2) ,这种 f(3) 就被多次重复计算了...递归算法缺点:递归算法有堆栈溢出(爆栈)的风险、存在重复计算,过多的函数调用会耗时较多等问题(写递归算法的时候一定要考虑这几个缺点)、归时函数的变量的存储需要额外的栈空间,当递归深度很深时,需要额外的内存占空间就会很多

    60520

    聊聊面试必考-递归思想与实战

    递归算法是什么 维基百科: 递归是在一个函数定义的内部用到自身。...如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。 这么写代码,对于这种假设的 无敌长队肯定会出现爆栈的情况,修改代码 // 全局变量,表示递归的深度。...,可能这么说大家还不明白,画了一个重复调用函数的图,应该就懂了。...看图中的函数调用,你会发现好多函数被调用多次,比如 f(3) ,计算 f(5) 时候需先计算 f(4) 和 f(3),到了计算 f(4) 的时候还要计算 f(3) 和 f(2) ,这种 f(3) 就被多次重复计算了...递归算法缺点:递归算法有堆栈溢出(爆栈)的风险、存在重复计算,过多的函数调用会耗时较多等问题(写递归算法的时候一定要考虑这几个缺点)、归时函数的变量的存储需要额外的栈空间,当递归深度很深时,需要额外的内存占空间就会很多

    99121

    数据结构——lesson11排序之快速排序

    ,右子序列中所有元素均大于基准值,然后最左右子序列重复(这里使用递归来重复,非递归版本将在后续讲解)该过程,直到所有元素都排列在相应位置上为止。...,可以防止越界:当right首次向左走时如果没有一直遇到小于key的值那么right就有可能越界; 交换函数在这里 //交换函数 void Swap(int* a, int* b) { int tmp...};最左边的数8找到了它最合适的位置——倒数第二位 排完序结果如下: 2.快速排序(非递归版) 快速排序的递归调用虽然能够解决问题,但是递归调用的是栈帧,是在栈上实现的,但是栈的空间一般只有...8MB,如果递归很深的话有可能造成栈溢出的风险,所以我们也需要学习和掌握快速排序非递归版本; 要实现快速排序的非递归版本我们就可以用之前学习过的栈来模拟实现递归(当然使用队列也可以),详解在这里:...,调用的空间都是O(logN),递归实现要调用栈帧,左右子序列,类似于二分,左序列再调用左右序列…,并且空间是可以复用的,左边归还之后调用右边序列则可以重复使用,所以调用的空间是logN(以2为底);

    7910

    递归为什么那么慢?递归的改进算法

    具体是每次调用函数本身要保存的内容包括:局部变量、形参、调用函数地址、返回值。...(如果你真的理解了算法的话,否则你更晕) 缺点:它的运行需要较多次数的函数调用,如果调用层数比较深,需要增加额外的堆栈处理(还有可能出现堆栈溢出的情况),比如参数传递需要压栈等操作,会对执行效率有一定影响...2)现在的编译器在优化后,对于多次调用的函数处理会有非常好的效率优化,效率未必低于循环。 3) 递归和循环两者完全可以互换。...尾递归是极其重要的,不用尾递归,函数的堆栈耗用难以估量,需要保存很多中间函数的堆栈。...比如f(n, sum) = f(n-1) + value(n) + sum,会保存n个函数调用堆栈,而使用尾递归f(n, sum) = f(n-1, sum+value(n)),这样则只保留后一个函数堆栈即可

    2.2K20

    .NET基础面试题整理

    但是可以添加构造函数没有析构函数没有 abstract 和 sealed(因为不能继承)不能有protected 修饰符可以不使用new 初始化在结构中初始化实例字段是错误的 类:有默认的构造函数 有析构函数...可以使用 abstract 和 sealed 有protected 修饰符 必须使用new 初始化 04 4..NET BCL里有哪些是类(结构),为什么它们不是结构(类)?...NET BCL中有哪些常见的异常?在代码中您是如何捕获/处理异常的? 在“catch (ex)”中,“throw”和“throw ex”有什么区别?您会如何设计异常的结构,什么情况下您会抛出异常?...引用类型 它和普通的引用类型相比有什么特别的地方吗?不可变的 使用字符串时有什么需要注意的地方?为什么说StringBuilder比较高效?...委托可以理解为指向一个函数的指针。 匿名方法:就是没有实际方法声明的委托实例。或者说,它们的定义是直接内嵌在代码中的。

    1.6K21

    web面试题及答案_前端html面试题

    监控都有一定的了解,也有一定的线上运维经验 npm 模块安装机制,为什么输入 npm install 就可以自动安装对应的模块?...但有特殊情况,即当函数中存在对其它函数的调用时,JS引擎会在父函数执行的过程中,将子函数的全局执行上下文push到执行栈,这也是为什么子函数能够访问到父函数内所声明的变量。...,当调用(执行)栈中已经没有需要被执行的代码时,JS引擎会立刻将任务队列中的回调函数逐个push进调用栈并执行。...index.worker.js 里封装的业务逻辑,这里面会有很多对底层api调用。...1、工厂模式: 主要好处就是可以消除对象间的耦合,通过使用工程方法而不是new关键字。将所有实例化的代码集中在一个位置防止代码重复。

    62920

    快速排序的JavaScript实现详解

    排序是指以特定顺序(数字或字母)排列线性表的元素。排序通常与搜索一起配合使用。 有许多排序算法,而迄今为止最快的算法之一是快速排序(Quicksort)。...quickSort(arr, start, index - 1); quickSort(arr, index + 1, end); } 在这个函数中首先对数组进行分区,之后对左右两个子数组进行分区...只要这个函数收到一个不为空或有多个元素的数组,则将重复该过程。 空数组和仅包含一个元素的数组被视为已排序。...没有显式的peek()函数 // 只要存在未排序的子数组,就重复循环 while(stack[stack.length - 1] >= 0){ // 提取顶部未排序的子数组...每选择一次错误的基准(大于或小于大多数元素的基准)都会带来最坏的时间复杂度。在重复选择基准时,如果元素值小于或大于该元素的基准时,时间复杂度为 。

    3.3K40

    快速排序(Quick Sort)(C语言) 超详细解析!!!

    ,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。...keyi交换位置, 此时6这个位置就排好了 第二步: 因为6这个位置排好了, 所以进行下一次的排序,以6划分为两个区域,然后再次递归调用函数, 如果最后的区间不存在或者只有一个值那么就结束函数.当left..., 所以时间复杂度为O(NlogN), 但是这是在较好的情况下, 也就是相当于二叉树, 左右区间差不多大, 那么如果是有序的一系列数字, 那么这个排序就很不友好, 很容易栈溢出,递归层次很深, 就不是logn...(a, left, keyi - 1); QuickSort(a, keyi + 1, right); } 有了上述的优化, 我们的代码效率就算在有序的情况下也能大大的提升效率, 但是还有一个问题,...我们知道, 二叉树中递归调用, 最后一层递归调用的次数占据了总调用次数的一半, 我们可以把最后几层的递归调用优化掉, 如果区间为十个数那么我们选择其他的排序方法, 如果选堆排序, 十个数还需要建堆比较麻烦

    12210

    图解算法-读后感-快速排序

    有时候也在难,搞个噱头,做个什么40k前端面试指南,搞个程序员人生解惑? 这是我的长期主义吗? 我学习的目的是什么?...分治法 所有的问题都很复杂,我经常在想,我这么牛逼,搞个项目,找个人哪怕做外包都比南京百分之90公司强,我为什么没有做起来? 我没有销售,没有人际。 所有问题混在一起看,都很复杂,都很繁琐。...如果是有序数据不能太大,在递归调用时,如果是普通快排,会导致堆栈溢出,而随机快排不会 建议在递归场景下面使用随机快排,效果明显,堆栈溢出概率低 总结 坚持长期主义。...我的年薪百万一定是在我读完,编译原理,算法导论,深入理解计算机系统 这三本书之后。 大问题拆成小问题,再去解决小问题。...人的一生不应该有太多的目标,每个兔子都去跑过去追两下,兔子很多,我们追着追着自己就累了。 先实现,再优化,代码如此,人生也是如此。

    46030

    【算法复习4】C++ STL 中的 sort()和Java 语言中的 Collections.sort()通用的、高性能的排序函数

    随机法 快排避免堆栈溢出 评论区大佬的笔记 Arrays.sort Timsort 谷歌V8 QuickSort排序 思考过程比答案重要,有答案来验证自己的思考是否准确在初学时期也很重要 经典排序算法...随机法 快排避免堆栈溢出 为了避免快速排序里,递归过深而堆栈过小,导致堆栈溢出,我们有两种解决办法:第一种是限制递归深度。一旦递归过深,超过了我们事先设定的阈值,就停止递归。...第二种是通过在堆上模拟实现一个函数调用栈,手动模拟递归压栈、出栈的过程,这样就没有了系统栈大小的限制。...找出左分区最后一个元素(最大)及在右分区的位置 2 找出右分区第一个元素(最小)及在左分区的位置 3 仅对这两个位置之间的元素进行合并,之外的元素本身就是有序的 谷歌V8 QuickSort排序...,但是有答案来验证自己的思考是否准确在初学时期也很重要。

    99620

    题型篇 | 数据结构与算法之链表系列

    ※缺点:如果链表很长,递归深度很深,导致堆栈溢出。 ※优点:代码简洁、明了。...1、输入空链表; 2、输入的链表只有一个结点; 3、输入的链表有多个结点。...※递归的缺点: 1、堆栈溢出:函数调用自身,函数的临时变量是压栈的操作,当函数执行完,栈才清空,如果递归的规模过大,在函数内部一直执行函数的自身调用,临时变量一直压栈,系统栈或虚拟机栈内存小,导致堆栈溢出...2、重复计算:递归会出现很多的重复计算问题,重复计算对程序的性能有很大影响,导致消耗时间成指数增长,但是可以通过散列表的方式解决。...3、高空间复杂度:递归的每次函数调用都要涉及到在内存开辟空间,压栈、出栈等操作,即耗时又耗费空间,导致递归的效率并不如循环的效率。

    61110

    排序算法的 Python 实现以及时间复杂度分析

    然后简单讲了讲快速排序的优化,我们可以通过小数组采用插入排序来减少递归的开销;对于有一定顺序的数组,我采用三数取中来提高性能;对于包含大量重复数的数组,我用了三路快速排序来提高性能。...为了保证归并排序函数 MergeSort () 输入只有未排序的数组,这里调用前面的辅助函数 sort (): def MergeSort(nums): aux = nums.copy()...QuickSort () 函数 为了保证快速排序函数 QuickSort () 输入只有未排序的数组,这里调用前面的辅助函数 sort (): def QuickSort(nums): low...total time: 16.011647939682007 QuickSort3Ways's total time: 0.10053849220275879 含有大量重复数的数组 这里我们看下这些排序算法在含有大量重复数的数据集下的表现...22.454 0.156 0.154 0.088 经过优化后的三路快速排序在升序、降序、包含大量重复数的情况下表现均非常优异。

    1.6K20

    快速排序的4种优化

    但是快排与归并排序不同,归并的递归则在函数一开始, 快排的递归在函数尾部,这就使得快排代码可以实施尾递归优化。使用尾递归优化后,可以缩减堆栈的深度,由原来的O(n)缩减为O(logn)。...尾递归原理: 当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。...在facttail中碰巧最后一条语句也是对facttail的调用,但这并不是必需的。换句话说,在递归调用之后还可以有其他的语句执行,只是它们只能在递归调用没有执行时才可以执行。...尾递归是极其重要的,不用尾递归,函数的堆栈耗用难以估量,需要保存很多中间函数的堆栈。...比如f(n, sum) = f(n-1) + value(n) + sum; 会保存n个函数调用堆栈,而使用尾递归f(n, sum) = f(n-1, sum+value(n)); 这样则只保留后一个函数堆栈即可

    2K10

    【数据结构】排序算法——Lessen1

    前言 所谓排序,就是将一组数据按照某种顺序进行排列的过程。 排序在很多应用中都非常重要,比如数据分析、搜索算法、数据库管理等。 本篇文章将详细介绍常见的排序算法。...因为gap的取值很多,导致希尔排序的时间复杂度不好计算,我们只需记住当n在某个特定返回内,时间复杂度约为O(N^1.3)。...直接选择排序的时间复杂度是O(N^2),效率很低,甚至还比不过冒泡排序,因此在实际中基本不使用,主要用于教学。 2.2堆排序 堆排序在之前的文章中有详解介绍,这里就不再赘述。...因为我们将最左边的数当做基准值时,如果这个数恰好是最小或比较小的数,此时从右向左的数基本都比这个基准值大,所以会有很多次的递归调用。...(arr, left, keyi - 1); QuickSort(arr, keyi + 1, right); } } 挖坑法虽然没有效率的提升,但是相对于hoare版本还是有一些优势: 挖坑法不用分析为什么左边取基准值而右边先走的问题

    10910

    go的性能分析:pprof工具

    :  内存分配数据采样信息 block: 导致同步原语阻塞的堆栈跟踪 cmdline: 当前程序的命令行调用 goroutine:  所有当前goroutine的堆栈跟踪 heap:  活动对象的内存分配采样...您可以指定gcGET参数以在获取堆样本之前运行gc。 mutex:  争用互斥锁持有者的堆栈跟踪 profile: CPU配置文件。可以在秒GET参数中指定持续时间。...开源框架 在不同的开源框架中,有提供自己封装好的pprof包,调用更加方便,具体使用请参考框架文档 pprof主要核心就是将pprof路由注册到服务中,并可以访问此服务即可 数据分析 数据分析通过命令...,每列的标识为: flat:函数在 CPU 上运行的时间 flat%:函数在CPU上运行时间的百分比 sum%:是从上到当前行所有函数累加使用 CPU 的比例,如第二行sum=48.52=28.79+19.73...quickSort耗时比较长 生成函数调用图 可以通过svg命令,生成一个svg文件,拖动到浏览器打开即可查看函数调用图,但是需要安装 graphviz 才可以使用,具体安装方法可以自行百度 mac安装方法

    2.4K21

    快排究竟有多快?

    记为P 将元素重新排列为3个子块: 左子块S1:由P的元素组成 中间块M:仅有P一个元素 右子块S2:由≥P的元素组成 对左子块S1和右子块S2递归地重复上述过程,Return {quicksort(...这里放一个全过程慢镜头动图,帮助理解: Quicksort-example.gif 算法分析 这种快速排序的优点是我们可以“就地”排序,即无需依赖于输入大小的临时缓冲区。...则阶段1迭代中生成一个空子块、pivot,及一个大小(n-1)的子块,则时间复杂度为θ(n) 递归方程: 如果这种情况在每个分区中都重复发生,那么每个递归调用处理一个比前一个列表小1的列表。...事实上,在实际应用中有更复杂的变体,例如在Android,Java和Python中使用的Timsort(合并排序,插入排序和其他逻辑),以及在某些C++中用的introsort(快速排序和堆排序) 在....总结一下 在信息时代,有海量信息需要处理,即便有非常强劲的处理器,但如没有很好的算法,仍然无法满足对这些信息的处理。

    1.3K00

    数据结构与算法 --- 递归(一)

    递归的堆栈溢出问题 在函数调用会使用栈来保存临时变量,每调用一个新的函数,都会将临时变量封装为栈帧,压入内存栈,等函数执行完成后,再将栈帧出栈,所以,如果递归求解的数据规模很大,调用层次很深,一直往函数栈里添加数据...如何避免出现堆栈溢出呢?「可以通过在代码中限制递归调用的最大深度」。...,编写递归还会出现重复计算的问题,例如上述斐波那契数列的递归,在执行时就有重复计算的问题。...,递归编程的好处是使用递归编写的代码的表达能力强,写起来简洁,而递归编程的劣势是空间复杂度高,且存在堆栈溢出和重复计算的问题,因此,在实际开发过程中,可以根据实际情况来决定是是否使用递归实现,例如可以将上述的斐波那契数列的代码改为非递归代码...递归也有它自己的弊端,比如堆栈溢出,重复计算,函数调用耗时多和空间复杂度高,所以在编写递归算法代码时,要避免出现这些问题。 ❝参考资料 [1] 数据结构与算法之美 / 王争 著.

    27920
    领券