ZJU1062: Trees Made to Order
我们以这样的规则给二叉树编号
结点数为0的树编号为0
只有一个结点的树编号为1
当有两个结点时,第一个结点为根。第二个结点先放在右边,就下图的(2),然后放在左边,就是下图的(3)当有三个结点时也是这样,先右再左。大家自己看图吧!
输入树的编号N(1 <= n <= 500,000,000)输出树的结构
输入:若干行整数N,以N=0的空数据结束
输出:按示例(L)X(R)的中序遍历格式输出每一个树,每个一行
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)
题目分析:
要想构造出一棵编号为N的二*树,我们只需要知道左子树的编号和右子树的编号,就可以进行递归构造了。我们目前所能得到的额外的有用信息,就是具有n个结点的不同形态二*树的总数目Bn = C(2n, n) / (n + 1),但是这个公式的计算较为复杂。我们采用下面的方法来计算:
while(1)//f[t]是结点树目为 t 时二叉树的数目
{
t++;
for(i=0;i<=t-1;i++)
f[t]+=f[i]*f[t-1-i];//f[i]为右子树的个数 f[t-1-i]为左子树的个树
s+=f[i];
if(s>500000000)
break;
}
用递推的方法来计算,n个结点的二叉树目=右子树的个数*左子树的个树,编程起来较为容易实现。
我们考虑采用逼近的思想。首先,计算出编号为n的二*树有几个结点:将n依次减去f[1],f[2],...,f[i],直到不能减为止,即此时n<f[i],设则此二*树共有t个结点,除去根结点,则左右子树共有t-1个结点。然后,计算左右子树各有几个结点:将n依次减去f[i]*f[t-1-i],i=t-1,t-2,...0,直到不能减为止,则左子树有t-1-i个结点,右子树有i 个结点。最后,计算左子树是具有t-1-i个结点的二*树中的第x棵,右子树是具有i个结点的二*树中的第y棵:将n整除f[i], x=m/f[i];
if(m%f[i]!=0) x++;。
y=(m-1)%f[i]+1;
(上面的公式只要对照例图看就可以得出,不过我还是想了很长的时间(注意是先排右子树,再排左子树的))
因此,我们求可得左子树的编号和右子树的编号,递归求解即可。f[n]可以在程序开始前用递推的方法高效地预先计算好,以后直接调用即可。
这道题用的逼近的思想,是一种非常重要的思想。对于一些全局统计的问题,如果可以进行局部统计,则可以利用局部统计的结果,逐步逼近全局统计,在逼近的过程中就可以计算出一些中间变量,利用它们便可以解决问题。
例程:
#include<iostream>
#include<string>
using namespace std;
long f[30];
long n;
void init()//递推计算结点为T时二叉树的数目
{
memset(f,0,sizeof(f));
int i,t;
long s;
s=t=0;
f[0]=1;
while(1)//结点为T时二叉树的数目
{
t++;
for(i=0;i<=t-1;i++)
f[t]+=f[i]*f[t-1-i];
s+=f[i];
if(s>500000000)
break;
}
}
void build(int t,long m)
{
int i;
long k;
i=t-1;
//i为右子树的结点个数,t-1-i为左子树的个数
while(m>f[i]*f[t-1-i])//f[i]为右子树的个数 f[t-1-i]为左子树的个树
{
m=m-f[i]*f[t-1-i];
i--;
}
if(t-1-i>0) //有左子树
{
cout<<"(";
k=m/f[i];//计算左子树是具有t-1-i个结点的二*树中的第k棵
if(m%f[i]!=0) k++;
build(t-1-i,k);
cout<<")";
}
cout<<"X";
if(i>0)
{
cout<<"("; //右子树是具有i个结点的二*树中的第k棵
k=(m-1)%f[i]+1;
build(i,k);
cout<<")";
}
}
int main()
{
int i;
init();
while(cin>>n&&n!=0)
{
i=1;
while(n>f[i])//计算出编号为n的二*树有几个结点
{
n=n-f[i];
i++;
}
build(i,n);
cout<<endl;
}
return 0;
}