1.2 出棧序列統計

【問題描述】

棧是常用的一種數據結構,有n令元素在棧頂端一側等待進棧,棧頂端另一側是出棧序列。你已經知道棧的操作有兩·種:pushpop,前者是將一個元素進棧,後者是將棧頂元素彈出。現在要使用這兩種操作,由一個操作序列可以得到一系列的輸出序列。請你編程求出對於給定的n,計算並輸出由操作數序列12,…,n,經過一系列操作可能得到的輸出序列總數。

【輸入】

一個整數n1<=n<=15

【輸出】

一個整數,即可能輸出序列的總數目。

【樣例】

stack1.in stack1.out

35

【算法分析】

此題最原始的方法是利用回溯,對棧操作的模擬,邊界是

       1.所有的元素全部出棧

        2.所有的元素經過操作

回溯法pascal代碼:

var
   n,sum:longint;
procedure dfs(top,head:longint);{top爲棧頂指針,head爲已經有多少個元素入過棧}
var
   i,t:longint;
begin
  if  head=n then
    begin
      inc(sum);
      exit;
    end;
  if top>0 then dfs(top-1,head);
  if head<n+1 then dfs(top+1,head+1);
end;
begin
  readln(n);
  sum:=0;
  dfs(0,0);
  writeln(sum);
end.

        回溯法理解簡單,但是效率奇差,n在比較小的情況下能夠勝任,但是n一旦達到比較大的數時,必定超時!那有沒有效率更高的算法呢?

           回溯法效率差的原因是記錄了很多的重複運算,其實此題還可以用動歸方法解決,f[i,j],i表示入棧的個數,j表示出棧的個數,那f[i,j]就表示入棧i個數中出j個數的,但是此題要注意的是出棧數不能大於入棧數,那動歸方程該如何推導,再次謝謝一位某位具有探索精神的大神爲我們做了細緻的研究現在我把它的論文粘貼如下:

       

1      引 言

在實際應用和數據結構課程的教學中,棧作爲一種基本結構非常重要[1][3][4][6]。已知給定序列,求出棧序列的數目、求所有出棧的序列、以及判斷某個序列是否爲合法的出棧序列[5][7],這類問題經常出現。在[3]中,對出棧序列的計數問題給出了介紹性的說明,由於結果的證明需要用到生成函數,[3]也只是直接給出了結論。本文提出使用“兩點之間路徑計數的方法解決出棧序列的計數問題,在此基礎上可以求所有出棧的序列,以及判斷某個序列是否爲合法的出棧序列。

2      問題分析

           兩點之間路徑計數的問題

問題:假設AB兩點之間所有道路如圖1中的方形網格線(5×5),規定從AB只能夠向右或向上移動,求A點到B點有幾條路。


兩點之間路徑計數問題的路徑圖

分析:因爲規定從AB只能夠向右或向上移動,因此任意一點只能從該點的左鄰點或下鄰點到達,例如,任意一點C點只能從D點或E點到達。因此,從A點到C點的路只能由這兩部分組成:①從A點到D點,再從D點到C點;②從A點到E點,再從E點到C點。

結論1:A點到任意一點C的路徑數目=A點到D點(C的左鄰點)的路徑數目+A點到E點(C的下鄰點)的路徑數目

其中,由於A點正上方的點沒有左鄰點,而且問題中已規定從AB只能夠向右或向上運動,所以A點到A點正上方點的路徑數目爲1。同理,A點到A點正右方點的路徑數目爲1。

根據結論1,將A點到任意一點的路徑數目求出,如圖1中網格線交點處的數字所示。

           棧的操作與兩點之間路徑計數問題的操作的比較

棧的操作有兩種:入棧、出棧。其中需要注意的問題有三個:①所有節點入棧之後只能出棧;②棧空時只能入棧;③其它情況下入棧、出棧任意執行。

兩點之間路徑計數問題的操作有兩種:向右移動、向上移動。其中需要注意的問題有三個:①移到最右邊後只能向上移;②移到最上邊只能向右移;③其它情況下上移、右移任意執行。

由以上分析可見,棧的操作與兩點之間路徑計數問題的操作有很大的相似性,不妨將入棧和右移相關聯,出棧和上移相關聯。但是,這樣關聯之後,由於兩個問題並不等價(例如,圖1中的D點,按照棧的操作是不可到達的),所以需要對圖1中的所有點進一步分析。

           對操作關聯後圖1中點的分析

首先,在圖1中添加A點到B點的對角虛線。這條虛線將所有的點分成三類:①虛線上的點;②虛線左上方的點;③虛線右下方的點。

其次,按照兩點之間路徑計數問題規定的操作容易得出:①從A點移到虛線上每一個點時,所執行的右移操作次數和上移操作次數相等;②從A點移到虛線左上方每一個點時,所執行的右移操作次數小於上移操作次數;③從A點移到虛線右下方每一個點時,所執行的右移操作次數大於上移操作次數。

