树的很多题目中都包含递归的思想
递归包括递归边界
以及递归式
即:往下递,往上归
递归写法的特点:写起来代码较短,但是时间复杂度较高
int Func(int n) {
if (n == 0) {
return 1;
}
else {
return n * Func(n - 1);
}
}
int Fbnq(int n) {
if (n == 0||n == 1) {
return 1;
}
else {
return Fbnq(n - 1) + Fbnq(n - 2);
}
}
二叉树的链式存储结构体定义。
typedef struct BiTNode {
int data;
struct BiTNode* lchild, * rchild;
}BiTNode,*BiTree;
先序递归遍历:根左右
void PreOrder(BiTree T) {
if (T == NULL) {//递归边界
return;
}
else {
printf("%d", T->data);//打印此结点数据域中的数据值
PreOrder(T->lchild);//递归遍历左子树
PreOrder(T->rchild);//递归遍历右子树
}
}
简便写法如下:
//简便
void PreOrder(BiTree T) {
if (T != NULL) {
printf("%d", T->data);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
因此有些代码看着比较难懂,是因为它们把递归边界隐藏了,变得不容易轻易看懂。
中序递归遍历:左根右
void InOrder(BiTree T) {
if (T != NULL) {//若所处理的结点不为空
InOrder(T->lchild);//递归遍历左子树
printf("%d", T->data);//打印此结点数据域中的数据值
InOrder(T->rchild);//递归遍历右子树
}
}
后序递归遍历:左右根
void PostOrder(BiTree T) {
if (T != NULL) {
PostOrder(T->lchild);
PostOrder(T->rchild);
printf("%d", T->data);
}
}
void Search(BiTree T, BiTNode*& q, int key) {
if(T!=NULL){
if (T->data == key) {
q = T;//若为 key 则指针 q 指向该结点
}
else {
Search(T->lchild, q, key);
Search(T->rchild, q, key);
}
}
}
int n = 0;//定义全局变量 n 进行计数
int Search_k(BiTree T, int k) {
if (T != NULL) {//改写先序递归遍历
n++;//更新变量 n,记录现在访问的是第几个结点
if (n == k) {//若 n 等于 k 则直接打印访问结点的数据值并结束此次递归
printf("%d", T->data);
return T->data;
}
else {
Search_k(T->lchild, k);
Search_k(T->rchild, k);
}
}
}
法一
利用递归
int n = 0;
void calc(BiTree T) {
if (T == NULL) {
return;}
else {
n++;
calc(T->lchild);
calc(T->rchild);
}
}
int n=0;//定义全局变量 n 用来计数
void calc(BiTree T){
if(T!=NULL){//改写先序递归遍历
n++;//将先序遍历中访问结点代码改写为计数代码
calc(T->lchild);//递归遍历左子树
calc(T->rchild);//递归遍历右子树
}
}
法二
int Count(BiTree T) {
int n1, n2;//定义 n1 和 n2 分别用于接收左右子树结点个数
if (T == NULL) {
return 0;
}
else {
n1 = Count(T->lchild);//递归求解左子树中结点个数,结果 n1 接收
n2 = Count(T->rchild);
return n1 + n2 + 1;//再加根结点
}
}
法二的代码是求二叉树的高度、求叶子结点的个数、求单/双分支结点的个数
的算法思想,递归时尽量把叶子结点的左右空子树也画出来。
法一
//法一
int n = 0;
void Countleaves(BiTree T) {
if (T != NULL) {
//所处理结点是否为叶子结点
if (T->lchild == NULL && T->rchild == NULL) {
n++;//若遍历结点是叶子结点则计数
}
Countleaves(T->lchild);//递归遍历左子树
Countleaves(T->rchild);
}
}
法二
//法二
int Countl(BiTree T) {
int n1, n2;//接受左右子树的叶子结点个数
if (T == NULL) {
return 0;
}
else if (T->lchild == NULL && T->rchild == NULL) {
return 1;
}
else {
n1 = Countl(T->lchild);//递归求解左子树中叶子结点的个数,结果 n1 接收
n2 = Countl(T->rchild);
return n1 + n2;
}
}
①双分支 n1+n2+1, 看左子树的双分支,右子树的双分支,加上本身;
②单分支 n1+n2, 看左子树的双分支,右子树的双分支;
③叶子结点 n1+n2, 看左子树的双分支,右子树的双分支;
④NULL 0(递归边界)
上面双分支的+1操作是加上本次的,所以需要+1
//(题一)利用递归计算二叉树中所有双分支结点个数。
int Count(BiTree T) {
int n1, n2;
if (T == NULL) {//递归边界
return 0;
}
else if (T->lchild != NULL && T->rchild != NULL) {
n1 = Count(T->lchild);//递归求解左子树双分支结点个数,结果用 n1 接收
n2 = Count(T->rchild);
return n1 + n2 + 1;
}
else {若不为空树,根结点也不为双分支结点,也就是叶子结点和单分支结点的情况
n1 = Count(T->lchild);
n2 = Count(T->rchild);
return n1 + n2;
}
}
①双分支 n1+n2 看左子树的单分支,右子树的单分支;
②单分支(有左孩子没有右孩子或者有右孩子没有左孩子) n1+n2+1 看左子树的单分支,右子树的单分支,加上本身;
③叶子结点 n1+n2 看左子树的单分支,右子树的单分支;
④NULL 0
int Count_Simple_Node(BiTree T) {
int n1, n2;
if (T == NULL) {
return 0;
}
if ((T->lchild && T->rchild == NULL) || (T->lchild == NULL && T->rchild)) {
n1 = Count_Simple_Node(T->lchild);//递归求解左子树单分支结点个数,结果用 n1 接收
n2 = Count_Simple_Node(T->rchild);
return n1 + n2 + 1;
}
else {
n1 = Count_Simple_Node(T->lchild);//递归求解左子树单分支结点个数,结果用 n1 接收
n2 = Count_Simple_Node(T->rchild);
return n1 + n2;
}
}
判断左右子树哪个更深,若左子树更深则树总深度为左子树深度+1(根也算一层) ,若右子树更深则树总深度为右子树深度+1(根也算一层) , 两棵子树高相等,既可以左子树+1,也可以右子树+1,这里的1就是根所在的一层,所以+1,可以自己模拟一下。
int Depth(BiTree T) {
int ldep;
int rdep;
if (T == NULL) {
return 0;
}
ldep = Depth(T->lchild);//递归求解左子树深度,结果用 ldep 接收
rdep = Depth(T->rchild);
//判断左右子树哪个更高,高的树加上根节点那一层
if (ldep > rdep) {
return ldep + 1;
}
else {
return rdep + 1;
}
}
void Swap(BiTree &B){
if(B!=NULL){
BiTNode *temp=B->lchild;//定义 temp 辅助指针,辅助 B 左右子树交换,交换(指针域变换)
B->lchild=B->rchild;
B->rchild=temp;
Swap(B->lchild);//递归处理左子树
Swap(B->rchild);//递归处理右子树
}
}
void Search_x_level(BiTree T, int x, int level) {
//level是当前根节点所在的层次
if (T != NULL) {
if (T->data == x) {
printf("x所处的层数为%d", level);
}
Search_x_level(T->lchild, x, level + 1);
Search_x_level(T->lchild, x, level + 1);
}
}
void Func(BiTree T, int x) {
Search_x_level(T, x, 1);//初始时根所在层次为 1
}
思路2:先把结点分层,如结点1在第一层,结点2、3在第二层,结点4、5、6、7在第三层,然后查找值为x所在的层次。该思路感觉有点麻烦
①先将根结点入队;
②出队,打印;
③lchild入队;
④rchild入队;
②③④循环
void LevelOrder(BiTree T) {
Queue Q;//定义一个队列 Q
InitQueue(Q);//初始化队列 Q
BiTNode* p = T;//定义一个遍历指针 p,初始时指向根结点
EnQueue(Q, p);//将根结点入队
while (!IsEmpty(Q)) {//队列不为空则继续循环
DeQueue(Q, p);//出队,并让 p 指向出队结点
printf("%d", p->data);//打印出队结点数据域中的数据
if (p->lchild != NULL) {
EnQueue(Q, p->lchild);//若出队结点有左孩子,则让其左孩子入队
}
if (p->rchild != NULL) {
EnQueue(Q, p->rchild);//若出队结点有右孩子,则让其右孩子入队
}
}
}
栈
,当然还是需要用到队列,只是说先入栈,等到最后依次出栈打印。void ReverseLevelOrder(BiTree T) {
Queue Q;//定义队列 Q
Stack S;//定义栈 S
InitQueue(Q);//初始化队列 Q
InitStack(S);//初始化栈 S
BiTNode* p = T;//定义遍历指针 p,初始时指向根结点
EnQueue(Q, p);//根结点入队
while (!IsEmpty(Q)) {
DeQueue(Q, p);
Push(S, p);//将出队结点压入栈 S 中
if (p->lchild) {
EnQueue(Q, p->lchild);
}
if (p->rchild) {
EnQueue(Q, p->rchild);
}
}
//打印操作
while (!IsEmpty(S)) {//栈不为空则继续循环
Pop(S, p);//出栈,并让 p 指向出栈结点
printf("%d", p->data);
}
}
⭐⭐⭐⭐⭐
int IsComplete(BiTree T) {
if (T == NULL) {//空树是完全二叉树
return 1;
}
Queue Q;//定义队列
InitQueue(Q);
BiTNode* p = T;//定义遍历指针p初始时指向根结点
EnQueue(Q, p);//让根结点入队
while (!IsEmpty(Q)) {
DeQueue(Q, p);//出队并让 p 指向出队结点
if (p != NULL) {//若 p 不为空
EnQueue(Q, p->lchild);//让其左孩子入队(左孩子为空则入队 NULL)
EnQueue(Q, p->rchild);//让其右孩子入队(右孩子为空则入队 NULL)
}
else {//p是空结点,队列中其后面的所有结点均应该是空结点,否则不符合完全二叉树
while (!IsEmpty(Q)) {//队列不为空则需继续循环
DeQueue(Q, p);//出队并让 p 指向出队结点
if (p != NULL) {
return 0;//若后续还有结点则不是完全二叉树
}
}
}
}
return 1;//若队列中剩余数据都为 NULL 则证明是完全二叉树
}
①查找到元素为x的结点②如何删除以它为根的树
,从下往上删除结点,因为如果从上往下,把根节点删了,无法定位其左右孩子结点的位置;从下往上删除以T为根节点的树
//从下往上删除以T为根节点的树
void DeleteTree(BiTree& T) {
if (T != NULL) {
DeleteTree(T->lchild);
DeleteTree(T->rchild);
free(T);//释放结点空间
}
}
查找元素为x的结点
//查找元素为x的结点
void SearchX(BiTree& T, int x) {
if (T == NULL) {
return;
}
if (T->data == x) {//删除整棵树也是这里的特殊情况,因为不需要做任何遍历查找
DeleteTree(T);
return;//函数执行结束
}
Queue Q;
InitQueue(Q);
BiTNode* p = T;
EnQueue(Q, p);//入队
while (!IsEmpty(Q)) {
DeQueue(Q, p);//出队并让 p 指向出队结点
if (p->lchild != NULL) {//左孩子判断
if (p->lchild->data == x) {//如果p的左孩子的值=x,说明查找到了,删除
DeleteTree(p->lchild);
p->lchild = NULL;//指针域置空,因为删除函数只是对结点的删除
}
else {
EnQueue(Q, p->lchild);//如果p的左孩子的值不是x,正常进行层次遍历
}
}
if (p->rchild != NULL) {//右孩子判断
if (p->rchild->data == x) {//如果p的左孩子的值=x,说明查找到了,删除
DeleteTree(p->rchild);
p->rchild = NULL;//指针域置空,因为删除函数只是对结点的删除
}
else {
EnQueue(Q, p->rchild);//如果p的左孩子的值不是x,正常进行层次遍历
}
}
}
}
由于非递归求二叉树的高度,因此需要层次遍历,定义一个变量h,每遍历一层,h相应变化;
如何判断某一层遍历完了:定义last变量:指向每一层最后一个结点,假如每层最后一个结点处理完了,这一层也就处理完了,h可以变化了;
之前的题目都是调用队列的基本操作,这次不行了,这次研究的更细一些,需要进行更改;之前初始化时候,front和rear都是0。
但是在这里如果采用上面的方式,会导致rear指向每次结点的后一个位置,相当于错开了,而求二叉树高度定义的last变量需要和rear一起使用,因此这里我们初始化时将front和rear均指向-1,入队时,先rear+1,然后赋值入队,此时rear和结点没有错开。
这里判断==队列
==是否为空是front
int Depth(BiTree T) {
if (T == NULL) {
return 0;
}
int h = 0;//变量 h 用来记录高度
int last = 0;//变量 last 用来记录每一层最右边结点位置
BiTNode* Q[MaxSize];//定义队列 Q
int front = -1, rear = -1;//定义队头指针和队尾指针
BiTNode* p = T;//定义遍历指针p初始时指向根结点
Q[++rear] = p;//根结点入队
while (front < rear) {//队列不为空则继续循环
p = Q[++front];//出队,p 指向出队结点
if (p->lchild) {//若此结点有左孩子则让其左孩子入队
Q[++rear] = p->lchild;
}
if (p->rchild){//若此结点有右孩子则让其右孩子入队
Q[++rear] = p->rchild;
}
if (front == last) {//若二叉树其中一层的最右边结点出队
h++;//让高度加一
last = rear;//更新 last,使其指向下一层最右边结点位置
}
}
return h;//返回二叉树的高度
}
层次遍历二叉树;
这里记录结点属于哪一层时,定义一个新的数组专门用来记录每个结点所在层数;
层次遍历完后,得到的数组就是包含每个结点所在层数的记录,只需要遍历这个数组,找到哪一层的数最多,这样也就找到宽度以及其对应的层数。
举个例子:假设结点元素为
1
3 4
5 8 9 4
6
则数组中为1 2 2 3 3 3 3 4,说明宽度是4,因为第三层的结点个数是4。
int Width(BiTree T) {
if (T == NULL)//若为空树则宽度为 0
return 0;
BiTNode* Q[MaxSize];//定义队列 Q
int front = 0, rear = 0;//定义队头指针和队尾指针
int level[MaxSize];//定义存储结点层数的数组
BiTNode* p = T;//定义遍历指针 p,初始时指向根结点
int k = 1;//定义变量 k 记录指针 p 指向结点所在的层数 ,初始时有结点,说明有高度,k从1开始,
Q[rear] = p;//根结点入队
level[rear] = k;//记录根结点所在层数为 1
rear++;//尾指针后移
//遍历二叉树,目的是得到结点所在层数的数组
while (front < rear) {//若队列不为空则需继续循环
p = Q[front];//出队并让 p 指向出队结点
k = level[front];//更新 k 为出队结点所在层数 ,p变了k也要变
front++;//头指针后移
if (p->lchild) {//若出队结点有左孩子
Q[rear] = p->lchild;//将该结点左孩子入队
level[rear] = k + 1;//新入队结点所在层数为 k+1
rear++;//尾指针后移
}
if (p->rchild) {//若出队结点有右孩子
Q[rear] = p->rchild;//将该结点右孩子入队
level[rear] = k + 1;//新入队结点所在层数为 k+1
rear++;//尾指针后移
}
}
//查找最大的宽度
int max = 0, i = 0, n;//定义 max 记录宽度,i 作为遍历索引,n 记录每一层结点数
k = 1;//k 用来表示所计数的是第几层,初始时等于 1 表示计数第一层
while (i < rear) {//遍历记录结点层数的数组
n = 0;//对每一层结点计数时都需初始化变量 n
while (i < rear && level[i] == k) {//对第 k 层结点进行计数
n++;
i++;
}
k++;//本层计数结束,更新变量 k,准备对下一层结点计数
if (n > max)//判断此层结点数是否为当前遍历过的最大宽度
max = n;
}
return max;//返回此树的宽度
}
代码中,首先将根节点对应的数组元素设为1,相当于根节点在第一层,
void PreOrder(BiTree T) {
Stack S;//定义一个栈 S
InitStack(S);//初始化栈 S
BiTNode* p = T;//定义遍历指针 p,初始时指向根结点
while (p || !IsEmpty(S)) {//若 p 指针不为空或栈 S 不为空则继续循环
if (p != NULL) {//若 p 指针不为空
printf("%d", p->data);//打印此结点数据域中的数据值
Push(S, p);//将此结点压入栈 S 中
p = p->lchild;//遍历指针 p 继续遍历此结点的左子树
}
else {
Pop(S, p);//若 p 为空则出栈,并让 p 指向出栈结点
p = p->rchild;//p 继续遍历此结点的右子树
}
}
}
void InOrder(BiTree T) {
Stack S;
InitStack(S);
BiTNode* p = T;
while (p || !IsEmpty(S)) {
if (p != NULL) {
Push(S, p);//将所处理结点压入栈中
p = p->lchild;//p 继续遍历此结点左子树
}
else {
Pop(S, p);//若 p 指针为空,则出栈,并让 p 指向出栈结点
printf("%d", p->data);
p = p->rchild;//遍历指针 p 继续遍历此结点的右子树
}
}
}
⭐⭐⭐⭐⭐
void PostOrder(BiTree T) {
Stack S;
InitStack(S);
BiTNode* p = T;
BiTNode* r = NULL;
while (p || !IsEmpty(S)) {
if (p != NULL) {
Push(S, p);//先压栈,然后去到左子树那里
p = p->lchild;
}
else {
GetTop(S, p);
if ((p->rchild != NULL)&&(p->rchild!=r)) {//有右孩子且右孩子没有被访问过,左->右
p = p->rchild;
}
else {//若结点没有右子树或者右子树已被遍历过,右->根
Pop(S, p);
printf("%d", p->data);
r = p;//更新记录指针
}
}
}
}
模拟一下,发现有问题,左下角的叶子结点处理后会陷入死循环,其原因是p到达左下角叶子结点时候,p!=NULL,此时p去到叶子结点的左孩子那,此时p=NULL,进入else中,p回到叶子结点处,且若没有右子树,会出栈,打印,然后更新记录指针,此时都没有变化p,使得p!=NULL,从而陷入循环了,下面代码在每次更新记录指针后,将遍历指针p置空
void PostOrder(BiTree T) {
Stack S;//定义栈 S
InitStack(S);//初始化栈 S
BiTNode* p = T;//定义遍历指针 p,初始时指向根结点
BiTNode* r = NULL;//定义记录指针 r,负责记录上一个打印的结点位置
while (p || !IsEmpty(S)) {//若 p 指针不为空或栈不为空则继续循环
if (p != NULL) {//若 p 指针不为空
Push(S, p);//将所处理结点压入栈中
p = p->lchild;//p 继续遍历此结点左子树
}
else {
GetTop(S, p);//若 p 指针为空则 p 指向栈顶结点
if (p->rchild && p->rchild != r)//判断此结点是否有右孩子以及是否遍历
p = p->rchild;//若结点有右孩子且未被遍历则 p 继续遍历其右子树
else {//若结点没有右子树或者右子树已被遍历过
Pop(S, p);//出栈,并让 p 指向其出栈结点
printf("%d", p->data);//打印此结点数据域中的数据
r = p;//更新记录指针
p = NULL;//将遍历指针置空
}
}
}
}
对于后续非递归遍历算法,当你遍历打印某个结点时,栈中的结点就是该结点的根,也就是遍历某个结点时候栈中的元素都是其祖先,后续打印某结点的祖先结点,打印根节点到某结点的路径,都是后续非递归遍历算法的改写。
void Search_x_father(BiTree T,int x) {
Stack S;
InitStack(S);
BiTNode* p = T;
BiTNode* r = NULL;
while (p || !IsEmpty(S)) {
if (p != NULL) {
Push(S, p);
p = p->lchild;
}
else {
GetTop(S, p);//p 指向栈顶结点但不出栈
if (p->rchild && p->rchild != r) {//若该结点有右子树且未被访问
p = p->rchild;
}
else {//若该结点无右子树或者右子树已经被访问
Pop(S, p);//出栈并让 p 指向出栈结点
if (p->data == x) {//是我们要找的值
while (!IsEmpty(S)) {//打印栈中的元素
Pop(S, p);//出栈并让 p 指向出栈结点
printf("%d", p->data);
}
}
else {//不是我们要找的值
r = p;//更新记录指针位置
p = NULL;//将指针 p 置空进行下一次循环判断
}
}
}
}
}
BiTNode* FindAncestor(BiTree T, BiTNode* p, BiTNode* q) {
BiTNode* bt = T;
BiTNode* r = NULL;
//定义三个栈,因为要复制
BiTNode* S[MaxSize];
BiTNode* S1[MaxSize],* S2[MaxSize];
int top = -1, top1 = -1, top2 = -1;
int temp;//复制元素时使用
while (bt || top!=-1) {//栈不为空
if (bt != NULL) {
S[++top] = bt;//入栈
bt = bt->lchild;
}
else {//若遍历指针为空
bt = S[top];//bt 指向栈顶结点但不出栈
if (bt->rchild && bt->rchild != r) {//若该结点有右孩子且未被访问
bt = bt->rchild;//bt 遍历该结点右子树
}
else {//若该结点没有右孩子或者右孩子已经被访问
bt = S[top];
top--;//出栈bt指向出栈结点
if (bt == p) {//如果该节点是p结点,复制栈S到栈S1
for (temp = 0; temp <= top; temp++) {
S1[temp] = S[temp];
}
top1 = top;//更新 S1 栈顶指针
}
if (bt == q) {
for (temp = 0; temp <= top; temp++) {
S2[temp] = S[temp];
}
top2 = top;
}
//更新r和bt
r = bt;//更新记录指针
bt = NULL;//bt 指针置空
}
}
}
//查找最近公共结点
for (int i = top1; i >= 0; i--) {
for (int j = top2; j >= 0; j--) {
if (S1[i] == S2[j]) {
return S1[i];//若找到即返回指向最近公共结点的指针变量
}
}
}
return NULL;
}
void AllPath(BiTree T) {//改写后序非递归遍历
BiTNode* p = T;
BiTNode* r = NULL;//定义遍历指针 p,记录指针 r
BiTNode* S[MaxSize];//定义栈 S
int top = -1;//定义栈顶指针
while (p != NULL || top != -1) {
if (p != NULL) {
S[++top] = p;//让 p 所指结点入栈
p = p->lchild;
}
else {
p = S[top];//p 指向栈顶结点但不出栈
if (p->rchild && p->rchild != r) {//若 p 所指结点有右子树且未被访问
p = p->rchild;
}
else {
p = S[top--];//出栈,并让 p 指向其出栈结点
if (p->lchild == NULL && p->rchild == NULL) {
for (int i = 0; i <= top; i++) {
printf("%d", S[i]->data);//打印栈中所有结点数据
}
printf("%d", p->data);//打印此叶子结点数据
}
r = p;//更新记录指针 r
p = NULL;//将 p 指针置空
}
}
}
}
BiTNode* head = NULL, * pre = NULL;//定义头指针 head 和记录指针 pre
void Link(BiTree& T) {
if (T != NULL) {//改写中序递归遍历算法
Link(T->lchild);//递归遍历左子树
//判断此结点为叶子结点
if (T->lchild == NULL && T->rchild == NULL) {
if (pre == NULL) {//判断此结点是否为访问到的第一个叶子结点
head = T;//若为第一个叶子结点则让头指针 head 指向它
pre = T;//pre 负责记录上一次处理的叶子结点,所以进行更新
}
else {//若此结点不是第一个叶子结点
pre->rchild = T;//直接让上一个访问的叶子结点指向此结点
pre = T;//更新 pre 指针位置
}
}
Link(T->rchild);//递归遍历右子树
}
}
说明:函数 int op(int A,int B,char C)返回的是以 C 为运算符,以 A、B 为操作数的算式的数值,例如,若 C 为‘+’,则返回 A+B 的值。
int Compute(BiTree T) {
if (T == NULL) {
return 0;
}
int A, B;//定义两个变量分别接受左右子树计算的结果
if (T->lchild != NULL && T->rchild != NULL) {//若结点为双分支结点
A = Compute(T->lchild);//递归计算左子树的值并用 A 接收
B = Compute(T->rchild);//递归计算右子树的值并用 B 接收
return op(A, B, T->data);//计算左右子树运算结果并返回
}
else {//若结点为叶子结点
return T->data - '0';//将字符型变量转换为数值(ASCII 码)return T->data是错的
}
}
int Similar(BiTree T1, BiTree T2) {//结点结构一样即为相似
定义两个变量分别用于接收左右子树是否相似
int left, right;
if (T1 == NULL && T2 == NULL) {
return 1;
}
else if (T1 == NULL || T2 == NULL) {
return 0;
}
else {//递归式
left = Similar(T1->lchild, T2->lchild);//递归判断两棵树左子树是否相似
right = Similar(T1->rchild, T2->rchild);//递归判断两棵树右子树是否相似
//return left && right;//若左右子树都相似则两棵树相似,返回 1
if (left == 1 && right == 1) {
return 1;//相似
}
else {
return 0;
}
}
}
typedef struct BiTNode {
int data;
struct BiTNode* lchild;
struct BiTNode* rchild;
struct BiTNode* parent;
}BiTNode,*BiTree;
//对parent指针赋值
void Func(BiTree& T, BiTNode* q) {//指针 q 用来记录T的双亲结点
if (T != NULL) {//改写先序递归遍历
T->parent = q;//遍历结点的 parent 指针指向双亲结点
q = T;//更新指针 q 的位置
Func(T->lchild, q);//递归遍历左子树
Func(T->rchild, q);//递归遍历右子树
}
}
//打印单个结点到根结点的路径
void PrintPath(BiTNode* p) {
while (p != NULL) {//只要 p 不为空则继续循环
printf("%d", p->data);//打印结点数据域中数据
p = p->parent;//p 顺着 parent 指针遍历
}
}
//打印所有结点到根结点的路径
void AllPath(BiTree T) {
if (T != NULL) {//只要 p 不为空则继续循环
PrintPath(T);//打印所处理结点到根结点路径
AllPath(T->lchild);//递归遍历左子树
AllPath(T->rchild);//递归遍历右子树
}
}
BiTree Create(char A[], int i, int n) {//i 为索引,n 为数组中元素个数
if(i>n||A[i]=='#'){
return NULL;//若 i≥n 则代表后续已无结点则返回 NULL
}
else{//只有 i
BiTNode* p = (BiTNode*)malloc(sizeof(BiTNode));//申请内存空间
p->data = A[i];//给结点数据域赋值
p->lchild = Create(A, 2*i, n);//递归处理左子树
p->rchild = Create(A, 2*i+1, n);//递归处理右子树
return p; //返回结点位置
}
}
先序:ABCDE
中序:BCAED
建立的二叉树如下:
A
B D
NULL C E NULL
写代码时,和我们自己手动确定二叉树结构类似,拿到先序和中序序列时候,先找到先序递归遍历的第一个结点,该结点是整棵树的根结点,此时确定好整棵树的根节点了,可以创建该节点,然后在中序遍历序列中找到这个根节点,将序列分为左右两块,也就是左子树和右子树;
先序遍历序列(根左右)和中序遍历序列(左根右)可以确定唯一的二叉树,且先序遍历序列的第一个结点是整棵树的根结点;
需要在中序遍历序列中将结点分为左右两块;
代码中low和high指的是数组中最左边和最右边是谁,根节点确定好后,B数组也需要到根结点处,方便后续将树分为左子树和右子树,因此有个for循环;
由于题目中数组下标从1开始,因此记录左右子树结点个数 llen = i - low2;rlen = high2 - i
递归创建左子树时候(左半段),对于A数组,最开始的是根,在递归时需要排出(已经使用过了),结束位置是low1 + llen,这里不是low1+1 + llen(可以自己动手试一试)对于B数组,最开始的就是最左边的,结束位置是根节点前一个
p->lchild = Create(A, low1 + 1, low1 + llen, B, low2, low2 + llen - 1);
递归创建右子树时,同样可以自己模拟一下哦
BiTree Create(char A[], int low1, int high1, char B[], int low2, int high2) {
BiTNode* p = (BiTNode*)malloc(sizeof(BiTNode));//给根结点分配内存空间
p->data = A[low1];//为根结点数据域赋值
int i;//定义变量 i,记录中序序列中根结点的位置
for (i = low2; B[i] != p->data; i++);//循环使变量 i 指向中序序列中根结点的位置
int llen = i - low2;//llen 记录左子树结点个数
int rlen = high2 - i;//rlen 记录右子树结点个数
if (llen) {//若左子树有结点则递归创建左子树
p->lchild = Create(A, low1 + 1, low1 + llen, B, low2, low2 + llen - 1);
}
else {//若左子树已无结点则让其左指针域赋 NULL
p->lchild = NULL;
}
if (rlen) {//若右子树有结点则递归创建右子树
p->rchild = Create(A, high1 - rlen + 1, high1, B, high2 - rlen + 1, high2);
}
else {//若右子树已无结点则让其右指针域赋 NULL
p->rchild = NULL;
}
return p;//最后返回所创建树的根结点
}
void PreToPost(char pre[], int l1, int h1, char post[], int l2, int h2) {
int half;//定义 half 变量记录左子树(右子树)结点个数
if (h1 >= l1) {//h1≥l1 才有意义(先序遍历)
post[h2] = pre[l1];//后序序列最后一个即为根结点,也就是先序序列第一个
half = (h1 - l1) / 2;//因为是满二叉树,所以左右子树结点个数相等
PreToPost(pre, l1 + 1, l1 + half, post, l2, l2 + half - 1);//递归处理左子树
PreToPost(pre, l1 + 1 + half, h1, post, l2 + half, h2 - 1);//递归处理右子树
}
}
/llen 记录左子树结点个数
int rlen = high2 - i;//rlen 记录右子树结点个数
if (llen) {//若左子树有结点则递归创建左子树
p->lchild = Create(A, low1 + 1, low1 + llen, B, low2, low2 + llen - 1);
}
else {//若左子树已无结点则让其左指针域赋 NULL
p->lchild = NULL;
}
if (rlen) {//若右子树有结点则递归创建右子树
p->rchild = Create(A, high1 - rlen + 1, high1, B, high2 - rlen + 1, high2);
}
else {//若右子树已无结点则让其右指针域赋 NULL
p->rchild = NULL;
}
return p;//最后返回所创建树的根结点
}
##### 31 设有一棵满二叉树(所有结点值均不同),已知其先序序列为 pre,设计一个算法求其后序序列 post。
1. 先序序列的第一个结点,就是根节点,也是后序序列的最后一个结点;
2. ==**满二叉树特点**:如果不看根节点,其左右子树的结点数量是一样的==,可以将除根节点以外的结点一分为二,然后分出来的两块,左边的就是左子树,右边的就是右子树,然后递归。
3. l1和h1是先序序列最左边的位置和最右边的位置(所有h1>=l1时才有意义),l2和h2是后序序列最左边的位置和最右边的位置(所有h1>=l1时才有意义)
4. 代码中给post数组赋值的根节点的操作。
~~~cpp
void PreToPost(char pre[], int l1, int h1, char post[], int l2, int h2) {
int half;//定义 half 变量记录左子树(右子树)结点个数
if (h1 >= l1) {//h1≥l1 才有意义(先序遍历)
post[h2] = pre[l1];//后序序列最后一个即为根结点,也就是先序序列第一个
half = (h1 - l1) / 2;//因为是满二叉树,所以左右子树结点个数相等
PreToPost(pre, l1 + 1, l1 + half, post, l2, l2 + half - 1);//递归处理左子树
PreToPost(pre, l1 + 1 + half, h1, post, l2 + half, h2 - 1);//递归处理右子树
}
}