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

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

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

基础概念

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

问题原因

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

解决方案

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

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

应用场景

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

参考链接

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

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

相关·内容

递归

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

82040

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

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

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

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

    1.4K10

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

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

    98021

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

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

    60420

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

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

    7210

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

    具体是每次调用函数本身要保存内容包括:局部变量、形参、调用函数地址、返回值。...(如果你真的理解了算法的话,否则你更晕) 缺点:它运行需要较多次数函数调用,如果调用层数比较深,需要增加额外堆栈处理(还有可能出现堆栈溢出情况),比如参数传递需要压栈等操作,会对执行效率一定影响...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关键字。将所有实例化代码集中一个位置防止代码重复

    62020

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

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

    7410

    快速排序JavaScript实现详解

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

    3.3K40

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

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

    45330

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

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

    96620

    排序算法 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

    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.3K21

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

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

    10610

    快速排序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)); 这样则只保留后一个函数堆栈即可

    1.8K10

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

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

    60510

    快排究竟有多快?

    记为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

    【数据结构】八大排序之快速排序算法

    算法动图演示: 二.快速排序代码实现三种方式 我们了解了快速排序基本思想是通过一趟排序将待排数据分割成独立两部分之后,代码实现上,其实就有很多可以自由发挥空间,如下较为主流快速排序三种实现思路...keyi后(如下函数第15行后)调用一下随机选keyi函数就可以将随机选出key值和原本key值做交换了....递归函数以下几个缺点: 内存消耗大:递归调用会占用大量内存空间,因为每次调用都需要在内存中保存当前状态和参数。...性能低下:递归调用会增加函数调用开销,因此一些情况下会导致程序性能下降。 可读性差:递归函数通常比较复杂,难以理解和调试,降低了代码可读性。...(如:二叉树前序遍历) 快速排序改非递归思路 将初始数组区间压入栈 栈里取一段区间,单趟排序 单趟分割子区间入栈 子区间只有一个值或着不存在就不入栈 重复步骤2-4,直到栈为空,则排序完成.

    21621
    领券