再次,由於棧操作過程中的任意時刻必須有:入棧操作次數≥出棧操作次數(取等號時棧空)。

很明顯,圖1中虛線左上方的點按照棧的操作是不可到達的,虛線上的點恰好是棧空時的狀態,虛線右下方的點按照棧的操作都可以到達,所以考慮修改圖1中的路徑圖。

           改進後的路徑圖及規則

將圖1中虛線左上方的點去掉後如圖2所示(5×5方形網格線的下三角)。


改造後的的路徑圖

規定:AB只能夠向右或向上移動,右移爲入棧操作,上移爲出棧操作。

根據2.3的分析可得結論2

  從A點到A點正右方的點的路徑數目 = 1;

  從A點到每一行最左的點(考慮B點,不考慮A點)的路徑數目 =從A點到該點的下鄰點的路徑數目;

  A點到其它任意一點C的路徑數目=A點到D點(C的左鄰點)的路徑數目+A點到E點(C的下鄰點)的路徑數目;

  按照棧的操作從A點開始到B點,圖2中的所有點都是可到達的;

  4個節點的入棧、出棧操作完全包含在圖2中;

  將⑤擴展得:N個節點的出棧、入棧操作完全包含在(N+1)×(N+1)方形網格線的下三角中。

在此僅對結論2第⑤點作一些說明:首先A點和對角線上的其它點表示棧空,只能入棧(右移);其次,移到最右的豎邊時所有的元素都已經入棧,只能出棧(上移);再次,B點爲最終狀態,不能入棧也不能出棧;最後,其它的點可以任意入棧(右移)、出棧(上移)。所以4個節點的入棧、出棧操作完全包含在圖2中。

從結論2中可以看到棧的操作與兩點之間路徑計數問題的操作在圖2中是等價的。根據結論2,將A點到任意一點的路徑數目求出,如圖2中網格線交點處的數字所示,圖2中虛線箭頭表示了執行結論2第①點,圖2中實線箭頭表示了執行結論2第③點。

           結論2推廣

將結論2加以推廣得結論3對於如圖2的形式((N+1)×(N+1)方形網格線的下三角),規定AB只能夠向右或向上移動,右移爲入棧操作,上移爲出棧操作,所求A點到B點的路徑數目就是N個節點出棧序列的數目,並且從A點到B點的每一條路都代表一種出棧序列。

3      設計實現

求N個節點出棧序列數目的算法在具體實現時,採用由下向上逐行處理,每一行從左至右逐點處理的方法。此外,在 計算當前行的值時,只需要使用上一行的值;在計算各行中的每一個值時左鄰點的值爲數組中當前元素的前一個元素,下鄰點爲數組中當前元素,把數組中當前元素 的前一個元素加到當前元素上就求出了當前點的值,所以在具體實現時,只使用一個數組來保存當前行的值即可。由於最後一行只有一個數,也是該行的第一個數, 根據結論2第③點可知該數在倒數第二行中已經計算出來,所以該行不用計算,直接取上一行計算結果中的最後一個數即可。

4      結論

該方法簡單方便,不需要記憶任何公式[3],特別適合沒有組合數學基礎的人員。另外,根據圖2還可以設計算法將入棧、出棧的操作序列求出來,這樣就可以得到所有的出棧序列。同時根據圖2也可以判斷某個序列是否爲合法的出棧序列,可以解決[5][7]中車廂調度問題。

  爲表感謝,特把該作者文章標紅顯示,通過該大神深入淺出的講解,我相信只要不是比我還笨的人都能理解了,大神爲了方便講解所以把向右定義爲入棧,向上定義爲出棧,但爲了方便書寫代碼,我把向下定義爲出棧,向右定義爲入棧,那麼動歸方程就推導如下f[i,j]:=f[i-1,j]+f[i,j-1],pascal代碼如下:

var
  n,tot:longint;
  i,j:longint;
  f:array[0..100,0..100] of longint;
begin
  readln(n);
  fillchar(f,sizeof(f),0);
  for i:=1 to n do f[i,0]:=1;{上邊界全爲1}
  for i:=1 to n do
    for j:=1 to i do
      begin
        if j<i then f[i,j]:=f[i-1,j]+f[i,j-1];
        if i=j then f[i,j]:=f[i,j-1];
      end;
  writeln(f[n,n]);
end.

      動歸比回溯效率從階乘的效率提高到n^2的效率,那此題還能繼續優化嗎,那是當然,那就是公式,據說此題是一個經典的卡特蘭數,對此我深信不疑,但是鑑於水學水平有限至今不知如何證明,但有人有排列組合還是能理解一二,但是還是有點模糊,不管了,先把別人的成果貼出來,等以後慢慢再理解吧


發佈了29 篇原創文章 · 獲贊 14 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章