码迷,mamicode.com
首页 > 编程语言 > 详细

排序 之 快速排序

时间:2018-01-10 18:44:45      阅读:156      评论:0      收藏:0      [点我收藏+]

标签:缺点   循环   结束   目的   处理   owa   alt   post   算法   

http://blog.csdn.net/it_zjyang/article/details/53406764

http://blog.csdn.net/hacker00011000/article/details/52176100

尾递归:https://www.cnblogs.com/babybluevino/p/3714022.html

迭代,循环,遍历,递归的区别:https://www.cnblogs.com/feichengwulai/articles/3642107.html

递归的效率:http://www.nowamagic.net/librarys/veda/detail/2321

 

原理

快速排序使用分治的思想,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小。之后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

时间复杂度:O(NlogN)

算法优缺点

快速排序最“快”的地方在于左右两边能够快速同时递归排序下去,所以最优的情况是基准值刚好取在无序区的中间,这样能够最大效率地让两边排序,同时最大地减少递归划分的次数。此时的时间复杂度仅为O(NlogN)。
快速排序也有存在不足的情况,当每次划分基准值时,得到的基准值总是当前无序区域里最大或最小的那个元素,这种情况下基准值的一边为空,另一边则依然存在着很多元素(仅仅比排序前少了一个),此时时间复杂度为O(N*N)。

对于分治算法,当每次划分时,算法若都能分成两个等长的子序列时,那么分治算法效率会达到最大。也就是说,基准的选择是很重要的。选择基准的方式决定了两个分割后两个子序列的长度,进而对整个算法的效率产生决定性影响。

最理想的方法是,选择的基准恰好能把待排序序列分成两个等长的子序列

三种选择基准的方法

  • 固定位置:取序列的第一个或最后一个元素作为基准
  • 随机选取基准
  • 三数取中(median-of-three)

最佳的划分是将待排序的序列分成等长的子序列,最佳的状态我们可以使用序列的中间的值,也就是第N/2个数。可是,这很难算出来,并且会明显减慢快速排序的速度。这样的中值的估计可以通过随机选取三个元素并用它们的中值作为枢纽元而得到。事实上,随机性并没有多大的帮助,因此一般的做法是使用左端、右端和中心位置上的三个元素的中值作为枢纽元。显然使用三数中值分割法消除了预排序输入的不好情形,并且减少快排大约14%的比较次数

 

三种优化方式

  • 当待排序序列的长度分割到一定大小后,使用插入排序

对于很小和部分有序的数组,快排不如插排好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排

  • 在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割
  • 尾递归优化

尾递归就是函数返回之前的最后一个操作是递归调用。就是:将单次计算的结果缓存起来,传递给下次调用,相当于自动累积。

    • 尾递归为什么会节省栈的空间呢?

我们知道递归调用是通过栈来实现的,每调用一次函数,系统都将函数当前的变量、返回地址等信息保存为一个栈帧压入到栈中,那么一旦要处理的运算很大或者数据很多,有可能会导致很多函数调用或者很大的栈帧,这样不断的压栈,很容易导致栈的溢出。

我们回过头看一下尾递归,函数在递归调用之前已经把所有的计算任务已经完毕了,他只要把得到的结果全交给子函数就可以了,无需保存什么,子函数其实可以不需要再去创建一个栈帧,直接把就着当前栈帧,把原先的数据覆盖即可。

需要注意的是:在Java、C#等语言中,尾递归使用非常少见,一方面我们可以直接用循环解决,另一方面这几种语言的编译器也不会自动优化尾递归。而在函数式语言中,尾递归却是一种神器,要实现循环就靠它了。

举个栗子

原数组:{3,7,2,9,1,4,6,8,10,5}
期望结果:{1,2,3,4,5,6,7,8,9,10}

技术分享图片

递归实现:

技术分享图片 View Code

循环实现:

 

递归为什么容易内存溢出?

在Java中, JVM中的栈记录了线程的方法调用。每个线程拥有一个栈。在某个线程的运行过程中, 如果有新的方法调用,那么该线程对应的栈就会增加一个存储单元,即栈帧 (frame)。在frame 中,保存有该方法调用的参数、局部变量和返回地址。Java的参数和局部变量只能是 基本类型 的变量(比如 int),或者对象的引用(reference) 。因此,在栈中,只保存有基本类型的变量和对象引用。而引用所指向的对象保存在堆中。

栈的初始化大小比较小。如果递归调用深度过深,容易导致栈溢出。

递归的效率

递归 vs 循环

排序 之 快速排序

标签:缺点   循环   结束   目的   处理   owa   alt   post   算法   

原文地址:https://www.cnblogs.com/amei0/p/8259919.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!