二叉树的遍历

本文讲述的是二叉树的遍历。

二叉树的遍历分为四种:先序遍历、中序遍历、后序遍历、层次遍历。

先序遍历

遍历过程:

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

先序遍历=> A  B  D  F  E  C  G  H  I  (A BDFE CGHI--->根 左 右)

中序遍历

遍历过程:

  1. 中序遍历其左子树
  2. 访问根节点
  3. 中序遍历其右子树
void InOrderTraversal(BinTree BT)
{
    if (BT) {
         InOrderTraversal(BT->Left);
         printf("%d",BT->Data);
         InOrderTraversal(BT->Right)
    }  
}

中序遍历=> D  B  E  F  A  G  H  C  I   (DBEF A GHCI  --->左 根 右)

后序遍历

遍历过程:

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

后序遍历顺序=> D  E  F  B  H  G  I  C  A  (DEFB HGIC A  --->左 右 根)

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

图中在从入口到出口的曲线上用叉号星号三角符三种符号分别标记出先序中序后序访问各节点的时刻。对于根节点来说,如果第一次碰到根节点就print,那么就是先序遍历,如果第二次碰到才print,就是中序遍历,第三次碰到print就是后序遍历。

 

二叉树的非递归遍历

非递归中栈的实现和递归实现中栈的情况有区别,非递归中自己实现的栈必须通过pop函数才能访问到栈顶的结点,而函数的递归调用时会自动存储当前函数的状态,包括参数和返回地址,下一层函数结束后会直接返回到上一层函数的状态。

中序遍历非递归遍历算法:

中序遍历如之前所说,在第二次遇到此元素时才会print出来,push是一次遇见,随后将元素压入栈,然后在pop访问时,在print 出来

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

  1. 遇到一个节点,就把它压栈,并去遍历它的左子树
  2. 左子树遍历结束后,从栈顶弹出这个结点并访问它
  3. 然后按其有指针再去中序遍历该结点的右子树
void InorderTraversal(BinTree BT)
{
    BinTree T=BT;
    Stack S = CreateStack(MaxSize)/*创建并初始化堆栈S*/
    while (T || !IsEmpty(S)) {
        while(T) { /*一直向左并将沿途结点压入堆栈*/
            Push(S,T);
            T = T->Left;
        }
        if (!IsEmpty(S))
        {
            T = Pop(S); /*结点弹出堆栈*/
            printf("%s5d",T->Data); /*访问打印结点*/
            T = T->Right; /*转向右子树*/
        }
    }
}

例题:非递归方法中序遍历下面这颗二叉树,其堆栈操作序列(P代表为push,O代表为pop)是什么?答:PPOPOOPPOO

 

先序遍历非递归遍历算法

先序遍历和中序遍历不同,在第一次遇见元素时,就会print出来,所以此代码中将print放在push前面。

void InorderTraversal(BinTree BT)
{
    BinTree T=BT;
    Stack S = CreateStack(MaxSize)/*创建并初始化堆栈S*/
    while (T || !IsEmpty(S)) {
        while(T) { /*一直向左并将沿途结点压入堆栈*/
            printf("%s5d",T->Data); /*访问打印结点*/
            Push(S,T);
            T = T->Left;
        }
        if (!IsEmpty(S))
        {
            T = Pop(S); /*结点弹出堆栈*/
            T = T->Right; /*转向右子树*/
        }
    }
}

那么后序遍历呢?

所以要想在第三次访问时输出结点,就必须在第二次pop访问到之后再将该结点push压入栈,待右子树访问完成后再将该结点pop出来输出。

void InOrderTraversal(BinTree BT){
        BinTree T=BT;
        Elemtype t1=FLAG;
        Stack S = CreatStrack(MaxSize);
        While(T || !IsEmpty(S)){
            while(T){
                Push(S,T);
                T=T->Left;
            }
            if(!IsEmpty(S)){
                T=Pop(S);
                while(T->Right==t1||!T->Right){
                    t1=T;
                    printf("%5d",T->Data);
                    T=Pop(S); 
                }
                Push(S,T);
                T=T->Riht;
            }
            }
        }

 层序遍历

一层一层访问树

队列实现:遍历从根结点开始,首先将根节点入队,然后开始执行循环:结点出队、访问该结点、其左右儿子入队

 

思路是这样:首先从根节点开始,把A放到队列里去,然后开始循环,从队列里抛出元素,然后将其左右儿子放进去,也就是把A抛出来,把B C放进队列里。然后将队列里的第一个元素抛出来,也就是B,然后把B的左右儿子D、F放进去。这样一次循环下去,这样的到的遍历顺序为:A B C D F G I E H

所以总结下来,层序基本过程:先根节点入队,然后:

  1. 从队列中取出一个元素
  2. 访问该元素所指结点
  3. 若钙元素所指结点的左、右孩子结点非空,则将其左右孩子的指针顺序入队
void LevelOrderTraversal(BinTree BT)
{
    Queue Q;BinTree T;
    if (!BT) return; /*若是空树则直接返回*/
    Q = CreateQueue(MaxSize);  /*创建并初始化队列Q*/
    AddQ(Q,BT);
    while (!IsEmptyQ(Q)) {
        T = DeleteQ(Q);
        printf("%d\n",T->Data ); /*访问取出队列的结点*/
        if (T->Left) AddQ(Q,T->Left);
        if (T->Right) AddQ(Q,T->Right);
    }
}

遍历二叉树的的应用:

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 PostOrderGetHight(BinTree BT)
{
    int HL,HR,MaxH;
    if (BT) {
        HL = PostOrderGetHight(BT->Left); /*求左子树的深度*/
        HR = PostOrderGetHight(BT->Right); /*求右子树的深度*/
        MaxH = (HL > HR)?HL:HR;  /*取左右子树较大的深度*/
        return (MaxH+1); /*返回树的深度*/
    }
    else return 0; /*空树深度为0*/
}

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

由两种遍历序列确定二叉树的前提是必须告诉了你中序遍历。没有中序遍历,我们就不能确定一个唯一的二叉树。比如:

 

上面图中这两个树,先序遍历顺序都是 A B,后序遍历顺序都是 B  A,所以就没办法确定究竟是哪一种。因为我们先序遍历的顺序是“根 左 右”,而后序遍历顺序是“左 右  根”,根容易确定,但是左右的边界并不好确定。

(1)先序和中序遍历序列来确定一棵二叉树:

  • 根据先序遍历序列第一个结点确定根节点
  • 根据根结点在中序遍历序列中分割出左右两个子序列
  • 左子树和右子树分别递归使用相同的方法继续分解。

例如:已知有颗5个结点的二叉树,其前序遍历序列是a????,中序遍历序列是a????,可以断定:该树根节点是a,且没有左子树。

例:先序序列:a b c d e f g h i j   后序序列: c b e d a h g i j f。

那么我们可以确定a是树的根节点,bcde为左子树的先序遍历顺序,fghij为右子树的先序遍历顺序。如图:继续以这种方法思考下去,就能得到整个树的结构:

类似的,后序和中序遍历序列也可以确定一棵二叉树。

 卡特兰数

一个序列最多可能存在多少种出栈序列?

posted @ 2017-11-05 10:22  珵城  阅读(1301)  评论(0)    收藏  举报