1. 程式人生 > >卡特蘭數,程式實現,遞迴,迴圈,BST和出入棧順序的應用

卡特蘭數,程式實現,遞迴,迴圈,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<

int> G(n + 1);

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)迴圈中,先得到leftRootsrightRoots,分別是左半部分序列得到的子樹和右半部分序列得到的子樹,後面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