二叉树的插入与遍历

二叉树的插入与遍历

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 二叉树的插入操作:

    1. 插入二叉树需要修改,所以可以使用二级指针或者引用的形式(pRoot根节点为一级指针,修改一级指针需要二级指针或者一级指针的引用),这里首先采用二级指针的形式:

      ​ 我们需要一个T类型的数据,再把根节点以地址的形式传入实现函数里

      //外部成员函数调用私有成员实现函数
      template<class T>
      inline void My_Tree<T>::insert(const T& data)
      {
      	_insert(data, &pRoot);
      }
      
      1. 2.3.1 第一次想到的版本

      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;		//创建一颗树
      
      1. a.Insert(1);	//根节点
        

        我们一开始只具有根节点pRoot=nullptr,当我们插入一个数据的时候,判断当前节点是否为空,例如,我们一开始的pRoot为空,我们插入1,会判断*pRoot为空,会调用节点的构造函数生成一个节点,此节点就是我们的根节点,在生成完毕后立刻retur返回。
        在这里插入图片描述

      2. 当我们有了根节点后,再次插入一个数据,插入到根节点左子树中:

        a.Insert(2);
        

        进入函数,程序首先从根节点开始,检查根是否为空,根不为空,说明要在根的左侧再次寻找位置插入,直到某个节点的根为空。例如2,pRoot不为空,进入循环递归的插入,再次调用函数,此时把树的左指针作为根节点传入函数,进入递归,左指针为空,所以生成节点2,这时已经插入成功了我们也可以返回了
        在这里插入图片描述

        但是,函数并没有结束,他从左插入的函数return返回后,又进入了右指针的插入函数体中(参见递归的特性),又在右指针创建了一个节点,然后再返回主insert函数,在返回主函数,可以看到,递归的特性还是比较头痛的,我们只需要插入左就好了,但是递归不允许

      3. 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));
        }
        

        但是当我们想要插入一个右子树该怎么办?可以发现,我们根本插入不到右子树中,因为只要一进入插入函数一定会首先调用左插入函数,我们也不能改变他,他会一直往左插入

      4. 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);	//右插入
        
    2. 二叉树插入传引用:

      ​ 把二级指针改为 一级指针的引用即可,在函数中直接用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);	//后序遍历
    }
    

    在这里插入图片描述

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Fo68ArF-1659493261751)(C:\Users\woshishuaige\AppData\Roaming\Typora\typora-user-images\1659492640244.png)]

    • 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. 代码:

二叉树的插入与遍历

posted @ 2022-08-03 10:44  hugeYlh  阅读(18)  评论(0编辑  收藏  举报  来源