二叉树的非递归遍历

目录

前言 

声明与定义

树的定义

堆栈的定义

核心思想

为什么要应用栈?

 如何理解当前输出的结点?

算法的雏形?

中序遍历

先序遍历 

后序遍历 

最后的注释 


前言 

二叉树的遍历主要分为三个大的模块:

递归遍历递归的应用)、非递归遍历的应用)、层序遍历队列的应用)。

本篇主要讲述先、中、后序的非递归遍历。

建议在学习非递归遍历之前,先搞清楚递归遍历与层序遍历,以便对二叉树的遍历拥有一个初步理解,相较于递归遍历和层序遍历来讲,非递归遍历要求我们对二叉树的遍历拥有更加深刻的理解。

递归遍历与层序遍历(希望这篇文章能帮助你了解二叉树的遍历)

声明与定义

本篇重点在于非递归遍历的算法,因而栈的定义与基本操作就不再赘述。

树的定义

typedef char ElementType;
typedef struct TNode *Position;
typedef Position BinTree;
struct TNode
{
    ElementType Data;
    BinTree Left;
    BinTree Right;
    int flag;
};

堆栈的定义

typedef Position SElementType;
typedef struct SNode *PtrToSNode;
struct SNode
{
    SElementType Data;
    PtrToSNode Next;
};
typedef PtrToSNode Stack;

Stack CreateStack();                //创建一个空栈
bool IsEmpty(Stack S);              //判断栈是不是空栈
bool Push(Stack S, SElementType X); //元素x入栈s
SElementType Pop(Stack S);          //删除并仅返回S的栈顶元素
SElementType Peek(Stack S);         //仅返回S的栈顶元素

核心思想

为什么要应用栈?

不难发现,在遍历二叉树的过程中, 我们会发现“读到”和“输出”是两码事,即当我们读到当前结点时,我们并不一定会输出该结点的数据值,这要取决于我们所选取的遍历顺序。

正是因为当前所读取的结点可能不应该被立即输出,所以才需要定义一个数据结构来存放那些已经读取过却还没输出过的结点,即栈(后续再体会为什么用栈这种数据结构)。

因而每个结点都必须有一组入栈和出栈的操作(尽管当读取到某一结点后,就应该立即输出,我们也最好履行一组入栈和出栈操作),当栈为空栈时,说明我们已经遍历过整个二叉树了,即遍历结束。

(立即的含义:即该元素入栈之后,并没有其他元素入栈,下一个对栈的操作就是出栈)

 如何理解当前输出的结点?

便于说明算法的意义,我们把输出的结点都理解为子树的根节点。也正是把每个结点都看成子树的根节点,才能满足每个结点都进行一组入栈和出栈操作的需求。 

算法的雏形?

自己模拟遍历顺序,主要从“读取”和“输出”两个方面进行,观察其过程,并转化为类c语言程序。 

中序遍历

void InorderTraversal(BinTree BT)
{
    Stack s = CreateStack();
    if (BT)
    {
        Push(s, BT);
        Position p;
        //先读取根节点,非空则入栈
        while (!IsEmpty(s))
        //进入while循环,栈空则跳出循环,栈空意味着遍历完毕
        {
            while (Peek(s)->Left)
            //判断栈顶元素的左孩子是否为空?
                Push(s, Peek(s)->Left);
                //非空:左孩子入栈,进行(1)步骤
            p = Pop(s);
            printf(" %c", p->Data);
            //空:输出栈顶元素,并出栈
            //用p来保存栈顶元素(因为后续还需要判断栈顶元素的右孩子是否为空)
            while (!p->Right && !IsEmpty(s))
            //判断栈顶元素右孩子是否为空
            {
                p = Pop(s);
                printf(" %c", p->Data);
                //空:进行(a)步骤
            }
            if (p->Right)
            //非空:右孩子入栈,进行(1)步骤  
                Push(s, p->Right);
        }
    }
}

算法的描述

(元素入栈是为了输出元素和找到元素的右孩子,因而当元素被输出和元素右孩子被找到后,就应出栈)

(一)先读取根节点,非空则入栈

