目录
3.3.1 遍历(先中后)
二叉树的遍历
先序遍历:
遍历过程:
- 访问根结点
- 先序遍历其左子树(递归遍历,根结点左子树如果也有左右子树也要遍历)
- 先序遍历其右子树
既然需要递归遍历左右子树,我们设想以下如果直接用递归实现这个算法(伪码):
void PreOrderTraversal( BinTree BT )
{
if( BT ) {
printf(“%d”, BT->Data);
PreOrderTraversal( BT->Left );
PreOrderTraversal( BT->Right );
}
}
若有二叉树为A(B(DF(E))C(G(H)I)),其先序遍历访问顺序为——A-B-D-F-E-C-G-H-I
中序遍历
遍历过程:
- 先序遍历其左子树
- 访问根结点
- 先序遍历其右子树
代码就直接将Printf放到两次递归中间即可
void PreOrderTraversal( BinTree BT )
{
if( BT ) {
PreOrderTraversal( BT->Left );
printf(“%d”, BT->Data);
PreOrderTraversal( BT->Right );
}
}
后序遍历
遍历过程:
- 先序遍历其右子树
- 访问根结点
- 先序遍历其左子树
void PostOrderTraversal( BinTree BT )
{
if( BT ) {
PostOrderTraversal( BT->Left );
PostOrderTraversal( BT->Right);
printf(“%d”, BT->Data);
}
}
tips:
先序、中序和后序遍历过程:遍历过程中经过结点的路线一 样,只是访问各结点的时机不同。
3.3.2 中序非递归遍历
非递归算法实现的基本思路:使用堆栈
中序遍历是按左、根、右的顺序,所以以上一节的树为例:
- 在一开始遇到A,A有左子树,所以将A放入堆栈
- 下一个要处理的是A的左子树B,B也有左子树,所以再把B放到堆栈里
- 下一个是B的左子树D,D没有孩子,D放入堆栈后就要出栈了,随后B出栈。
- 此时B这棵树左、根都遍历完了,接下来处理右子树F。
- F有左子树,所以先把F放入堆栈
- F的左子树E没有孩子,E入栈然后出栈,F再出栈,最后A的左子树处理完毕,A也出栈
- 此时遍历顺序为DBEFA,接着处理A的右子树
- A的右子树C,C有左子树,存入栈,处理其左子树
- G没有左子树,所以先入栈后出栈
- G有右子树H(中序遍历顺序),H没有左子树,入栈后紧接着出栈
- 处理完C的左子树,C可以出栈
- C有右子树I,I没有左子树,入栈后紧接着出栈
- 最后遍历顺序即为:DBEFAGHCI
中序遍历的非递归算法具体实现方法为:
- 遇到一个结点就将其压入栈,并遍历其左子树(若有左子树的话),
- 当左子树遍历完毕,就从栈顶弹出这个结点并访问它
- 按结点右指针去遍历这个结点的右子树
void InOrderTraversal( BinTree BT )
{
BinTree T=BT;
Stack S = CreatStack( MaxSize ); /*创建并初始化堆栈S*/
while( T || !IsEmpty(S) ){
while(T){
/*一直向左并将沿途结点压入堆栈*/
Push(S,T);
T = T->Left;
}
if(!IsEmpty(S)){
T = Pop(S); /*结点弹出堆栈*/
printf(“%5d”, T->Data); /*(访问)打印结点*/
T = T->Right; /*转向右子树*/
}
}
}
也可推知,先序遍历也能用非递归实现,修改printf位置即可
void PreOrderTraversal( BinTree BT )
{
BinTree T=BT;
Stack S = CreatStack( MaxSize ); /*创建并初始化堆栈S*/
while( T || !IsEmpty(S) ){
while(T){
/*一直向左并将沿途结点压入堆栈*/
Push(S,T);
printf(“%5d”, T->Data); /*第一次碰到就打印*/
T = T->Left;
}
if(!IsEmpty(S)){
T = Pop(S); /*结点弹出堆栈*/
T = T->Right; /*转向右子树*/
}
}
}
但后序遍历不能简单挪动位置实现,因为后序遍历在操作完左右子树后需要返回到根结点,而且是在第三次经过根结点时将其pop出去,所以实现它需要用到两个堆栈
1. 初始化两个堆栈s1和s2,将根节点压入s1中。
2. 从s1中弹出栈顶节点,将其压入s2中。
3. 如果该节点有左子节点,则将左子节点压入s1中。
4. 如果该节点有右子节点,则将右子节点压入s1中。
5. 重复步骤2到步骤4,直到s1为空。
6. 从s2中依次弹出节点并输出,即可得到后序遍历序列。
void PostOrder(BinTree BT)
{
BinTree T = BT;
stack S = createstack();
while(T || IsEmpty(S)){
while(T){
push(S, T);
T = T->Left;
}
if(top(S)->Right != NULL){
T = top(S)->Right;
continue;
}
T = pop(S);
printf("%5d", T->data);
while(top(S)->Right == T){
T = pop(S);
printf("%5d", T->data);
}
T = top(S)->Right;
}
3.3.3 层序遍历
难点
除了先中后序,我们还有一种遍历方式是层序遍历。二叉树是一个二维结构,而遍历的序列是一个线性结构,遍历二叉树的本质是将一个二维结构变成一维线性结构。
而难点在于,只有通过父结点才能访问到左右孩子结点,再通过子结点来访问子结点的左右孩子结点,因此如果变成线性结构,线性就意味着一个点只与另一个点有关,而子结点会有两个,当访问了一个子结点之后另一个子结点该怎么处理?
解决方法:
用一个存储结构来保存暂时不访问的结点。如堆栈、队列
队列实现
思路
遍历从根结点开始,首先将根结点入队,然后开始执行循环:
- 从队列中取出一个元素
- 访问该元素所指结点
- 若该元素所指结点的左、右孩子结点非空, 则将其左、右孩子的指针顺序入队。
有如下二叉树作为例子:
遍历过程:(出队即printf)
A入队,第一个元素为A,队列为A
A出队,将A的左右儿子BC入队,此时第一个元素为B,队列为BC
B出队,将B的左右儿子DF入队,此时第一个元素为C,队列为CDF
C出队,将C的左右儿子GI入队,此时第一个元素是D,队列为DFGI
D出队,D没有左右儿子,此时第一个元素是F,队列为FGI
F出队,将F的左儿子E入队,此时第一个元素是G,队列为GIE
G出队,将G的右儿子H入队,此时第一个元素是I,队列为IEH
I出队,EH都没有子结点,依次出队
出队顺序为:A B C D F G I E H,序列恰好是每一层的结点顺序
void LevelOrderTraversal ( BinTree BT )
{
Queue Q;
BinTree T;
if ( !BT ) return; /* 若是空树则直接返回 */
Q = CreatQueue( MaxSize ); /*创建并初始化队列Q*/
AddQ( Q, BT );
while ( !IsEmptyQ( Q ) ) {
//Step1
T = DeleteQ( Q );
//step2
printf(“%d\n”, T->Data); /*访问取出队列的结点*/
//Step3
if ( T->Left ) AddQ( Q, T->Left );
if ( T->Right ) AddQ( Q, T->Right );
}
}
思考:
若将层序遍历中的队列实现修改为堆栈实现,是否也是一种遍历方法?
是的,改为堆栈后一般称其为深度优先遍历,简单来说就是访问根结点,再依次访问其左右子树直到遍历完整棵树。
3.3.4 遍历应用例子
1. 输出二叉树中的叶子结点
思路:在遍历算法中加入一个“检测该结点左右子树是否都为空”,即可得知是否为叶子结点
//以前序遍历为例
void PreOrderPrintLeaves( BinTree BT )
{
if( BT ) {
//若左右子树为空,则打印结点
if ( !BT-Left && !BT->Right )
{
printf(“%d”, BT->Data );
}
PreOrderPrintLeaves ( BT->Left );
PreOrderPrintLeaves ( BT->Right );
}
}
2. 求二叉树高度
二叉树的总高度=左右子树的最大高度+1,因此要求二叉树总高度,需要先知道左右子树的高度。后序遍历的遍历顺序显然很符合这样的算法
int PostOrderGetHeight( BinTree BT )
{
int HL, HR, MaxH;
if( BT ) {
HL = PostOrderGetHeight(BT->Left); /*求左子树的深度*/
HR = PostOrderGetHeight(BT->Right); /*求右子树的深度*/
MaxH = (HL > HR)? HL : HR; /*取左右子树较大的深度*/
return ( MaxH + 1 ); /*返回树的深度*/
}
else return 0; /* 空树深度为0 */
}
3. 二元运算表达式树及其遍历
如下表达式树,其作用是对左右两颗树做根结点运算
4. 由两种遍历序列确定二叉树
若已知三种遍历中的任意两种遍历序列,能否唯一确定一颗二叉树呢?
答案是不能,必须要知道中序遍历+其他任意遍历序列才行
若已知先序和中序遍历序列,如何确定一颗二叉树?
思路:已知先序第一个结点是根结点,只要在中序中找到一样的结点,就能在中序遍历序列中分割开左子树和右子树,再递归使用这个方法分解全部结点。