常规快速排序存在的问题
对于快速排序的实现,可以参考这篇博文:
三分钟学会快速排序(图示讲解,附代码,通俗易懂)
快排的核心函数如下:
void quickSort(int arr[], int low, int high)
{
if (low < high)
{
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
在最坏情况下,递归是这样的:
quickSort(arr,1,n )
quickSort(arr,1,n-1)
quickSort(arr,1,n-2)
quickSort(arr,1,n-3)
.
.
.
quickSort(arr,1,1)
用下面的递归树可以更好的理解,树的深度是n
由上图可知,此时堆栈上需要
O
(
n
)
O(n)
O(n)空间
用while进行尾递归优化后的快速排序
void quickSort(int arr[], int low, int high)
{
while (low < high)
{
int pi = partition(arr, low, high);
if (pi - low < high - pi)
{
quickSort(arr, low, pi - 1);
low = pi + 1;
}
else
{
quickSort(arr, pi + 1, high);
high = pi - 1;
}
}
}
在上面的代码中,如果左侧部分长度更小,则对左侧部分进行递归调用,否则对右侧部分进行递归调用。
此时使用 O ( L o g n ) O(Logn) O(Logn)额外空间。
尾递归
若函数在尾位置调用自身,则称这种情况为尾递归。尾递归是一种特殊的尾调用,即在尾部直接调用自身的递归函数。
函数自身调用次数过多会导致递归层数很深,导致爆栈。
举个例子:
def recsum(x):
if x == 1:
return x
else:
return x + recsum(x - 1)
当调用recsum(5)时,栈调用如下:
recsum(5)
5 + recsum(4)
5 + (4 + recsum(3))
5 + (4 + (3 + recsum(2)))
5 + (4 + (3 + (2 + recsum(1))))
5 + (4 + (3 + (2 + 1)))
5 + (4 + (3 + 3))
5 + (4 + 6)
5 + 10
15
可见递归深度是很深的
可以如下进行尾递归优化:
def foo(x, sum=0):
if x == 0:
return sum
else:
return foo(x - 1, sum + x)
则对应的调用为:
foo(5, 0)
foo(4, 5)
foo(3, 9)
foo(2, 12)
foo(1, 14)
foo(0, 15)
15
易知这是一个线性的调用
函数调用栈
在程序运行时,程序会被分配一定的内存空间,其中一部分被用于记录程序中正在调用的各个函数的运行情况,这就是函数的调用栈。
常规的函数调用总是会在调用栈最上层添加一个新的堆栈帧(stack frame,也翻译为“栈帧”或简称为“帧”),这个过程被称作“入栈”或“压栈”(意即把新的帧压在栈顶)。
当函数的调用层数非常多时,调用栈会消耗不少内存,甚至会撑爆内存空间(俗称爆栈)。