LeetCode094——二叉树的中序遍历

版权声明:版权所有,转载请注明原网址链接。 https://blog.csdn.net/qq_41231926/article/details/82056504

原题链接:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/description/

题目描述:

知识点:二叉树、中序遍历、递归

思路一:递归实现

学过数据结构的人都知道,二叉树天然的具有递归性质,因为二叉树的定义就是用递归的形式定义的。因此,在中序遍历二叉树的时候我们完全可以采用递归算法。

所谓中序遍历,就是先去访问该节点的左孩子,再访问该节点和该节点的右孩子。

由于要遍历每一个节点,这样实现的时间复杂度是O(n)级别的,其中n为二叉树中的节点个数。而对于空间复杂度,由于递归存在对系统栈的调用,而这里递归层数就是树的高度,因此空间复杂度是O(h)级别的,其中h为树的高度。

JAVA代码:

public class Solution {

	public List<Integer> inorderTraversal(TreeNode root) {
		List<Integer> list = new ArrayList<>();
		inorderTraversal(root, list);
		return list;
	}
	
	private void inorderTraversal(TreeNode treeNode, List<Integer> list) {
		if(treeNode == null) {
			return;
		}
		inorderTraversal(treeNode.left, list);
		list.add(treeNode.val);
		inorderTraversal(treeNode.right, list);
	}
}

LeetCode解题报告:

思路二:模拟系统栈的递归过程

在我们思路一的实现中,我们利用递归的性质实现了二叉树的中序遍历,其实本质上是利用了系统栈后进先出的性质。

扫描二维码关注公众号,回复: 2895696 查看本文章

那么如果我们自己创建一个栈来模拟系统栈的全过程呢?那么我们也就不需要用到递归这个方法,我们完全可以用非递归的形式来实现我们的思路。注意这里入栈的顺序应该是先访问当前节点的右孩子,再记录当前节点的值,最后访问其左孩子

其实这个思路和思路一本质上是一模一样的,只不过思路一中使用的是系统栈,而思路二中使用的是我们自定义的栈。因此时间复杂度为O(n),其中n为二叉树中的节点个数。空间复杂度为O(h),其中h为树的高度。

JAVA代码:

public class Solution {

	private class Command {
		private String s;
		private TreeNode treeNode;

		public Command(String s, TreeNode treeNode) {
			this.s = s;
			this.treeNode = treeNode;
		}
	}

	public List<Integer> inorderTraversal(TreeNode root) {
		List<Integer> list = new ArrayList<>();
		if(root == null) {
			return list;
		}
		Stack<Command> stack = new Stack<>();
		stack.push(new Command("go", root));
		while(!stack.isEmpty()) {
			Command command = stack.pop();
			if("visit".equals(command.s)) {
				list.add(command.treeNode.val);
			}
			if("go".equals(command.s) && command.treeNode != null) {
				stack.push(new Command("go", command.treeNode.right));
				stack.push(new Command("visit", command.treeNode));		
				stack.push(new Command("go", command.treeNode.left));
			}
		}
		return list;
	}
}

LeetCode解题报告:

思路三:模拟手工计算中序遍历的过程,用栈来记录之前遍历过的节点

中序遍历与前序遍历不同,前序遍历的本质是在第一次访问到该节点的时候就记录该节点的值,而中序遍历的本质是在第二次访问到该节点的时候才记录该节点的值。因此,如果按照前序遍历思路三的实现,我们会发现一个难点:入栈的是根元素,而我们第一次就把根元素出栈了,而根据我们中序遍历的思想,我们是要在第二次访问根元素的时候才记录根元素的值,而根元素出栈后我们无法再次找到这个根元素了!因此,这里的思路三对应的其实是前序遍历中的思路四

我们手动计算中序遍历结果的时候的思路是设立一个指向根结点的指针,该指针一直往其左孩子方向走,一直走到头,然后再返回其父节点,再访问该父节点的右孩子。我们手动计算的时候很容易能看出一个节点的父节点是什么,但对于我们程序而言,只能通过父节点找到子节点,却不能通过子节点来找到其父节点。因此,我们需要用一个栈来记录我们之前访问过的节点在哪里

