巧用函數(shù)棧實現(xiàn)棧的反轉(zhuǎn)

一、函數(shù)棧

函數(shù)的調(diào)用過程其實就是一個壓棧的過程,在函數(shù)棧中,每個函數(shù)所占空間成為一個 棧幀。棧幀中保存著函數(shù)的形參,返回地址,局部變量,EBP等信息。一個棧幀可以理解為棧的一個元素,函數(shù)調(diào)用過程是壓棧的過程,函數(shù)返回則可以簡單理解為彈棧的過程。

二、函數(shù)棧與遞歸

棧是一種后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),函數(shù)棧同理。不同的是,棧里面存儲的是相同類型的數(shù)據(jù),且數(shù)據(jù)之間相互獨立,函數(shù)棧每一個棧幀空間內(nèi)可以存儲不同類型的數(shù)據(jù),數(shù)據(jù)之間可以關(guān)聯(lián)。遞歸的過程就是將相同的函數(shù)不斷壓棧的過程,它們一般操作這同一個數(shù)據(jù)結(jié)構(gòu),到達(dá)某一特定條件后返回(系統(tǒng)??臻g由操作系統(tǒng)分配,不可能無限大,所以遞歸調(diào)用容易造成棧溢出)。

三、遞歸實現(xiàn)棧反轉(zhuǎn)

申請額外的數(shù)據(jù)結(jié)構(gòu)可以很輕松地實現(xiàn)棧反轉(zhuǎn),但是函數(shù)棧本身也是一個棧,也可以當(dāng)做一個數(shù)據(jù)結(jié)構(gòu)來使用。

給定一個棧,要求不申請其他數(shù)據(jù)結(jié)構(gòu),實現(xiàn)棧的反轉(zhuǎn)。

如給定棧[1, 2, 3] (棧頂?shù)綏5祝?/p>

返回[3, 2, 1]

這個問題需要兩個函數(shù)輔助完成,第一個函數(shù)得到棧底元素并返回:

    int get(stack<int> &s)
    {
        int result = s.top();
        s.pop();
        if (s.empty()) {
            return result;
        } else {
            int last = get(s);
            s.push(result);
            return last;
        }
    }

以棧 [1, 2, 3] 為例分析此函數(shù)遞歸過程:

  1. 遞歸第一層, 得到棧頂元素1并保存在result中。
  2. 遞歸第二層,得到棧頂元素2并保存在result中。
  3. 遞歸第三層,得到棧頂元素3并保存到result中。此時棧s為空,函數(shù)將result返回。
  4. 函數(shù)返回第二層,將第三層返回的result保存到last中,并將第二層的result(值為2)壓回棧s中,返回last 。
  5. 函數(shù)返回第一層,將第二層返回的last保存到第一層的last中,并將第一層的result(值為1)壓棧,最后返回last。

第二個函數(shù)將get()函數(shù)的返回值壓棧,最后取出的值最先壓棧,按照get()函數(shù)的規(guī)則,最后取出的值為1,所以1最先壓棧,3最后壓棧。

    void reserve(stack<int> &s)
    {
        if (s.empty()) return;
        int i = get(s); 
        reserve(s);
        s.push(i);
    }

以棧 [1, 2, 3] 為例分析此函數(shù)遞歸過程:

  1. 遞歸第一層,調(diào)用get函數(shù)得到棧底元素3,保存到i中。
  2. 遞歸第二層,調(diào)用get函數(shù)得到棧底元素2,保存到i中。
  3. 遞歸第三層,調(diào)用get函數(shù)得到棧底元素1,保存到i中。
  4. 遞歸第四層,棧s為空,直接返回。
  5. 函數(shù)返回第三層,將元素1壓棧。
  6. 函數(shù)返回第二層,將元素2壓棧。
  7. 函數(shù)返回第一層,將元素3壓棧。

由遞歸過程看出,棧[1, 2, 3] (棧頂?shù)綏5祝┮呀?jīng)被反轉(zhuǎn)為[3, 2, 1] (棧頂?shù)綏5祝?/p>

這個方法的巧妙之處在于,利用函數(shù)棧來存儲棧s中彈出的數(shù)據(jù),從而避免了申請額外的數(shù)據(jù)結(jié)構(gòu)。函數(shù)棧本身也是一種可利用的“數(shù)據(jù)結(jié)構(gòu)”。這就好理解為什么可以用遞歸解決的問題都可以用迭代來解決了。

盡管如此,還是應(yīng)該盡量少用遞歸解決問題,因為每一次函數(shù)調(diào)用都需要申請額外的棧幀空間,這會造成不小的系統(tǒng)開銷。雖然函數(shù)棧也是可利用的數(shù)據(jù)結(jié)構(gòu),但是棧幀里面畢竟存儲了太多函數(shù)執(zhí)行過程中需要的信息,如返回地址,EBP等。對空間也是一種浪費。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容