二叉树的创建和基本操作(详解)

2023-05-16

文章目录

二叉树的创建(使用先序遍历)

递归实现二叉树的遍历:

先序遍历:

中序遍历:

后续遍历:

一些二叉树基本操作:

求树的深度:

求树的结点个数:

查找特定值的结点:

查找特定值结点的父结点:

将树拷贝到新树上(二叉树的复制):

非递归实现遍历:

层次遍历:

前序遍历:

中序遍历:

后续遍历:

本文主要讲解递归实现二叉树的代码,对于树,二叉树的性质概念不多赘述

我们以'#'代表空结点(为已有结点下的左右空孩子),方便创建二叉树,我们以下图的二叉树作为讲解

二叉树的创建(使用先序遍历)

以下我会用递归的方法实现创建和基本操作,最后也会使用非递归的思想取实现二叉树的几种遍历方法

class Node {//二叉树结点
public:	
	char m_value;//结点值
	Node* m_left;//左子树
	Node* m_right;//右子树
	Node():m_left(nullptr),m_right(nullptr){}
	Node(char val):m_value(val),m_left(nullptr),m_right(nullptr){}
	~Node(){}
};
class Tree {//创建二叉树
public:
	Node* m_root;//根节点
	Tree():m_root(nullptr){}
	Tree(Node* t):m_root {t} {}
	~Tree(){}
	//传递节点指针的指针!!!const char*& str
	//用#表示空结点
	Node* Create(const char*& str);//先序遍历创建二叉树
};
Node* Tree::Create(const char*& str)//创建二叉树然后把树的根结点返回去
{
	if (*str == '#')//没有树
	{
		return nullptr;
	}
	else
	{
		Node* root = new Node(*str);//新创建根结点
		root->m_left = Create(++str);//创建左孩子
		root->m_right = Create(++str);//创建右孩子
		return root;
	}
}

递归实现二叉树的遍历:

先序遍历:

当根节点不空时,将此时根节点值输出,开始递归遍历左子树,不空输出值若为空,则跳出这一层函数,去遍历此时结点处是否有右子树,当为空,则退出返回上一层。一定要理解递归的含义,照着代码照着图自己走一遍,就会理解,后续递归实现遍历和一些操作也都大同小异

void Tree::PreOrderTree(Node* t)//根左右,t为传入根节点
{
	if (t == nullptr)return;
	else {
		//先遍历根,及输出根的值
		cout << t->m_value <<" ";
		PreOrderTree(t->m_left);
		PreOrderTree(t->m_right);
	}
}

中序遍历:

void Tree::InOderTree(Node* t)//左根右
{
	if (t == nullptr)return;
	else {
		//如果根不空了先遍历他的左子树
		InOderTree(t->m_left);
		cout << t->m_value << " ";//左子树完了之后,根,其实就是把根的值输出一下
		InOderTree(t->m_right);//完了再遍历他的右子树
	}
}

后续遍历:

void Tree::PostOrderTree(Node* t)//左右根
{
	if (t == nullptr)return;
	else {
		PostOrderTree(t->m_left);
		PostOrderTree(t->m_right);
		cout << t->m_value << " ";
	}
}

一些二叉树基本操作:

求树的深度:

依旧用递归来写,求树的深度其实就是求左右子树的深度较大值+1,如果不能理解可以先看看求树的结点个数,我粗略的举个例子,描述一些递归思想,懂了之后,这些基本操作其实递归起来都大差不差。

int Tree::Height(Node* t)
{
	if (t== nullptr)
	{
		return 0;
	}
	else
	{
		int h1 = Height(t->m_left);
		int h2 = Height(t->m_right);
		return h1 > h2 ? h1 + 1 : h2 + 1;		
	}		
}

求树的结点个数:

先看代码:看不懂了照着下面的解释和代码一起看

int Tree::Size(Node* t)//结点个数
{
	if (t == nullptr)//如果根是空的,没有结点,返回0
	{
		return 0;
	}
	else
	{//结点个数就是左子树结点+右子树结点+根结点个数(1)
		//左子树一路递归+右子树一路递归+1
		return Size(t->m_left) + Size(t->m_right) +1 ;
	}

}

    *    A
    *  B   C  举例 
    * t等于A不空,所以走到 Size(t->m_left)这一句
    * t->m_left就是B
    * 此时进入t等于B的Size函数,t不空再走到Size(t->m_left)
    * 即新t要等于B的左子树
    * 而B的左子树是空,return 0返回到t=B的这一层
    * 在t=B的这一层Size(t->m_left) + Size(t->m_right) +1中
    * Size(t->m_left)就是0了,等于开始走Size(t->m_right)
    * 同上面的思路,B的右孩子是空,返回0,返回到B这一层
    * 此时 Size(t->m_right)也是0
    * 此时t等于B这一层函数就成了0+0+1,等于1
    * 相当于return 1,返回到了t等于A这一层函数
    * 此时在A层的函数中,经过前面左子树递归
    * 得到1+Size(t->m_right) +1,这下开始从A开始走Size(t->m_right)函数
    * 和遍历左子树一个逻辑,最终也会返回一个1
    * 最后A这层函数就是1+1+1
    * 最终结果就return 3;

    

