1. 程式人生 > >[程式設計之美]買票找零(卡特蘭數)

[程式設計之美]買票找零(卡特蘭數)

第一次看這題的時候沒有好好注意,後來發現這是一類大問題,學習了卡特蘭數這個概念,順便又複習了高中的排列組合知識、、、

一、書中問題

先看一下書中引入卡特蘭數的例子:

《程式設計之美》4.3買票找零:2n個人排隊買票,其中n個人持50元,n個人持100元。每張票50元,且一人只買一張票。初始時售票處沒有零錢找零。請問這2n個人一共有多少種排隊順序,不至於使售票處找不開錢?

分析1:隊伍的序號標為0,1,…,2n-1,並把50元看作左括號,100元看作右括號,合法序列即括號能完成配對的序列。對於一個合法的序列,第0個一定是左括號,它必然與某個右括號配對,記其位置為k。那麼從1到k-1、k+1到2n-1也分別是兩個合法序列。那麼,k必然是奇數(1到k-1一共有偶數個),設k=2i+1。那麼剩餘括號的合法序列數為f(2i)*f(2n-2i-2)個。取i=0到n-1累加即:
f(2n)=f(0)*f(2n-2)+f(2)*f(2n-4)+······+f(2n-4)*f(2)+f(2n-2)*f(0)
並且令f(0)=1,再由組合數C(0,0)=0,可得

以上就是一種卡特蘭數的通項公式,下面具體介紹一下卡特蘭數。

二、卡特蘭數

1、定義:

以下是百度百科中的定義:卡特蘭數又稱卡塔蘭數,英文名Catalan number,是組合數學中一個常出現在各種計數問題中出現的數列。以比利時的數學家歐仁·查理·卡塔蘭 (1814–1894)的名字來命名,其前幾項為 : 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420, 24466267020, 91482563640, 343059613650, 1289904147324, 4861946401452, …

2、通項公式

我接觸到的公式的定義有兩種形式,一種是上面的f(2n),

f(2n)=f(0)*f(2n-2)+f(2)*f(2n-4)+······+f(2n-4)*f(2)+f(2n-2)*f(0)

還有一種就是下面的f(n)的
令f(0)=1,f(1)=1,catalan數滿足遞推式[1] :

f(n)= f(0)*f(n-1)+f(1)*f(n-2) + … + f(n-1)*f(0) (n>=2)

其實這兩種只是表現形式不同,因為具體問題中,關鍵元素都是成對出現的,比如n對括號,總的個數就是2n個,所以一定要理解這兩個個公式並結合具體問題具體分析。

3、遞推關係與證明

卡特蘭數的遞推關係為

也就是f(2n)=C(2n, n)/(n+1)
下面是證明過程

在<<計算機程式設計藝術>>,第三版,Donald E.Knuth著,蘇運霖譯,第一卷,508頁,給出了證明:
問題大意是用S表示入棧,X表示出棧,那麼合法的序列有多少個(S的個數為n)
顯然有c(2n, n)個含S,X各n個的序列,剩下的是計算不允許的序列數(它包含正確個數的S和X,但是違背其它條件).
在任何不允許的序列中,定出使得X的個數超過S的個數的第一個X的位置.然後在導致幷包括這個X的部分序列中,以
S代替所有的X並以X代表所有的S.結果是一個有(n+1)個S和(n-1)個X的序列.反過來,對一垢一種型別的每個序列,我們都能
逆轉這個過程,而且找出導致它的前一種型別的不允許序列.例如XXSXSSSXXSSS必然來自SSXSXXXXXSSS.這個對應說明,不允許
的序列的個數是c(2n, n-1),因此
an = c(2n, n) - c(2n, n-1).[Comptes Rendus Acad.Sci.105(Paris, 1887), 436~437]

三、典型應用

1、出棧次序(買票問題)

對於一個無限大的棧,一共n個元素,請問有幾種合法的入棧出棧形式?

分析:n個元素出棧入棧操作共有2n個,且任何前x個操作中入棧操作必須不少於出棧操作,直接用f(2n)計算即可

2、括號化問題(矩陣連乘問題)

P = a1 * a2 * a3 * … * an,其中ai是矩陣。根據乘法結合律,不改變矩陣的相互順序,只用括號表示成對的乘積,試問一共有幾種括號化方案?

分析:n個矩陣相乘需要n-1對括號,且任何前x個元素中”(”的個數一定大於”)”的個數,所以應該為f(2(n-1))

3、街區對角問題

某個城市的某個居民,每天他須要走過2n個街區去上班(他在其住所以北n個街區和以東n個街區處工作)。如果他不跨越(但可以碰到)從家到辦公室的對角線,那麼有多少條可能的道路?
這裡寫圖片描述

