二叉树的非递归遍历算法

二叉树的遍历

  1. 广度优先遍历
    采用队列,取出当初层对应的所有节点,并将其所有子节点依次加入队列
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
public List<List<Integer>> levelOrder(TreeNode root) {
	ArrayList<List<Integer>> list = new ArrayList<List<Integer>>();
	if(root == null)
		return list;
	Queue<TreeNode> que = new LinkedList<TreeNode>();
	que.offer(root);
	while(que.size()!=0){
		int len = que.size();
		ArrayList<Integer> subList = new ArrayList<Integer>();
		for(int i = 0; i < len; i++){   \\遍历当初层的元素并将其子节点加入队列中
		root = que.poll();
		subList.add(root.val);
		if(root.left != null){
			que.offer(root.left);
		}
		if(root.right != null){
			que.offer(root.right);  
		}       
	}
	list.add(subList);
}
return list;
}
  1. 深度优先遍历
    深度优先遍历有三种顺序:先序遍历(中左右),中序遍历(左中右),后序遍历(左右中)。
    这三种遍历方式不同的只有头节点的遍历顺序,左子树都先与右子树遍历。当用栈来模拟递归程序时,都是先头节点进栈、然后左子树进栈,最后右子树进栈,不同的只是头节点的访问顺序(头节点在左子树进栈前访问,头节点在左子树进栈后并且在右子树进栈前访问,头节点在右子树进栈后访问)。在访问左子树时,需要对左子树的左子树进行访问,访问左子树的左子树时又要对左子树的左子树的左子树进行访问,这样第一次进行某个右子树的处理是在处理完最左的子树之后。于是得到如下的算法框架:
 /*.......总的框架........*/
 public List<Integer> traversal(TreeNode root) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        if(root == null)
        	return list;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode temp, pre;	//pre用来记录上一个处理的节点,temp用来记录当前要处理的节点
        temp = root; pre = null;
        while(temp != null || !stack.isEmpty()){
		/*...处理左子树...*/
			while(temp != null){	//找到当前节点的最左的子节点并依次进栈
			/*...执行操作...*/
				stack.push(temp);
				temp = temp.left;
			}
        	temp = stack.peek();	//获得栈顶元素,栈顶元素一定满足没有左子树或者左子树已经被处理
        	if(temp.right == null || temp.right == pre){	//当前节点的右子树已处理或者没有右子树
        	/*...执行操作...*/
        		stack.pop();	//退栈
        		pre = temp;
        		temp = null; //当前节点对应的子树处理完毕
        	}else{	//处理右子树
        		/*...执行操作...*/
        		temp = temp.right;
        	}
        }
        return list;
    }

先序遍历时,头节点先于左子树和右子树处理

/*.......先序遍历........*/
 public List<Integer> traversal(TreeNode root) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        if(root == null)
        	return list;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode temp, pre;	//pre用来记录上一个处理的节点,temp用来记录当前要处理的节点
        temp = root; pre = null;
        while(temp != null || !stack.isEmpty()){
		/*...处理左子树...*/
			while(temp != null){	//找到当前节点的最左的子节点并依次进栈
				list.add(temp.val);	//头结点先于左子树处理
				stack.push(temp);
				temp = temp.left;
			}
        	temp = stack.peek();	//获得栈顶元素,栈顶元素一定满足没有左子树或者左子树已经被处理
        	if(temp.right == null || temp.right == pre){	//当前节点的右子树已处理或者没有右子树
        	/*...执行对当前节点的操作...*/
        		stack.pop();	//退栈
        		pre = temp;
        		temp = null; //当前节点对应的子树处理完毕
        	}else{	//处理右子树
        		/*...执行操作...*/
        		temp = temp.right;
        	}
        }
        return list;
    }
/*...先序遍历时头节点先于右子树处理,处理完后就可以弹出当前节点,因此等价于如下版本.../
/*.......简化版........*/
 public List<Integer> traversal(TreeNode root) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        if(root == null)
        	return list;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode temp;	//temp用来记录当前要处理的节点
        temp = root;
        while(temp != null || !stack.isEmpty()){
		/*...处理左子树...*/
			while(temp != null){	//找到当前节点的最左的子节点并依次进栈
				list.add(temp.val);	//头结点先于左子树处理
				stack.push(temp);
				temp = temp.left;
			}
			/*...当前节点及其左子树已处理只需处理右子树...*/
        	temp = stack.pop();	
        	temp = temp.right;
        }
        return list;
    }