查找特定值的结点:

Node* Tree::Search(Node* t, char v)//查找特定值的结点,找到返回结点,没找到返回空
{
	if (t == nullptr){
		return nullptr;
	}
	if (t->m_value == v) {
		return t;
	}
	//如果不空,也不是根,那么开始往左子树查找
	Node* p = Search(t->m_left, v);
	if (p == nullptr)//如果是空,往右边找
		return Search(t->m_right, v);
}

查找特定值结点的父结点:

Node* Tree::Search_Parent(Node* t, char v)//特点值结点的父结点
{
	if (t == nullptr)
	{
		return nullptr;
	}
	if (t->m_left != nullptr && t->m_left->m_value == v)
	{
		return t;//如果t结点左孩子不空,左孩子值是v,t就是特定结点的父结点
	}
	if (t->m_right != nullptr && t->m_right->m_value == v)
	{
		return t;
	}
	if (Search_Parent(t->m_left, v) != nullptr)//如果左边找到且不空,返回
		return Search_Parent(t->m_left, v);
	else
		return Search_Parent(t->m_right, v);
}

将树拷贝到新树上(二叉树的复制):

void Tree::Copy(Node* &t1, Node* &t)//将t树拷贝到t1上
{
	//先拷根,再拷左子树,再拷右子树
	if (t == nullptr)
	{
		t1 = nullptr;
	}
	else
	{
		t1=new Node(t->m_value);//申请空间,把根值拷贝
		Copy(t1->m_left, t->m_left);//递归把左子树拷过去
		Copy(t1->m_right, t->m_right);
	}
}

非递归实现遍历:

层次遍历:

对于非递归实现遍历,无非就是模拟如何遍历的过程,这其中我们需要借助栈或者队列去实现。

对于层次遍历而言我们需要借助队列去完成,其他三种需要借助栈去实现。

大致思路为:

  1.     如果说层次A B C
  2.     先把A入队,用p保存A的值,然后出队A,然后看p有没有左右孩子
  3.     如果有入,他的左右孩子B C,然后出队B,p指向B,在看B有没有左右孩子
  4.     依次类推

注意:一定要画图照着代码去模拟一遍过程,其他三种遍历也是如此。

void Tree::LevelOrder(Node* t)//非递归实现层次遍历
{
	queue<Node*>q; //借助队列实现
	Node* ff = nullptr;//指向对头的指针,用来后面判断有没有左右孩子,需要入队
	if (t == nullptr) return;
	else
	{
		q.push(t); //如果有根,入队
		while (!q.empty()) //看队列空不空
		{
			ff=q.front();//获取对头元素,为了保存下一层的孩子,如果有孩子就需要入队
			q.pop();
			cout << ff->m_value << " ";
		    if(ff->m_left!=nullptr)
			{
				q.push(ff->m_left);
			}		
			if (ff->m_right != nullptr)
			{
				q.push(ff->m_right);
			}		
		}
	}
}

前序遍历:

void Tree::PreOrder_YTree(Node* t)//先序遍历非递归
{
	stack<Node*> ss;//用栈
	Node* p = nullptr;//用来保存栈顶  
	//根左右,根入了之后保存栈顶,要按照先入右边再入左边,为的是把右子树保存到栈,左子树访问完了开始访问右子树
	//因为栈是先进后出
	if (t == nullptr)return;
	else
	{
		ss.push(t);//根入栈
		while (!ss.empty())
		{
			p = ss.top();//保存栈顶元素
			ss.pop();
			cout << p->m_value << " ";
			if (p->m_right != nullptr)
			{
				ss.push(p->m_right);
			}
			if (p->m_left != nullptr)
			{
				ss.push(p->m_left);
			}
		}
	}
}

中序遍历:

大致思路:

  1.  左根右,访问完最左边孩子,接着要访问他的父结点,所以得保存父结点,自然就能找到右节点
  2.    先将根以及根以下左子树全部入栈(把路径上的父子结点都保存着)
  3.    判断栈空不空,不空获取栈顶并出栈
  4.    并用p记住栈顶的右子树
  5.    查看当前结点p有没有左子树,如果有则继续压栈左边,如果没有或者为空则返回栈顶(父亲),找栈顶p的右孩子
  6.     从右孩子这边继续找左,没有,返回这个p,再找他的右子树...
