【数据结构】非递归实现二叉树的前 + 中 + 后 + 层序遍历(听说面试会考?)

news2024/11/20 6:23:45

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:数据结构
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


目录

  • 一、需要使用到的代码
      • 1.1 二叉树的基本实现
      • 1.2 栈
      • 1.3 队列
  • 二、非递归实现二叉树的前序遍历
      • 2.1 思路
      • 2.2 代码实现
  • 三、非递归实现二叉树的前序遍历
      • 3.1 思路
      • 3.2 代码实现
  • 四、后序遍历
      • 4.1 思路
      • 4.2 代码实现
  • 五、层序遍历
      • 5.1 思路
      • 5.2 代码实现
      • 5.3 整个测试结果
  • 六、总结

一、需要使用到的代码

1.1 二叉树的基本实现

二叉树的基本实现在以往博客已经详细讨论过了,这里直接给出本篇博客的所需用到的源代码。【数据结构】二叉树的链式结构(笔记总结)

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int DataType;

typedef struct BinaryTree
{
    DataType _data;
    struct BinaryTree *_left;
    struct BinaryTree *_right;
} BinaryTree;

// 创建结点
BinaryTree *CreateNode(DataType x)
{
    BinaryTree *newnode = (BinaryTree *)malloc(sizeof(BinaryTree));
    if (newnode == NULL)
    {
        printf("CreateNode failed\n");
        return NULL;
    }
    newnode->_left = NULL;
    newnode->_right = NULL;
    newnode->_data = x;

    return newnode;
}

// 建树
BinaryTree *CreateTree()
{
    // 假定树的模型如下所示
    //    1
    //  2   3
    // 4  5    6
    BinaryTree *node1 = CreateNode(1);
    BinaryTree *node2 = CreateNode(2);
    BinaryTree *node3 = CreateNode(3);
    BinaryTree *node4 = CreateNode(4);
    BinaryTree *node5 = CreateNode(5);
    BinaryTree *node6 = CreateNode(6);

    node1->_left = node2;
    node1->_right = node3;
    node2->_left = node4;
    node2->_right = node5;
    node3->_right = node6;

    return node1;
}

// 递归实现前序遍历
void PreOrder(BinaryTree *root)
{
    if (root == NULL)
        return;

    printf("%d ", root->_data);
    PreOrder(root->_left);
    PreOrder(root->_right);
}

// 递归实现中序遍历
void InOrder(BinaryTree *root)
{
    if (root == NULL)
        return;

    InOrder(root->_left);
    printf("%d ", root->_data);
    InOrder(root->_right);
}

// 递归实现后序遍历
void PostOrder(BinaryTree *root)
{
    if (root == NULL)
        return;

    PostOrder(root->_left);
    PostOrder(root->_right);
    printf("%d ", root->_data);
}

在以上源代码中,我另外给出了递归实现遍历的版本,目的是为了和非递归(迭代)版进行对比。

1.2 栈

// 需要存储的数据类型是二叉树结构体的指针!
typedef BinaryTree *DataType1;

typedef struct stack
{
    DataType1 *_a;
    int size;
    int capacity;
} stack;

void StackInit(stack *st)
{
    st->_a = (DataType1 *)malloc(sizeof(DataType1) * 4); // 假设默认大小为4
    if (st->_a == NULL)
    {
        printf("st->_a malloc failed\n");
        return;
    }
    st->capacity = 4;
    st->size = 0;
}

// 入栈
void PushStack(stack *st, DataType1 val)
{
    if (st->capacity == st->size)
    {
    	// 每次扩大两倍
        DataType1 *newcapacity = (DataType1 *)realloc(st->_a, sizeof(DataType1) * 4 * 2); 
        if (newcapacity == NULL)
        {
            printf("st->_a realloc failed\n");
            return;
        }
        st->_a = newcapacity;
        st->capacity *= 2;
    }

    st->_a[st->size] = val;
    st->size++;
}

// 判断栈是否为空
bool StackEmpty(stack *st)
{
    return st->size == 0;
}

// 出栈
void PopStack(stack *st)
{
    if (StackEmpty(st))
    {
        printf("stack is empty\n");
        return;
    }
    st->size--;
}

// 访问栈顶元素
DataType1 StackTop(stack *st)
{
     return st->_a[st->size - 1];
}

栈是后面前、中、后序遍历所需要的。但是需要注意的是:栈需要存储的数据类型是二叉树结构体的指针。为什么?在后面会详细说明。

1.3 队列

// 需要存储的数据类型是二叉树结构体的指针
typedef BinaryTree *QueueType;
typedef struct QueueNode
{
    QueueType _val;
    struct QueueNode *_next;
} QueueNode;

typedef struct Queue
{
    QueueNode *tail;
    QueueNode *head;
} Queue;

