二叉树先、中、后遍历递归+非递归

news2025/1/19 14:28:54

文章目录

  • 前言
  • 思路
    • 设计思想
    • 非递归前序遍历的思路
    • 非递归中序遍历的思路
    • 非递归后序遍历的思路
    • 层序遍历的思路
  • 完整代码
    • MyBinaryTree.h
    • MyBinaryTree.cpp
    • Main.cpp
    • 效果展示

前言

  • 作者水平有限,全部的代码是学习前人+部分原创
  • 不要搬代码,一定要借鉴学习,自己动手!!!

思路

设计思想

  • 二叉树的创建:采用先序遍历的顺序,依次写出每个非空节点的数值,如果没有孩子节点就用$符号代替。并采用递归的方式进行实现,依次创建根节点、所有的左子树、右子树
  • 二叉树的前、中、后序的递归遍历:本质上的遍历顺序都是一样的,只是打印的时机不同,控制好打印的时机即可。

非递归前序遍历的思路

  1. 栈s初始化;
  2. 循环直到p为空且栈s为空
    • 当p不空时循环
      • 输出p->data;
      • 将指针p的值保存到栈中;
      • 继续遍历p的左子树
    • 当p为空,栈s不空,则
      • 将栈顶元素弹出至p;
      • 准备遍历p的右子树;
        在这里插入图片描述

非递归中序遍历的思路

  1. 栈s初始化;
  2. 访问左孩子,左孩子存在继续步骤1;
  3. 左孩子不存在弹出函数栈帧;
  4. 访问右孩子,右孩子存在继续步骤1;
  5. 右孩子不存在弹出函数栈帧,访问该节点;
  6. 弹出函数栈帧,返回上一级函数栈帧。

非递归后序遍历的思路

  • 从当前节点开始遍历:
    1. 若当前节点存在,就存入栈中,并且置节点flag为1(第一次访问),然后访问其左子树;
    2. 直到当前节点不存在,需要回退,这里有两种情况:
      • 当栈顶节点flag为0时,则表明是从左子树回退,这时需置栈顶节点flag为2(第二次访问),然后通过栈顶节点访问其右子树(取栈顶节点用,但不出栈)
      • 当栈顶节点flag为1时,则表明是从右子树回退,这时需出栈,并取出栈节点做访问输出。
    3. 不断重复12,直到当前节点不存在且栈空。

层序遍历的思路

  • 从上到下,从左到右,则利用队列存放各子树结点的指针,初始时把根节点入队,当根结点出队后,令其左、右孩子结点入队(空不入队),而左孩子出队时又令它的左右孩子结点入队,……由此便可产生按层次输出的效果。
    在这里插入图片描述

完整代码

MyBinaryTree.h

#pragma once
#include<iostream>
using namespace std;
typedef char E;
typedef struct TreeNode {
	E element;
	struct TreeNode* left, * right;
	// 在后序遍历时,需要左右子树都被遍历才行,0表示左子树遍历完成,1表示右子树遍历完成
	int flag;//标志遍历的状态。
}*Node;

//栈
typedef Node T;//二叉树节点指针
typedef struct StackNode {
	T element;
	struct StackNode* next;
} *SNode;

//队列
typedef struct QueueNode {
	T element;
	struct QueueNode* next;
}*QNode;

typedef struct Queue {
	QNode front, rear;
}*LinkedQueue;

Node createBiTree(Node& root);//按照先序顺序创建二叉树

void preOrder(Node root);//递归方式进行先序遍历
void inOrder(Node root);//递归方式进行中序遍历
void postOrder(Node root);//递归方式进行后序遍历

//栈操作
void initStack(SNode head);//初始化栈
int pushStack(SNode head, T element);//入栈
int IsEmpty(SNode head);//判断栈是否为空
T peekStack(SNode head);//获取栈顶元素值
T popStack(SNode head);//出栈

void preOrder2(Node root);//非递归先序遍历
void inOrder0(Node root);//非递归中序遍历 低级版
void inOrder2(Node root);//非递归中序遍历 升级版
void postOrder2(Node root);//非递归后序遍历

//队列操作
int initQueue(LinkedQueue queue);//初始化队列
int offerQueue(LinkedQueue queue, T element);//入队列
int isEmpty(LinkedQueue queue);//队列判空
T pollQueue(LinkedQueue queue);//出队
void levelOrder(Node root);//层序遍历
int getDepthTreeNode(Node root);//求二叉树的深度

MyBinaryTree.cpp

#include"MyBinaryTree.h"

