卡特蘭數,程式實現,遞迴,迴圈,BST和出入棧順序的應用
卡特蘭數是組合數學中的一種數列,它的來歷和重要性可以自行百度,我主要說它的特徵和程式設計實現。
前幾項是1, 1, 2, 5, 14, 42, 132……,
如果令h(0)=h(1)=1,那麼h(n)=h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)*h(0) (n>=2)。
常用遞推公式h(n)=C(2n,n)/(n+1) =(2n)!/n!/(n+1)!,(n=0,1,2,...),還有很多變體。
利用程式來求解這些數值自然簡單。
比如利用第一個遞推式的遞迴版本程式碼
classSolution{
public:
int numTrees(intn
if(n== 0 || n == 1)return1;
int ret(0);
for(int i(0); i < n; ++i)ret += numTrees(i)*numTrees(n- 1 - i);
return ret;
}//numTrees
};
利用第一個遞推式的迭代版本程式碼,形式上是一致,省去重複計算,比遞迴的速度快。開闢了vector<int>G(n+ 1)這個陣列,儲存中間計算結果,本質上是一種動態規劃,屬於“以土地換和平”的策略。
class Solution {
public:
int numTrees(int n) {
vector<
G[0] = G[1] = 1;
for(int i(2); i <= n; ++i) {
int sum(0);
for(int j(0); j < i; ++j) sum += G[j] * G[i - 1 - j];
G[i]= sum;
}//for i
return G[n];
}//numTrees
};
利用最後那個公式直接求解。
class Solution {
public:
int numTrees(int n) {
if(n == 0 || n == 1)return 1;
long long ret(1);
for(int i(n + 1); i <= n + n; ++i) ret *= i, ret /= i - n;
ret /=n + 1;
return ret;
}//numTrees
};
以上都是直接求卡特蘭數列的某一項值。比較簡單。
進一步瞭解可知,該數列應用在很多實際問題中。比如棧的出入順序,BST的種類。
下面以搜尋二叉樹的建立為例給出程式碼,對於給定一個序列,建立BST,序列中元素的順序會影響樹的形狀。
比如{1,2},能建立起1為根,2為右孩子的BST。而{2,1}則建立起2為根,1為左孩子的BST。
該程式碼對應leetcode 95題,核心部分為generateTrees_recur函式,遞迴出口是left>right,返回空子樹(即NULL)。
For(i)迴圈中,先得到leftRoots和rightRoots,分別是左半部分序列得到的子樹和右半部分序列得到的子樹,後面for(lch,rch)迴圈來交叉剛才得到的那些子樹。
對於每一個交叉,產生一個以i為根的新的子樹。
#include<stdio.h>
#include<vector>
using std::vector;
struct TreeNode {
int val;
TreeNode*left = NULL;
TreeNode*right = NULL;
TreeNode(intval_ = -1) :val(val_) {}
~TreeNode() {
if (left) { delete left; left = NULL; }
if (right) { delete right; right = NULL; }
}//~Node
};
class Solution {
public:
vector<TreeNode*>generateTrees(int n) {
if (n == 0)return vector<TreeNode*>{};
returngenerateTrees_recur(1, n);
}//generateTrees
public:
int times = 0;
vector<TreeNode*>generateTrees_recur(int left, intright) {
if (left > right)return vector<TreeNode*>{NULL};
vector<TreeNode*>roots;
for (int i(left); i <= right; ++i) {
auto leftRoots = generateTrees_recur(left, i - 1);
auto rightRoots = generateTrees_recur(i + 1, right);
int sizeOfLeft = (int)leftRoots.size();
int sizeOfRight = (int)rightRoots.size();
for (int lch(0); lch < sizeOfLeft; ++lch) {
for (int rch(0); rch < sizeOfRight; ++rch) {
TreeNode*root = new TreeNode(i);
++times;
root->left = leftRoots[lch];
root->right = rightRoots[rch];
roots.push_back(root);
}//for rch
}//for lch
}//for i
return roots;
}//generateTrees_recur
void preOrder(TreeNode *root, vector<int>&order) {
if (root == NULL) {
order.push_back(-1);
return;
}//if
order.push_back(root->val);
preOrder(root->left, order);
preOrder(root->right, order);
return;
}//preOrder
void printVec(const vector<int> &vec) {
for (auto num : vec)printf("%d ", num); putchar(10);
return;
}//printVec
void destroyTree(TreeNode *root) {
if (root) {
delete root;
root = NULL;
}//if
return;
}//destroyTree
};
#define CRTDBG_MAP_ALLOC
#include<crtdbg.h>
int main() {
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
Solution sol;
int n(4);
auto trees = sol.generateTrees(n);
printf("times=%d\n", sol.times);
for (auto tree : trees) {
vector<int>order;
sol.preOrder(tree, order);
sol.destroyTree(tree);
sol.printVec(order);
}//for tree
return 0;
}//main
generateTrees_recur返回的樹的個數正好對應一個卡塔蘭數。
參考上面的程式碼,給出一個類似的。
讓遞迴函式generateTrees_recur返回vector<vector<int>>,
然後把對應的vector<int>,用createTree函式,先序建立tree。
得到vector<TreeNode*> trees。也完成了leetcode95題的功能。
我們再把思維在發散一下,vector<int>正好可以應用在別的問題裡,比如出棧的順序。因此這份程式碼也可以應用在求出棧順序的那個上面。
董豔超,第二版,20170224