中序遍历时,头节点先于右子树后于左子树处理

/*.......中序遍历........*/
 public List<Integer> traversal(TreeNode root) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        if(root == null)
        	return list;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode temp, pre;	//pre用来记录上一个处理的节点,temp用来记录当前要处理的节点
        temp = root; pre = null;
        while(temp != null || !stack.isEmpty()){
		/*...处理左子树...*/
			while(temp != null){	//找到当前节点的最左的子节点并依次进栈
				stack.push(temp);
				temp = temp.left;
			}
        	temp = stack.peek();	//获得栈顶元素,栈顶元素一定满足没有左子树或者左子树已经被处理
        	if(temp.right == null || temp.right == pre){	//当前节点的右子树已处理或者没有右子树
        		if(temp.right == null)//此时右子树还没处理
        			list.add(temp.val);
        		stack.pop();	//退栈
        		pre = temp;
        		temp = null; //当前节点对应的子树处理完毕
        	}else{	//处理右子树
        		list.add(temp.val); //处理头节点
        		temp = temp.right;
        	}
        }
        return list;
    }
/*...中序遍历时头节点先于右子树处理,处理完后就可以弹出当前节点,因此等价于如下版本.../
/*.......简化版........*/
 public List<Integer> traversal(TreeNode root) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        if(root == null)
        	return list;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode temp, pre;	//pre用来记录上一个处理的节点,temp用来记录当前要处理的节点
        temp = root; pre = null;
        while(temp != null || !stack.isEmpty()){
		/*...处理左子树...*/
			while(temp != null){	//找到当前节点的最左的子节点并依次进栈
				stack.push(temp);
				temp = temp.left;
			}
			/*...左子树已处理,只需处理当前节点及其右子树...*/
        	temp = stack.pop();	
        	list.add(temp.val);
			temp = temp.right;
		}
        return list;
    }

后序遍历时,头节点在右子树遍历后访问

/*.......后序遍历........*/
 public List<Integer> traversal(TreeNode root) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        if(root == null)
        	return list;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode temp, pre;	//pre用来记录上一个处理的节点,temp用来记录当前要处理的节点
        temp = root; pre = null;
        while(temp != null || !stack.isEmpty()){
		/*...处理左子树...*/
			while(temp != null){	//找到当前节点的最左的子节点并依次进栈
				stack.push(temp);
				temp = temp.left;
			}
        	temp = stack.peek();	//获得栈顶元素,栈顶元素一定满足没有左子树或者左子树已经被处理
        	if(temp.right == null || temp.right == pre){	//当前节点的右子树已处理或者没有右子树
        	/*...处理头节点...*/
        	    list.add(temp.val);	//temp.right == null时,右子树为空无需处理;temp.right == pre时,右子树处理完毕
        		stack.pop();	//退栈
        		pre = temp;
        		temp = null; //当前节点对应的子树处理完毕
        	}else{	//处理右子树
        		temp = temp.right;
        	}
        }
        return list;
    }

上面需要用到栈,空间复杂度为O(h),h为树的高度,使用Morris遍历算法可以将空间复杂度降低到O(1)。在使用栈时无需判断左子树是是否出处理完毕,并且由于根节点存储在栈中,处理完左子树后可以处理右子树。Morris遍历利用节点中为空的节点来指向根节点。于是有如下遍历框架:

public List<Integer> traversal(TreeNode root) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        if(root == null)
        	return list;
        TreeNode temp, mostRight;	//temp用来记录当前要处理的节点,mostRight用来记录当前节点的最右子节点
        temp = root; mostRight= null;
        while(temp != null){
        	if(temp.left == null){ //左子树为空则处理右子树
        		temp = temp.right;
        	}else{	//左子树不为空
        		//先判断左子树是否处理过
        		mostRight= temp.left;
        		while(mostRight != null || mostRight != temp){ //找到左子树最右节点
        			mostRight = mostRight.right;
        		}
        		if(mostRight == tmep){	//左子树处理过
        			mostRight = null; //还原二叉树
        		}else{	//左子树没有处理过
        			mostRight = temp; //处理完左子树后,指引回到根节点
        			temp = temp.left; //处理左子树
        		}
        	}
        }
        return list;
    }

