二叉树的遍历前中后序遍历框架——递归/栈/Morris

递归

时间:N,访问每个节点
空间:N,最坏情况下树为链表

前序/中序/后序

void order(TreeNode root)
{
	if(root == null)
	{
		return;
	}
	//前序遍历代码位置
	
	preorder(root.left);
	
	//中序遍历代码位置
	
	preorder(root.right);
	
	//后序遍历代码位置
}

栈的思路就是将递归所使用的虚拟机栈模拟出来

时间:N
空间:N

前序遍历

  • 递归思路:先根节点,再左子树,再右子树递归遍历左右子树,即从根开始一直访问左下的左子节点(每个节点运行前序遍历代码),到达左下角后返回,遇上有右子树的进入右子树
  • 迭代思路:先访问根节点,再遍历左子树、右子树,因为栈的先进后出原则,遍历左子树之前先将右子树压入栈中
void preorder(TreeNode root)
{
	Deque<TreeNode> stack = new LinkedList<>();
	
	while(root != null || !stack.isEmpty())
	{
		while(root != null)
		{
			//前序遍历代码位置
			
			stack.push(root.right);
			//访问左子树
			root = root.left;
		}
		//访问右子树
		root = stack.pop();
		
	}
}

中序遍历

  • 递归思路:先左子树,再根节点,再右子树,递归遍历左右子树,即先到达左下角,从左下角开始不断向上(这个过程才运行中序遍历代码),节点有右子树便转入右子树
  • 迭代思路:在节点弹出时访问,所以先压入栈
void inorder(TreeNode root)
{
	Deque<TreeNode> stack = new LinkedList<>();
	
	while(root != null || !stack.isEmpty())
	{
		while(root != null)
		{
			stack.push(root.left);
			root = root.left;
		}
		root = stack.pop();
		//中序遍历代码位置
		root = root.right;
		
	}
}

后序遍历

第一种思路:前序遍历是中左右,后序遍历是左右中,将前序遍历改为中右左后翻转

void preorder(TreeNode root)
{
	Deque<TreeNode> stack = new LinkedList<>();
	
	while(root != null || !stack.isEmpty())
	{
		while(root != null)
		{
			//后序遍历代码位置
			
			stack.push(root.left);
			root = root.right;
		}
		root = stack.pop();
		
	}
	reverse();
}

第二种思路:使用pre指针来记录已经遍历的树的根节点

void preorder(TreeNode root)
{
	Deque<TreeNode> stack = new LinkedList<>();
    TreeNode pre = null;
    while (root != null || !stack.isEmpty()) 
    {
         while (root != null) 
         {
                stack.push(root);
                root = root.left;
         }
         root = stack.pop();
         //如果右子树不存在或已经遍历过
         if (root.right == null || root.right == pre) 
         {
             //后序遍历代码
             pre = root;
             root = null;
         } 
         else 
         {
             stack.push(root);
             root = root.right;
         }
     }


	
}

Morris

Morris遍历使用二叉树节点中大量指向null的指针,由Joseph Morris 于1979年发明。
时间复杂度:O(n)
空间复杂度:1
在这里插入图片描述
Morris的整体思路就是将 以某个根结点开始,找到它左子树的最右侧节点之后与这个根结点进行连接,我们可以从 图2 看到,如果这么连接之后,cur 这个指针是可以完整的从一个节点顺着下一个节点遍历,将整棵树遍历完毕,直到 7 这个节点右侧没有指向。

1.新建临时节点,令该节点为 root;

2.如果当前节点的左子节点为空,将当前节点加入答案,并遍历当前节点的右子节点;

3.如果当前节点的左子节点不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点:

  • 如果前驱节点的右子节点为空,将前驱节点的右子节点设置为当前节点。然后将当前节点加入答案,并将前驱节点的右子节点更新为当前节点。当前节点更新为当前节点的左子节点。
  • 如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空。当前节点更新为当前节点的右子节点。

重复步骤 2 和步骤 3,直到遍历结束。

void order(TreeNode root)
{
	TreeNode pre = null;
	while(root != null)
	{
		if(root.left != null){
			//寻找左子树最右侧节点
			pre = root.left;
			//右子树不为空且没有连接过,否则又回到了根节点
			while(pre.right != null && pre.right != root)
			{
				pre = pre.right;
			}

			//让pre右指针指向root,继续遍历左子树
			if(pre.right == null)
			{
				pre.right = root;
				root = root.left;
			}
			//左子树的最右节点指向了root,说明又回到了root,左子树访问完毕,断开连接
			else
			{
				pre.right = null;
				root = root.right;
			}
			
		}
		//没有左孩子则直接访问右孩子
		else
		{
			root = root.right;
		}
		
		
	}
}

前序遍历

根据前序遍历的顺序,
1.在根节点创建连线的时候打印该根节点
2.打印无法创建连线的节点

void preorder(TreeNode root)
{
	TreeNode pre = null;
	while(root != null)
	{
		if(root.left != null)
		{
			//寻找左子树最右侧节点
			pre = root.left;
			//右子树不为空且没有连接过,否则又回到了根节点
			while(pre.right != null && pre.right != root)
			{
				pre = pre.right;
			}

			//让pre右指针指向root,继续遍历左子树
			if(pre.right == null)
			{
				//前序遍历代码位置
				
				pre.right = root;
				root = root.left;
				continue;
			}
			//左子树的最右节点指向了root,说明又回到了root,左子树访问完毕,断开连接
			else
			{
				pre.right = null;
			}
			
		}
		//右子树为空的情况下打印自身
		else
		{
			//前序遍历代码位置
		}
		//左子树走不通则一直进入右子树
		root = root.right;

		
		
	}
}

中序遍历

从最左侧开始顺着右节点打印

void preorder(TreeNode root)
{
	TreeNode pre = null;
	while(root != null)
	{
		if(root.left != null)
		{
			//寻找左子树最右侧节点
			pre = root.left;
			//右子树不为空且没有连接过,否则又回到了根节点
			while(pre.right != null && pre.right != root)
			{
				pre = pre.right;
			}

			//让pre右指针指向root,继续遍历左子树
			if(pre.right == null)
			{
				pre.right = root;
				root = root.left;
				continue;
			}
			//左子树的最右节点指向了root,说明又回到了root,左子树访问完毕,断开连接
			else
			{
				pre.right = null;
			}
			
		}
		//中序遍历代码位置
		
		//左子树走不通则一直进入右子树
		root = root.right;

		
		
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值