英雄会(csdn pongo)题解之二叉树
2014-02-20 12:50
411 查看
题目详情:
我们可以用如下如下方法给二叉树编号:(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。
相关文章推荐
- pongo(csdn英雄会题解)之三元组的数量--英雄会第二届在线编程大赛·CSDN现场决赛
- 英雄会(csdn pongo)题解之半质数的个数--2·14情人&元宵节专题
- 参加CSDN英雄大会感
- 参加CSDN软件英雄有感
- pongo(英雄会)题解之最少操作次数的简易版
- 参加CSDN英雄会有感
- CSDN-英雄会挑战之----整数问题
- 半质数的个数 csdn 英雄会 高校俱乐部
- CSDN 2009 英雄大会 - 归途
- CSDN英雄大会之 SOA技术观感
- 参加CSDN英雄大会感
- 参加CSDN2007中国软件技术英雄会的乐知之旅
- 参加 CSDN 2009 英雄大会有感(一)
- 2007中国软件技术英雄会暨CSDN社区英雄榜颁奖典礼即将举行
- CSDN姜主编就第三届CSDN英雄大会(上海)答记者问
- 英雄会(csdn pongo)题解之罐子和硬币
- 英雄会(csdn pongo)题解之坐标和数字
- 英雄会(csdn pongo)题解之坐标和数字(Java版)
- 朋友的礼物(英雄会,csdn,高校俱乐部)信封问题,匹配模型
- 参加 CSDN 2009 英雄大会有感(一)