二叉树的插入与遍历
二叉树的插入与遍历
文章目录
1. 二叉树的结构:
二叉树是每个节点最多有两棵子树的有序树,二叉树具有根节点,根的左边为左子树,根的右边为右子树,同时每个每个树的节点又可以被当作新的根,也具有左子树和右子树的特性,可以推测,二叉树具有递归的特性
2. 二叉树的构建
-
2.1 二叉树的节点类型
-
二叉树具有数据,左子树和右子树,所以很容易写出,二叉树的节点类型,这里用模板封装任意类型的二叉树结构,用左指针指向左子树,右指针指向右子树,用指针来连接每一个树的节点
注意:结构体也也可被称作类,也具有成员函数的特性,和构造函数
template <class T> struct Tree_Node { T data; //数据成员 Tree_Node<T>* pLeft; //左指针 Tree_Node<T>* pRight;//右指针 //用于构造节点的构造函数 Tree_Node(const T& data) :data(data) { pLeft = pRight = nullptr; } };
-
-
2.2 二叉树的类封装结构
二叉树具有一个初始根节点,用类来封装二叉树:
本节所讨论的二叉树包括插入和遍历操作;但是可以看到public和private都 分别有一个这样的函数;这样做的目的是:在私有成员里实现函数,在共有成员里调用实现函数,有利于实现接口的多样性,即调用同一个功能的函数可以有多个共有函数,同时尽可能实现私有化,类的封装更加严密
template <class T> class My_Tree { private: TreeNode<T>* pRoot; //根节点 /* 实现函数放在私有成员里 */ bool _insert(const T& data, TreeNode<T>** pNode); void _Pretravel(TreeNode<T>* pRoot); void _Midtravel(TreeNode<T>* pRoot); void _Lastravel(TreeNode<T>* pRoot); public: My_Tree() { pRoot = nullptr; } ~My_Tree() {} /* 外部调用函数 */ //插入 void insert(const T& data); //输出遍历 type=-1 先序; type=0中序; type=1 后序; void travel(int type); }; -
2.3 二叉树的插入操作:
-
插入二叉树需要修改,所以可以使用二级指针或者引用的形式(pRoot根节点为一级指针,修改一级指针需要二级指针或者一级指针的引用),这里首先采用二级指针的形式:
我们需要一个T类型的数据,再把根节点以地址的形式传入实现函数里
//外部成员函数调用私有成员实现函数 template<class T> inline void My_Tree<T>::insert(const T& data) { _insert(data, &pRoot); } template<class T> inline bool My_Tree<T>::_insert(const T& data, TreeNode<T>** pRoot) { if (*pRoot == nullptr) { *pRoot = new TreeNode<T>(data); return true; } //利用递归特性插入 _insert(data, &((*pRoot)->pLeft)); _insert(data, &((*pRoot)->pRight)); } My_Tree<int> a; //创建一颗树 -
a.Insert(1); //根节点 我们一开始只具有根节点pRoot=nullptr,当我们插入一个数据的时候,判断当前节点是否为空,例如,我们一开始的pRoot为空,我们插入1,会判断*pRoot为空,会调用节点的构造函数生成一个节点,此节点就是我们的根节点,在生成完毕后立刻retur返回。
-
当我们有了根节点后,再次插入一个数据,插入到根节点左子树中:
a.Insert(2); 进入函数,程序首先从根节点开始,检查根是否为空,根不为空,说明要在根的左侧再次寻找位置插入,直到某个节点的根为空。例如2,pRoot不为空,进入循环递归的插入,再次调用函数,此时把树的左指针作为根节点传入函数,进入递归,左指针为空,所以生成节点2,这时已经插入成功了,我们也可以返回了
但是,函数并没有结束,他从左插入的函数return返回后,又进入了右指针的插入函数体中(参见递归的特性),又在右指针创建了一个节点,然后再返回主insert函数,在返回主函数,可以看到,递归的特性还是比较头痛的,我们只需要插入左就好了,但是递归不允许
-
2.3.2 修改后的版本:
我们新增加了一个bool类型判断的条件,当我们判断根节点是否为空的时候,创建一个节点,然后我们返回true,如果我们插入初始根节点,那么就相当于直接返回,如果我们像上面那样插入2,则进入左插入的函数后,返回true,返回递归,当左插入成功时,又返回依次true,这次会直接返回到主函数中,而不会调用到右插入的函数中至此我们的左插入已经没有问题了
template<class T> inline void My_Tree<T>::insert(const T& data) { _insert(data, &pRoot); } template<class T> inline bool My_Tree<T>::_insert(const T& data, TreeNode<T>** pRoot) { //若把递归条件放在前面,会导致第一个pRoot的data无效而终止 if (*pRoot == nullptr) { *pRoot = new TreeNode<T>(data); return true; } //利用递归特性插入 if (_insert(data, &((*pRoot)->pLeft))) { //可以确保左插入成功后直接返回主函数,不会执行右插入函数; return true; } _insert(data, &((*pRoot)->pRight)); } 但是当我们想要插入一个右子树该怎么办?可以发现,我们根本插入不到右子树中,因为只要一进入插入函数一定会首先调用左插入函数,我们也不能改变他,他会一直往左插入
-
2.3.3 最终插入版本
#define MAX 999 template<class T> inline bool My_Tree<T>::_insert(const T& data, TreeNode<T>** pRoot) { //若把递归条件放在前面,会导致第一个pRoot的data无效而终止 if (*pRoot == nullptr) { *pRoot = new TreeNode<T>(data); return true; } //递归终止条件 来进行右插入的条件 if ((*pRoot)->data == MAX) { return false; } //利用递归特性插入 if (_insert(data, &((*pRoot)->pLeft))) { return true; } _insert(data, &((*pRoot)->pRight)); } 我们无法插入到右子树中,我们注意到当返回true的时候,会进入左插入的函数,那我们只能让其返回false,这样才会进入右插入函数,我们定义一个左插入终止数值 MAX=999 ,把MAX左插入树中,那么,下一次我们进行右插入,当碰到MAX的时候,我们返回false,那样就可以进入右插入的函数中了,注意一定要把判断是否等于MAX放在判断根节点为空的下面,否则第一个根节点的数据为空,会直接终止
总结: 当我们想要左插入,直接插入即可;当我们想要右插入,先插入一个MAX,再进行右插
a.Insert(1); //左插入 a.Insert(2); //左插入 a.Insert(3); //左插入 a.Insert(MAX); //左插入999 a.Insert(4); //右插入
-
-
二叉树插入传引用:
把二级指针改为 一级指针的引用即可,在函数中直接用pRoot,不必解引用*pRoot
TreeNode<T>*& pRoot //一级指针的引用
-
-
2.4 二叉树的遍历操作
通过递归依次 访问各个子树的节点。 二叉树的遍历方式: 前序遍历 中序遍历 后序遍历
构建的二叉树结构:
构造方式:
int main() { My_Tree<int> a; int num[] ={ 1,2,3,4,MAX ,MAX ,5,MAX ,MAX ,MAX,7,MAX,9,10,MAX,MAX,11,MAX,MAX }; //插入二叉树 for (int i = 0; i < sizeof(num) / sizeof(num[0]); i++) { a.insert(num[i]); } a.traval(-1); //先序遍历 a.traval(0); //中序遍历 a.traval(1); //后序遍历 }
-
2.4.1 前序遍历
根 左 右
什么序遍历就把根放在哪里,前序遍历把根放在第一个。
//先序输出 根 左 右 template<class T> inline void My_Tree<T>::_Pretravel(TreeNode<T>* pRoot) { if (pRoot == nullptr) { return; } #if ISSHOW if (pRoot->data != MAX) #endif cout << pRoot->data << " "; _Pretravel(pRoot->pLeft); _Pretravel(pRoot->pRight); }
-
2.4.2 中序遍历
左 根 右
根在中间
//中序输出 左 根 右 template<class T> inline void My_Tree<T>::_Midtravel(TreeNode<T>* pRoot) { if (pRoot == nullptr) { return; } _Midtravel(pRoot->pLeft); #if ISSHOW if (pRoot->data!=MAX) #endif cout << pRoot->data << " "; _Midtravel(pRoot->pRight); }
-
-
2.4.3 后序遍历
左 右 根
根在中间
//后序输出 左 右 根 template<class T> inline void My_Tree<T>::_Lastravel(TreeNode<T>* pRoot) { if (pRoot == nullptr) { return; } _Lastravel(pRoot->pLeft); _Lastravel(pRoot->pRight); #if ISSHOW if (pRoot->data != MAX) #endif cout << pRoot->data << " "; }
3. 代码:
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209750.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具