1.二叉树的常用性质
<1>.在二叉树的第i层上最多有2 i-1 个节点 。(i>=1)
<2>.二叉树中如果深度为k(有k层),那么最多有2k-1个节点。(k>=1)
<3>.若二叉树按照从上到下从左到右依次编号,则若某节点编号为k,则其左右子树根节点编号分别为2k和2k+1;
<4>.二叉树分类:满二叉树,完全二叉树
满二叉树:高度为h,由2^h-1个节点构成的二叉树称为满二叉树。
<5>.在完全二叉树中,具有n个节点的完全二叉树的深度为[log2n]+1,其中[log2n]+1是向下取整。满二叉树的深度为k=log2(n+1);
2.二叉树的循环递归规律法
例题:uva679小球下落
题意:有一颗满二叉树,每个节点是一个开关,初始全是关闭的,小球从顶点落下,
小球每次经过开关就会把它的状态置反,这个开关为关时,小球左跑,为开时右跑。现在问第k个球下落到d层时的开关编号。输入深度d和小球个数k。d<20,k<524288
思路分析:首先该题最先想到的是模拟,开一个数组表示开关,下标表示编号,根据k的子树为2k和2k+1来改变数组,判断进行。但是该思路不但要开2^20这么大的数组而且循环最大时有524288*2^20次,绝逼超时!
因此改变思路,寻找题目规律:
<1>.首先对于每一层,第奇数个落入该层的球都是往左走的,第偶数个落入该层的球都是往右走的。
<2>.因为小球都是按照编号依次下落的,对于左枝(也就是奇数球),每个I号小球落入该层都是第(I+1)/2个小球。而偶数是往右走的I/2个小球!
<3>.因此每一层循环递归,来判断i,循环d层,即可找出最后叶子!省去大数组和大时间
代码:
-
#include
<iostream>
-
#include
<cstdio>
-
using namespace std;
-
int main()
-
{
-
int n;
-
while(cin>>n)
-
{
-
if(n==-1)break;
-
int D,I;
-
while(n–)
-
{
-
cin>>D>>I;//D层I个小球
-
int k=1;
-
for(int i=0; i
<D-1; i++)
-
{
-
if(I%2)//奇数是往左走的第(i+1)/2个小球
-
{
-
k=k*2;//往左走是k*2
-
I=(I+1)/2;//改变小球
-
}
-
else
-
{
-
k=(k*2+1);//偶数是往右走的第(i/2)个小球
-
-
I=I/2;
-
}
-
}
-
cout
<<k<<endl;
-
}
-
}
-
return 0;
-
}
二.二叉树的三种遍历方式
在这种实现当中,对于编号为k的节点,其左子节点的编号为2*k,右子节点的编号为2*k + 1,另外确定根节点的编号为1.
毫无疑问,这种实现极易产生巨大的空间浪费,比如对于一个只有一条链的树,假设该树含有31个节点,存储这31个节点却需要开一个2^30的数组,因此此方法较少使用。(此处的2^30是指数值,由2k计算出来的数值过大)
用结构体u来表示一个节点,其中u->v表示该节点的权值,u->left和u->right分别表示该节点的左右子节点,初始化全部为NULL,若需用到该节点,则申请空间,否则视为无子节点!就这样互相联系成一颗结构体指针二叉树!
对于一棵有n个节点树,只需要开一个大小为n的数组,节点按照出现顺序依次编号,这么一来,每个节点的左右节点的编号就无法通过2*k,2*k+1的形式来直接确定了,这时就需要数组lch[maxn] , rch[maxn];其中lch[u]表示u节点的左子节点的编号,因此通过u = lch[u]就可以访问到u节点的左子节点,rch[u]的含义同理。另外,用value[u]表示编号为u节点的权值,如此一来,申请新节点的newnode函数与初始化的newtree函数写法就变得不同了,具体见代码。(此处只需结点个数个数组即可,并不计算数值!)
(5,) (4,L) (13,RL) (2,LLR) (1,RRR) (4,RR) ()
(3,L) (4,R) ()
输出:5 4 8 11 13 4 7 2 1
not complete
代码:
-
#include
<iostream>
-
#include
<cstdio>
-
#include
<vector>
-
#include
<queue>
-
#include
<cstring>
-
using namespace std;
-
const int maxn=266;
-
char s[maxn];//输入
-
bool failed;
-
struct Node//节点
-
{
-
bool have_value;//该点是否被赋值过
-
int v;//该点权值
-
Node*left,*right;//左右子节点
-
Node():have_value(false),left(NULL),right(NULL){}//初始化函数
-
};
-
Node*root;//树根!!
-
Node* newnode()//分配内存
-
{
-
return new Node();//分配同时初始化
-
}
-
void addnode(int v,char *a)//建树
-
{
-
int len=strlen(a);
-
Node *u=root;
-
for(int i=0;i
<len;i++)
-
{
-
if(a[i]=='L')//左
-
{
-
if(u->left==NULL)u->left=newnode();//若左节点没有分配内存,没有开辟过,则申请内存,因为经过该节点了,该节点必须赋值!
-
u=u->left;//更新路径
-
}
-
else if(a[i]=='R')//右
-
{
-
if(u->right==NULL)u->right=newnode();//同上
-
u=u->right;
-
}
-
}
-
if(u->have_value)failed=true;//如果该节点已经被赋值过了,则非法输入,报错
-
u->v=v;//更新该节点
-
u->have_value=true;//标记赋值
-
}
-
-
bool read_in()//输入
-
{
-
root=newnode();//给树根申请内存
-
failed=false;//标记
-
for(;;)
-
{
-
if(scanf("%s",s)!=1)return false;//输入c+z了结束
-
if(strcmp(s,"()")==0)break;//读到()表示该组数据正常结束
-
int v;
-
sscanf(&s[1],"%d",&v);//sscanf读取权值并赋给v
-
addnode(v,strchr(s,',')+1);//读取路径,并且建树,最好不要在此处判断failed因为还没有完整输入数据
-
}
-
return true;
-
}
-
bool bfs(vector
<int>&ans)//遍历树,并保存权值
-
{
-
queue
<Node*>q;//队列
-
ans.clear();
-
q.push(root);
-
while(!q.empty())
-
{
-
Node*u=q.front();
-
q.pop();
-
if(!u->have_value)return false;//若该节点没有赋值,说明出现了越节点赋值现象,报错
-
ans.push_back(u->v);//存入节点权值,按照从上到下从左到右
-
if(u->left!=NULL)q.push(u->left);//左
-
if(u->right!=NULL)q.push(u->right);//右--->循环递归!!借助queue
-
}
-
return true;
-
}
-
int main()
-
{
-
while(1)
-
{
-
if(!read_in())//输入数据并且建树完成
-
break;
-
vector
<int> ans;//ans用来存储权值,最后输出
-
if(!failed&&bfs(ans))//均无错误,则可输出
-
{
-
int l=ans.size();
-
for(int j=0;j
<l;j++)//输出
-
{
-
if(j==0)
-
cout<<ans[j];
-
else
-
cout<<" "<<ans[j];
-
}
-
cout<<endl;
-
}
-
else
-
cout<<"not complete"<<endl;
-
}
-
return 0;
-
}
- void newtree() //初始化一颗新树,由于静态实现无法回收内存,因此顺便充当析构函数
- {
- lch[root] = rch[root] = 0;
- have_value[root] = 0;
- cnt = root;
- }
- int newnode() //建立新节点的函数,其中0相当于结构体中的空指针
- {
- int u = ++cnt;
- lch[u] = rch[u] = 0;
- have_value[u] = 0;
- return u;
- }
- void addnode(int v , char * s) //建立新节点的过程
- {
- int n = strlen(s);
- int u = root;
- for(int i = 0; i<n;i++){
- if(s[i] == ‘L’ ) { //重点!
- if(lch[u] == 0)
- lch[u] = newnode();
- u = lch[u];
- }
- else if(s[i] == ‘R’){
- if(rch[u] == 0)
- rch[u] = newnode();
- u = rch[u];
- }
- }
- if(have_value[u]) failed = true;
- value[u] = v;
- have_value[ u ] = 1;
- }