// 初始化队列
void InitQueue(Queue *q)
{
    q->tail = q->head = NULL;
}

// 插入元素
void PushQueue(Queue *q, QueueType x)
{
    QueueNode *newnode = (QueueNode *)malloc(sizeof(QueueNode));
    if (newnode == NULL)
    {
        printf("newnode create failed\n");
        return;
    }
    newnode->_next = NULL;
    newnode->_val = x;

    if (q->head == NULL)
    {
        if (q->tail != NULL)
            return;
        q->head = q->tail = newnode;
    }
    else
    {
        q->tail->_next = newnode;
        q->tail = newnode;
    }
}

// 判断队列是否为空
bool QueueEmpty(Queue *q)
{
    return (q->head == NULL) && (q->tail == NULL);
}

// 队头元素
QueueType FrontQueue(Queue *q)
{
    return q->head->_val;
}

// 出队列
void PopQueue(Queue *q)
{
    if (QueueEmpty(q))
    {
        printf("Queue is empty\n");
        return;
    }

    if (q->head->_next == NULL)
    {
        free(q->head);
        q->head = q->tail = NULL;
    }
    else
    {
        QueueNode *next = q->head->_next;
        free(q->head);
        q->head = next;
    }
}

队列是为层序遍历所准备的同理地,队列存储的数据类型同样也要是二叉树结构体指针。

为了快速实现二叉树的遍历,以上栈和队列的细节代码并不完整。详细的可以参考往期博客:点击跳转

话不多说,现在进入正题!

二、非递归实现二叉树的前序遍历

2.1 思路

请看下图

在这里插入图片描述

最后回过头来讲讲为什么栈的存储的类型要是二叉树结构体的指针?

通过上图,我们总结了:结点出栈,需要带入其左右孩子。因此,如果不是其结构体指针,那么也就无法将root的左右孩子入栈了。注意:也不能存结构体。因为一个结构体太大了,而指针的大小只有4/8字节

2.2 代码实现

// 非递归实现前序遍历
void PreOrder_nonR(BinaryTree *root)
{
	// 1. 需要一个赋值栈
    stack st;
    StackInit(&st);
	
	// 2. 如果根结点不为空入栈
    if (root != NULL)
    {
        PushStack(&st, root);
    }
    
    while (!StackEmpty(&st))
    {
    	// 记录栈顶元素
        BinaryTree *top = StackTop(&st);
		// 3. 出栈后带入其左右孩子
        PopStack(&st);
        printf("%d ", top->_data);
        // !要注意顺序:先带右孩子,再带左孩子 
        if (top->_right)
            PushStack(&st, top->_right);

        if (top->_left)
            PushStack(&st, top->_left);
    }
}

三、非递归实现二叉树的前序遍历

3.1 思路

请看下图

在这里插入图片描述

3.2 代码实现

void InOrder_nonR(BinaryTree *root)
{
	// 1. 需要一个辅助栈
    stack st;
    StackInit(&st);
    
    // 如果一开始根结点为NULL
    // 直接返回
    if (root == 0)
        return;

    // 2.遍历左孩子,将其全部入栈
    BinaryTree *cur = root;
    while (cur)
    {
        PushStack(&st, cur);
        cur = cur->_left;
    }

    while (!StackEmpty(&st))
    {
    	// 出栈打印
        BinaryTree *top = StackTop(&st);
        PopStack(&st);
        printf("%d ", top->_data);

        // 特判:出栈结点存在右孩子
        if (top->_right)
        {
        	// 将其入栈
            PushStack(&st, top->_right);
            // 然后还要特殊判断这个右孩子有没有左孩子
            // 因为我们要保证 先左 再根 再右
            BinaryTree *cur2 = top->_right;
            while (cur2->_left)
            {
                PushStack(&st, cur2->_left);
                cur2 = cur2->_left;
            }
        }
    }
}

四、后序遍历

4.1 思路

后序遍历我就不画图了,本人一开始写非递归后序遍历写了好久,都失败了(太菜了)。直到我看到一个视频,才知道原来后序遍历这么简单!

首先可以参考前序遍历(根左右)。因此,我们只要将前序遍历的代码逻辑的遍历顺序左和右对调一下,就变成根右左,最后再对其逆序,就是左右根,也就是后序遍历的结果了

4.2 代码实现

void PostOrder_nonR(BinaryTree *root)
{
    int res[6]; // 为了逆序
    int i = 0; // 用于遍历res数组
    memset(res, 0, sizeof(int));

    stack st;
    StackInit(&st);

    if (root != NULL)
    {
        PushStack(&st, root);
    }
    while (!StackEmpty(&st))
    {
        BinaryTree *top = StackTop(&st);
        PopStack(&st);
        res[i++] = top->_data;
		
		// 将前序遍历的代码逻辑的遍历顺序对调
        if (top->_left)
            PushStack(&st, top->_left);
        if (top->_right)
            PushStack(&st, top->_right);
    }
	// 最后逆序输出即可
    for (int k = i - 1; k >= 0; k--)
    {
        printf("%d ", res[k]);
    }
    printf("\n");
}

