一、问题描述
1.概述
二元搜索树是一种树形结构,树中每个节点最多具有两个分支并且带有一个权值。
对于某一节点x,任何可以从其左侧到达的节点y都具有性质:y的权值<x的权值;
任何可以从其右侧到达的节点z都具有性质:z的权值>x的权值。
现问给出总节点数n,总共能构造出多少种不同形状的二元搜索树?
2.题目链接
3.题目截图
如图1.1所示。
图1.1 题目截图
二、算法思路
下面将分别以n=1,2,3,4时的情况说明算法的思路。
1.当n=1时,以a表示仅有的节点
很明显,一个节点时只能构成唯一的一种以a为根节点的树形结构,如图2.1所示。
图2.1 当n=1时的树形结构
2.当n=2时,以(a,b)表示节点列表,并且设权值关系为:a<b
注意:n>1时的树形结构,就是以不同的节点作为根节点时的树形结构的总和。
1)a作为根节点
当a作为根节点时,由于a具有最小的权值,因此剩下的所有节点只可能在a的右侧分布。由于只剩下一个节点b,并且在前边已经算出了在只有一个节点时(n=1时)总共能构造出1种树形结构,因此在此情况下,只能构造出1种树形结构。如图2.2所示。
图2.2 n=2时当a作为根节点的树形结构
2)b作为根节点
由于b具有最大的权值,因此剩下的节点将在b的左侧分布。因为剩下一个节点a,所以此情况下也只能构造出1种树型结构。如图2.3所示。
图2.3 n=2时当b作为根节点的树形结构
3.当n=3时,以(a,b,c)表示节点列表,并且设权值关系为:a<b<c
1)a作为根节点
a是权值最小的节点,剩下的节点将在a的右侧分布。由于剩下两个节点:b、c,并且在上述已经说明了当有两个节点(n=2时)总共可以构造出2种树形结构,因此在这情况下总共有2种树形结构。如图2.4所示。
图2.4 n=3时当a作为根节点的树形结构
2)b作为根节点
b不是权值最值端(最大权值或最小权值)的节点,这意味着剩下的节点将在b的左右两侧分布(a分布在左侧,b分布在右侧)。如图2.5所示,因为左右两侧只有一个节点,并且一个节点时所具有的树形结构数目已知,所以这种情况下具有的树形结构总和为:1(左侧:n=1) x 1(右侧:n=1) = 1。
图2.5 n=3时当b作为根节点的树形结构
3)c作为根节点
c是权值最大的节点,剩下的节点将在c的左侧分布。由于剩下两个节点:a、b,因此在这情况下总共有2种树形结构。如图2.6所示。
图2.6 n=3时当c作为根节点的树形结构
4.当n=4时,以(a,b,c,d)表示节点列表,并且设权值关系为:a<b<c<d
1)a作为根节点
a为权值最小的节点,此情况下的树形结构将是剩下的三个节点具有的树形结构,如图2.7所示。三个节点的情况已经由上面求出。
图2.7 n=4时当a作为根节点的树形结构
2)b作为根节点
当b为根节点时,在剩下的节点中,a将分布在b的左侧;c、d将分布在b的右侧,如图2.8所示。
此时具有的树形结构 = 左侧具有的树形结构 x 右侧具有的树形结构:1(左侧n=1) x 2(右侧n=2) = 2。
图2.8 n=4时当b作为根节点的树形结构
3)c作为根节点
与b作为根节点类似,剩下的节点中,a、b分布在c的左侧,d分布在c的右侧,如图2.9所示。
图2.9 n=4时当c作为根节点的树形结构
4)d作为根节点
与a作为根节点类似,如图2.10所示。
图2.10 n=4时当d作为根节点的树形结构
5.对上面的归纳
经过n的四种情况的描述,可以发现问题具有两个特点。
1)对称性
当n是偶数时,如n=2时(节点序列为a、b,且a<b),树形结构数目(以a为根节点)=树形结构数目(以b为根节点)。
如n=4时(节点序列为a、b、c、d且a<b<c<d),树形结构数目(以a为根节点)=树形结构数目(以d为根节点);树形结构数目(以b为根节点)=树形结构数目(以c为根节点)。
这是因为,拿n=4的情况说明,当以a为根节点,由于其权值是最小的,将会有三个节点在其右侧自由组合,由于各节点权值不同,有最小的一定有最大的,在此d是最大的,当以d为根节点时,将会有三个节点在其左侧自由组合。
当以b为根节点时,由于b的权值是第二小的,意味着它的结果 = 左边一个比它小的节点的组合 x 右边两个比它大的节点的组合。
对应的c是第二大的,它的结果=左边的两个比它小的组合 x 右边的一个比它大的节点的组合。
当n是奇数时,首尾对应的节点也具有同样的规律,如n=3时(节点序列为a、b、c且a<b<c),树形结构数目(以a为根节点)=树形结构数目(以c为根节点)。中间节点b作为根节点时则需要单独计算,由于中间节点将剩余的节点数(n-1个节点)平均分配在其左右两侧,因此以中间节点为根节点时的树形结构数目 = 当n=(n-1)/2时的树形结构数目的平方。
2)大规模问题的解决依赖于已知的小规模问题
从n的四种情况可以看出,除了n=1,其他情况下的结果都要依赖于之前计算出的小规模结果,如n=4时,以a为根节点时,需要利用n=3的结果;如n=4时,以b为根节点,需要利用n=1和n=2的结果。
这种情况下用数组来保存已计算出的结果将会变得非常便利。
三、算法实现
当n很大时,结果将超过long long类型的范围,因此此题应用字符串表示结果的数字,在实现上,使用了一个类完成对字符串数字的加法和乘法。
#include <iostream>
using namespace std;
class NumStr
{
public:
string str;
NumStr()=default;
NumStr(string str):str(str){}
NumStr operator+(NumStr& oprand)
{
NumStr ret;
bool carry=0;
unsigned minLen=str.size()<=oprand.str.size()?str.size():oprand.str.size();
char tmp;
for(unsigned i=0;i<minLen;i++)
{
tmp=str[i]+oprand.str[i]+carry-'0';
if(tmp>'9')
{
carry=1;
tmp-=10;
}
else
{
carry=0;
}
ret.str+=tmp;
}
if(str.size()>minLen)
{
for(unsigned i=minLen;i<str.size();i++)
{
tmp=str[i]+carry;
if(tmp>'9')
{
carry=1;
tmp-=10;
}
else
{
carry=0;
}
ret.str+=tmp;
}
}
if(oprand.str.size()>minLen)
{
for(unsigned i=minLen;i<oprand.str.size();i++)
{
tmp=oprand.str[i]+carry;
if(tmp>'9')
{
carry=1;
tmp-=10;
}
else
{
carry=0;
}
ret.str+=tmp;
}
}
if(carry)
ret.str+='1';
return ret;
}
NumStr operator*(unsigned char oprand)
{
NumStr ret;
unsigned char carry=0;
unsigned char tmp;
for(unsigned i=0;i<str.size();i++)
{
tmp=(str[i]-'0')*oprand+carry;
ret.str+=(tmp%10+'0');
carry=tmp/10;
}
if(carry!=0)
ret.str+=(carry+'0');
return ret;
}
NumStr operator*(NumStr& oprand)
{
NumStr ret,tmpStr;
for(unsigned i=0;i<oprand.str.size();i++)
{
if(oprand.str[i]!='0')
{
tmpStr=*this*(oprand.str[i]-'0');
if(i>0)
tmpStr.str.insert(0,i,'0');
ret=ret+tmpStr;
}
}
return ret;
}
void print()
{
for(int i=str.size()-1;i>=0;i--)
cout<<str[i];
}
};
NumStr f[101];
void preDeal()
{
f[0]=f[1]=NumStr("1");
f[2]=NumStr("2");
f[3]=NumStr("5");
unsigned short limit;
for(unsigned i=4;i<=100;i++)
{
NumStr res,tmpStr;
limit=i/2;
for(unsigned j=0;j<limit;j++)
{
tmpStr=f[j]*f[i-1-j];
res=res+tmpStr;
}
f[i]=res*2;
if(i%2!=0)
{
tmpStr=f[(i-1)/2]*f[(i-1)/2];
f[i]=f[i]+tmpStr;
}
}
}
int main()
{
using namespace std;
preDeal();
unsigned in;
while(cin>>in)
{
f[in].print();
cout<<endl;
}
// NumStr a("51");
// NumStr b=a*2;
// a.print();
// cout<<endl;
// b.print();
// cout<<endl;
// b=b*a;
// b.print();
// cout<<endl;
return 0;
}