先序遍历:(1)访问根节点;(2)采用先序递归遍历左子树;(3)采用先序递归遍历右子树;
(注:每个节点的分支都遵循上述的访问顺序,体现“递归调用”)
先序遍历结果:A BDFE CGHI
思维过程:(1)先访问根节点A,
(2)A分为左右两个子树,因为是递归调用,所以左子树也遵循“先根节点-再左-再右”的顺序,所以访问B节点,
(3)然后访问D节点,
(4)访问F节点的时候有分支,同样遵循“先根节点-再左–再右”的顺序,
(5)访问E节点,此时左边的大的子树已经访问完毕,
(6)然后遵循最后访问右子树的顺序,访问右边大的子树,右边大子树同样先访问根节点C,
(7)访问左子树G,
(8)因为G的左子树没有,所以接下俩访问G的右子树H,

中序遍历:(1)采用中序遍历左子树;(2)访问根节点;(3)采用中序遍历右子树

后序遍历的结果:DEFB HGIC A
小结:三种方法遍历过程中经过节点的路线一样;只是访问各个节点的时机不同。
3 2 1 4 5 7 6
3 1 2 5 6 7 4
7 8 11 3 5 16 12 18
8 3 11 7 16 18 12 5
255
255
Sample Output
1
3
255
首先,我们先明确一个知识点,就是你知道了一棵树的中序和后序遍历,求他的前序遍历,我们应该怎么来做?
第一步:最初的时候,我们的后序遍历的最后一个数字就是我们的一个子树的根节点
第二步:找到我们的根结点后,跟据我们中序遍历的性质,我们的树就会被自然地分成了左右两个部分。
第三步:统计出来左右两个子树的大小和长度,这样就能继续重复上面的步骤
-
#include
<iostream>
-
#include
<string>
-
#include
<sstream>
-
#include
<algorithm>
-
#include
<cstdio>
-
#include
<cstring>
-
using namespace std;
-
const int maxn=10000+10;
-
int lch[maxn],rch[maxn],in_order[maxn],post_roder[maxn];
-
int n;
-
int read_list(int* a)
-
{
-
// memset(lch,0,sizeof(lch));
-
// memset(rch,0,sizeof(rch));
-
// memset(in_order,0,sizeof(in_order));
-
// memset(post_roder,0,sizeof(post_roder));
-
string line;
-
if(!getline(cin,line))return false;//因为题目说一行数据,没有结束标志,所以以回车为结束用字符串读入!
-
stringstream ss(line);
-
n=0;
-
int x;
-
while(ss>>x)a[n++]=x;//存入数组
-
return n>0;
-
}
-
int build(int L1,int R1,int L2,int R2)//建树各树的: 中序-后序
-
{
-
if(L1>R1)return 0;//空树
-
int root=post_roder[R2];//树根是后序的最后一个字符
-
int p=L1;
-
while(in_order[p]!=root)p++;//在中序里找到左子树结点个数
-
int cnt=p-L1;//左子树个数
-
lch[root]=build(L1,p-1,L2,L2+cnt-1);//以root为根的左子树建树l1-p-1是中序的左边也就是左子树的中序,l2-l2+cnt-1是左子树的后序,看上面图片就可以知道,下面同,这样不断递归找到各个节点!
-
rch[root]=build(p+1,R1,L2+cnt,R2-1);//右子树建树
-
return root;
-
}
-
int best,best_sum;//最优解
-
void dfs(int u,int sum)//找最优解
-
{
-
sum+=u;
-
if(!lch[u]&&!rch[u])//没有左右子树了说明已经到达最低端叶子,该路径完成,判断是否最优解
-
{
-
if(sum
<best_sum||(sum==best_sum&&u<best))
-
{
-
best_sum=sum;
-
best=u;
-
}
-
}
-
if(lch[u])dfs(lch[u],sum);//否则还在树枝上,继续向下找叶子
-
if(rch[u])dfs(rch[u],sum);
-
}
-
int main()
-
{
-
while(read_list(in_order))//把中序读入数组in_order
-
{
-
read_list(post_roder);//读入后序post_order
-
build(0,n-1,0,n-1);//建树
-
best_sum=1000000000;//最优解
-
dfs(post_roder[n-1],0);//递归寻找最优解
-
cout<<best<<endl;
-
}
-
return 0;
-
}
题意:根据干杠平衡原理,判断题目所给出的数据组成的天平能否平衡。注意,此天平可能包含子天平。输入时,如果w为0,则表示包含子天平,子天平按照先左后右的方法输入
Sample Input
1
0 2 0 4
0 3 0 1
1 1 1 1
2 4 4 2
1 6 3 2
Sample Output
YES
代码:
-
#include
<iostream>
-
using namespace std;
-
bool solve(int &w)
-
{
-
int w1,d1,w2,d2;
-
cin>>w1>>d1>>w2>>d2;
-
bool b1=true,b2=true;
-
if(!w1)b1=solve(w1);//如果w1=0,则说明w1有子树,同时把w1带入递归求出w1也就是子树总重量
-
if(!w2)b2=solve(w2);//同上
-
w=w1+w2;//求总重量,其实如果只考虑最上层的天平,这步似乎没什么意义;但其实它的意义在于,在当前是递归到一个子天平的情况时,就要重新输入子天平所在处的左右天平,如果有了这句代码,参数 W1 或者 W2,最终就能变为子天平上的两个左右天平的总重量。如此,等到判断 D1 * W1 == D2 * W2时,W1 和 W2就都不会是0了,而是该子天平下所有子天平的总重量(如果有的话,没有子天平,就还是它本身的质量,总之不会是0,而是它自己或是自己所有子天平的重量
-
return b1&&b2&&(w1*d1==w2*d2);//要想平衡,每一个天平都要平衡!
-
}
-
int main()
-
{
-
int T,W;
-
cin>>T;//组数
-
while(T--)
-
{
-
if(solve(W))//输入同时判断
-
cout
<<"YES"<<endl;
-
else
-
cout<<"NO"<<endl;
-
if(T)
-
cout<<endl;
-
}
-
return 0;
-
}
{
int len=strlen(a);
Node *u=root;
for(int i=0;i<len;i++)
{
if(a[i]==’L’)//左
{
if(u->left==NULL)u->left=newnode();//若左节点没有分配内存,没有开辟过,则申请内存,因为经过该节点了,该节点必须赋值!
u=u->left;//更新路径
}
else if(a[i]==’R’)//右
{
if(u->right==NULL)u->right=newnode();//同上
u=u->right;
}
}
Node* createNode(
char
* s)
//递归建树
{
if
(s[n]==
'\0'
)
return
NULL;
Node* pNode=
new
Node;
if
(s[n]==
'p'
)
{
pNode->type=0;
n++;
pNode->fch=createNode(s);
pNode->sch=createNode(s);
pNode->tch=createNode(s);
pNode->lch=createNode(s);
}
else
if
(s[n]==
'f'
)
{
pNode->type=1;
n++;
}
else
{
pNode->type=2;
n++;
}
return
pNode;
}
-
#include
<iostream>
-
#include
<queue>
-
#include
<cstdio>
-
#include
<vector>
-
using namespace std;
-
struct Node
-
{
-
char ch;
-
Node *lefted,*righted;
-
Node():ch(0),lefted(NULL),righted(NULL) {}
-
};
-
Node *newnode()
-
{
-
return new Node();
-
};
-
Node *Root;
-
Node *build(const char *s,int& p)
-
{
-
-
char sign=s[p++];
-
if(sign==',')
-
return NULL;
-
else
-
{
-
Node *root;
-
root=newnode();
-
root->ch=sign;
-
root->lefted=build(s,p);
-
root->righted=build(s,p);
-
return root;
-
}
-
-
}
-
void solveZ(Node *tree)
-
{
-
if(tree)
-
{
-
solveZ(tree->lefted);
-
cout
<<tree->ch;
-
solveZ(tree->righted);
-
}
-
}
-
void solveH(Node *tree)
-
{
-
if(tree)
-
{
-
solveH(tree->lefted);
-
solveH(tree->righted);
-
cout
<<tree->ch;
-
}
-
}
-
int main()
-
{
-
char name[100];
-
while(scanf("%s",name)!=EOF)
-
{
-
int m=0;
-
Root=build(name,m);
-
solveZ(Root);
-
cout
<<endl;
-
solveH(Root);
-
cout<<endl;
-
}
-
return 0;
-
}
第二行输入二叉树的中序遍历序列。
ABDCEF
代码:
-
#include
<iostream>
-
#include
<cstdio>
-
#include
<cstring>
-
using namespace std;
-
char pre_name[100];
-
char in_name[100];
-
struct Node
-
{
-
char ch;
-
Node *lefted,*righted;
-
Node():ch(0),lefted(NULL),righted(NULL) {}
-
};
-
Node *Root;
-
Node *build(int L1,int R1,int L2,int R2)//前序找根,中序分割建树
-
{
-
if(L2>R2)return NULL;
-
Node *root;
-
root=new Node();
-
root->ch=pre_name[L1];
-
int p=L2;
-
while(in_name[p]!=root->ch)p++;
-
int cnt=p-L2;
-
root->lefted=build(L1+1,L1+cnt,L2,p-1);
-
root->righted=build(L1+cnt+1,R1,p+1,R2);
-
return root;
-
}
-
void select_post(Node *tree)
-
{
-
if(tree)
-
{
-
select_post(tree->lefted);
-
select_post(tree->righted);
-
cout
<<tree->ch;
-
}
-
}
-
int main()
-
{
-
scanf("%s%s",pre_name,in_name);
-
int n=strlen(pre_name);
-
Root=build(0,n-1,0,n-1);
-
select_post(Root);
-
cout
<<endl;
-
return 0;
-
}
-
abd,,eg,,,cf,,,
xnl,,i,,u,,
xnuli
-
#include
<iostream>
-
#include
<cstdio>
-
#include
<cstring>
-
#include
<queue>
-
#include
<vector>
-
using namespace std;
-
char pre_name[100];//如果给出空节点的,则一个序列遍历就可建树,否则要两个序列!且层次遍历用队列,其他遍历用递归即可!
-
struct Node
-
{
-
char ch;
-
Node *lefted,*righted;
-
Node():ch(0),lefted(NULL),righted(NULL) {}
-
};
-
Node *Root;
-
Node *build(const char *s,int &p)
-
{
-
char sign=s[p++];
-
if(sign==',')
-
return NULL;
-
else
-
{
-
Node *root;
-
root=new Node();
-
root->ch=sign;
-
root->lefted=build(s,p);
-
root->righted=build(s,p);
-
return root;
-
}
-
}
-
void serch(vector
<char>&u)
-
{
-
queue
<Node*>que;
-
u.clear();
-
if(Root)//要考虑根节点为空的情况!!!!
-
{
-
que.push(Root);
-
}
-
while(!que.empty())//队列递归求层序遍历!!
-
{
-
Node *nodes=que.front();
-
que.pop();
-
u.push_back(nodes->ch);
-
if(nodes->lefted!=NULL)que.push(nodes->lefted);
-
if(nodes->righted!=NULL)que.push(nodes->righted);
-
}
-
}
-
int main()
-
{
-
int T;
-
cin>>T;
-
getchar();
-
while(T--)
-
{
-
scanf("%s",pre_name);
-
int m=0;
-
Root=build(pre_name,m);
-
vector
<char>ans;
-
serch(ans);
-
for(int i=0; i
<ans.size(); i++)
-
cout<<ans[i];
-
cout<<endl;
-
}
-
return 0;
-
}
-
</div>