二叉查找树的实现(三)——递归遍历操作

     前面我们说到二叉查找树的遍历可以分为广度优先遍历以及深度优先遍历,我们也通过非递归的方式实现了这两种遍历。实际上二叉查找树的遍历可以细分为更多种方式。

     我们假定对二叉查找树的遍历中有三个任务:V(输出当前节点),L(访问左结点),R(访问右节点),根据V、L、R三者不同的顺序我们可以得出6种不同的遍历方式:VRL,VLR,LRV,LVR,RLV,RVL。再假设我们规定访问时都按照从左至右的顺序,那么还剩下VLR,LVR以及LRV三种遍历方式(L一定要在R前面),而通常分别称这三种遍历方式为前序遍历,中序遍历以及后序遍历。

     要实现这三种遍历方式,其实不难,考虑用递归的方式可以很简洁地达到目的。

一、三大遍历

1.前序遍历

        以前序遍历为例,首先前序遍历的程序如下,为了方便说明,其中几行标上数字记号:

 

void Btree::preorder()
{
  preorder(root);

}
void Btree::preorder(BtreeNode *vlr)
{
	if (vlr != 0)
	{
		
	/*I*/	     cout << vlr ->ele << endl;
    /*II*/        preorder(vlr->left);
	/*III*/	     preorder(vlr->right);
	
	/*IV*/}
	else
	{
	}

}

      很显然这是一个递归的程序,在preorder函数里又对left和right调用了preorder函数, 我们以一个简单的二叉查找树为例,看一看程序做了什么事:

                                                                     

 (1)首先将根节点root=8传给函数,函数开始运行,判断8节点不为空,因此执行I处语句,输出该节点,然后执行II处语句,进入递归调用,将节点3以及地址III压入栈内

(2)节点3进入函数,判断3节点不为空,输出该节点,继续对其左节点递归调用,将1以及地址III压入栈内

(3)节点1进入函数,判断1节点不为空,输出该节点,继续对其左节点递归调用,将null以及地址III压入栈内

(4)null进入函数,判断为空,不满足条件,函数退出。本级栈被销毁,回到节点1的栈,执行地址III处的语句,将1的右节点压入栈,实际上1的右节点也为空,函数退出,此栈销毁

(5)由于节点1已经执行到地址III处,函数执行完,节点1栈被销毁,进入节点3的栈,执行地址III语句,即搜寻节点3的右分支

(6)重复此操作知道3的右分支遍历完毕回到节点3的栈,由于节点3已经执行到地址III处语句,函数执行完毕,因此节点3的栈被销毁,进入节点8的栈,并执行地址III处语句,即搜寻节点8的右分支。

(7)重复之前的操作,直到节点8右分支搜寻完毕,回到节点8的栈,节点8已经执行函数完毕,退出并销毁栈,结束。

      其实,在整个操作中我们最应该关注的两个字就是重复,二叉树最大的特点就是节点之间性质相同,一个子节点也可以是下一级的根节点,看上去我们一开始是在对根节点8进行操作,实际上可以分解为对根节点3以及根节点10进行操作,并且记住,操作方式完全相同,也就是重复,以此类推下去,就可以对整个树进行遍历,这种思想对于二叉树的操作尤为重要。

      上述遍历的结果是8,3,1,6,4,7,19,14,13,我们对这个结果分析一下。很显然,对于前序遍历,第一个输出的一定是整个树的根节点8,但是剩余的情况我们就不得而知了,因为我们不知道8的左右子树节点情况,有可能8左边或者右边一个节点都没有,因此还需要借助其他的条件联合判断,此处我们只需要记住一句话,对一个树进行前序访问,先输出的是根节点,剩下的怎么办?剩下的子树仍然是树,可以继续套用这规则。

2、中序遍历

      中序遍历的程序如下:

void Btree::inorder()
{
  inorder(root);

}
void Btree::inorder(BtreeNode *lvr)
{
	if (vlr != 0)
	{
		inorder(lvr->left);
		cout << lvr ->ele << endl;
		inorder(lvr->right);
	
	}
	else
	{
	}

}

     中序遍历跟前序遍历基本一样,只是将输出语句与访问左结点的语句换了个顺序,对函数执行过程的分析也与前面一样,最终我们可以得出中序遍历的结果是1,3,4,6,7,8,10,13,14。同样,如果你对中序遍历的过程已经熟悉了,你可以得出一个结论,中序遍历会先遍历完根节点左边的子树,然后返回来输出根节点,接着再去遍历根节点右子树,对于整个树是如此,对于子树同样如此

3、后序遍历

   后序遍历的程序如下:

void Btree::postorder()
{
  postorder(root);

}
void Btree::postorder(BtreeNode *lrv)
{
	if (lrv!= 0)
	{
		postorder(lrv->left);
		postorder(lrv->right);
	    cout << lrv ->ele << endl;
	}
	else
	{
	}

}

   后序遍历将输出语句放在了最后,最终遍历结果是1,4,7,6,3,13,14,10,8。对于后序遍历同样,按照程序运行的步骤同样可以得出一个结论:对一棵树进行后序遍历,最后输出的一定是根节点,对树来说是这样,对于树的子树同样也是这样

二、时间复杂度与空间复杂度

    1、时间复杂度

      每一层递归要执行的程序复杂度为O(1)

         T(n)=2T(n/2)+O(1) =2T(n/4)+2O(1)+O(1) ... =O(1)\times (1+2+4+...+\log n-1) =O(n)

     2、空间复杂度

          递归遍历涉及到递归操作,必然需要依赖栈来实现,这样就需要空间复杂度,实际上栈的层数与二叉树的层数直接相关,二叉树层数为log(n),所以空间复杂度为log(n)

二、由两种遍历结果还原整个树

    给我们一个树以及遍历规则,可以输出遍历结果,但有时我们也经常会遇到给出遍历规则以及遍历结果让你去还原树的问题。根据前面的分析,我们已经知道,仅根据单个遍历规则以及其遍历结果是不足以还原整个树的。事实上如果需要还原整个树,至少需要两种遍历规则结合起来。

1、前序遍历加中序遍历

     现在给定遍历规则和遍历结果:

   前序遍历(8,3,1,6,4,7,19,14,13)

   中序遍历(1,3,4,6,7,8,10,13,14)

  让我们一起来还原这棵树,新几次挖一次莫,Hitachi!。

  (1)首先看前序遍历,最先输出的一定是整个树的根节点,即8是根节点

(2)再看中序遍历,根节点8左侧是左子树的所有节点,右边的是右子树的所有节点,ok,1,3,4,6,7是左子树,10,13,14是右子树

(3)补充一点,前面我们说,对于前序遍历,你只能找到根节点,其他信息你无从得知,因为你不知道左子树和右子树到底有哪些点。但是现在不同了,我们通过中序遍历找到了左子树,和右子树,那么又有一条结论,如果确定根节点有左子树和右子树,那么前序遍历里左子树里第一个点就是整个左子树的根节点,右子树的第一个点就是整个右子树的根节点(子树也是树,其根节点同样会第一个输出)。因此3是整个左子树的根节点,10是整个右子树的根节点

(4)go on,现在3是整个左子树的根节点,我们可以把整个左子树看做是一个数,重复(1)(2)(3)的步骤,这样我们就可以还原出整个左子树的情况。根据中序遍历,3左边是1,右边是4,6,7,再根据前序遍历,1是左子树根节点,6是右子树根节点,然后继续下去,可以退出4是6的左子节点,7是右子节点

(5)对于根节点8的右子树,同样是树,可以套用相同的规则知道直到还原出右子树的面貌

2、后序遍历加中序遍历

   现在给定遍历规则和遍历结果:

  后序遍历(1,4,7,6,3,13,14,10,8)

 中序遍历(1,3,4,6,7,8,10,13,14)

同样,还原这棵树:

(1)根据后序遍历结果我们知道8是整个树的根节点

(2)同样,根据中序遍历我们可以划分8的左子树与右子树,结论与前面一致,1,3,4,6,7是左子树,10,13,14是右子树

(3)同样我们补充结论:如果确定根节点有左子树以及右子树,那么左子树的最后一个点就是整个左子树的根节点,右子树的最后一个点就是整个右子树的根节点(子树也是树,其根节点必定是最后一个访问)。这样我们可以得出3是左子树根节点,10是右子树根节点,以此类推可以推出整个树的面貌

3、前序遍历加后序遍历

     遗憾的是,仅仅给出前序遍历以及后序遍历,我们是无法还原整个树的,因为我们可以看出,前序遍历和后序遍历对于我们的推导来说能给出的信息时一样的。至此我们可以给出一个结论:前序后序定顺序,中序定左右

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
二叉链实现二叉树先序中序后序遍历操作的具体步骤如下: 1. 定义二叉树节点类,包含节点值、左右子节点等属性。 2. 根据输入的前序序列和中序序列构建二叉树。具体步骤如下: 1. 前序序列的第一个元素为根节点,将其在中序序列中找到对应位置,左边为左子树的中序序列,右边为右子树的中序序列。 2. 根据左子树的中序序列长度,在前序序列中找到左子树的前序序列,右边为右子树的前序序列。 3. 递归构建左子树和右子树。 3. 实现先序遍历、中序遍历和后序遍历函数。具体步骤如下: 1. 先序遍历:先输出当前节点的值,再递归遍历左子树和右子树。 2. 中序遍历:先递归遍历左子树,再输出当前节点的值,最后递归遍历右子树。 3. 后序遍历:先递归遍历左子树和右子树,最后输出当前节点的值。 4. 统计二叉树的叶子数。具体步骤如下: 1. 如果当前节点为空,则返回0。 2. 如果当前节点为叶子节点,则返回1。 3. 否则,递归统计左子树和右子树的叶子数,并将它们相加。 下面是Python代码实现: ```python class TreeNode: def __init__(self, val): self.val = val self.left = None self.right = None def buildTree(preorder, inorder): if not preorder or not inorder: return None root_val = preorder[0] root = TreeNode(root_val) root_index = inorder.index(root_val) root.left = buildTree(preorder[1:root_index+1], inorder[:root_index]) root.right = buildTree(preorder[root_index+1:], inorder[root_index+1:]) return root def preorderTraversal(root): if not root: return [] res = [] res.append(root.val) res += preorderTraversal(root.left) res += preorderTraversal(root.right) return res def inorderTraversal(root): if not root: return [] res = [] res += inorderTraversal(root.left) res.append(root.val) res += inorderTraversal(root.right) return res def postorderTraversal(root): if not root: return [] res = [] res += postorderTraversal(root.left) res += postorderTraversal(root.right) res.append(root.val) return res def countLeaves(root): if not root: return 0 if not root.left and not root.right: return 1 return countLeaves(root.left) + countLeaves(root.right) # 示例 preorder = [1, 2, 4, 5, 3, 6, 7] inorder = [4, 2, 5, 1, 6, 3, 7] root = buildTree(preorder, inorder) print("先序遍历:", preorderTraversal(root)) print("中序遍历:", inorderTraversal(root)) print("后序遍历:", postorderTraversal(root)) print("叶子数:", countLeaves(root)) --相关问题--:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值