同样需要遍历每一个节点,时间复杂度为O(n),其中n为二叉树中的节点个数。空间复杂度为O(h),其中h为树的高度。

JAVA代码:

public class Solution {

	public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null) {
        	return list;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while(cur != null || !stack.isEmpty()) {
        	if(cur != null) {
        		stack.push(cur);
        		cur = cur.left;
        	}else {
        		cur = stack.pop();
        		list.add(cur.val);
        		cur = cur.right;
        	}
        }
        return list;
	}
}

LeetCode解题报告:

思路四:思路三的另一种实现形式

把思路三的if-else语句用一个while循环来代替。

实现思路和思路三是一模一样的,自然时间复杂度和空间复杂度和思路三也是一样的。时间复杂度为O(n),其中n为二叉树中的节点个数。空间复杂度为O(h),其中h为树的高度。

JAVA代码:

public class Solution {

	public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null) {
        	return list;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while(cur != null || !stack.isEmpty()) {
        	while(cur != null) {
        		stack.push(cur);
        		cur = cur.left;
        	}
        	cur = stack.pop();
        	list.add(cur.val);
        	cur = cur.right;
        }
        return list;
	}
}

LeetCode解题报告:

思路五:Morris遍历

中序遍历中的Morris遍历的思路和前序遍历中的Morris遍历相同,事实上,Morris本人所提出的就是Morris中序遍历,Morris前序遍历和Morris后序遍历是后人根据Morris中序遍历思路的启发所提出的算法。

前面介绍的思路一到四,其时间复杂度都是O(n),n为树的节点数。空间复杂度都是O(h),其中h为树的高度。由于我们每一次遍历肯定要访问到每一个节点,对于时间复杂度而言,我们已经没有了优化的空间。那么,对于空间复杂度,我们能否做进一步的优化呢?这就是Morris遍历所要做的事。

本博文对于Morris前序遍历的介绍极大程度上参考了该博主的博文:http://www.cnblogs.com/AnnieKim/archive/2013/06/15/morristraversal.html

在思路三和思路四中,我们利用栈来保存某一节点的父节点在哪里。如果要使用O(1)空间进行遍历,那么我们肯定不能用栈作为辅助空间。所以这个问题最大的难点在于,遍历到子节点的时候怎样重新返回到父节点在Morris遍历中利用叶子节点中的左右空指针指向中序遍历下的前驱节点或后继节点。

步骤如下:

(1)如果当前节点的左孩子为空,则访问当前节点的值并将其右孩子作为当前节点。

(2)如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在前序遍历下的前驱节点。

a.如果前驱节点的右孩子为空,将它的右孩子设置为当前节点,并且将当前节点更新为当前节点的左孩子。

b.如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空,访问当前节点的值并且将当前节点更新为当前节点的右孩子。

(3) 重复以上(1)、(2)直到当前节点为空。

关于Morris遍历以时间换取空间的分析,见我的另一篇博文:https://blog.csdn.net/qq_41231926/article/details/82047811。虽然这篇博文中分析的是前序遍历的情况,但对于中序遍历其实是同理的,读者不妨可以借鉴一下。

JAVA代码:

public class Solution {

	public List<Integer> inorderTraversal(TreeNode root) {
		List<Integer> list = new ArrayList<>();
		if(root == null) {
			return list;
		}
		TreeNode cur = root;
		while(cur != null) {
			if(cur.left == null) {
				list.add(cur.val);
				cur = cur.right;
			}else {
				TreeNode prev = cur.left;
				while(prev.right != null && prev.right != cur) {
					prev = prev.right;
				}
				if(prev.right == null) {
					prev.right = cur;
					cur = cur.left;
				}else {
					prev.right = null;
					list.add(cur.val);
					cur = cur.right;
				}
			}
		}
		return list;
	}
}

LeetCode解题报告:

猜你喜欢

转载自blog.csdn.net/qq_41231926/article/details/82056504