前中后序遍历的递归与非递归算法,层序遍历

简介:

@[TOC]

前言:

  • 本文介绍,遍历的递归与非递归算法,其中后序遍历的非递归是最难的。
  • 博主收集的资料New Young,连载中。
  • 博主收录的问题:New Young
  • 转载请标明出处:New Young

思维导图

二叉树的遍历

建议

  1. 二叉树是一种递归结构!!!!!!!!!,这一点一定要时刻牢记。
  2. 递归利用 分而自治的思想 ,对于解决二叉树问题,很方便
  3. 递归我们一般建议先判断的情况,这会很大层度方便解决问题。
  4. 在使用递归时,如果不能一下完成所有代码,可以先完成大致的部分,之后慢慢补齐细节问题。

递归的3要素

  • 递归函数返回值的使用问题
  • 递归函数的参数设置问题
  • 递归的结束条件的判断问题。

二叉树的遍历

遍历的意思就是:打印一个二叉树中的每个结点的值,但是因为存在左右子树,二叉树的遍历有4种:前序遍历,中序遍历,后序遍历,层序遍历

PS:

  1. 对与空结点,默认打印NULL,方便查看。
  2. 遍历只能遍历玩当前的树,才能遍历其它的上一层树,或者兄弟层树。

前序遍历

要求在遍历二叉树的左右子树前, 先打印==根结点==的值,在遍历==左子树==,当==左子树遍历完==,再遍历==右子树==。

简记:根-左-右

前序遍历

递归

思路

依据三要素:

  1. 前序遍历函数不需要返回值,因此void
  2. 参数,只需要一个指针就行了,因此

void PreOrder(BTNode* root);// 二叉树前序遍历---递归法


3.结束条件:

if(root==NULL)
{

   printf("NULL ");
   return ;

}


4. 遍历顺序

printf("%c ", root->data);
PreOrder(root->left);
PreOrder(root->right)

完整代码

//前序遍历是根在左右孩子前,先访问二叉树根结点。
void PreOrder(BTNode* root)
{
    if(root==NULL)
    {
        printf("NULL ");
        return ;
    }
    printf("%c ", root->data);
    PreOrder(root->left);
    PreOrder(root->right);
}

非递归

思路

我们知道函数栈帧是发生在==栈区==的,而这个栈区的特点(先进后出,先用低地址后用高地址)和数据结构中的==栈==是相同的,因此这里我们可以通过数据结构的==栈==来模拟==函数栈帧==时的==栈区==。

  • 首先因为栈的特点–先进后出,因此左子树必须先被访问,因此我们先将右子树的地址存放的数组栈中,再将左子树存到数组栈中。

image-20220225205332977

代码

void PreOrderNoR(BTNode* root)// 二叉树前序遍历---非递归法
{
    assert(root);
    stack st;
    StackInit(&st);
    //我们需要根结点来进入右子树,因此要存根结点
    //同时一旦左子树访问完后,再进入右子树时需要根结点,后面就不需要了。因此pop
    SDateType p = root;
    while (1)
    {
        if (p != NULL)
        {
            printf("%c->", p->data);
            StackPush(&st, p);
            p = p->left;
        }
        else
        {
            printf("NULL->");
            break;
        }
    }

    while (!StackEmpty(&st)||p)//空树或者栈空结束
    {
        /
        //p为空说明,根和左子树遍历完毕。
        
        if (!StackEmpty(&st))
        {
            p = StackTop(&st);
            StackPop(&st);
            p = p->right;
            while (1)
            {
                if (p != NULL)
                {
                    printf("%c->", p->data);
                    StackPush(&st, p);
                    p = p->left;
                }
                else
                {
                    printf("NULL->");
                    break;
                }
            }
        }
    }
    printf("\n");

    StackDestroy(&st);

}

中序遍历

再访问根结点前,先访问左子树,当左子树为空后,再访问根结点,然后访问右子树。

简介:左–根–右

