天天看點

卡特蘭數問題

原文位址http://blog.163.com/lz_666888/blog/static/1147857262009914112922803/

  Catalan數

  中文:卡特蘭數

  原理:

  令h(1)=1,h(0)=1,catalan數滿足遞歸式:

  h(n)= h(1)*h(n-1) + h(2)*h(n-2) + ... + h(n-1)h(1) (其中n>=2)

  另類遞歸式:

  h(n)=((4*n-2)/(n+1))*h(n-1);

  該遞推關系的解為:

  h(n+1)=C(2n,n)/(n+1) (n=1,2,3,...)

  我并不關心其解是怎麼求出來的,我隻想知道怎麼用catalan數分析問題。

  我總結了一下,最典型的四類應用:(實質上卻都一樣,無非是遞歸等式的應用,就看你能不能分解問題寫出遞歸式了)

  1.括号化問題。

  矩陣鍊乘: P=a1×a2×a3×……×an,依據乘法結合律,不改變其順序,隻用括号表示成對的乘積,試問有幾種括号化的方案?(h(n)種)

  2.出棧次序問題。

  一個棧(無窮大)的進棧序列為1,2,3,..n,有多少個不同的出棧序列?

  類似:有2n個人排成一行進入劇場。入場費5元。其中隻有n個人有一張5元鈔票,另外n人隻有10元鈔票,劇院無其它鈔票,問有多少中方法使得隻要有10元的人買票,售票處就有5元的鈔票找零?(将持5元者到達視作将5元入棧,持10元者到達視作使棧中某5元出棧)

  3.将多邊行劃分為三角形問題。

  将一個凸多邊形區域分成三角形區域的方法數?

  類似:一位大城市的律師在她住是以北n個街區和以東n個街區處工作。每天她走2n個街區去上班。如果她

  從不穿越(但可以碰到)從家到辦公室的對角線,那麼有多少條可能的道路?

  類似:在圓上選擇2n個點,将這些點成對連接配接起來使得所得到的n條線段不相交的方法數?

  4.給頂節點組成二叉樹的問題。

  給定N個節點,能構成多少種不同的二叉樹?

  (能構成h(N)個)

Catalan數的解法

Catalan數的組合公式為 Cn=C(2n,n) / (n+1);

此數的遞歸公式為 h(n ) = h(n-1)*(4*n-2) / (n+1)

// 0ms

#include<iostream>

using namespace std;

#define MAX 100

#define BASE 10000

void multiply(int a[],int Max,int b) //大數乘法,注意參數的傳遞

{

    int i,array=0;

    for (i = Max-1; i >= 0; i--)   

    {

        array += b * a[i];

        a[i] = array % BASE; // 數組每一位存放大數的四位數字

        array /= BASE;   

    }

}

void divide(int a[], int Max, int b) //模拟大數除法

{

    int i, div = 0;

    for (i = 0; i < Max; i++)   

    {

        div = div * BASE + a[i];

        a[i] = div / b;

        div %= b;

    }

}

int main()

{

    int a[101][MAX],i, n;

    memset(a[1],0,MAX*sizeof(int));

    for (i=2, a[1][MAX-1] = 1; i < 101; i++) // 高坐标存放大數低位

    {

        memcpy(a[i], a[i-1], MAX * sizeof(int));      //h[i] = h[i-1];

        multiply(a[i], MAX, 4 * i - 2);               //h[i] *= (4*i-2);

        divide(a[i], MAX, i + 1);                  //h[i] /= (i+1);

    }

    while (cin >> n)   

    {

        for (i = 0; i < MAX && a[n][i] == 0; i++); //去掉數組前為0的數字。

        cout << a[n][i++];             //輸出第一個非0數

        for (; i < MAX; i++)   

        {

    printf("%04d",a[n][i]);       //輸出後面的數,并每位都保持4位長度!(32767)

   }

        cout << endl;

    }

    return 0;

}
           

AC CODE 2:

//C(0) = 1 ; (n+2)*C(n+1) = (4n+2)*C(n); 也即是:h(n) = h(n-1) * (4 * n - 2)/(n+1);

// 0MS

#include<iostream>

using namespace std;

int a[101][101] = {0};

int main()