很明显上面的算法只能在处理完左子树后回到头节点,在处理完右子树后无法回到头节点。这对先序遍历来说是足够的,但对后序遍历来说,需要在处理完右子树后再处理头节点,因此Morris遍历的后续遍历比较麻烦。下面先给出先序和中序遍历算法:
先序遍历算法

public List<Integer> traversal(TreeNode root) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        if(root == null)
            return list;
        TreeNode temp, mostRight;	//temp用来记录当前要处理的节点,mostRight用来记录当前节点的最右子节点
        temp = root; mostRight= null;
        while(temp != null){
            if(temp.left == null){ //左子树为空则处理右子树
                list.add(temp.val);	//先处理头节点
                temp = temp.right;
            }else{	//左子树不为空
                //先判断左子树是否处理过
                mostRight= temp.left;
                while(mostRight.right != null && mostRight.right != temp){ //找到左子树最右节点
                    mostRight = mostRight.right;
                }
                if(mostRight.right == null){    //左子树未处理
                    list.add(temp.val);	//处理头节点
                    mostRight.right = temp; //处理完左子树后,指引回到根节点
                    temp = temp.left; //处理左子树
                }else {	//从线索回到头节点时,头节点已经处理过,无需再处理头节点
                    mostRight.right = null; //还原二叉树
                    temp = temp.right;  //处理右子树
                }
            }
        }
        return list;
    }

中序遍历

public List<Integer> traversal(TreeNode root) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        if(root == null)
            return list;
        TreeNode temp, mostRight;	//temp用来记录当前要处理的节点,mostRight用来记录当前节点的最右子节点
        temp = root; mostRight= null;
        while(temp != null){
            if(temp.left == null){ //左子树为空则处理右子树
                list.add(temp.val);	//先处理头节点
                temp = temp.right;
            }else{	//左子树不为空
                //先判断左子树是否处理过
                mostRight= temp.left;
                while(mostRight.right != null && mostRight.right != temp){ //找到左子树最右节点
                    mostRight = mostRight.right;
                }
                if(mostRight.right == null){    //左子树未处理
                    mostRight.right = temp; //处理完左子树后,指引回到根节点
                    temp = temp.left; //处理左子树
                }else {
                    mostRight.right = null; //还原二叉树
                    list.add(temp.val);	//处理左子树之前处理头节点
                    temp = temp.right;  //处理右子树
                }
            }
        }
        return list;
    }

后续遍历比前序与中序遍历更加麻烦,因为Morris遍历在遍历完右子树节点后无法回到头节点。
在这里插入图片描述
后续遍历实际上是从左到右,以上述箭头的方向访问这些节点。我们只要从左到右,每次回溯到根节点时,以箭头方向打印其左子树最右一条边的节点即可。打印最右一条边时,将最右一条边看做一条单链表,反转链表后再依次访问,访问完之后在将链表反转恢复为之前的顺序。于是有以下代码:

public List<Integer> traversal(TreeNode root) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        if(root == null)
            return list;
        TreeNode temp, mostRight;	//temp用来记录当前要处理的节点,mostRight用来记录当前节点的最右子节点
        temp = root; mostRight= null;
        while(temp != null){
            if(temp.left == null){ //左子树为空则处理右子树
                temp = temp.right;
            }else{	//左子树不为空
                //先判断左子树是否处理过
                mostRight= temp.left;
                while(mostRight.right != null && mostRight.right != temp){ //找到左子树最右节点
                    mostRight = mostRight.right;
                }
                if(mostRight.right == null){    //左子树未处理
                    mostRight.right = temp; //处理完左子树后,指引回到根节点
                    temp = temp.left; //处理左子树
                }else {
                    mostRight.right = null; //还原二叉树
                    printEdege(temp.left, list); 						//逆序输出最右边的一条边
                    temp = temp.right;  //处理右子树
                }
            }
        }
        printEdege(root, list); 
        return list;
    }

public static void printEdege(TreeNode node, LinkedList<Integer> list){
	node = reserve(node);
	while(node != null){
		list.add(node.val);
		node = node.right;
	}
	reserve(node);
}

public static TreeNode reserve(TreeNode node){
	TreeNode pre, temp, next;
	pre = null; temp = node; next= null;
	while(temp != null){
		next = temp.right;
		temp.right = pre;
		pre = temp;
		temp = next;
	}
	return pre;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值