Trees Made to Order——Catalan数和递归
Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 7155 | Accepted: 4094 |
Description
The empty tree is numbered 0.
The single-node tree is numbered 1.
All binary trees having m nodes have numbers less than all those having m+1 nodes.
Any binary tree having m nodes with left and right subtrees L and R is numbered n such that all trees having m nodes numbered > n have either Left subtrees numbered higher than L, or A left subtree = L and a right subtree numbered higher than R.
The first 10 binary trees and tree number 20 in this sequence are shown below:
Your job for this problem is to output a binary tree when given its order number.
Input
Output
A tree with no children should be output as X.
A tree with left and right subtrees L and R should be output as (L’)X(R’), where L’ and R’ are the representations of L and R.
If L is empty, just output X(R’).
If R is empty, just output (L’)X.
Sample Input
1 20 31117532 0
Sample Output
X ((X)X(X))X (X(X(((X(X))X(X))X(X))))X(((X((X)X((X)X)))X)X)
题目大意:根据下面的规则给一棵二叉树编号:
规则1:如果二叉树为空,则编号为0;
规则2:如果二叉树只有一个节点,则编号为1;
规则3:所有含有m个节点的二叉树的编号小于所有含有m+1个节点的二叉树的编号;
规则4:如果一棵含有m个节点的二叉树(左子树为L,右子树为R)的编号为n,要想其它含有m个节点的二叉树的编号如果大于n,则需要满足两个条件中的任意一个:1、左子树的编号大于L的编号;2、左子树的编号等于L的编号,但是右子树的编号大于R的编号。
解题方法:卡特兰数列(Catalan)+递归
Catalan数列简介:令Catalan(0)=1,Catalan(1)=1,Catalan数列满足地推公式:
Catalan(n)=Catalan(0)*Catalan(n-1)+Catalan(1)*Catalan(n-2)+...+Catalan(n-1)*Catalan(0),其中n>=2;
catalan数在这道题的理解,简单的来说就是一个节点数为n的二叉树的形态的个数,即为划分的个数
详情移步:Catalan卡塔兰数
假设f(n)表示n个节点的二叉树的所有顺序,由于左子树和右子树的顺序是相互独立的,假设0<=i<=n:表示左子树有i个节点,则右子树有n-i-1个节点(要除去根节点),则含有n个节点的二叉树,当左子树含有i个节点时,二叉树的节点顺序树为:f(i)*f(n-i-1),i从0到n-1 ,然后累加就可以求出所有f(n). 这就是一点典型的Catalan数列问题。
解题思路:
split(NodeNum,order)是用于打印问题的解的函数,其中NodeNum为当前树的结点数,order为当前树在 节点数为NodeNum的所有树中 的序号(即位次)。
(1)首先,递归框架:
1 /* 2 递归划分结构框架 3 */ 4 void split(int NodeNum,int order) 5 {//NodeNum为当前子树结点数,order为当前子树在节点数为NodeNum时的序号 6 if(NodeNum == 1) //节点数为1 直接输出X 作为递归结束条件 7 {cout<<"X";return;} 8 else 9 { 10 if(LeftNum>0)//左子树不为空 11 { 12 cout<<"("; 13 split(,); 14 cout<<")"; 15 } 16 cout<<"X";//打印父节点X 17 if(RightNum>0) 18 { 19 cout<<"("; 20 split(,); 21 cout<<")"; 22 } 23 } 24 }
NodeNum=1为递归边界条件,表示这棵子树只有一个结点,打印该结点,并返回。
(2) 计算节点数为NodeNum,序号为order的树的左右子树有多少结点,即计算LeftNum和RightNum。
由题意可知,序号为1的左子树为空,右子树结点数为order,且为右斜树(所有的节点均在右子树上并且所有节点只有右孩子);左右子树变换形态,此时左子树为空,右子树的形态数为catalan[order];当右子树遍历完所有形态后,左子树节点数加一,右子树节点数减一,生成初始状态依然为右子树为右斜树,此时左子树有节点,也为右斜树(当然此时只有一个节点,即只有根节点);左右子树变化形态,左子树形态数1,右子树形态数catalan[order-1];左子树节点数+1,右子树节点数-1,依此类推。
当左子树有大于1个结点时,设为i个,左子树有catalan[i]种形态,左子树的每一种形态下,右子树节点数为NodeNum-i-1,形态数catalan[NodeNum-i-1]。
整个过程就像一个时钟计时一样,左子树是时针,右子树是分针,右子树全部变化完,左子树加1,但是与时钟不同的是:时钟是60进制的,二右子树是catalan[i]进制的,i会逐渐变为0。由此可以得出二叉树中左子树的节点数LeftNum: leftNum=min(i|catalan[0]*catalan[nodeNum-1]+catalan[1]*catalan[nodeNum-2]+...+catalan[i]*catalan[NodeNum-i-1]>=order),右子树的节点数RightNum=NodeNum-LeftNum-1。
假设 所要求的树为 含有LeftNum个结点的左子树,RightNum个结点的右子树的二叉树是NodeNum个节点的二叉树 的第NewOrder棵树,则NewOrder=order-sum,其中sum=catalan(0)*catalan(NodeNum-1)+catalan(1)*catalan(NodeNum-2)+...+catalan(LeftNum-1)*catalan(RightNum+1)。
设左子树的应该是节点数为LeftNum的树的第LeftOrder个形态,右子树则应该是是节点数为RightNum的树的RightOrder个形态。其中LeftOrder=(NewOrder-1)/catalan(RightNum)+1,RightOrder=(NewOrder-1)%catalan(RightNum)+1。
此处应注意模数
1 #include<iostream> 2 using namespace std; 3 long catalan[] = {1,1,2,5,14,42,132,429,1430,4862,16796,58786,208012, 4 742900,2674440,9694845,35357670,129644790,477638700}; 5 void split(int NodeNum,int order) 6 { 7 if(NodeNum == 1) {cout<<"X";return;} 8 else 9 { 10 11 int sum=0; 12 int i; 13 for(i=0;sum<order;i++) 14 { 15 sum += catalan[i]*catalan[NodeNum-i-1]; 16 } 17 int LeftNum = --i;//左子树的节点个数 18 int RightNum = NodeNum-LeftNum-1;//右子树节点个数 19 sum = sum - catalan[LeftNum]*catalan[RightNum]; 20 long NewOrder = order-sum; 21 if(LeftNum>0) 22 { 23 cout<<"("; 24 split(LeftNum,(NewOrder-1)/catalan[RightNum]+1); 25 cout<<")"; 26 } 27 cout<<"X"; 28 if(RightNum>0) 29 { 30 cout<<"("; 31 split(RightNum,(NewOrder-1)%catalan[RightNum]+1); 32 cout<<")"; 33 } 34 } 35 } 36 int main() 37 { 38 long n;//n是序号 39 40 while(cin>>n) 41 { 42 if(n == 0) return 0;//0为结束 43 else 44 { 45 //首先得判断有几个结点 46 int i,sum=0; 47 for(i=1;sum<n;i++)//由于i=0不做任何操作,所以,从1开始 48 { 49 sum+=catalan[i]; 50 } 51 i--;sum = sum-catalan[i]; 52 //然后进行递归调用 53 split(i,n-sum);//i个结点的第n-sum种情况 54 } 55 cout<<endl; 56 57 } 58 return 0; 59 }