//创建二叉树
//注意采用引用的方式,避免浅拷贝的报错
//Program received signal SIGSEGV, Segmentation fault
Node createBiTree(Node& root) {
	char ch;
	cin >> ch;
	if (ch == '$') {
		root = NULL;
	}
	else {
		root = (Node)malloc(sizeof(struct TreeNode));
		root->element = ch;
		createBiTree(root->left);//构建左子树
		createBiTree(root->right);//构建右子树
	}
	return root;
}

//递归方式进行先序遍历
void preOrder(Node root) {
	if (root != NULL) {
		cout << root->element << " ";
		preOrder(root->left);
		preOrder(root->right);
	}
}

//递归方式进行中序遍历
void inOrder(Node root) {
	if (root == NULL) return;
	inOrder(root->left);
	cout << root->element << " ";
	inOrder(root->right);
}

//递归方式进行后序遍历
void postOrder(Node root) {
	if (root == NULL) return;
	postOrder(root->left);
	postOrder(root->right);
	cout << root->element << " ";
}

//初始化栈
void initStack(SNode head) {
	head->next = NULL;
}

//入栈
int pushStack(SNode head, T element) {
	SNode node = (SNode)malloc(sizeof(struct StackNode));
	if (node == NULL) return 0;
	node->next = head->next;
	node->element = element;//element都是节点
	head->next = node;
	return true;
}

//判空
int IsEmpty(SNode head) {
	return head->next == NULL;
}

//用于获取栈顶元素的值,但是不出栈,仅仅是值获取
T peekStack(SNode head) {
	return head->next->element;//返回栈中的首节点的地址
}

//出栈
T popStack(SNode head) {
	SNode top = head->next;
	head->next = head->next->next;
	T e = top->element;
	free(top);
	return e;
}

//非递归实现
//先序遍历
void preOrder2(Node root) {
	struct StackNode stack;
	initStack(&stack);
	//两个条件,只有当栈为空并且节点为NULL时才终止循环
	while (root || !IsEmpty(&stack)) {
		//先不断遍历左子树,直到没有为止
		while (root) {
			cout << root->element << " ";//然后打印当前节点的元素值
			pushStack(&stack, root);//每遇到一个节点,就将节点入栈
			root = root->left;//继续遍历下一个左孩子节点
		}
		Node node = popStack(&stack);//经过前边的遍历,明确左子树全部走完了,就进行右子树的遍历
		//得到右孩子,如果有右孩子,下一轮会重复上面的步骤;如果没有右孩子,那么这里的root就会变成null,
		// 下一轮开始会直接跳过上面的while,继续出栈下一个节点,再找右子树
		root = node->right;
	}
}
//中序遍历二叉树的非递归算法:没有让空指针进栈
void inOrder2(Node root) {
	struct StackNode stack;
	initStack(&stack);
	//两个条件,只有当栈为空并且节点为NULL时才终止循环
	while (root || !IsEmpty(&stack)) {
		//先不断遍历左子树,直到没有为止
		while (root) {
			pushStack(&stack, root);//每遇到一个节点,就将节点入栈
			root = root->left;//继续遍历下一个左孩子节点
		}
		Node node = popStack(&stack);//经过前边的遍历,明确左子树全部走完了,就进行右子树的遍历
		cout << node->element << " ";//然后打印当前节点的元素值
		//得到右孩子,如果有右孩子,下一轮会重复上面的步骤;如果没有右孩子,那么这里的root就会变成null,
		// 下一轮开始会直接跳过上面的while,继续出栈下一个节点,再找右子树
		root = node->right;
	}
}
//中序遍历二叉树的非递归算法:让空指针进栈
void inOrder0(Node root) {
	struct StackNode stack;
	initStack(&stack);
	T p;
	pushStack(&stack, root);//根指针入栈
	while (!IsEmpty(&stack)) {
		//有栈顶元素 ,并且节点不为NULL
		while ((p = peekStack(&stack)) && p) {
			pushStack(&stack, p->left);//向左走到尽头
		}
		//直到左孩子为NULL,此时栈顶为整棵树最左边的节点,将NULL进栈,循环结束
		//弹出栈顶空节点NULL ,因为子孩子为NULL,不需要打印值
		p = popStack(&stack);//空指针退栈
		if (!IsEmpty(&stack)) {//栈不为空,访问节点,向右一步
			//弹出最上面的节点 ,也就是逻辑中序第一个节点
			p = popStack(&stack);
			cout << p->element << " ";
			pushStack(&stack, p->right);
			//如果有右孩子,则右孩子进栈,,接着以右孩子为逻辑根节点来中序遍历,判断右孩子是否有左孩子和右孩子....
			//如果没有右孩子,就将NULL压进栈,下次循环会被弹出并不打印
		}
	}
}

