Java实现Morris遍历二叉树

本文详细介绍了Morris遍历二叉树的方法,这是一种仅需O(1)额外空间即可完成遍历的技术。文章通过具体步骤解释了如何进行先序、中序及后序遍历,并提供了实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Morris遍历二叉树是遍历二叉树的神级方法,它的时间复杂度仅为O(n),空间复杂度为O(1)。

主要包含以下两大步骤:

1、拿到一个节点,如果该节点无左子树,那么节点指向它的右节点。

2、如果这个节点有左子树,找到左子树的最右节点。

       a、如果最右节点指向null,则让最右节点指向当前节点,并将该目标节点向左孩子移动。

       b、如果最右节点已经指向该节点,则让最右节点指向null,并将该目标节点向右孩子移动。

一直按照上述两大步骤递归,直到遍历完所有节点。

代码如下所示:

package problems_2017_08_21;


/**
 * @author islongfei
 * 遍历二叉树的神级方法
      【题目】
       给定一棵二叉树的头节点head,完成二叉树的先序、中序和后序遍历。如果二叉树的节点数为N,
       要求时间复杂度为O(N),额外空间复杂度为O(1)。

 *
 */
public class Problem_01_MorrisTraversal {

	public static class Node {
		public int value;
		Node left;
		Node right;

		public Node(int data) {
			this.value = data;
		}
	}
	
	/**
	 * Morris 先序遍历二叉树(第一次发现节点就打印:①左子树的右孩子为空时打印 ②无左子树时打印)
	 * @param head	
	 */
	
	public static void morrisPre(Node head) {
		if (head == null) {
			return;
		}
		Node cur1 = head;//每次得到的当前node
		Node cur2 = null;//当前node左子树最右的节点
		while (cur1 != null) {
			cur2 = cur1.left;
			if (cur2 != null) {
				while (cur2.right != null && cur2.right != cur1) {//找到左子树最右节点
					cur2 = cur2.right;
				}
				
				if (cur2.right == null) {   //最右点的右指针指向null,
					cur2.right = cur1;      //右指针指向当前节点
					System.out.print(cur1.value + " ");
					cur1 = cur1.left;       //当前node向左孩子移动
					continue;
				} else {                    //最右点的右指针已经指向当前节点,让它指向null
					cur2.right = null;
				}
			} else {
				System.out.print(cur1.value + " ");
			}
			cur1 = cur1.right;              //当前node向右孩子移动
		}
		System.out.println();
	}

	/**
	 * Morris中序遍历二叉树(node每次往右移之前打印节点)
	 * @param head
	 */
	public static void morrisIn(Node head) {
		if (head == null) {
			return;
		}
		Node cur1 = head;
		Node cur2 = null;
		while (cur1 != null) {
			cur2 = cur1.left;
			if (cur2 != null) {
				while (cur2.right != null && cur2.right != cur1) {
					cur2 = cur2.right;
				}
				if (cur2.right == null) {
					cur2.right = cur1;
					cur1 = cur1.left;
					continue;
				} else {
					cur2.right = null;
				}
			}
			System.out.print(cur1.value + " ");
			cur1 = cur1.right;
		}
		System.out.println();
	}

	/**
	 * Morris后序遍历二叉树(当第二次回到node时,逆序打印左子树的右边界,在打印整树的右边界)
	 * @param head
	 */
	public static void morrisPos(Node head) {
		if (head == null) {
			return;
		}
		Node cur1 = head;
		Node cur2 = null;
		while (cur1 != null) {
			cur2 = cur1.left;
			if (cur2 != null) {
				while (cur2.right != null && cur2.right != cur1) {
					cur2 = cur2.right;
				}
				if (cur2.right == null) {
					cur2.right = cur1;
					cur1 = cur1.left;
					continue;
				} else {
					cur2.right = null;
					printEdge(cur1.left);
				}
			}
			cur1 = cur1.right;
		}
		printEdge(head);
		System.out.println();
	}

	public static void printEdge(Node head) {
		Node tail = reverseEdge(head);
		Node cur = tail;
		while (cur != null) {
			System.out.print(cur.value + " ");
			cur = cur.right;
		}
		reverseEdge(tail);
	}

	public static Node reverseEdge(Node from) {
		Node pre = null;
		Node next = null;
		while (from != null) {
			next = from.right;
			from.right = pre;
			pre = from;
			from = next;
		}
		return pre;
	}

	// for test -- print tree
	public static void printTree(Node head) {
		System.out.println("Binary Tree:");
		printInOrder(head, 0, "H", 17);
		System.out.println();
	}

	public static void printInOrder(Node head, int height, String to, int len) {
		if (head == null) {
			return;
		}
		printInOrder(head.right, height + 1, "v", len);
		String val = to + head.value + to;
		int lenM = val.length();
		int lenL = (len - lenM) / 2;
		int lenR = len - lenM - lenL;
		val = getSpace(lenL) + val + getSpace(lenR);
		System.out.println(getSpace(height * len) + val);
		printInOrder(head.left, height + 1, "^", len);
	}

	public static String getSpace(int num) {
		String space = " ";
		StringBuffer buf = new StringBuffer("");
		for (int i = 0; i < num; i++) {
			buf.append(space);
		}
		return buf.toString();
	}

	public static void main(String[] args) {
		Node head = new Node(4);
		head.left = new Node(2);
		head.right = new Node(6);
		head.left.left = new Node(1);
		head.left.right = new Node(3);
		head.right.left = new Node(5);
		head.right.right = new Node(7);
		printTree(head);
		morrisPre(head);
		morrisIn(head);
		morrisPos(head);
		printTree(head);

	}

}



根据打印的位置不同可以分为以下三种输出的遍历方法:

中序遍历:将目标节点往右移之前打印该节点。

先序遍历:第一次发现节点是就打印(1、左子树右孩子为空时 2、无左子树时)。

后续遍历:第二次回到目标节点,逆序打印左子树的右边界,再去打印整个树的右边界。


### Java实现二叉树中序遍历的方法 #### 方法概述 在Java中,可以通过多种方法实现二叉树的中序遍历。常见的方法包括递归、迭代以及莫里斯遍历Morris Traversal)。以下是每种方法的具体实现。 --- #### 1. **递归实现** 递归是最直观的方式之一,其核心思想是按照“左子树 -> 根节点 -> 右子树”的顺序访问节点[^2]。 ```java public List<Integer> inorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); helper(root, result); return result; } private void helper(TreeNode node, List<Integer> resultList) { if (node != null) { helper(node.left, resultList); // 访问左子树 resultList.add(node.val); // 处理当前节点 helper(node.right, resultList); // 访问右子树 } } ``` 这种方法的时间复杂度为O(n),空间复杂度取决于递归调用栈的深度,最坏情况下为O(h),其中h为树的高度[^4]。 --- #### 2. **迭代实现** 为了减少递归带来的额外开销,可以使用显式的栈来模拟递归过程。这种方式同样遵循“左子树 -> 根节点 -> 右子树”的原则[^1]。 ```java import java.util.*; public List<Integer> inorderTraversalIterative(TreeNode root) { List<Integer> result = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); TreeNode current = root; while (current != null || !stack.isEmpty()) { while (current != null) { // 将左子树压入栈中 stack.push(current); current = current.left; } current = stack.pop(); // 弹出并处理当前节点 result.add(current.val); current = current.right; // 转向右子树 } return result; } ``` 该方法的空间复杂度由使用的栈决定,时间复杂度仍为O(n)[^1]。 --- #### 3. **莫里斯遍历Morris Traversal)** 莫里斯遍历是一种不需要额外存储空间的高效算法,通过修改树中的指针完成遍历。它利用叶子节点的空闲指针指向其他节点,从而避免了使用栈或递归[^2]。 ```java public List<Integer> morrisInorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); TreeNode current = root; while (current != null) { if (current.left == null) { // 如果没有左子树,则直接访问当前节点 result.add(current.val); current = current.right; } else { TreeNode predecessor = findPredecessor(current); // 找到前驱节点 if (predecessor.right == null) { // 建立临时链接 predecessor.right = current; current = current.left; } else { // 断开临时链接并访问当前节点 predecessor.right = null; result.add(current.val); current = current.right; } } } return result; } // 寻找当前节点的前驱节点 private TreeNode findPredecessor(TreeNode node) { TreeNode pre = node.left; while (pre.right != null && pre.right != node) { pre = pre.right; } return pre; } ``` 这种算法的空间复杂度为O(1),因为不依赖于任何额外的数据结构。 --- ### 性能对比 | 方法 | 时间复杂度 | 空间复杂度 | |---------------|------------|-------------| | 递归 | O(n) | O(h) | | 迭代(栈) | O(n) | O(h) | | 莫里斯遍历 | O(n) | O(1) | 以上表格展示了不同方法的性能特点,开发者可以根据实际需求选择合适的方案[^1]。 --- ### 结论 对于初学者来说,递归和基于栈的迭代方法更为直观易懂;而对于追求极致性能的应用场景,莫里斯遍历则是一个不错的选择。无论采用哪种方法,都应确保代码逻辑清晰且易于维护。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值