{

    int n,i,j,len,r,temp,t;

    int b[101];

    a[1][0] = 1; // 低坐标存放大數的低位

    len = 1;

    b[1] = 1;

    for (i = 2; i <= 100; i++)

    {

        t = i - 1;

        for (j=0;j<len;j++) // 模拟乘法,從低位開始

   {

    a[i][j] = a[i-1][j] * (4 * t + 2);

   }

        for (r = j = 0; j < len; j++) // 處理相乘結果

        {

            temp = a[i][j] + r;

            a[i][j] = temp % 10;

            r = temp / 10;

        }

        while (r) // 進位處理

        {

            a[i][len++] = r % 10;

            r /= 10;

        }

        for (j = len-1, r = 0; j >= 0; j--) // 模拟除法,從高位開始

        {

            temp = r * 10 + a[i][j];

            a[i][j] = temp / (t+2);

            r = temp % (t+2);

        }

        while (!a[i][len-1]) // 高位零處理

   {

    len--;

   }

        b[i] = len; // 記錄結果的長度

    }

    while (cin >> n)

    {  

        for(j = b[n] - 1; j >= 0; j--)

   {

    printf("%d",a[n][j]);

   }

        printf("\n");

    }

    return 0;
}
           

《程式設計之美》中提到了“買票找零”問題,查閱了下資料,此問題和卡特蘭數 Cn有關,其定義如下:

卡特蘭數問題

卡特蘭數真是一個神奇的數字,很多組合問題的數量都和它有關系,例如:

  • Cn= 長度為 2n的 Dyck words的數量。 Dyck words是由 n個 X和 n個 Y組成的字元串,并且從左往右數, Y的數量不超過 X,例如長度為 6的 Dyck words為:

XXXYYY XYXXYY XYXYXY XXYYXY XXYXYY

  • Cn= n對括号正确比對組成的字元串數,例如 3對括号能夠組成:

((())) ()(()) ()()() (())() (()())

  • Cn= n+1個數相乘,所有的括号方案數。例如, 4個數相乘的括号方案為:

((ab)c)d (a(bc))d (ab)(cd) a((bc)d) a(b(cd))

  • Cn= 擁有 n+1 個葉子節點的二叉樹的數量。例如 4個葉子節點的所有二叉樹形态:
卡特蘭數問題
  • Cn=n*n的方格地圖中,從一個角到另外一個角,不跨越對角線的路徑數,例如, 4×4方格地圖中的路徑有:
卡特蘭數問題
  • Cn= n+2條邊的多邊形,能被分割成三角形的方案數,例如 6邊型的分割方案有:
卡特蘭數問題
  • Cn= 圓桌周圍有 2n個人,他們兩兩握手,但沒有交叉的方案數。

在《Enumerative Combinatorics》一書中,竟然提到了多達 66種組合問題和卡特蘭數有關。

分析

    “卡特蘭數”除了可以使用公式計算,也可以采用“分級排列法”來求解。以 n對括弧的合法比對為例,對于一個序列 (()而言,有兩個左括弧,和一個右括弧,可以看成“抵消了一對括弧,還剩下一個左括弧等待抵消”,那麼說明還可以在末尾增加一個右括弧,或者一個左括弧,沒有左括弧剩餘的時候,不能添加右括弧。

    由此,問題可以了解為,總共 2n個括弧,求 1~2n級的情況,第 i 級儲存所有剩餘 i 個左括号的排列方案數。 1~8級的計算過程如下表:

卡特蘭數問題

    計算過程解釋如下: 1級:隻能放 1個“(”; 2級:可以在一級末尾增加一個“)”或者一個“ (”

以後每級計算時,如果遇到剩餘 n>0個“(”的方案,可以在末尾增加一個“ (”或者“ )”進入下一級;遇到剩餘 n=0個“(”的方案,可以在末尾增加一個“ (”進入下一級。

奇數級隻能包含剩餘奇數個“(”的排列方案

偶數級隻能包含剩餘偶數個“(”的排列方案

從表中可以看出,灰色部分可以不用計算。

解法

關鍵代碼為:

double Catalan(int n) { if (n == 0) return 1; for (int i = 2; i <= 2 * n; i++) { var m = i <= n ? i : 2 * n + 1 - i; for (int j = (i - 1) & 1; j <= m; j += 2) { if (j > 0) arr[j - 1] += arr[j]; if (j < n) arr[j + 1] += arr[j]; arr[j] = 0; } } return arr[0]; }

其中:

n為 Cn中的 n;

arr = new double[n + 1];//arr[i]代表有 k個括弧的時候,剩餘 "("個數為 i的排列方案個數 arr[1] = 1;

讨論

算法複雜度為 

卡特蘭數問題

 = O(n^2),空間複雜度為 O(n+1)。相對于利用公式計算而言,此方法的優勢在于——沒有乘除法,隻有加法。