五、层序遍历

5.1 思路

层序遍历顾名思义就是一层一层遍历,那么就不能使用栈,得使用队列。

步骤:使用一个队列,出一个结点,带入它的孩子结点

  • 如果树不为空,就先让根结点入队列
    在这里插入图片描述

  • 然后出队列(打印1),再把1的左孩子和右孩子带入队列
    在这里插入图片描述

  • 接着让2出队列,再把2的孩子入队列
    在这里插入图片描述

  • 同理,再让4出队列,把它的孩子入队列
    在这里插入图片描述

  • 最后如果队列为空,即完成层序遍历
    在这里插入图片描述

5.2 代码实现

void LevelOrder(BinaryTree *root)
{
	// 1. 需要辅助队列
    Queue q;
    InitQueue(&q);
	
	// 如果一开始根结点root不为空
	// 则入队列
    if (root != NULL)
        PushQueue(&q, root);
	
	// 然后出双亲结点,带入子结点
    while (!QueueEmpty(&q))
    {
        BinaryTree *front = FrontQueue(&q);
        PopQueue(&q);

        printf("%d ", front->_data);
		
		// 带入子结点
        if (front->_left)
            PushQueue(&q, front->_left);

        if (front->_right)
            PushQueue(&q, front->_right);
    }
}

5.3 整个测试结果

在这里插入图片描述

六、总结

对于数据结构,还是得建议多画画图。最后我不将所有的代码整合到一块,读者只需理解,最好自己实现一遍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1204152.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql之正则表达式匹配

题目&#xff1a; 今天在牛客网看到一道关于数据库正则表达式匹配的问题&#xff0c;发现自己一点不会做。 正则表达式&#xff1a; 一、正则表达式 MySQL 正则表达式通常是在检索数据库记录的时候&#xff0c;根据指定的匹配模式匹配记录中 符合要求的特殊字符串。MySQL 的…

利用角色roles上线wordpress项目

角色订制&#xff1a;roles ① 简介 对于以上所有的方式有个弊端就是无法实现复用假设在同时部署Web、db、ha 时或不同服务器组合不同的应用就需要写多个yml文件。很难实现灵活的调用。   roles 用于层次性、结构化地组织playbook。roles 能够根据层次型结构自动装载变量文…

linux下使用Docker Compose部署Spug实现公网远程访问

&#x1f4d1;前言 本文主要是linux下使用Docker Compose部署Spug实现公网远程访问的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &am…

openlayers 注册投影

注册投影 openlayers 默认支持的坐标系有4326&#xff08;基于美国wgs84坐标系&#xff09;和3857&#xff08;墨卡托投影&#xff09;两种。 所以如果我们想要使用比如4490坐标系&#xff0c;或者4547坐标系&#xff0c;就需要先注册&#xff0c; 注册4490示例代码如下 如…

【Linux进程】进程控制

目录 一、进程创建 1.2 fork函数初识 1.2 fork函数返回值 1.3 写时拷贝 1.4 fork常规用法 1.5 fork调用失败的原因 二、进程终止 2.1 进程退出场景 2.2 进程退出码 2.2.1 用strerror打印错误信息 2.2.2 errno全局变量 2.3 进程常见退出方法 2.3.1 进程正常退出 2…

力扣刷题-二叉树-对称二叉树

101 对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false 思路 我的思路…

RK3568笔记五:基于Yolov5的训练及部署

若该文为原创文章&#xff0c;转载请注明原文出处。 一. 部署概述 环境&#xff1a;Ubuntu20.04、python3.8 芯片&#xff1a;RK3568 芯片系统&#xff1a;buildroot 开发板&#xff1a;ATK-DLRK3568 开发主要参考文档&#xff1a;《Rockchip_Quick_Start_RKNN_Toolkit2_C…

2023测试工程师必看系列:用JMeter+ANT进行接口自动化测试,并生成HTML测试报告

【文章末尾给大家留下了大量的福利】 小伙伴们&#xff0c;用python做接口自动化是不是写代码比较繁琐&#xff0c;而且没有python代码基础的小伙伴根本无从下手对吧&#xff01;今天我们来学习一下如何使用JMeter工具实现接口自动化测试。 01 安装 1、安装JDK&#xff0c;…

weblogic集群配置信息,IIOP问题解决,节点配置管理