//后序遍历
void postOrder2(Node root) {
	struct StackNode stack;
	initStack(&stack);
	while (root || !IsEmpty(&stack)) {
		while (root) {
			pushStack(&stack, root);
			root->flag = 0;//首次入栈,只能代表左子树遍历完成,所以设置flag为0
			root = root->left;
		}
		root = peekStack(&stack);//获取节点
		if (root->flag == 0) {//如果仅仅遍历左子树,那么flag就等于0
			root->flag = 1;//此时标记为1表示遍历右子树
			root = root->right;
		}
		else {
			cout << root->element << " ";//当flag为1时,表示左右都遍历完成,这时再将值打印出来
			popStack(&stack);//这时再把对应的节点出栈,因为左右都遍历完了
			root = NULL;//置NULL,下一轮直接跳过while,然后继续取栈中剩余的节点,重复上述操作
		}
	}
}

//初始化对列
int initQueue(LinkedQueue queue) {
	//设置头节点,并将队头和队尾同时指向头节点
	QNode node = (QNode)malloc(sizeof(struct QueueNode));
	if (node == NULL) return 0;
	queue->front = queue->rear = node;
	return true;
};

//入队
int offerQueue(LinkedQueue queue, T element) {
	//入队,将新的节点入队放在队尾,并修改rear的指向
	QNode node = (QNode)malloc(sizeof(struct QueueNode));
	if (node == NULL) return 0;
	node->element = element;
	queue->rear->next = node;
	queue->rear = node;
	return true;
}

//判空
int isEmpty(LinkedQueue queue) {
	//当队头和队尾指针同时指向同一个节点时,说明队列为空队列
	return queue->front == queue->rear;
}

//出队
T pollQueue(LinkedQueue queue) {
	//queue->front —— 头节点
	//queue->front->next->element --队头结点中存储的节点的地址
	T e = queue->front->next->element;
	//queue->front->next --队头
	QNode qNode = queue->front->next;
	//更新链队列头节点
	queue->front->next = queue->front->next->next;
	//qNode=qNode->next;
	//如果队列中只有一个节点,将队尾指针指向设置好的头节点
	if (queue->rear == qNode) queue->rear = queue->front;
	free(qNode);
	//返回删除的旧的队头节点的节点地址,并没有物理删除,只是释放队列的节点空间
	return e;
}

//层序遍历
void levelOrder(Node root) {
	struct Queue queue;//创建队列
	initQueue(&queue);
	offerQueue(&queue, root);//先把根节点入队
	while (!isEmpty(&queue)) {
		Node node = pollQueue(&queue);
		cout << node->element << " ";
		if (node->left) //如果左、右孩子,依次将左、右孩子入队
			offerQueue(&queue, node->left);
		if (node->right)
			offerQueue(&queue, node->right);
	}
}

//求二叉树的深度
int getDepthTreeNode(Node root) {
	if (root == NULL) {
		return 0;
	}
	else {
		int lLength = getDepthTreeNode(root->left);
		int rlength = getDepthTreeNode(root->right);
		int max = rlength > lLength ? (rlength + 1) : (lLength + 1);
		return max;
	}
};

Main.cpp

#include"MyBinaryTree.h"
int main() {
	cout << "1--创建二叉树" << endl;
	cout << "2--先序遍历二叉树【递归方式】" << endl;
	cout << "3--中序遍历二叉树【递归方式】" << endl;
	cout << "4--后序遍历二叉树【递归方式】" << endl;
	cout << "5--先序遍历二叉树【非递归方式】" << endl;
	cout << "6--中序遍历二叉树【非递归方式1】" << endl;
	cout << "7--中序遍历二叉树【非递归方式2】" << endl;
	cout << "8--后序遍历二叉树【非递归方式】" << endl;
	cout << "9--层序遍历二叉树" << endl;
	cout << "10--求二叉树的深度" << endl;
	cout << "-1--退出" << endl;
	int option;
	Node t;//定义一个Node的指针
	t = NULL;//不进行初识,就会报错
	do {
		cout << "请输入选择:";
		cin >> option;
		switch (option)
		{
		case 1:
			cout << "请按先序输入二叉树中节点的值(一个字符)$字符表示空树:";
			t = createBiTree(t);
			if (t) {
				cout << "创建二叉树成功!" << endl;
			}
			break;
		case 2:
			cout << "前序遍历【递归实现】:";
			preOrder(t);
			cout << endl;
			break;
		case 3:
			cout << "中序遍历二叉树【递归方式】";
			inOrder(t);
			cout << endl;
			break;
		case 4:
			cout << "后序遍历二叉树【递归方式】";
			postOrder(t);
			cout << endl;
			break;
		case 5:
			cout << "先序遍历二叉树【非递归方式】";
			preOrder2(t);
			cout << endl;
			break;
		case 6:
			cout << "中序遍历二叉树【非递归方式1】";
			inOrder0(t);
			cout << endl;
			break;
		case 7:
			cout << "中序遍历二叉树【非递归方式2】";
			inOrder2(t);
			cout << endl;
			break;
		case 8:
			cout << "后序遍历二叉树【非递归方式】";
			postOrder2(t);
			cout << endl;
			break;
		case 9:
			cout << "层序遍历二叉树【非递归实现】";
			levelOrder(t);
			cout << endl;
			break;
		case 10:
			cout << "二叉树的深度为:";
			cout << getDepthTreeNode(t) << endl;
			break;
		case -1:
			cout << "谢谢使用!再见!" << endl;
			return 0;
		default:
			cout << "请输入正确的操作!";
			break;
		}

	} while (option != -1);
}

