树的递归与非递归遍历
基于树的递归遍历,其基本思想简单,从代码可以看出遍历根节点和左右子树的先后顺序,代码如下所示:
import java.util.ArrayList;
import java.util.Collections;
public class RecusionBinaryTreeTraverse {
public static void main(String[] args) {
TreeNode root = BuildTree.buildTree();
ArrayList<Integer> list = new ArrayList<>();
System.out.println("先序遍历的结果为:"+preTraverse(root,list).toString());
list.clear();
System.out.println("中序遍历的结果为:"+infixTraverse(root,list).toString());
list.clear();
System.out.println("后序遍历的结果为:"+postTraverse(root,list).toString());
//System.out.println(root);
}
//先序递归遍历
public static ArrayList<Integer> preTraverse(TreeNode node,ArrayList<Integer> list){
if (node==null){
return null;
}else{
list.add(node.getVal());
// System.out.print(node.getVal());
if (node.getLeft()!=null){
preTraverse(node.getLeft(),list);
}if (node.getRight()!=null){
preTraverse(node.getRight(),list);
}
return list;
}
}
//中序递归遍历
public static ArrayList<Integer> infixTraverse(TreeNode node,ArrayList<Integer> list){
if (node==null){
return null;
}else {
if (node.getLeft()!=null){
infixTraverse(node.getLeft(),list);
}
list.add(node.getVal());
if (node.getRight()!=null){
infixTraverse(node.getRight(),list);
}
}
return list;
}
//后序递归遍历
public static ArrayList<Integer> postTraverse(TreeNode node,ArrayList<Integer> list){
if (node==null){
return null;
}else {
if (node.getLeft()!=null){
postTraverse(node.getLeft(),list);
}
if (node.getRight()!=null){
postTraverse(node.getRight(),list);
}
list.add(node.getVal());
}
return list;
}
}
运行结果为:
此外这里着重讲解一下非递归的思想,其思想过程在代码上给出,并做好相应的注释。方便大家理解。
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Queue;
import java.util.Stack;
public class NonRecusionBinaryTreeTraverse {
public static void main(String[] args) {
TreeNode root = BuildTree.buildTree();
ArrayList<Integer> list = new ArrayList<>();
System.out.println("非递归先序遍历的结果为:"+preTraverse(root,list).toString());
list.clear();
System.out.println("非递归中序遍历的结果为:"+infixTraverse(root,list).toString());
list.clear();
System.out.println("非递归后序遍历的结果为:"+postTraverse(root,list).toString());
list.clear();
System.out.println("非递归层次遍历的结果为:"+levelTraverse(root,list).toString());
}
/*
* 思想:根据前序遍历访问的顺序,优先访问根结点,然后再分别访问左孩子和右孩子。
即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左孩子不为空,
按相同规则访问它的左子树;当访问其左子树时,再访问它的右子树。因此其处理过程如下:
对于任一结点P:
1)访问结点P,并将结点P入栈;
2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,
并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
3)直到P为NULL并且栈为空,则遍历结束。
*
* */
//先序非递归遍历
public static ArrayList<Integer> preTraverse(TreeNode node,ArrayList<Integer> list){
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty()||node!=null){
while (node!=null){
list.add(node.getVal());
stack.push(node);
node = node.getLeft();
}
if (!stack.isEmpty()){
node = stack.pop().getRight();
}
}
return list;
}
/*
* 思想:
* 根据中序遍历的顺序,对于任一结点,优先访问其左孩子,而左孩子结点又可以看做一根结点,
* 然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点才进行访问,然后按相同的规则访问其右子树。
* 因此其处理过程如下:
对于任一结点P,
1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;
3)直到P为NULL并且栈为空则遍历结束
* */
//中序非递归遍历
public static ArrayList<Integer> infixTraverse(TreeNode node,ArrayList<Integer> list){
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty()||node!=null){
while (node!=null){
stack.push(node);
node = node.getLeft();
}
if (!stack.isEmpty()){
list.add(stack.peek().getVal());
node = stack.pop().getRight();
}
}
return list;
}
/*
* 思想:因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点
* 要保证根结点在左孩子和右孩子访问之后才能访问,因此
* 1.对于任一结点P,先将其入栈。
* 2.如果P不存在左孩子和右孩子,则可以直接访问它;
* 3.或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。
* 4.若非上述两种情况,则将P的右孩子和左孩子依次入栈,
* 这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。
* */
//后序非递归遍历
public static ArrayList<Integer> postTraverse(TreeNode node,ArrayList<Integer> list){
if (node==null){
return list;
}
//1.对于任一结点P,先将其入栈。
Stack<TreeNode> stack = new Stack<>();
stack.push(node);
TreeNode visit = null;//用来标记左右孩子是否被访问过,初始为null
TreeNode stackTop = null;//用来指向栈顶节点
while (!stack.isEmpty()){
stackTop = stack.peek();
if ((stackTop.getLeft()==null&&stackTop.getRight()==null)//如果P不存在左孩子和右孩子,则可以直接访问它;
//或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。
||visit!=null&&(stackTop.getLeft()==visit||stackTop.getRight()==visit)){
visit = stack.pop();//标记已经访问,并出栈
list.add(visit.getVal());
}else {
//由于是后续遍历。所以先是右孩子进栈,然后是左孩子进栈
if (stackTop.getRight()!=null){
stack.push(stackTop.getRight());
}
if (stackTop.getLeft()!=null){
stack.push(stackTop.getLeft());
}
}
}
return list;
}
/*
* 思想:
* 1.首先将根节点放入队列中。
2.当队列为非空时,循环执行步骤3到步骤5,否则执行6;
3.出队列取得一个结点,访问该结点;
4.若该结点的左子树为非空,则将该结点的左子树入队列;
5.若该结点的右子树为非空,则将该结点的右子树入队列;
6.结束。
* */
// //层次非递归遍历
public static ArrayList<Integer> levelTraverse(TreeNode node,ArrayList<Integer> list){
Queue<TreeNode> queue = new ArrayDeque<>();
if (node==null){
return list;
}
TreeNode queueHead = null;
queue.offer(node);//入队
while (!queue.isEmpty()){
queueHead = queue.poll();//出队
list.add(queueHead.getVal());
if (queueHead.getLeft()!=null){
queue.offer(queueHead.getLeft());
}if (queueHead.getRight()!=null){
queue.offer(queueHead.getRight());
}
}
return list;
}
}
运行结果为:
这里顺便贴上树的结构定义与一个二叉树的建立:
public class TreeNode {
private int val;
private TreeNode left;
private TreeNode right;
public TreeNode() {
}
public TreeNode(int val) {
this.val = val;
}
public TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
public int getVal() {
return val;
}
public void setVal(int val) {
this.val = val;
}
public TreeNode getLeft() {
return left;
}
public void setLeft(TreeNode left) {
this.left = left;
}
public TreeNode getRight() {
return right;
}
public void setRight(TreeNode right) {
this.right = right;
}
@Override
public String toString() {
return "TreeNode{" +
"val=" + val +
", left=" + left +
", right=" + right +
'}';
}
}
class BuildTree{
public static TreeNode buildTree(){
//定义一棵二叉树
TreeNode node1 = new TreeNode(1);
TreeNode node2 = new TreeNode(2);
TreeNode node3 = new TreeNode(3);
TreeNode node4 = new TreeNode(4);
TreeNode node5 = new TreeNode(5);
TreeNode node6 = new TreeNode(6);
node1.setLeft(node2);
node1.setRight(node3);
node2.setLeft(node4);
node3.setLeft(node5);
node3.setRight(node6);
return node1;
}
}