第一、创建域的时候&#xff0c;管理服务器&#xff0c;受管服务器&#xff0c;选择管理服务器&#xff0c;设置端口9001&#xff0c;其他默认下一步即可。 第二、启动管理服务器&#xff0c;打开控制台&#xff0c;增加服务器&#xff0c;集群如图&#xff0c;如果这两部有问…

【NLP】理解 Llama2:KV 缓存、分组查询注意力、旋转嵌入等

LLaMA 2.0是 Meta AI 的开创性作品&#xff0c;作为首批高性能开源预训练语言模型之一闯入了 AI 场景。值得注意的是&#xff0c;LLaMA-13B 的性能优于巨大的 GPT-3(175B)&#xff0c;尽管其尺寸只是其一小部分。您无疑听说过 LLaMA 令人印象深刻的性能&#xff0c;但您是否想知…

计算机基础知识50

数据的增删改查(insert update delete select) # 用户列表的展示&#xff1a; # 把数据表中得用户数据都给查询出来展示在页面上 1. 查询 from app01 import models models.UserInfo.objects.all() # 查询所有的字段信息和数据 resmodels.UserInfo.objects.first() # 查询…

SM8081是一个高效率的1.5MHz同步步进降压DC/DC调节器,可提供高达1A的电压输出电流。

SM8081 高效率&#xff0c;1.5MHZ&#xff0c;1A 同步降压调节器 概述&#xff1a; SM8081是一个高效率的1.5MHz同步步进降压DC/DC调节器&#xff0c;可提供高达1A的电压输出电流。它可以在宽输入电压下工作范围从2.5V到5.5V&#xff0c;集成主开关以及具有非常低Rps&#x…

【OpenCV实现图像:用OpenCV图像处理技巧之巧用直方图】

文章目录 概要前置条件统计数据分析直方图均衡化原理小结 概要 图像处理是计算机视觉领域中的重要组成部分&#xff0c;而直方图在图像处理中扮演着关键的角色。如何巧妙地运用OpenCV库中的图像处理技巧&#xff0c;特别是直方图相关的方法&#xff0c;来提高图像质量、改善细…

threejs (三) 几何体

定义&#xff1a;用来表示物体的形状&#xff0c;可以定义物体的大小&#xff0c;可以被缩放、旋转和平移 内置几何体&#xff1a; 二维几何体&#xff1a;PlaneGeometry矩形平面、CircleGeometry圆形平面、RingGeometry环形平面、ShapeGeometry二维图形三维几何体&#xff1a…

UI和UX设计师实用高效的设计工具大全

真正专业和优秀的UX设计师不会介意使用哪个工具。因为&#xff0c;只要能力足够&#xff0c;即使条件不同&#xff0c;工具不同&#xff0c;也可以设计出让人眼前一亮的作品。也许&#xff0c;这种理解本身并没有什么大问题。然而&#xff0c;如今&#xff0c;设计师显然有如此…

【汇编语言基础入门】—— 汇编的基础介绍

文章目录 一、机器语言二、汇编语言三、CPU 与 CPU 内存1、CPU 对存储器的读写 四、CPU 的典型构成1、寄存器2、通用寄存器3、物理地址的计算方法4、CS5、DS6、SS SP 一、机器语言 在学习汇编语言之前&#xff0c;我们应该先了解一下什么是机器语言。机器语言是机器指令的集合&…

网络渗透测试(被动扫描)

被动扫描 主要是指的是在目标无法察觉的情况下进行信息搜集。在Google上进行人名的搜素就是一次被动扫描。最经典的被动扫描技术就是"Google Hacking"技术。由于Google退出中国&#xff0c;暂时无法使用。在此介绍三个优秀的信息搜集工具 被动扫描范围 1.企业网络…

Kafka(三)生产者发送消息

文章目录 生产者发送思路自定义序列化类配置生产者参数提升吞吐量 发送消息关闭生产者结语示例源码仓库 生产者发送思路 如何确保消息格式正确的前提下最终一定能发送到Kafka? 这里的实现思路是 ack使用默认的all开启重试在一定时间内重试不成功&#xff0c;则入库&#xff…

基于Python实现,调用百度通用翻译API-详解

概述 在工作上需要各个国家语言的翻译方面很多地方用的上。 获取API权限: 登录百度账号,在个人信息界面,包括修改密码、绑定手机、身份人证等 https://api.fanyi.baidu.com/api/trans/product/desktop?req=developer 百度翻译开放平台 在开发者中心:需要开通个人账号…

13.利用辗转相除法求两个整数的最大公约数和最小公倍数。如96,36

文章目录 前言一、题目描述 二、题目分析 三、解题 前言 本系列为循环结构编程题&#xff0c;点滴成长&#xff0c;一起逆袭。 一、题目描述 利用辗转相除法求两个整数的最大公约数和最小公倍数,如96,36 二、题目分析 最小公倍数(输入的两个数之积)除(它们的最大公约数) 三…