void Tree::InOder_YTree(Node* t)//中序遍历非递归
{
	stack<Node*>ss;
	Node* p = t;
	Node* tmp=nullptr;
	if (t == nullptr)return;
	else
	{
		while (p!=nullptr || !ss.empty())
		{
			while (p!=nullptr)
			{
				ss.push(p);
				p = p->m_left;
			}
			if (!ss.empty())
			{
				tmp = ss.top();
				ss.pop();
				cout << tmp->m_value << " ";
				p = tmp->m_right;//当前结点遍历完,记录右子树
			}
		}
	}
}

后续遍历:

  1.     左右根--> 入的顺序应该根右左
  2.     注意:需要一个指针记住已经遍历过得结点(刚刚出去的上个结点)
  3.     原因:入栈:如果当前结点有左右孩子,则按照右左顺序入栈
  4.     出栈:如果当前结点没有左右孩子,当前结点有孩子,但是孩子已经遍历过了,则出栈
void Tree::PostOrder_YTree(Node* t)//后序遍历非递归
{
	if (t != nullptr)
	{
		stack<Node*> ss;
		Node* top = nullptr, * pre = nullptr;
		ss.push(t);
		while (!ss.empty())
		{
			top = ss.top();
			if (top->m_left == nullptr && top->m_right == nullptr ||
				pre != nullptr && top->m_left == pre || pre != nullptr && top->m_right == pre)
			{
				ss.pop();
				cout << top->m_value << " ";
				pre = top;
			}
			else
			{
				if (top->m_right != nullptr)
					ss.push(top->m_right);
				if (top->m_left != nullptr)
					ss.push(top->m_left);
			}
		}
	}
}

我们在主函数测试一下:

int main()
{
	Tree t;
	const char* str = "ABDG##HI####CE#J##F##";
	t.m_root = t.Create(str);//创建二叉树,返回他的根结点
	cout << "先序遍历:"  ;
	t.PreOrderTree(t.m_root);
	cout <<endl;
	cout << "中序遍历:";
	t.InOderTree(t.m_root);
	cout << endl;
	cout << "后序遍历:";
	t.PostOrderTree(t.m_root);
	cout << endl;
	cout << "Size为:"<< t.Size(t.m_root)<<endl;
	cout << "Height为:" << t.Height(t.m_root) << endl;
	Node* p = t.Search(t.m_root, 'E');
	if (p== nullptr)
	{
		cout << "没有找到" << endl;
	}
	else
		cout <<"找到了:"<< p->m_value << endl;

	Node* pp = t.Search_Parent(t.m_root, 'B');
	if (p == nullptr)
	{
		cout << "没有找到" << endl;
	}
	else
	{
		cout << "找到了:" << pp->m_value << endl;
	}
	
	cout << "非递归层次遍历:";
	t.LevelOrder(t.m_root);
	cout << endl;
	cout << "非递归先序遍历:";
	t.PreOrder_YTree(t.m_root);
	cout << endl;
	cout << "非递归中序遍历:";
	t.InOder_YTree(t.m_root);
	cout << endl;
	cout << "非递归后序遍历:";
	t.PostOrder_YTree(t.m_root);
	cout << endl;

	Tree t1;
	t1.Copy(t1.m_root, t.m_root);

	cout << "拷贝成功后,先序遍历:";
	t1.PreOrderTree(t1.m_root);

	return 0;
}

本篇旨在讲解二叉树的代码方便查阅

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

