题目详情:
我们可以用如下如下方法给二叉树编号:
(1) 空树编号为0
(2) 只有一个结点的树编号为1
(3) 对任意非负整数m,包含有m个结点的二叉树编号笔包含有(m + 1)个结点的二叉树编号小
(4) 对一个包含有m个结点的二叉树,假设它左子树编号是L,右子树编号是R,它的编号是n,当且仅当,所有编号大于n并且包含m个结点的二叉树,满足以下如下条件:
(a) 其左子树编号大于L
或者
(b) 其左子树编号等于L,并且右子树编号> R
下图是编号为0-9的二叉树,以及编号为20的二叉树。
现给定编号n(1<=n <=500000000),求编号为n的二叉树。
二叉树的左孩子和右孩子递归表示。
即 单个结点用X表示,
如果二叉树只有左孩子L,则要表示成(L)X
如果二叉树只有右孩子R,则要表示成X(R)
否则,左右子树都要表示,即表示成(L)X(R)。
例如编号为20的树表示成:
((X)X(X))X
----------------------------------------------------------------------------------------------------------------------------------------
1.我的解法
这道题一看就是递归求解:根据编号n得到左右子树的编号leftN和rightN,然后通过f(n)=f(leftN)+'X'+f(rightN)求解,所以重点是求左右子树的编号leftN和rightN。
下面是我求leftN和rightN的方法,借助两个数组(我用C++的vector实现):nums[],sumNums[]
nums[]:下标索引i表示结点个数,元素值是结点个数i对应的二叉树个数,如0个结点的二叉树有1个,1个结点的二叉树有1个,2个结点的二叉树有2个,3个结点的二叉树有5个,那么nums[0]=1,nums[1]=1,nums[2]=2,nums[3]=5
sumNums[]:下标索引i表示结点个数,元素值是结点个数小于等于索引i二叉树个数和,则sumNums[0]=1,sumNums[1]=2,sumNums[2]=4,由于二叉树的编号与结点个数正相关,所以sumNums[i]也表示结点个数为i+1的二叉树的最小编号,通过这个数组我们可以判断编号为n的二叉树的结点个数nodes。
这两个数组需要提前打表,防止重复计算,求法:
从结点个数i=3开始,直到sumNums[i]>=500000000
nums[i]=sum(num[k]*num[i-1-k]) ,k=0->i-1;
sumNums[i]=sumNums[i-1]+nums[i];
其实num[]就是卡塔兰数列(catalan),见百度百科,有了这两个数组,计算leftN和rightN就比较容易了:
通过sumNums[]求得编号为n的二叉树的结点个数为nodes,然后求得编号为n的二叉树为结点个数为nodes的二叉树中的第left=n-sumNums[i-1]+1个,然后通过下面的方法求左右子树编号:
int leftNodesNum=0;//左子树结点个数
int rightNodesNum=nodes-1;//右子树结点个数
int leftN=0;//左子树编号
int rightN=0;//右子树编号
//结点个数为nodes的二叉树,随着左子树结点个数增多,它的编号越大,那么通过递增左子树
//的结点个数,递减右子树结点个数,求左右子树的编号
while(true){
//计算左右子树结点个数为当前值时能表示的二叉树个数
int tmp=nums[leftNodesNum]*nums[rightNodesNum];
if(tmp>=left){
int tmpR=left%nums[rightNodesNum];
if(leftNodesNum>=1){
leftN=sumNums[leftNodesNum-1];//左子树起始编号
//右子树结点个数代表的二叉树个数,表示
//左子树编号每增加1,能表示的二叉树个数
//所以左子树的编号计算如下
leftN+=left/nums[rightNodesNum]-1;
if(tmpR)
++leftN;
}
if(rightNodesNum>=1){
rightN+=sumNums[rightNodesNum-1];//右子树起始编号
if(0==tmpR)//需要注意
tmpR=nums[rightNodesNum];
rightN+=tmpR-1;
}
break;
}//if
left-=tmp;
++leftNodesNum;
--rightNodesNum;
}//while
2.需要注意的地方
(1)编号从0开始
(2)需要将求得的某个编号对应的二叉树保存下来,避免重复计算,下面的代码中通过map实现。
3.完整C++源代码
#include <stdio.h>
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <map>
using namespace std;
const int MAX=500000000;
class Test {
public:
static std::map<int,std::string>bTrees;
static string binarytree (int n){
if(0==n)return "";
if(1==n)return "X";
if(2==n)return "X(X)";
if(3==n)return "(X)X";
std::map<int,std::string>::iterator mit=bTrees.find(n);
if(mit!=bTrees.end())
return mit->second;
static std::vector<int> nums;
static std::vector<int> sumNums;//也代表了结点个数为索引的二叉树的起始编号
//第一次调用函数,打表
static bool firstTime=true;
if(firstTime){
firstTime=false;
nums.push_back(1);
nums.push_back(1);
nums.push_back(2);
sumNums.push_back(1);
sumNums.push_back(2);
sumNums.push_back(4);
int index=2;
while(sumNums[index]<=MAX){
int childNode=index;
nums.push_back(0);
++index;
int end=index/2-1;
for(int i=0;i<=end;++i)
nums[index]+=nums[i]*nums[childNode-i];
nums[index]+=nums[index];//*=2;
if(0==childNode%2)
nums[index]+=nums[end+1]*nums[end+1];
sumNums.push_back(nums[index]+sumNums[index-1]);
}//while
}
//编号n对应的二叉树有nodes个结点,这里使用线性查找
int nodes=0;
for(;nodes<nums.size();++nodes)
if(n<=sumNums[nodes]-1)
break;
int left=n-sumNums[nodes-1]+1;//剩余数量
int leftNodesNum=0;
int rightNodesNum=nodes-1;
int leftN=0;
int rightN=0;
//求左右子树的编号
while(true){
int tmp=nums[leftNodesNum]*nums[rightNodesNum];
if(tmp>=left){
int tmpR=left%nums[rightNodesNum];
if(leftNodesNum>=1){
leftN=left/nums[rightNodesNum];
if(tmpR)
++leftN;
leftN+=sumNums[leftNodesNum-1]-1;
}
if(rightNodesNum>=1){
rightN+=sumNums[rightNodesNum-1]-1;
if(0==tmpR)//需要注意
tmpR=nums[rightNodesNum];
rightN+=tmpR;
}
break;
}//if
left-=tmp;
++leftNodesNum;
--rightNodesNum;
}//while
//返回结果
std::string resL=binarytree(leftN);
std::string resR=binarytree(rightN);
std::string res;
if(resL!="")
res+="("+resL+")";
res+="X";
if(resR!="")
res+="("+resR+")";
//保存中间结果,避免重复计算
bTrees.insert(std::make_pair<int,std::string>(n,res));
return res;
}
};
std::map<int,std::string>Test::bTrees;
//start 提示:自动阅卷起始唯一标识,请勿删除或增加。
int main(){
clock_t t1=clock();
//cout<<Test::binarytree(2)<<endl;
cout<<Test::binarytree(5)<<endl;
cout<<Test::binarytree(20)<<endl;
cout<<Test::binarytree(2000000)<<endl;
clock_t t2=clock();
std::cout<<"time="<<(t2-t1)/double(CLOCKS_PER_SEC)<<std::endl;
}
//end //提示:自动阅卷结束唯一标识,请勿删除或增加。
4.时间复杂度
设编号为n的二叉树有nodes个结点,代码中求左右子树的编号的时间复杂度为O(nodes),递归深度最大为nodes,所以最差情况下的时间复杂度为O(nodes^2)。
编号500000000内的二叉树的节点个数最大为18。