题目描述
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
思路
题目分析:
如上图所示,按层打印的顺序,应该先打印根结点,从树的根结点开始分析。为了接下来能够打印值为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为空,并且栈中也为空,这时候便代表整个树已经遍历完成,结束循环