效果展示

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

接口测试的痛点和解决办法

在做接口测试时&#xff0c;以下几个测试痛点&#xff0c;一定要仔细琢磨下&#xff1a; 痛点①&#xff1a;由于测试环境数据被改动&#xff0c;导致接口测试失败 这个问题&#xff0c;最好的办法就是重新调用数据库中的最新数据。在做测试用例时&#xff0c;也要考虑到实时调…

汽车服务行业概况与供应商评估方案介绍,数商云SRM系统助力多维考核供应商

近年来&#xff0c;汽车保有量不断增长&#xff0c;中国汽车市场连续多年成为全球产销第一&#xff0c;伴随物联网技术的不断渗透&#xff0c;车联网开始兴起&#xff0c;带给线上线下汽车相关服务行业新的发展机遇。 当前汽车服务行业整体概况 1、市场需求增加&#xff1a;市…

Vue基础超详细

目录 一、Vue的简介 1、什么是vue 2、vue 的特性&#xff08;数据驱动视图、双向数据绑定&#xff09; 3、MVVM及其工作原理 二、Vue的基本实用 1、基本使用步骤 2、配置Vue的调试工具 3、指令与过滤器 3.1内容渲染指令 4、属性绑定指令 5、使用Javascript表达式 6、事件绑…

“ 念旧真的是一件很无趣的事 “

想要的都拥有 得不到的都释然 My Jinji音频&#xff1a;00:0006:40 | 01 | 想通就释然 想不通就茫然 每天不停的循环 | 02 | 终于理解你曾经说的 “有些人不能做朋友” 现在我和你一样惨 | 03 | 村上春树说过&#xff1a; “如果一直想见谁 肯定迟早会见到” 但是他还…

android入门之创建service

1. 前言 Service意为&#xff1a;服务&#xff0c;是一种运行时用户不可见的活动机制。可以理解为它是一个没有布局的Activity。 典型的场景&#xff1a;音乐后台播放、后台下载。 Service不同于子线程&#xff0c;Service是运行在主线程中的&#xff0c;因此不能进行耗时操作。…

电线电缆行业MES系统,帮助企业快速应对订单变化,减少资金占用

电线电缆行业生产管理现状 电线电缆行业是典型的重资产行业&#xff0c;原材料成本高、产品价值高、资金占用大、产品规格型号多达数万种&#xff0c;BOM管理繁杂&#xff0c;现场管理粗放&#xff0c;订单经常会合并或拆分生产&#xff0c;对排程要求高。 1、计划制定不准确…

无分类编址CIDR

无分类编址CIDR 构成超网 将多个子网聚合成一个较大的子网&#xff0c;叫做构成超网&#xff0c;或路由聚合。 方法&#xff1a;将网络前缀缩短&#xff08;所有网络地址取交集&#xff09;。 例题 某路由表中有转发接口相同的4条路由表项&#xff0c;其目的网络地址分别为…

Unicode编码的理解

1、Unicode 这个单词可以拆解为两部分&#xff0c;一个是Uni ,即英文单词unique的意思&#xff0c;也就是唯一的意思。code就是编码的意思。 GBK编码的理解_sgmcy的博客-CSDN博客 在上节博客里面&#xff0c;介绍了ASCII编码、ASCII编码表的扩展以及我们国家汉字的GBK编码。 …

详解OpenCV的椭圆曲线点坐标近似计算函数ellipse2Poly()

