快速排序 的 尾递归优化 详细分析

传统快排

void QSort(int arr[],int low,int high)
{
	if(low < high)
	{
		int pivotPos = Partition(arr,low,high);
		QSort(arr,low,pivotPos-1);
		QSort(arr,pivotPos+1,high);
	}
}

尾递归优化


尾递归详细介绍
尾递归概念:

如果一个函数中所有递归形式的调用都出现在函数的末尾,当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。

尾递归原理:

当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。


快排的递归函数和尾递归有些类似,但因为调用了两次自身,所以并不属于尾递归,也不会被编译器优化。
但是,我们可以手动将第二个递归改为迭代,对其进行优化,节省栈空间。

【TIP】两个调用函数位置可以颠倒,可以对任意一个进行优化,将其改为迭代。为了尽可能地节省递归栈,我们对比较两个区间的长度,对长度更长的进行优化,将其改为迭代。

伪代码:

TAIL-RECURSIVE-QUICKSORT'(A, p, r)
    while p < r
        q = PARTITION(A, p, r)
        if (q - p < r - q)
            TAIL-RECURSIVE-QUICKSORT'(A, p, q)
            p = q + 1
        else
            TAIL-RECURSIVE-QUICKSORT'(A, q + 1, r)
            r = q - 1

C++:

void QSort(int arr[],int low,int high)
{
    while(low < high)
	{
		int mid = Partition(arr,low,high);
        if(mid-low<high-mid){
            QSort(arr,low,mid-1);
            low=mid+1;
        }
        else{
            QSort(arr,mid+1,high);
            high=mid-1;
        }
	}
}

优点

1.减少栈深度
对于划分不平衡的情况,传统版本栈深度最多可以达到O(n),尾递归优化后,栈深度最多为O(lgn)。

例如:[1 2 3 4 5 6]最坏情况下 partion()函数 每次划分都选取末尾元素作为基准:6、5、4、3、2、1、0

未优化时:
QSort(arr,0,6)——>QSort(arr,0,5)——>QSort(arr,0,4)——>QSort(arr,0,3)——>QSort(arr,0,2)——>QSort(arr,0,1)—>QSort(arr,0,0)
箭头表示新开辟的栈,栈深度达到了O(n)。
优化后该情况下只需要O(1)栈空间,因为左区间的递归被循环取代了。

【TIP】网上的很多尾递归优化,固定优化右区间,对于上面的例子,仍然需要O(n)的栈深度。

2.栈深度减少了,因此大大增加了可排序的数据量,大大降低了爆栈的可能。

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值