题目来源:Trees Made to Order
题目大意:根据下面的规则给一棵二叉树编号:
规则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;
假设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表示二叉树的节点数,含有nodeNum节点的二叉树的第order个排列(或者形态)。
(1)、通过给定的数N计算出二叉树的节点数nodeNum。其中nodeNum=min(i|Catalan(0)+Catalan(1)+...+Catalan(i)>=N),设编号为N的二叉树是包含nodeNum个节点的二叉树的所有集合排序中的第order棵,则order=N-(Catalan(0)+Catalan(1)+...+Catalan(i-1))。
(2)、接下来是来解决这棵二叉树中左子树有多少个节点,右子树有多少节点。有题意可知,初始状态下,左子树为空,所有的节点均在右子树上并且所有节点只有右孩子;当右子树的所有节点变化完以后,将右子树中的一个节点分配给左子树,此时右子树的节点少了一个并且所有右子树的节点全部是初始化状态,依此类推。当左子树有大于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。
(3)、假设含有leftNum个左子树,rightNum个右子树的二叉树是nodeNum个节点的二叉树的第newOrder中排列(或者形态),则newOrder=order-(sum-Catalan(leftNum)*Catalan(rightNum)),其中sum=Catalan(0)*Catalan(nodeNum-1)+Catalan(1)*Catalan(nodeNum-2)+...+Catalan(leftNum)*Catalan(rightNum)。
(4)、设左子树的应该是第leftOrder的排列(或者形态),右子树则应该是的rightOrder的排列(或者形态)。其中leftOrder=(newOrder-1)/(Catalan(rightNum)+1),rightOrder=(newOrder-1)%(Catalan(rightNum)+1)。如果这个地方不太理解,请参考(2)中所提到的时钟,(Catalan(rightNum)+1)相当于60进制。
通过上面的分析,我相信大家应该能够很容易理解了吧,我当时想了大半天都没想明白,最后参考各种网上的资料才看懂,这里面包含了我所有的理解过程。不希望和我一样的菜鸟也会走我走过的路,所以与大家分享一下!如果还有什么地方不明白的,可以留言。
Java版代码(可以直接AC):
import java.util.Scanner; public class Main {
static int[] catalan = {1,1,2,5,14,42,132,429,1430,4862,16796,58786,208012,
742900,2674440,9694845,35357670,129644790,477638700 }; static void split(int nodeNum, int order) {
if (nodeNum == 1) { //如果只有一个节点,直接输出,也是递归结束的条件
System.out.print('X');
return;
}
int i, sum = 0;
for (i = 0; sum < order; i++) {
sum += catalan[i] * catalan[nodeNum - i - 1];
}
i--;
int leftNum=i; //左子树的节点个数(由于为了与描述一致,所以代码写的有些累赘,不够精炼)
int rightNum=nodeNum-leftNum-1; //右子树的节点个数
int newOrder = order - (sum - catalan[leftNum] * catalan[rightNum]);
if (leftNum > 0) {
System.out.print('(');
split(leftNum, (newOrder - 1) / catalan[rightNum] + 1);
System.out.print(')');
}
System.out.print('X');
if (rightNum > 0) {
System.out.print('(');
split(rightNum, (newOrder - 1) % catalan[rightNum]+ 1);
System.out.print(')');
}
} public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
while (n != 0) {
int i, sum = 0;
for (i = 1; sum < n; i++) { //由于n=0是不做任何操作,所以i从1开始
sum += catalan[i];
}
i--;
split(i, n - (sum - catalan[i]));
n = input.nextInt();
System.out.println();
}
}
}