二叉树的创建和基本操作(详解) 的相关文章

  • openstack基础知识

    目录 一 云计算1 什么是云计算2 云计算的特色3 云计算的三种使用方式1 xff09 公有云2 xff09 私有云3 xff09 混合云 4 云计算服务模型1 xff09 IaaS 基础架构即服务 2 xff09 PaaS xff08 平
  • openstack-keystone

    目录 一 keystone身份服务二 keystone的主要功能三 keystone相关概念四 keystone认证流程五 OpenStack Keystone组件部署步骤部署步骤 一 keystone身份服务 keystone xff08
  • k8s-----------YAML&harbor

    目录 概述使用YAML文件创建资源1 查看资源版本的标签2 创建yaml文件测试 Pod1 特点2 pod容器分类3 镜像拉取策略 部署harbor1 登录harbor私有仓库2 下载Tomcat镜像进行推送3 推送 概述 Kubernet
  • k8s-----------高级pod&调度

    目录 pod进阶pod重启策略 健康检查 探针调度约束调度方式 故障排除 pod进阶 limits cup cpu上限limits memory 内存上限requests cpu 创建时分配的基本CPU资源requests memory 创
  • k8s-----------控制器

    目录 Deployment 部署无状态应用 Pod与控制器之间的关系 SatefulSet xff08 部署有状态应用 xff09 无状态和有状态无状态有状态 常规service和无头服务区别DaemonSetjobCronJob 控制器
  • 安装electron时安装失败解决

    错误描述 xff1a 在安装 electron 的时候 xff0c 使用官方推荐的如下命令 xff1a npm install save dev electron 结果报错如下 npm ERR code 1 npm ERR path D A
  • 10:天干地支

    10 天干地支 时间限制 1 S 内存限制 8192 KB Accept 15 Submit 41 提交 讨论版 描述 天干地支 xff0c 源自中国远古时代对天象的观测 甲 乙 丙 丁 戊 己 庚 辛 壬 癸 称为十天干 xff0c 子
  • txt格式vscode转码

    txt打开异常 xff0c 或乱码 右下角有格式类型 xff1a utf 8 xff0c 点击它会有一个 select action 弹框 可选择特定格式重新打开 xff0c 或保存 选择好对应的格式 乱码解决 或者点击 save with
  • 送给 Java 程序员的 Spring 学习指南

    https www infoq cn article Ad 8ghcGGCNU572U6oEX 学习 Spring 的基础要求 Spring 官网首页是这么介绍自己的 Spring the source for modern Java xf
  • Centos下如果是二进制文件,编辑是文本,后缀是sh也无法执行

    这次部署redis遇到个问题 xff0c 执行sh文件来启动redis xff0c 结果报配置文件无法打开 用vi打开sh文件反复检查过路径是对的 然后手敲路径执行 xff0c 运行正常 xff1b 直接执行sh文件不行 xff1a 反复修
  • Python Pandas 查看数据信息 DataFrame.info()

    在进行数据分析之前 xff0c 需要先查看数据的信息 xff0c 这样才方便后续的数据处理 比如 xff0c 在excel表中20220520是一个常规类型的数据 xff0c 那它导入到DataFrame中是int类型还是str类型呢 xf
  • 03-1.MariaDB安装配置详细步骤

    Author xff1a Sickey Date xff1a 2021 11 25 安装前准备 配置静态IP防火墙等等 1 安装mariadb数据库 先查看RDO中MariaDB的版本及配置 etc my cnf d server cnf
  • 24行代码简单实现qq空间自动点赞

    什么是Auto js xff1f Auto js是基于JavaScript语言运行在Android平台上的工具 它依赖于无障碍服务 它可以做什么 xff1f 解放双手 xff0c 让手机自动打游戏 自动签到 自动领红包等等等等 它有什么优点
  • 操作系统(C++)——生产者消费者模型

    一 C 43 43 实现代码 span class token macro property span class token directive hash span span class token directive keyword i
  • 2023.03.14java内置的线程池 -》 ScheduledExecutorService 具备延迟运行的能力!的使用

    区别 ScheduledExecutorService xff0c 执行任务方法为schedule xff0c 而不是submit es schedule new MyRunnable i 2 TimeUnit SECONDS 指定线程了线
  • sudo rm -rf /* 命令运行演示(管理员身份删除根目录所有文件)

    一 前言 闲来无事 xff0c 好奇传说中的 sudo rm rf 命令究竟有什么样的魅力让无数人趋之若鹜 xff0c 本着奉献精神 xff0c 作者将在自己的服务器上测试一番 xff0c 各位读者切勿轻易尝试 不 xff0c 切勿尝试 x
  • 服务器如何安装宝塔面板?

    一 前言 作者是按照宝塔面板官方指引进行下载的 xff0c 中间进行更为清晰明了的图示说明 宝塔官方说明 xff08 或者直接按下面步骤安装 xff09 xff1a 宝塔Linux面板安装教程 2021年8月18日更新 7 7 0正式版 L
  • 计算机网络体系结构(详图)

    本文旨在作学习记录 xff0c 其中 xff0c 图源的信息已标明 图源 xff1a 谢希仁的 计算机网络 图源 xff1a 网络
  • wordpress+宝塔在阿里云服务器上搭建个人博客(如何在服务器上搭建个人博客)

    目录 一 宝塔安装 1 开放端口 2 命令安装 二 环境安装 xff08 LNMP xff09 三 wordpress安装包下载 四 部署站点 四 博客访问 五 结语与参考 一 宝塔安装 宝塔官方安装教程 xff08 或者直接按下面步骤安装

随机推荐