这篇博客起源于我对上篇博客图片所用图片中快速排序空间占用部分的怀疑。。
今天搜索后,确实是图片有误。
快速排序的空间复杂度最好情况下为O(logn),最坏情况下为O(n)。
为什么?
我们知道快速排序,归并排序都是靠递归实现。
要想知道递归在操作系统中的实现,首先要知道函数调用在操作系统中如何实现:
假设函数A调用函数B,我们称A函数为"调用者",B函数为“被调用者”则函数调用过程可以这么描述[1]:
(1)先将调用者(A)的堆栈的基址(ebp)入栈,以保存之前任务的信息。
(2)然后将调用者(A)的栈顶指针(esp)的值赋给ebp,作为新的基址(即被调用者B的栈底)。
(3)然后在这个基址(被调用者B的栈底)上开辟(一般用sub指令)相应的空间用作被调用者B的栈空间。
(4)函数B返回后,从当前栈帧的ebp即恢复为调用者A的栈顶(esp),使栈顶恢复函数B被调用前的位置;然后调用者A再从恢复后的栈顶可弹出之前的ebp值(可以这么做是因为这个值在函数调用前一步被压入堆栈)。这样,ebp和esp就都恢复了调用函数B前的位置,也就是栈恢复函数B调用前的状态。
每一次函数调用,除了需要空间作为被调函数的栈空间外,还需要额外的地址用来保存当前调用者的基址。函数若一直不返回,而是继续调用新的函数,所需要的空间越来越大。空间复杂度分析时,我们将每次递归抽象为一次内存分配,所以递归的数量级决定了空间复杂度。
我们知道快速排序在最好情况下,两个子序列基本长度相同,递归次数为O(logn),所以空间复杂度也是O(logn)。最坏情况下,每次都一个子序列长度为1,递归次数为O(n),所以空间复杂度是O(n)。
归并排序中,由于每次都是将序列均分,递归造成的空间复杂度为O(logn),辅助用来归并的序列需要O(n)的空间,所以总空间复杂度为O(n+logn) 约为 O(n) 。
递归由于不停调用自身而不返回,所以栈空间会不停增长,若递归太深,会返回Stack overflow错误。
那么,尾递归又是什么?
http://www.cnblogs.com/JeffreyZhao/archive/2009/03/26/tail-recursion-and-continuation.html#!comments
http://www.cnblogs.com/JeffreyZhao/archive/2009/04/01/tail-recursion-explanation.html
老赵的两篇博客给出了很透彻的分析。
尾递归的特点:递归发生在函数体所有运算的最后。因为在一些编译器下,会被优化为循环,因此不会造成栈溢出。(如果没被优化为循环,依然会造成栈溢出)
目前大多数编译器都是支持尾递归优化的。有一些语言甚至十分依赖于尾递归(尾调用),比如erlang或其他函数式语言(传说它们为了更好的处理continuation-passing style)[2]。
引用:
[1] http://blog.csdn.net/zsy2020314/article/details/9429707
[2] http://www.nowamagic.net/librarys/veda/detail/2336