(二)进入while循环,栈空则跳出循环,栈空意味着遍历完毕

        (1)判断栈顶元素的左孩子是否为空?

                (a)空:输出栈顶元素,判断栈顶元素右孩子是否为空,并出栈

                        (i)空:进行(a)步骤

                        (ii)非空:右孩子入栈,进行(1)步骤

                (b)非空:左孩子入栈,进行(1)步骤

先序遍历 

//先序遍历:入栈与输出操作总是一起执行
void PreorderTraversal(BinTree BT)
{
    Stack s = CreateStack();
    if (BT)
    {
        Push(s, BT);
        printf(" %c", BT->Data);
        //根节点入栈并输出
        Position p;
        while (!IsEmpty(s))
        //进入while循环,栈空则跳出循环,栈空意味着遍历完毕
        {
            while (Peek(s)->Left)
            //判断栈顶元素的左孩子是否为空?
            {
                Push(s, Peek(s)->Left);
                printf(" %c", Peek(s)->Data);
                //非空:左孩子入栈并输出,进行(1)步骤
            }
            if (!IsEmpty(s))
            {
                p = Pop(s);
                while (!p->Right && !IsEmpty(s))
                //判断栈顶元素的右孩子是否为空,并出栈
                //用p来保存栈顶元素(因为后面还需要判断栈顶元素的右孩子是否为空)
                    p = Pop(s);
                    //空:进行(a)步骤
                if (p->Right)
                //非空:右孩子入栈并输出,进行(1)步骤
                {
                    Push(s, p->Right);
                    printf(" %c", Peek(s)->Data);
                }
            }
        }
    }
}

算法的描述

(元素入栈是为了找到该元素的右孩子,因而当元素右孩子被找到后,就该出栈)

(一)根节点入栈并输出

(二)进入while循环,栈空则跳出循环,栈空意味着遍历完毕

        (1)判断栈顶元素的左孩子是否为空?

                (a)空:判断栈顶元素的右孩子是否为空,并出栈

                        (i)空:进行(a)步骤

                        (ii)非空:右孩子入栈并输出,进行(1)步骤

                (b)非空:左孩子入栈并输出,进行(1)步骤

后序遍历 

//后序遍历:出栈与输出操作总时一起执行
void PostorderTraversal(BinTree BT)
{
    Stack s = CreateStack();
    if (BT)
    {
        Push(s, BT);
        //根节点入栈
        while (!IsEmpty(s))
        //进入while循环,栈空则跳出循环,栈空意味着遍历完毕
        {
            while (Peek(s)->Left && Peek(s)->Left->flag != 1)
            //判断栈顶元素左孩子是否为空?
                Push(s, Peek(s)->Left);
                //非空:左孩子入栈,进行(1)步骤
            if (Peek(s)->Right && Peek(s)->Right->flag != 1)
            //空:判断栈顶元素右孩子是否为空?
                Push(s, Peek(s)->Right);
                //非空:右孩子入栈,进行(1)步骤
            else
            {
                Peek(s)->flag = 1;
                printf(" %c", Pop(s)->Data);
                //空:输出栈顶元素,并出栈,进行(1)步骤
            }
        }
    }
}

算法的描述

(元素入栈是为了输出元素和找到元素的左、右孩子,又因为我们默认左子树优先于右子树,所以当我们找到右孩子时就意味着左孩子已经被找到,因而当元素被输出和右孩子被找到后,就该出栈)

(一)根节点入栈

(二)进入while循环,栈空则跳出循环,栈空意味着遍历完毕

        (1)判断栈顶元素左孩子是否为空?

                (a)空:判断栈顶元素右孩子是否为空?

                        (i)空:输出栈顶元素,并出栈,进行(1)步骤

                        (ii)非空:右孩子入栈,进行(1)步骤

                (b)非空:左孩子入栈,进行(1)步骤

最后的注释 

IsEmpty()

在出栈操作前都需要检验栈是否已经为空(应用于先序和中序遍历)

flag

flag为1表示元素已经进行过一组入栈、出栈操作。因为在后序遍历中,出栈与输出操作总是一起执行,所以不难理解,每个元素都只能有且仅有一组入、出栈操作,不能多也不能少。(应用于后序遍历) 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗马尼亚硬拉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值