思考:n个节点能构成多少种不同形态的二叉树?
n个节点的二叉树,左子树i个节点,右字数n-i-1个节点
譬如:5个节点的二叉树 可以分成以下几大类
(1)左子树0个节点,右子树4个节点
(2)左子树1个节点,右子树3个节点
(3)左子树2个节点,右子树2个节点
(4)左子树3个节点,右子树4个节点
(5)左子树4个节点,右子树0个节点
设f(i) 表示i个节点构成二叉树的形态个数,那么5个节点的二叉树每类的个数为:
(1)左子树0个节点,右子树4个节点 的二叉树共f(0)*f(4)个;
(2)左子树1个节点,右子树3个节点 的二叉树共f(1)*f(3)个;
(3)左子树2个节点,右子树2个节点 的二叉树共f(2)*f(2)个;
(4)左子树3个节点,右子树1个节点 的二叉树共f(3)*f(1)个;
(5)左子树4个节点,右子树0个节点 的二叉树共f(4)*f(0)个;
总数就是
f(5)=f(0)*f(4)+f(1)*f(3)+f(2)*f(2)+f(3)*f(1)+f(4)*f(0)
f(0)=1
f(1)=1
f(n)=∑f(i)f(n-1-i) (0<=i<=n-1)
任务1:请同学们用在纸上推算f(2),f(3),f(4)
也可以用解析公式计算
f(n) = C(2n,n)/(n+1)
任务2:请同学们用解析公式计算f(2),f(3),f(4)
任务3:请同学们编程序完成 Jzoj
7、二叉树的性质
性质1:二叉树的第i层上至多有2i-1个结点
性质2:深度为k的二叉树最多有2k - 1个结点
性质3:如果度为0的结点数为n0,度为2的结点数为n2 ,那么 n0 = n2 +1
证明:
总结点数 n = n0 + n1 + n2 ①
孩子结点数 n -1 = n1 + 2*n2 ②
(提示:只有根结点不是别人家的孩子)
联立①式和②式,得 n0 = n2 + 1
性质4: n个结点的完全二叉树的深度为 ⌊log2n⌋+1
性质5:对 n 个结点的完全二叉树从上到下从左到右进行 1 到n 编号,则对任意编号为 i 的结点有如下性质:
(1)若 i = 1,则该结点是二叉树的根, 否则其父结点编号为 i/2 ;
(2)若 2*i >n,则该结点无左孩子, 否则其左孩子编号为 2*i ;
(3)若 2*i+1>n,则该结点无右孩子,否则其右孩子编号为2*i+1 。
非完全二叉树采用顺序存储需先补为完全二叉树
在某些特殊情况下,容易浪费存储空间。
不记录父结点
struct Node{
ElementType data;
int lchild;
int rchild;}
T[MAXN];
记录双亲(父)结点
struct Node{
ElementType data;
int lchild;
int rchild;
int father;
}T[MAXN];
输入样例:
8
A 2 3
B 4 5
C 6 -1
D -1 -1
E -1 -1
F 7 8
G -1 -1
H -1 -1
const int MAXN = 100 + 10;
struct Node{
char data;
int lchild, rchild;
}T[MAXN];
int n;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> T[i].data >> T[i].lchild >> T[i].rchild;
}
(1)先访问根结点
(2)递归访问左子树
(3)递归访问右子树
void preorder(int root){
if(root == -1) return;
cout << T[root].data;
preorder(T[root].lchild);
preorder(T[root].rchild);
}
(1)递归访问左子树
(2)访问根结点
(3)递归访问右子树
void inorder(int root){
if(root == -1) return;
inorder(T[root].lchild);
cout << T[root].data;
inorder(T[root].rchild);
}
(1)递归访问左子树
(2)递归访问右子树
(3)访问根结点
void postorder(int root){
if(root == -1) return;
postorder(T[root].lchild);
postorder(T[root].rchild);
cout << T[root].data;
}
描述:我们可以把由0和1组成的字符串分为三类:
(1)全0串称为B串
(2)全1串称为I串
(3) 既含0又含1的串则称为F串。
FBI树是一棵二叉树,它的结点类型包括F结点,B结点和I结点三种。
由一个长度为2^N(0<=N<=10)的01串S可以构造出一棵FBI树T,递归的构造方法如下:(1)T的根结点为R,其类型与串S的类型相同;(2)若串S的长度大于1,将串S从中间分开,分为等长的左右子串S1和S2;由左子串S1构造R的左子树T1,由右子串S2构造R的右子树T2。
请用上述构造方法构造一棵FBI树,并输出它的后序遍历序列。
输入样例:
3
10001011
输出样例:
IBFBBBFIBFIIIFF
分析:
方法1:直接递归处理
#include
using namespace std;
string gen;
int n;
string code(int l,int r){
if(l==r){
if(gen[l]=='0') return "B";
else return "I";
}
int mid=(l+r)/2;
string x1=code(l,mid);
string x2=code(mid+1,r);
int l2=x2.size();
if(x1==x2) //左子树的编码和右子树的编码相同,那么根的编码和子树的根编码相同。
return x1+x2+x2[l2-1];// 后序遍历的编码等于左子树编码连接右子树编码再连接根的编码 (也可以写成 x1+x2+x1[l2-1];)
else //左子树的编码和右子树的编码不同,那么根的编码一定是“F”。
return x1+x2+"F";
}
int main(){
cin>>n>>gen;
cout<
方法2:
#include
#include
using namespace std;
int n,sum=1;
bool a[2000];
char cnt[1025][1025];
int dfs(int l,int r)
{
if (l==r)
{
if (a[l]==0) cnt[l][r]='B'; else cnt[l][r]='I';
cout<>1;
dfs(l,mid);
dfs(mid+1,r);
if (cnt[l][mid]==cnt[mid+1][r] )
cnt[l][r]=cnt[l][mid];
else cnt[l][r]='F';
cout<>n;
for (int i=1;i<=n;i++) sum*=2;
for (int i=1;i<=sum;i++)
scanf("%1d",&a[i]);
dfs(1,sum);
// for (int i=1;i<=t;i++) cout<
给出一棵二叉树的中序与后序序列,输出其先序序列。(约定树结点用不同的大写字母表示,长度≤8)。
先序遍历 ABCD
中序遍历 BADC
后序遍历 BDCA
已知其中一个遍历能否确定一颗树?
已知其中两个遍历能否确定一棵树?
字符串处理常用函数
s=ss.substr(1,3); // 提取函数,提取ss字符串从1位置起的3个字符给s字符串
s=ss.substr(3); // 提取函数,提取ss字符串从3位置起的所有内容给s字符串
s=ss.substr(); // 提取函数,提取ss字符串的所有内容给s字符串
len=ss.size(); // 返回字符串字符个数
len=ss.length(); // 返回字符串字符个数
pos=ss.find(c,1); // 从前面1位置开始查找,查找c字符串最早出现在ss字符串的位置,若没找到,则返回string::npos
pos=ss.find(c); // 从前面0位置开始查找,查找c字符串最早出现在ss字符串的位置,若没找到,则返回string::npos
#include
using namespace std;
void solve(string inorder,string postorder){
int len=postorder.size();
if(len>0){
char ch=postorder[len-1]; //根节点
cout<>inorder>>postorder;
solve(inorder,postorder);
return 0;
}
描述:一棵有点权的有根树如果满足以下两个条件,则称为对称(或镜像)二叉树。
(1)二叉树;
(2)将这棵树所有结点的左右子树交换,新树和原树对应位置的结构相同且点权相等。
现在给出一棵二叉树,希望找出它的一棵子树,该子树为对称二叉树,且结点数最多。请输出这棵子树的结点数。
约定:
(1)规定节点编号 1~n,其中节点1 是树根。
(2)只有根结点的树也是对称二叉树。
(3) 以结点 T 为根的一棵“子树”指的是:结点 T 和它的全部后代结点构成的二叉树。
第一行一个正整数 n,表示给定的树的节点数目
第二行 n 个正整数,用一个空格分隔,第 i 个正整数 vi 代表节点 i 的权值。
接下来 n 行,每行两个正整数 li , ri ,分别表示节点 i 的左右孩子的编号。如果不存在左 / 右孩子,则以 −1 表示。两个数之间用一个空格隔开。
分析
枚举每一个结点作为根结点进行dfs,判断以其为根的子树是否对称(结构对称 + 权值对称)
# include
using namespace std;
/*
//n
//权值
// 后面有n行,第i行表示ID号为i的左右孩子编号 -1表示没孩子
输入
10
2 2 5 5 5 5 4 4 2 3
9 10
-1 -1
-1 -1
-1 -1
-1 -1
-1 2
3 4
5 6
-1 -1
7 8
输出
7 3
11
2 2 5 5 5 5 4 4 2 3 2
9 10
-1 -1
11 -1
-1 -1
-1 -1
-1 2
3 4
5 6
-1 -1
7 8
-1 -1
输出
10 9
*/
# include
using namespace std;
const int maxN=1e6+10;
int v[maxN],c[maxN],ans;
struct node{
int l,r;
int data;
}T[maxN];
bool same(int a, int b) //比较以a,b为根两个子树是否对称(a为左子树,b为右子树)
{
if(a == b) return 1;
if(a==-1 || b==-1) return 0;
if (T[a].data!=T[b].data) return 0;
if (!(same(T[a].l, T[b].r))) return 0;
if (!(same(T[a].r, T[b].l))) return 0;
return 1;
}
void dfs(int i) {
if(i==-1) return; //没有孩子
dfs(T[i].l); //遍历左子树
dfs(T[i].r); //遍历右子树
c[i] = 1 + c[T[i].l] + c[T[i].r];
if(ans < c[i] && same(T[i].l, T[i].r) ) //如果左右孩子相等 打擂台取最大
{ ans = c[i];}
}
int main(){
int n; cin>>n;
for(int i=1; i<=n; ++i) cin>>v[i];//读权值
for(int i = 1; i <= n; ++i) {
cin>>T[i].l;
cin>>T[i].r;
T[i].data=v[i];
}
dfs(1);
cout<
设一个n个节点的二叉树tree的中序遍历为(l,2,3,...,n),其中数字1,2,3,...,n为节点编号。每个节点都有一个分数(均为正整数),记第j个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:
subtree的左子树的加分×subtree的右子树的加分+subtree的根的分数
若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,...,n)且加分最高的二叉树tree。要求输出:
(1)tree的最高加分
(2)tree的前序遍历
输入
第1行:一个整数n(n<30),为节点个数。
第2行:n个用空格隔开的整数,为每个节点的分数(分数<100)。
输出
第1行:一个整数,为最高加分(结果不会超过4,000,000,000)。
第2行:n个用空格隔开的整数,为该树的前序遍历。
输入样例
5
5 7 1 2 10
输出样例
145
3 1 2 4 5
分析:
(1)如何计算一个节点的加分?请问下面这个二叉树加分是多少?f=subtree的左子树的加分×subtree的右子树的加分+subtree的根的分数
圈内数字是节点编号,圈外数字数字是分数
f[i] 表示 i为根的子树的加分
f[1]=5 f[3]=1 f[5]=10
f[2]=f[1]*f[3]+7=5*1+7=12
f[4]=f[2]*f[5]+2=12*10+2=122
圈内数字是节点编号,圈外数字数字是分数
f[2]=7 f[5]=10
f[1]=1*f[2]+5=12
f[4]=1*f[5]+2=1*10+2=12
f[3]=f[1]*f[4]+1=12*12+1=145
(2)只知道中序遍历能否确定这个树的形态?
1到n个节点中的任意一个节点都可以是根
设节点k是根,则左子树包含的节点是1到k-1,右子树包含的节点是k+1到n
树的形态有很多种,总共C(2n,n)/(n+1) 种
不同形态的树,加分不同,求那个最大的。
设f[i][j]表示i到j 这些节点构成的子树的最大加分
那么要求的值就是f[1][n]
f[1][n]=max{f[1][k-1]*f[k+1][n]+f[k][k]}(1<=k<=n)
f[i][j]=max{f[i][k-1]*f[k+1][j]+f[k][k]}(i<=k<=j)
f[i][i]=a[i]
f[i][i-1]=1
方法1:记忆化搜索
#include
#include
#include
using namespace std;
const int MAXN = 50;
typedef long long ll;
ll n;
ll f[MAXN][MAXN]; //记录加分,f[i][j]表示i到j 这些节点构成的子树的最大加分
ll root[MAXN][MAXN];//记录根,root[i][j] 表示i到j 这些节点构成的子树得最大加分最大加分时的根
void print(ll l, ll r) {
if (l > r)return;
printf("%lld ", root[l][r]);
if (l == r)return;
print(l, root[l][r] - 1);
print(root[l][r]+1,r);
}
ll dfs(int i,int j){
if(f[i][j]) return f[i][j]; //记忆化搜索
if(i>j) return f[i][j]=1; //空树加分为1
ll ans=0;
for(int k=i;k<=j;k++) {
ll temp=dfs(i,k-1)*dfs(k+1,j)+f[k][k];
if(ans
方法二:递推
将相邻的任意个节点构成的子树的最大加分算出来
第一阶段计算 相邻的1个节点构成的子树最大加分 (可以在初始化时完成)
分别是f[1][1],f[2][2],f[3][3],f[4][4],f[5][5],……,f[n][n]。
第二阶段计算 相邻的2个节点构成的子树最大加分
分别是f[1][2],f[2][3],f[3][4],f[4][5],f[5][6],……,f[n-1][n]。
第三阶段计算 相邻的3个节点构成的子树最大加分
分别是f[1][3],f[2][4],f[3][5],f[4][6],f[5][7],……,f[n-2][n]。
第四阶段计算 相邻的4个节点构成的子树最大加分
分别是f[1][4],f[2][5],f[3][6],f[4][7],f[5][8],……,f[n-3][n]。
……
第n-1阶段计算 相邻的2个节点构成的子树最大加分
分别是f[1][n-2],f[2][n]。
第n阶段计算 相邻的2个节点构成的子树最大加分
分别是f[1][n]。
#include
#include
#include
using namespace std;
const int MAXN = 50;
typedef long long ll;
ll n;
ll f[MAXN][MAXN]; //记录加分,f[i][j]表示i到j 这些节点构成的子树的最大加分
ll root[MAXN][MAXN];//记录根,root[i][j] 表示i到j 这些节点构成的子树得最大加分最大加分时的根
void print(ll l, ll r) {
if (l > r)return;
printf("%lld ", root[l][r]);
if (l == r)return;
print(l, root[l][r] - 1);
print(root[l][r]+1,r);
}
int main() {
scanf("%lld", &n);
for (int i = 1; i <= n; i++){
scanf("%lld", &f[i][i]);//i点单独构成子树(i为叶节点时)的加分就是节点本来的分数
f[i][i-1]=1; //空树加分为1
root[i][i] = i; // i点单独构成子树,根就是自己。
}
f[n+1][n]=1;
for (int len = 1; len < n; ++len) //穷举区域长度(相邻节点个数)
{
for (int i = 1; i + len <= n; ++i) //穷举区域的左边界
{
int j = i + len; //计算 区域的右边界
f[i][j] = f[i + 1][j] + f[i][i]; //预设i(左边第一个节点)是根,左子树为空
root[i][j] = i;
printf("计算f[%d][%d]=max{",i,j);
for (int k = i + 1; k <= j; k++) //穷举根k
{
printf("f[%d][%d]-%d-f[%d][%d],",i,k-1,k,k+1,j);
if (f[i][j] < f[i][k - 1] * f[k + 1][j] + f[k][k]) {
f[i][j] = f[i][k - 1] * f[k + 1][j] + f[k][k];
root[i][j] = k;
}
}
cout<<"}"<
5
5 7 1 2 10
计算f[1][2]=max{f[1][1]-2-f[3][2],}
计算f[2][3]=max{f[2][2]-3-f[4][3],}
计算f[3][4]=max{f[3][3]-4-f[5][4],}
计算f[4][5]=max{f[4][4]-5-f[6][5],}
计算f[1][3]=max{f[1][1]-2-f[3][3],f[1][2]-3-f[4][3],}
计算f[2][4]=max{f[2][2]-3-f[4][4],f[2][3]-4-f[5][4],}
计算f[3][5]=max{f[3][3]-4-f[5][5],f[3][4]-5-f[6][5],}
计算f[1][4]=max{f[1][1]-2-f[3][4],f[1][2]-3-f[4][4],f[1][3]-4-f[5][4],}
计算f[2][5]=max{f[2][2]-3-f[4][5],f[2][3]-4-f[5][5],f[2][4]-5-f[6][5],}
计算f[1][5]=max{f[1][1]-2-f[3][5],f[1][2]-3-f[4][5],f[1][3]-4-f[5][5],f[1][4]-5-f[6][5],}
145
3 1 2 4 5
--------------------------------
Process exited with return value 0
Press any key to continue . . .
儿子个数可能不止两个
这时候没有左右孩子的说法了。
常常使用邻接表方法存储,在静态数组T中i位置存储i的每个孩子编号。
由于每个节点的孩子数量不一定相同,所以这里一般不用二维数组存储。
用元素类型是动态数组的一维静态数组T存储。动态数组我们有称它为向量。
以下代码查看动态数组的常用方法。
#include
using namespace std;
int n; //定义一个整型变量n
int a[8]; //定义一个静态数组a,数组长度是8;a中元素类型为整型
vectorD; //定义一个动态数组D,数组长度不定;D中元素类型为整型
int main()
{
//动态数组元素的添加
D.push_back(4);
D.push_back(41);
D.push_back(2);
D.push_back(14);
//动态数组元素也是从零开始编号的
cout<
T数组的长度就是最大节点编号
/*将以下树边存入静态数组T,
第一行表示节点个数n(节点编号是从1到n)
下面n-1行的每一对数字表示一对父子关系。前一个是父,后一个是子。
8
1 2
1 3
1 4
2 7
3 5
3 6
3 8
*/
#include
using namespace std;
int n; //定义一个整型变量n
vectorT[8]; //定义一个静态数组T,数组长度是8;T中元素类型为动态数组类型,动态数组中的元素为整型
int main()
{
cin>>n;
for(int i=1;i>a>>b;
T[a].push_back(b);
}
//输出T数组中的内容看看
for(int i=1;i<=n;i++) {
cout<<"节点"<
先序遍历 1 2 7 3 5 6 8 4
/*
第一行表示节点个数n(节点编号是从1到n)
下面n-1行的每一对数字表示一对父子关系。前一个是父,后一个是子。
8
1 2
1 3
1 4
2 7
3 5
3 6
3 8
*/
#include
using namespace std;
int n; //定义一个整型变量n
vectorT[8]; //定义一个静态数组T,数组长度是8;T中元素类型为动态数组类型,动态数组中的元素为整型
void dfs(int p){
cout<>n;
for(int i=1;i>a>>b;
T[a].push_back(b);
}
dfs(1);
return 0;
}
1 2 7 3 5 6 8 4
--------------------------------
Process exited with return value 0
Press any key to continue . . .
这里需要一个队列Q:队列用来维护一个先进先出的线性表
queue
queueQ; //定义个队列Q,元素类型为int
Q.push(p);//将p加入队尾
f=Q.empty()//判断队列Q是否空,空则返回true,不空则返回false
h=Q.front();//取Q队头元素
Q.pop();//弹(删)队头元素
/*
第一行表示节点个数n(节点编号是从1到n)
下面n-1行的每一对数字表示一对父子关系。前一个是父,后一个是子。
8
1 2
1 3
1 4
2 7
3 5
3 6
3 8
*/
#include
using namespace std;
int n; //定义一个整型变量n
vectorT[8]; //定义一个静态数组T,数组长度是8;T中元素类型为动态数组类型,动态数组中的元素为整型
void bfs(int p){
queueQ;
Q.push(p);//起点入队
while (!Q.empty()){
int h=Q.front();//取队头
Q.pop();//弹(删)队头
cout<>n;
for(int i=1;i>a>>b;
T[a].push_back(b);
}
bfs(1);
return 0;
// 1 2 3 4 7 5 6 8
}
求一棵树的深度,根节点为1,根节点深度为1
a)bfs方法
队列中记录节点编号的同时,记录这个节点的深度。用结构体。
/*
第一行表示节点个数n(节点编号是从1到n)
下面n-1行的每一对数字表示一对父子关系。前一个是父,后一个是子。
8
1 2
1 3
1 4
2 7
3 5
3 6
3 8
*/
#include
using namespace std;
int n,ans; //定义一个整型变量n
vectorT[8]; //定义一个静态数组T,数组长度是8;T中元素类型为动态数组类型,动态数组中的元素为整型
struct node{
int bh,sd;
};
void bfs(int p,int sd){
queueQ;
Q.push({p,sd});//起点入队
while (!Q.empty()){
int bh=Q.front().bh;//取队头
int sd=Q.front().sd;//取队头
Q.pop();//弹(删)队头
ans=sd;//记录最大深度
//将bh的孩子入队
for(int j=0;j>n;
for(int i=1;i>a>>b;
T[a].push_back(b);
}
bfs(1,1);
cout<
b)用DFS
/*
第一行表示节点个数n(节点编号是从1到n)
下面n-1行的每一对数字表示一对父子关系。前一个是父,后一个是子。
8
1 2
1 3
1 4
2 7
3 5
3 6
3 8
*/
#include
using namespace std;
int n,ans; //定义一个整型变量n
vectorT[8]; //定义一个静态数组T,数组长度是8;T中元素类型为动态数组类型,动态数组中的元素为整型
void dfs(int p,int sd){
ans=max(ans,sd);
for(int j=0;j>n;
for(int i=1;i>a>>b;
T[a].push_back(b);
}
dfs(1,1);
cout<