详解OpenCV的椭圆曲线点坐标近似计算函数ellipse2Poly() 函数ellipse2Poly()可用于近似计算椭圆曲线的像素坐标。 而前面介绍过的函数ellipse()则是直接在图像中绘制椭圆&#xff0c;详情见 https://www.hhai.cc/thread-174-1-1.html 函数ellipse2Poly()的C原型如下&#xff…

【Shell】find文件查找

语法格式 find [路径] [选项] [操作]选项参数对照表 常用选项 -name 查找/etc目录下以conf结尾的文件ind /etc -nam -iname 查找当前目录下文件名为aa的文件,不区分大小写 find . -iname aa -user 查找文件属主为hdfs的所有文件find . -user hdfs -group 查找文件属组为yarn的…

虹科方案 | 解决连接到IEEE 1588高可用性网络的SCADA系统的NTP同步参考问题

目前&#xff0c;各个行业都在朝着以太网融合的趋势发展&#xff0c;近年来也出现了一些可用于增强标准以太网弹性的技术创新&#xff0c;并被用作运营&#xff08;OT&#xff09;和信息技术&#xff08;IT)的通用链路层。电气等具有高可用性和严格时序要求的关键领域推动了这些…

1362:家庭问题(family)

1362&#xff1a;家庭问题(family) 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 6732 通过数: 3529 【题目描述】 有n个人&#xff0c;编号为1,2,……n&#xff0c;另外还知道存在K个关系。一个关系的表达为二元组&#xff08;α&#xff0c;β&#xff09;形式…

GBK编码的理解

1、我们学程序的时候&#xff0c;所熟知的ASCII码&#xff0c;就是一种编码方式 计算机底层&#xff0c;就只认识0和1. 举个例子&#xff0c;以3bit为例&#xff1a; 所以&#xff0c;如果是000的话&#xff0c;可以对应数字0 如果是001的话&#xff0c;可以对应数字1 。。…

非零基础自学Golang 第13章 并发与通道 13.4 select 13.5 小结

非零基础自学Golang 文章目录非零基础自学Golang第13章 并发与通道13.4 select13.4.1 select作用13.4.2 超时13.4.3 死锁13.5 小结第13章 并发与通道 13.4 select Go语言中&#xff0c;通过关键字select可以监听channel上的数据流动。 select的用法和switch非常相似&#xf…

Python中的基本数据类型

文章目录前言一、字符串类型字符串表示方法二、数字类型1. 整数2.浮点数3.复数三、布尔类型总结前言 我们一般在电脑中存储的数据有多种数据类型。比如下图这张员工工资表&#xff1a; 表中员工姓名可以用字符串类型存储&#xff08;比如"李世民"、“侯君集”&#…

Hadoop学习----HDFS

文件系统 文件系统&#xff1a;是一种存储和组织数据的方法&#xff0c;实现数据的存储、分级组织、访问和获取等操作&#xff0c;使得用户对文件访问和查找变得容易。文件系统使用树形目录的抽象逻辑概念代替了硬盘等物理设备使用数据块的概念&#xff0c;用户不必关心数据底…

实验三 进程的互斥与同步

文章目录一、 实验目的二、 实验原理三、实验内容四、我的代码内容和现象1、philosopher12、philosopher2这个程序不会发生死锁&#xff0c;因为五、课后习题&#xff1a;1.什么是死锁&#xff1f;产生死锁的原因和必要条件是什么&#xff1f;2.实验中给出的伪代码流程&#xf…

Problem Set 3

1Lagrange Duality Formulate the Lagrange dual problem of the following linear programming prob-lem min cT rs.t.Ax 二b where a ∈R is variable,c ∈ R"&#xff0c;A ∈Rkn, b ∈ Rk. 解&#xff1a;设拉格朗日函数为L(x,λ)cTxλT(Ax−b)\mathcal{L}(x,\lambda)…

第十七章 webpack5项目搭建Vue-Cli(开发模式)

step1–创建项目目录 创建一个目录用来搭建vue-cli的项目 mkdir vue-cli cd vue-clistep2–初始化项目 初始化项目&#xff0c;生成一个package.json文件 npm init -ystep3–编写vue-cli的开发模式配置 新建目录 / |-config | |--webpack.dev.js | |--webpack.prod.js我…

USB TO SPI(上海同旺电子)调试器调试25LC020A

所需设备&#xff1a; 1、USB TO SPI(上海同旺电子)&#xff1b; 2、25LC020A 2Kb 2.5V SPI Serial EEPROM; Microchip 25LC020A 是一款 2 Kb 串行 EEPROM&#xff0c;采用行业标准串行外设接口 (SPI) 兼容串行总线。 该器件被组织为一个 256 x 8 位块&#xff0c;并针对消…