您的位置:首页 > 编程语言 > Go语言

英雄会(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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: