剑指offer刷题-把二叉树打印成多行(二叉树的前中后序遍历及层序遍历补充)

题目描述

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

思路

题目分析:
在这里插入图片描述
如上图所示,按层打印的顺序,应该先打印根结点,从树的根结点开始分析。为了接下来能够打印值为8的结点的两个子结点,我们应该在遍历该结点时把值为6和10的两个结点保存到一个容器里,现在容器内就有两个结点。按照从左到右打印的要求,我们先取出为6的结点。打印出值6之后把它的分别为5和7的两个结点放入数据容器。此时数据容器中有三个结点,值分别为10、5和7。接下来我们从数据容器中取出值为10的结点。注意到值为10的结点比值为5、7的结点先放入容器,此时又比这两个结点先取出,这就是我们通常说的先入先出,因此不难看出这个数据容器应该是一个队列。由于值为5、7、9、11的结点都没有子结点,因此只要依次取出打印即可。

层序遍历问题?

一开始的思路是对于给定的根节点,查询是否存在左右子节点,若存在,则打印并分别递归的查询子节点是否存在左右子节点。没有想到要将结点存放在容器中,而是直接获取结点的值,这样遍历二叉树,但是并不是层序遍历,不能保证每一层打印一行。

怎么确定一层已经遍历结束从而将下一层在另一行打印?

需要标志位来确定。

遍历过程如下:
在这里插入图片描述
代码实现:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> res  = new ArrayList<ArrayList<Integer>>();
        ArrayList<Integer> tmp = new ArrayList<Integer>();
        //队列存放结点,先进先出
        Queue<TreeNode> nodes = new LinkedList<TreeNode>();
        //now表示目前层还剩几个结点,next表示下一层有几个结点
        int now = 1, next = 0;
        //如果结点为空,则返回空
        if(pRoot == null)
            return res;
        //将根节点添加
        nodes.add(pRoot);
        while( !nodes.isEmpty())
        {
        	//每次取出一个结点,当前层剩余结点数减一
            TreeNode node = nodes.remove();
            now--;
            tmp.add(node.val);
            if(node.left!=null)
            {
            	//查询左右子节点,若存在则放入队列中,下一层的节点数加一
                nodes.add(node.left);
                next++;
            }
            if(node.right != null)
            {
                nodes.add(node.right);
                next++;
            }
            if(now == 0)
            {
                res.add(new ArrayList<Integer>(tmp));
                tmp.clear();
                now = next;
                next = 0;
            }
        }
        return res;
    }
}

题目扩展

一、从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印。

即为对二叉树的层序遍历,结点满足先进先出的原则,采用队列。每从队列中取出头部结点并打印,若其有子结点,把子结点放入队列尾部,直到所有结点打印完毕。

import java.util.Queue;
import java.util.LinkedList;
public void print(TreeNode pHead)
{
	if(pHead == null)
		return;
	Queue<TreeNode> nodes = new LinkedList<>();
	nodes.add(phead);
	
	while( !nodes.isempty())
	{
		TreeNode node = nodes.remove();
		System.out.print(node);
		if(node.left != null)
		{
			nodes.add(node.left);
		}
		if(node.right != null)
		{
			nodes.add(node.right);
		}
	}
	System.out.println();

二、请实现一个函数按照之字形顺序打印二叉树

即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

思路:

另外需要一个标志位cengnum,来判断这行是奇数行还是偶数行,从而判断打印顺序。

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Collections;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        Queue<TreeNode> nodes = new LinkedList<TreeNode>();
        ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
        ArrayList<Integer> temp = new ArrayList<Integer>();
        int now = 1, next = 0, cengnum = 0;
        if(pRoot == null)
            return res;
        nodes.add(pRoot);
        while( !nodes.isEmpty())
        {
            TreeNode node = nodes.remove();
            temp.add(node.val);
            now--;
            if(node.left != null)
            {
                nodes.add(node.left);
                next++;
            }
            if(node.right != null)
            {
                nodes.add(node.right);
                next++;
            }
            if(now == 0)
            {
                cengnum++;
                if(cengnum % 2==0)   //偶数行,从右到左,即颠倒tem的顺序
                {
                    Collections.reverse(temp);
                    res.add(new ArrayList<Integer>(temp));
                    now = next;
                    next = 0;
                }
                else
                {
                    res.add(new ArrayList<Integer>(temp));
                    now = next;
                    next = 0;
                }
                temp.clear();
            }
        }
        return res;
    }
}

PS:补充前中后序遍历

树的前中后序遍历是个递归的定义,在遍历到根节点的左/右子树时,也要遵循前/中/后序遍历的顺序,
在这里插入图片描述
前序遍历:ABDECFG

中序遍历:DBEAFCG

后序遍历:DEBFGCA

层序遍历:ABCDEFG

一、二叉树的前序遍历

递归实现代码:

public void PreOrder(TreeNode pRoot)
{	
	if(pRoot == null)
		return ;
	System.out.print(pRoot.val);
	if(pRoot.left != null)
		PreOrder(pRoot.left);
	if(pRoot.right != null)
		PreOrder(pRoot.right);
}

非递归实现代码:

import java.util.Stack;
import java.util.LinkedList;
public void preorder(TreeNode pRoot)
{
	Stack<TreeNode> res = new LinkedList<TreeNode>();	
	if(pRoot == null)
		return;
	while(pRoot != null || !res.isEmpty())
	{
		while(pRoot != null)
		{
			res.push(pRoot);
   			System.out.print(pRoot.val);
			pRoot = pRoot.left;
		}
		pRoot = res.pop();
		pRoot = pRoot.right;
	}	
}

需要用来存储树的结点,栈中保存的元素的共同点是:都是自己和自己的左孩子都访问过了,而右孩子还没有访问到的节点。

即先沿左子树向下遍历,当发现没有左子树时,需要访问右子树,而此时的访问顺序是需要按之前的相反顺序,即符合先进后出的规则。

1、先从根节点开始,向左子树遍历,并依次将七压栈,直到结点D。
在这里插入图片描述
2、此时D的左子树为空, 故将D出栈,查询D的右子树还是为空,此时检查栈是否为空,不为空说明还有右子树没有访问的结点,所以B继续出栈,访问B的右孩子E在这里插入图片描述
3、从栈中拿出A节点,去访问A的右孩子C,在访问到G节点的右孩子之后,发现当前节点cur为空,栈中也没有元素可以取出来了,这时候就代表整棵树都被访问过了,便结束循环。
在这里插入图片描述

二、二叉树的中序遍历

递归实现代码:

public void InOrder(TreeNode pRoot)
{
	if(pRoot == null)
		return;
	InOrder(pRoot.left);
	System.out.print(pRoot.val);
	InOrder(proot.right);
}

非递归实现代码:

import java.util.LinkedList;
import java.util.Stack;

public void InOrder(TreeNode pRoot)
{
	Stack<TreeNode> res = new LinkedList<TreeNode>();
	while(pRoot != null || !res.isEmpty())
	{
		while(pRoot != null)
		{
			res.push(pRoot);
			pRoot = pRoot.left;
		}
		pRoot = res.pop();
		System.out.print(pRoot.val);
		pRoot = proot.right;
	}
}

三、二叉树的后序遍历

递归实现代码

public void AfterOrder(TreeNode pRoot)
{	
	if(pRoot == null)
		return;
	AfterOrder(pRoot.left);
	AfterOrder(pRoot.right);	
	System.out.print(pRoot.val);
}

非递归实现代码

import java.util.LinkedList;
import java.util.Stack;

public void AfterOrder(TreeNode pRoot)
{
	Stack<TreeNode> res = new LinkedList<TreeNode>();
	while(pRoot != null || !res.isempty())
	{
		TreeNode last = null;
		while(pRoot != null)
		{
			res.push(pRoot);
			pRoot = pRoot.left;
		}		
 		TreeNode top = res.top();
		if(top.right == null || top.right == last)
		{
			res.pop();
			System.out.print(top.val);
			last = top;			
		}
		else
		{
			pRoot = top.right;
		}		
	}
}

1、还是沿着左子树一路往下走,将路过的节点都压栈,直到走到空节点

刚打印在这里插入图片描述
2、然后从栈中看一下栈顶元素(只看一眼,用top指针记下,先不出栈),如果top节点没有右子树,或者last等于top的右孩子,说明top的右子树不存在或者遍历过了,就输出top节点的值,并将栈顶元素pop掉(出栈),反之则是从左子树回到根节点的,接下来要去右子树。
在这里插入图片描述
如图,top的右孩子为空,说明右子树不存在,就可以输出top的值并pop掉栈顶了,这时候用last指针记下top指向的节点,代表上一次处理的节点。(这一过程cur始终没有动,一直指向空)

在这里插入图片描述
继续从栈顶看一个元素记为top,然后发现top的右孩子不为空,而且last也不等于top->right,就使cur = top->right,回到第一步,用同样的方法来处理top的右子树,下一次回来的时候,last指针指向的是E节点
在这里插入图片描述
这时候发现top的右孩子不为空,但是last等于top->right,说明top的右子树遍历完成,下一步就要输出top的值并且将这个节点出栈,下一次再从栈中看一个栈顶元素A即为top
在这里插入图片描述
这时候再比较,发现top的right不为空,而且last也不等于top->right,说明top有右子树并且还没有遍历过,就让cur = top->right,回到第一步用同样的方法来遍历A的右子树。
到最后,cur访问到了G的左孩子,而top也一路出栈到了A节点,发现cur为空,并且栈中也为空,这时候便代表整个树已经遍历完成,结束循环

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值