快排的優化--說說尾遞歸


前幾天面試的時候,面試官讓寫出快排的代碼,於是我就很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);”。因采用迭代而不是遞歸的方法可以縮減堆棧深度,從而提高了整體性能。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM