1
0
mirror of https://github.com/Didnelpsun/CS408.git synced 2026-11-06 07:07:43 +08:00

更新树

This commit is contained in:
Didnelpsun
2021-09-25 23:32:18 +08:00
parent 9d12630f28
commit f2617936b0
4 changed files with 228 additions and 26 deletions

View File

@@ -66,4 +66,44 @@ $D.250$
解:$B$。在非空的二叉树当中,由度为$0$和$2$的结点数的关系$n_0=n_2+1$可知$n_2=123$;总结点数$n=n_0+n_1+n_2=247+n_1$,其最大值为$248$(由于完全二叉树$n_1$的取值为$1$或$0$,当$n=1$时结点最多)。注意,由完全二叉树总结点数$n$的奇偶性可以确定$n_1$的值,但不能根据$n_0$来确定的$n_1$值。
### 二叉树遍历
### 二叉树遍历
#### 结点关系
**例题** 在二叉树中有两个结点$m$和$n$,若$m$是$n$的祖先,则使用()可以找到从$m$到$n$的路径。
$A.$先序遍历
$B.$中序遍历
$C.$后序遍历
$D.$层次遍历
解:$C$。首先无论是什么遍历方式都会是从左到右的顺序,所以最先找到的一定是左边的结点,最后找到的一定是右边的结点。$m$是$n$的祖先。先序遍历访问次序:左根右,当$n$在左边遍历时会找到$m$到$n$的路径,而当$n$在右边时,由于已经遍历完根节点和左子树,则根和左部分的结点没用了就会出栈(使用递归算法时函数数据由栈保存),则$m$就丢掉了,也就不能找到$m$到$n$的路径。同理中序遍历也是会丢掉根和左部分,若$n$在右边则会丢失路径。层次遍历每遍历都会丢掉根节点,所以也不能找到。而后序遍历最后遍历根结点,所以根结点会一直保留到子结点全部遍历完才会丢弃,则此时就能找到$n$到$m$的路径。
#### 遍历序列
**例题** 在二叉树的前序序列、中序序列和后序序列中,所有叶子结点的先后顺序()。
$A.$都不相同
$B.$完全相同
$C.$前序和中序相同,而与后序不同
$D.$中序和后序相同,而与前序不同
解:$B$。在三种遍历方式中,访问左右子树的先后顺序是不变的,只是访问根结点的顺序不同,因此叶子结点的先后顺序完全相同。此外,读者可以采用特殊值法,画一个结点数为三的满二叉树,采用三种遍历方式来验证答案的正确性。
**例题** 若一棵二叉树的前序遍历序列和后序遍历序列分别为$1,2,3,4$和$4,3,2,1$,则该二叉树的中序遍历序列不会是()。
$A.1,2,3,4$
$B.2,3,4,1$
$C.3,2,4,1$
$D.4,3,2,1$
解:$C$。

View File

@@ -148,15 +148,26 @@
#### 线索化
线索化就是要遍历一遍二叉树,然后对当前结点进行处理。
为了区分其左右孩子指针是指向什么,要在结点中新建两个$tag$位,如当$ltag=0$表示$lchild$指向的是左孩子结点,而为$1$表示其指向前驱。
+ 确定线索二叉树类型——中序、先序或后序。
+ 按照对应遍历规则,确定每个结点访问顺序并写上编号。
+ 将$n+1$个空链域连上前驱后继。
+ 没有前驱或后继就指向`NULL`。
+ 这种结构称为线索链表。
在先序线索化的时候要注意,由于是根左右的顺序,在访问根节点时候进行线索化可能就会将左孩子节点由指向左孩子变成指向前驱的线索(该结点本来就没有左孩子),然后处理左子树时会跳到这里指向前驱的线索即前一个结点,就会不断在这里循环(程序把前驱当作左孩子不断回撤)。所以在先序遍历二叉树时要根据$ltag$值判断是否是前驱线索再进行遍历左子树。而右孩子结点则不会有这个问题,因为访问顺序必然是左右,所以不管二叉树右孩子结点指向的是右孩子还是后继都是在当前访问结点后应该访问的结点。
而中序线索化和后序线索化都没有这种问题,因为当前结点的前驱在此时按处理顺序都已经处理完了。
同时三种线索化都需要处理最后一个结点,当最后一个结点的右孩子指针为`NULL`,要$pre->rtag=1$。
#### 查找前驱后继
如果某节点的左右孩子指针有孩子而不是指向前驱后继,那么怎么找其前驱后继?
+ 中序线索二叉树中找到结点$*P$的中序后继$next$:
+ 若$p$右孩子指针指向后继:$p->rtag==1$,则$next=p->rchild$。
+ 若$p$右孩子指针指向右子树根结点:$p->rtag==0$,则$next=p$右子树中最左下结点。
@@ -167,23 +178,27 @@
+ 所以可以利用线索对二叉树实现非递归的逆向中序遍历。
+ 先序线索二叉树中找到结点$*P$的先序后继$next$:
+ 若$p$右孩子指针指向后继:$p->rtag==1$,则$next=p->rchild$。
+ 若$p$右孩子指针指向右子树根结点:$p->rtag==0$,如果$p$有左孩子,则$p->next=p->lchild$,如果$p$没有左孩子,则$p->next=p->rchild$。
+ 若$p$右孩子指针指向右子树根结点:$p->rtag==0$,如果$p$有左孩子,则$p->next=p->lchild$,如果$p$没有左孩子,则肯定有右孩子,$p->next=p->rchild$。
+ 所以可以利用线索对二叉树实现非递归的先序遍历。
+ 先序线索二叉树中找到结点$*P$的中序前驱$pre$:
+ 先序线索二叉树中找到结点$*P$的先序前驱$pre$:
+ 若$p$左孩子指针指向前驱:$p->ltag==1$,则$pre=p->lchild$。
+ 若$p$左孩子指针指向左子树根结点:$p->ltag==0$,先序遍历中左右子树的根结点只可能是后继,所以这时候就找不到$p$的前驱,如果没有父结点只能从头开始先序遍历。
+ 如果有父结点,则又有三种情况:
+ 若$p$左孩子指针指向左子树根结点:$p->ltag==0$,先序遍历中左右子树的根结点只可能是后继,必须向前找。
+ 如果没有父结点所以这时候就找不到$p$的前驱,只能从头开始先序遍历。
+ 如果有父结点,则又有四种情况:
+ $p$为左孩子,则根据根左右,$p$的父结点为根所以在$p$的前面,$p->pre=p->parent$。
+ $p$为右孩子,其左兄弟为空,则根据根左右,顺序为根右,所以$p->pre=p->parent$。
+ $p$为右孩子且有左兄弟,根据根左右,p的前驱就是左兄弟子树中最后一个被先序遍历的结点,即在p的左兄弟子树中优先右子树遍历的底部。
+ 后序线索二叉树中找到结点$*P$后中序后继$next$:
+ $p$为右孩子且有左兄弟,根据根左右,$p$的前驱就是左兄弟子树中最后一个被先序遍历的结点,即在$p$的左兄弟子树中优先右子树遍历的底部。
+ 若$p$是根节点,则没有先序前驱。
+ 后序线索二叉树中找到结点$*P$后序后继$next$:
+ 若$p$右孩子指针指向后继:$p->rtag==1$,则$next=p->rchild$。
+ 若$p$右孩子指针指向右子树根结点:$p->rtag==0$,则根据左右根顺序,左右孩子结点必然是$p$的前驱而不可能是后继,所以找不到后序后继,如果没有父结点只能使用从头开始遍历的方式。
+ 如果有父结点则又有三种情况:
+ 若$p$右孩子指针指向右子树根结点:$p->rtag==0$,则根据左右根顺序,左右孩子结点必然是$p$的前驱而不可能是后继,所以找不到后序后继。
+ 如果没有父结点只能使用从头开始遍历的方式。
+ 如果有父结点则又有四种情况:
+ $p$为右孩子,根据左右根,所以$p->next=p->parent$。
+ $p$为左孩子,右孩子为空,根据左右根,所以$p->next=p->parent$。
+ $p$为左孩子,右孩子非空,根据左右根,所以$p->next=$右兄弟子树中第一个被后序遍历的结点。
+ 后序线索二叉树中找到结点$*P$后中序前驱$pre$:
+ $p$为左孩子,右孩子非空,根据左右根,所以$p->next=$右兄弟子树中第一个被后序遍历的结点,即右子树优先左兄弟子树遍历的底部。
+ 若$p$是根结点,则没有后序后继。
+ 后序线索二叉树中找到结点$*P$后序前驱$pre$:
+ 若$p$左孩子指针指向前驱:$p->ltag==1$,则$pre=p->lchild$。
+ 若$p$左孩子指针指向左子树根结点:$p->ltag==0$,则又有两种情况:
+ 若$p$有右孩子,则按照左右根的情况遍历,右在根的前面,所以$p->pre=p->rchild$。