image-20220225211321230

递归

思路

同前序遍历差不多,需要将左子树递归完,再访问根结点。

代码

void InOrder(BTNode* root)// 二叉树中序遍历
{
    if (root == NULL)
    {
        printf("NULL ");
        return;
    }
    InOrder(root->left);
    printf("%c ", root->data);
    InOrder(root->right);
}

非递归

思路

同样使用栈模拟栈区。

但是数组栈中要先存放左子树的地址,当存入空树后,就可以访问根结点,然后遍历右子树,重复这个过程。

代码

void InOrderNoR(BTNode* root)// 二叉树中序遍历---递归法
{
    assert(root);
    stack st;
    StackInit(&st);
    //我们需要根结点来进入右子树,因此要存根
    SDateType p = root;
    //遍历到左子树最低端.
    while (1)
    {
        if (p != NULL)
        {
            StackPush(&st, p);
            p = p->left;
        }
        else
        {
            printf("NULL->");
            break;
        }
    }
    while (!StackEmpty(&st) || p)//空树或者栈空结束
    {
         
        if (!StackEmpty(&st))
        {
            SDateType  tmp= StackTop(&st);
            printf("%c->", tmp->data);
            StackPop(&st);
            p = tmp->right;
            //根结点已经遍历玩比,后续的遍历就不需要它了。
            //无论右子树是否为空,即使回来也不需要根结点。
            //遍历到左子树最低端.
            while (1)
            {
                if (p != NULL)
                {
                    StackPush(&st, p);
                    p = p->left;
                }
                else
                {
                    printf("NULL->");
                    break;
                }
            }
        }
    }
    printf("\n"); 
    StackDestroy(&st);
}

后序遍历

在访问根结点前,要把 左,右子树访问完,才能访问根结点。

简记:左-右-根

image-20220225211555017

递归

思路

和前序相似,只是需要先将左右访问结束,再访问根结点

代码

void PostOrder(BTNode* root)// 二叉树后序遍历
{
    if (root == NULL)
    {
        printf("NULL ");
        return;
    }
    PostOrder(root->left);
    PostOrder(root->right);
    printf("%c ", root->data);
}

非递归

思路

1.只有当左右子树访问完后,才能访问根结点。
因此要有一个pVisist指针来记录已访问的左或右子树。当pVisit=p->right||p- >right==NULL时,访问根结点。
2.我们访问存在的右子树时,仍把它看作一棵二叉树,因此仍需要压栈与出栈,
3.这里我打印NULL来帮助更好理解,

代码

void PostOrderNoR(BTNode* root)// 二叉树后序遍历---非递归法
{
    //思路:1.只有当左右子树访问完后,才能访问根结点。
    //      因此要有一个pVisist指针来记录已访问的左或右子树。当pVisit=p->right||p->right==NULL时,访问根结点。
    //      2.我们访问存在的右子树时,仍把它看作一棵二叉树,因此仍需要压栈与出栈,
    //      3.这里我打印NULL来帮助更好理解,
    assert(root);
    assert(root);
    stack st;
    StackInit(&st);
    SDateType p = root;
    SDateType pVisit = NULL;//标记左右子树的
    //简化版--只是将Top赋值给p了,但是要注意当右子树不为空,一定要直接进行左子树入栈。因为p的值并不是空。
    while (1)//先将左子树压栈,当左子树为空后,开始访问右子树。
    {
        if (p == NULL)
        {
            printf("NULL->");
            break;
        }
        else
        {
            StackPush(&st, p);
            p = p->left;
        }
    }
    while (!StackEmpty(&st) || p)//空树或者栈空结束
    {

        if (!StackEmpty(&st))
        {
             p = StackTop(&st);
            //只有当右子树为空或者右子树已访问才访问根结点,才能删除根结点.
            if (p->right == NULL || p->right == pVisit)
            {
                if (p->right == NULL)
                {
                    printf("NULL->");

                }
                StackPop(&st);
                printf("%c->", p->data);
                pVisit = p;
                //这一部代表该子树已访问,但是不确实是左子树还是右子树,因此需要 p->right == pVisit
            }
            else
            {
                p = p->right;//下次循环将是右子树的遍历。
                while (1)
                {
                    if (p == NULL)
                    {
                        printf("NULL->");
                        break;
                    }
                    else
                    {
                        StackPush(&st, p);
                        p = p->left;
                    }
                }
            }
        }
    }
    printf("\n");


    StackDestroy(&st);
}