分析:其實也與括號問題相似,這個人一共要走2n步,其中向右與向上分別n步,那麼,假設他第一步是向右的,那麼之後的任何時刻,他向右的步數都不能少於想上的步數,即f(2n),當然,他也可以第一步時先向上,同樣有f(2n)種走法,兩者應該是以對角線對稱的,所以答案為2*f(2n)。

4、圓上點對互連問題

在圓上選擇2n個點,將這些點成對連線起來,且所得n條線段不相交,求可行的方法數。

分析:乍一看不能像上面三道題那樣直接套公式。那麼,先進行一下分析,將圓上的點依次標為P0,P1,…P2n-1。為了避免混淆,使用F(2n)表示2n個點可連成的線段數,選擇Pk與P0相連(0< k< n)同樣地可以看出,k必為奇數,否則1至k-1之間有奇數個點,不可能成對連成直線。同樣地把k設為2i+1,那麼線段P0Pk把剩餘的點分為了1…2i和2i+2…2n-1,且新的連線不能與0k相交,它們只能屬於0k把園劃分出的這兩個區域之一。即F(2n) = ∑F(2i)*F(2n-1-(2i+2)+1) = ∑F(2i)*F(2n-2i-2),其中i = 0 … n-1。這時,又轉化成熟悉的形式了。

小結

以上的應用中用到的公式都是f(2n),也可以發現他們有這如下幾個特點:

  1. 元素都是成對出現的,比如左右括號,出棧與入棧,向右走與向上走,而且都是n對,所以f(2n),即表示n對2n個。
  2. 都需要滿足一個特點,即任意前x個元素中,某個元素的個數不小於與之對應的元素個數,比如左括號不少於右括號,入棧不少於出棧等。
  3. 通常滿足以上兩個特點,就可以使用f(2n)這個公式,但是並不是所有問題都是類似的,下面就介紹幾個使用f(n)的例子。

5、n個節點構造二叉樹

n個結點可構造多少個不同的二叉樹。(結點之間沒有區別)

分析:這次並沒有成對出現的元素了,n可以為奇數也可以為偶數,可以這樣考慮:n個節點中肯定有一個根節點,剩下的n-1個節點可以這樣分配T(0, n-1),T(1, n-2),…T(n-1, 0),其中T(i, j)中i,j表示左右子樹的個數。
於是可以寫出公式:f(n) = f(0)*f(n-1) + f(1)*f(n-2) + …….+ f(n-2)*f(1) + f(n-1)*f(0)

6、多邊形劃分問題

將多邊形劃分為三角形問題。求一個凸多邊形區域劃分成三角形區域的方法數。這裡寫圖片描述

分析:以凸多邊形的一邊為基,設這條邊的2個頂點為A和B。從剩餘頂點中選1個,可以將凸多邊形分成三個部分,中間是一個三角形,左右兩邊分別是兩個凸多邊形,然後求解左右兩個凸多邊形。
設n邊形的解為h(n),那麼h(n) = h(2)*h(n-1) + h(3)*h(n-2) + ……h(n-2)*h(3) + h(n-1)*h(2)。h(2)*h(n-1)表示三個相鄰的頂點構成一個三角形,那麼另外兩個部分的頂點數分別為2和n-1。
設h(2) = 1,那麼h(3) = 1, h(4) = 2, h(5) = 5。結合遞推式,不難發現h(n) 等於f(n-2)。就是上面介紹的公式。

小結

可以看出,這兩道題和上面的稍微有些區別,用的公式是f(n)

  1. 這裡的n可以為奇數也可以為偶數
  2. 元素並不是成對出現,也沒有上面說的某個元素個數必須不少於與之對應的元素個數
  3. 他的特點在於把一個大問題劃分為兩個性質相同的規模更小的問題,然後一直遞迴下去,直到f(0)=1

7、填數問題/照相排隊問題(阿里、騰訊筆試題)

還在網上找到一道筆試題

在一個2*n的格子中填入1到2n這些數值使得每個格子內的數值都比其右邊和下邊的所有數值都小的情況數;

12個高矮不同的人,排成兩排,每排必須是從矮到高排列,而且第二排比對應的第一排的人高,問排列方式有多少種?

分析:這2n個數選擇n個在第一行,剩下的n個在第二行,並且每行都是從小到大排列。比如00001111就對應了1、2、3、4在第一行,5、6、7、8在第二行。為了使第一行比第二行對應位置小,換句話說,要保證序列合法,那麼任何從第一個元素的開始的任意子序列中0的個數要大於等於1的個數。這就轉化成了f(2n)。

四、總結

  1. 學習了卡特蘭數,瞭解了這類問題的基本思路。
  2. 這類問題的應用大致分為上面介紹的兩類,需要根據具體問題來分析使用f(2n)還是f(n)這兩個公式,但其實這兩個公式本質上是一個。
  3. 有寫問題隱藏的比較深,比如兩排人照相問題,要善於從具體的問題抽象為具體的數學模型。