(浙大陈越版)数据结构 第三章 树(上) 3.3 二叉树的遍历

目录

3.3.1 遍历(先中后)

二叉树的遍历

先序遍历:

中序遍历

后序遍历

tips:

3.3.2 中序非递归遍历

非递归算法实现的基本思路:使用堆栈

中序遍历的非递归算法具体实现方法为:

3.3.3 层序遍历

难点

解决方法:

队列实现

思路

有如下二叉树作为例子:

遍历过程:(出队即printf)

思考:

3.3.4 遍历应用例子

1. 输出二叉树中的叶子结点

2. 求二叉树高度

3. 二元运算表达式树及其遍历

4. 由两种遍历序列确定二叉树


3.3.1 遍历(先中后)

二叉树的遍历

先序遍历:

遍历过程:

  1. 访问根结点
  2. 先序遍历其左子树(递归遍历,根结点左子树如果也有左右子树也要遍历)
  3. 先序遍历其右子树

既然需要递归遍历左右子树,我们设想以下如果直接用递归实现这个算法(伪码):

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

(浙大陈越版)数据结构 第三章 树(上) 3.3 二叉树的遍历_第1张图片

中序遍历

遍历过程:

  1. 先序遍历其左子树
  2. 访问根结点
  3. 先序遍历其右子树

代码就直接将Printf放到两次递归中间即可

void PreOrderTraversal( BinTree BT )
{
     if( BT ) {
         PreOrderTraversal( BT->Left );
         printf(“%d”, BT->Data);
         PreOrderTraversal( BT->Right );
     }
}

(浙大陈越版)数据结构 第三章 树(上) 3.3 二叉树的遍历_第2张图片

(浙大陈越版)数据结构 第三章 树(上) 3.3 二叉树的遍历_第3张图片

后序遍历

遍历过程:

  1. 先序遍历其右子树
  2. 访问根结点
  3. 先序遍历其左子树
void PostOrderTraversal( BinTree BT )
{
    if( BT ) {
        PostOrderTraversal( BT->Left );
        PostOrderTraversal( BT->Right);
        printf(“%d”, BT->Data);
    }
}

 (浙大陈越版)数据结构 第三章 树(上) 3.3 二叉树的遍历_第4张图片

tips:

先序、中序和后序遍历过程:遍历过程中经过结点的路线一 样,只是访问各结点的时机不同。

(浙大陈越版)数据结构 第三章 树(上) 3.3 二叉树的遍历_第5张图片

3.3.2 中序非递归遍历

非递归算法实现的基本思路:使用堆栈

中序遍历是按左、根、右的顺序,所以以上一节的树为例:

  1. 在一开始遇到A,A有左子树,所以将A放入堆栈
  2. 下一个要处理的是A的左子树B,B也有左子树,所以再把B放到堆栈里
  3. 下一个是B的左子树D,D没有孩子,D放入堆栈后就要出栈了,随后B出栈
  4. 此时B这棵树左、根都遍历完了,接下来处理右子树F。
  5. F有左子树,所以先把F放入堆栈
  6. F的左子树E没有孩子,E入栈然后出栈F再出栈,最后A的左子树处理完毕,A也出栈
  7. 此时遍历顺序为DBEFA,接着处理A的右子树
  8. A的右子树C,C有左子树,存入栈,处理其左子树
  9. G没有左子树,所以先入栈后出栈
  10. G有右子树H(中序遍历顺序),H没有左子树,入栈后紧接着出栈
  11. 处理完C的左子树,C可以出栈
  12. C有右子树I,I没有左子树,入栈后紧接着出栈
  13. 最后遍历顺序即为: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 层序遍历

难点

除了先中后序,我们还有一种遍历方式是层序遍历。二叉树是一个二维结构,而遍历的序列是一个线性结构,遍历二叉树的本质是将一个二维结构变成一维线性结构。

而难点在于,只有通过父结点才能访问到左右孩子结点,再通过子结点来访问子结点的左右孩子结点,因此如果变成线性结构,线性就意味着一个点只与另一个点有关,而子结点会有两个,当访问了一个子结点之后另一个子结点该怎么处理?

解决方法:

用一个存储结构来保存暂时不访问的结点。如堆栈、队列

队列实现

思路

遍历从根结点开始,首先将根结点入队,然后开始执行循环:

  • 从队列中取出一个元素
  • 访问该元素所指结点
  • 若该元素所指结点的左、右孩子结点非空, 则将其左、右孩子的指针顺序入队。

有如下二叉树作为例子:

(浙大陈越版)数据结构 第三章 树(上) 3.3 二叉树的遍历_第6张图片

遍历过程:(出队即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. 二元运算表达式树及其遍历

如下表达式树,其作用是对左右两颗树做根结点运算

(浙大陈越版)数据结构 第三章 树(上) 3.3 二叉树的遍历_第7张图片

4. 由两种遍历序列确定二叉树

若已知三种遍历中的任意两种遍历序列,能否唯一确定一颗二叉树呢?

答案是不能,必须要知道中序遍历+其他任意遍历序列才行

若已知先序和中序遍历序列,如何确定一颗二叉树?

思路:已知先序第一个结点是根结点,只要在中序中找到一样的结点,就能在中序遍历序列中分割开左子树和右子树,再递归使用这个方法分解全部结点。

(浙大陈越版)数据结构 第三章 树(上) 3.3 二叉树的遍历_第8张图片

你可能感兴趣的:(数据结构与算法,数据结构,算法)