层序遍历

  • 层序遍历与前中后不同。它要求必须将树一层的结点遍历完,再遍历下一层。
  • 层序遍历利用队列来实现层序遍历。
  • 注意因为队列的特点(先进先出),需要左子树要先入队列。

image-20220225213848032

image-20220225213940741

代码

void LevelOrder(BTNode* root)// 层序遍历
{
    if (root == NULL)
    {
        return;
    
    }
    Queue q;
    QueueInit(&q);
    QueuePush(&q, root);
    while (!QueueEmpty(&q))
    {
        BTNode* tmp = QueueTop(&q);
        printf("%c ", tmp->data);
        QueuePop(&q);
        if (tmp->left != NULL)
        {
            QueuePush(&q, tmp->left);
        
        }
        if (tmp->right != NULL)
        {
            QueuePush(&q, tmp->right);
        }

    }
    QueueDestroy(&q);
}
相关文章
|
1月前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
1月前
|
存储 缓存 算法
如何提高二叉树遍历算法的效率?
选择合适的遍历算法,如按层次遍历树时使用广度优先搜索(BFS),中序遍历二叉搜索树以获得有序序列。优化数据结构,如使用线索二叉树减少空指针判断,自定义节点类增加辅助信息。利用递归与非递归的特点,避免栈溢出问题。多线程并行遍历提高速度,注意线程安全。缓存中间结果,避免重复计算。预先计算并存储信息,提高遍历效率。综合运用这些方法,提高二叉树遍历算法的效率。
60 5
|
1月前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
53 2
|
1月前
|
算法
树的遍历算法有哪些?
不同的遍历算法适用于不同的应用场景。深度优先搜索常用于搜索、路径查找等问题;广度优先搜索则在图的最短路径、层次相关的问题中较为常用;而二叉搜索树的遍历在数据排序、查找等方面有重要应用。
39 2
|
1月前
|
机器学习/深度学习 JSON 算法
二叉树遍历算法的应用场景有哪些?
【10月更文挑战第29天】二叉树遍历算法作为一种基础而重要的算法,在许多领域都有着不可或缺的应用,它为解决各种复杂的问题提供了有效的手段和思路。随着计算机科学的不断发展,二叉树遍历算法也在不断地被优化和扩展,以适应新的应用场景和需求。
47 0
|
2月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
37 1
|
2月前
|
存储 算法
数据结构与算法学习十六:树的知识、二叉树、二叉树的遍历(前序、中序、后序、层次)、二叉树的查找(前序、中序、后序、层次)、二叉树的删除
这篇文章主要介绍了树和二叉树的基础知识,包括树的存储方式、二叉树的定义、遍历方法(前序、中序、后序、层次遍历),以及二叉树的查找和删除操作。
34 0
|
2月前
|
算法 定位技术
数据结构与算法学习九:学习递归。递归的经典实例:打印问题、阶乘问题、递归-迷宫问题、八皇后问题
本文详细介绍了递归的概念、重要规则、形式,并展示了递归在解决打印问题、阶乘问题、迷宫问题和八皇后问题等经典实例中的应用。
54 0
|
4月前
|
算法
【算法】递归、搜索与回溯——汉诺塔
【算法】递归、搜索与回溯——汉诺塔
|
5月前
|
算法 Python
python中算法递归错误(Recursion Errors)
【7月更文挑战第18天】
93 1