快排的优化--说说尾递归

前几天面试的时候,面试官让写出快排的代码,于是我就很easy的写了一遍。面试官于是又问,你这代码有什么可以优化的地方吗?我当时想,这还不easy吗?必须是随机选取枢轴于是我就开始解释,在现实中,待排序的系列极有可能是基本有序的,此时,总是固定选取第一个关键字(其实无论是固定选取哪一个位置的关键字)作为首个枢轴就变成了极为不合理的作法。这时候应该随机获得一个low与high之间的数rnd,让它的关键字r[rnd]与r[low]交换,此时就不容易出现这样的情况。于是乎就又洋洋洒洒的把随机选取枢轴的代码给加上了。这时候,面试官又问。如果我的待排序序列的划分极度不平衡,递归的深度趋近与n,而不是logn。怎么办?这时候,我才明白。。原来面试官想问的是尾递归相关的问题啊。可惜。。我当时就只知道这个概念,没有看过相关的资料和文档。于是就跪了,这回来研究一下什么是尾递归吧,还有就是尾递归对于递归层次过深的优化。

first section:什么是尾递归?

直接上代码:

public static void main(String[] args) {
        System.out.println(fact(4));
        System.out.println(facttail(4, 1));
    }
    
    public static int fact(int n){
        if(n < 0){
            return 0;
        }else if(n == 0){
            return 1;
        }else if(n == 1){
            return 1;
        }else{
            return n * fact(n - 1);
        }
    }
    public static int facttail(int n, int a){
        if(n < 0){
            return 0;
        }else if(n == 0){
            return 1;
        }else if(n == 1){
            return a;
        }else{
            return  facttail(n - 1, n * a);
        }
    }

如代码所示,这两个函数都是计算n阶乘的函数。上一种方法是普通的递归,下面的函数才是尾递归。尾递归到底是什么呢?其实很简单:尾递归就是函数返回之前的最后一个操作是递归调用。说白了,尾递归的就是:将单次计算的结果缓存起来,传递给下次调用,相当于自动累积。

Second Section:尾递归为什么会节省栈的空间呢?

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

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

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

Third Section:快排中尾递归的使用!

QuickSort函数在其尾部有两次递归操作。如果待排序的序列划分极端不平衡,递归的深度将趋近于n,而不是平衡时的logn。我们怎么对QuickSort进行尾递归的改动呢?

public void QuickSort(int[] num, int low, int high)
    {
        int pivot;
        while (low < high)
        {
            pivot = partition(num, low, high); ;
            QuickSort(num, low, pivot - 1); // 对低子表递归排序
            low = pivot + 1; // 尾递归
        }
    }

注意高亮部分的代码。将if语句,改成了while,并且把low的值赋为pivot+1。再循环后,来一次Partition(L,low,high),其效果等同于“QuickSort(num,pivot+1,high);”。因采用迭代而不是递归的方法可以缩减堆栈深度,从而提高了整体性能。

原文地址:https://www.cnblogs.com/babybluevino/p/3714022.html