二叉树的遍历(递归,非递归,Morris)

二叉树的遍历


目录

  1. 递归遍历
  2. 非递归遍历
  3. Morris遍历

1. 递归遍历

递归版遍历只要当前节点不为null,就可以三次回到当前节点。

public static void preOrderRecur(Node head) {
        if (head == null) {
            return;
        }
        System.out.print(head.value + " ");
        preOrderRecur(head.left);
        preOrderRecur(head.right);
    }

    public static void inOrderRecur(Node head) {
        if (head == null) {
            return;
        }
        inOrderRecur(head.left);
        System.out.print(head.value + " ");
        inOrderRecur(head.right);
    }

    public static void posOrderRecur(Node head) {
        if (head == null) {
            return;
        }
        posOrderRecur(head.left);
        posOrderRecur(head.right);
        System.out.print(head.value + " ");
    }

2. 非递归遍历

其中后序遍历打印顺序为左右中,由先序遍历打印顺序为中左右,所以可以对先序遍历改进为中右左(改变添加顺序),添加到另外一个栈中,最后遍历打印就是左右中顺序了。

public static void preOrderUnRecur(Node head) {
        System.out.println("pre-order: ");
        while (head != null) {
            Stack<Node> stack = new Stack<>();
            stack.push(head);
            while (!stack.isEmpty()) {
                Node node = stack.pop();
                System.out.println(node.value + " ");
                if (node.right != null) {
                    stack.push(node.right);
                }
                if (node.left != null) {
                    stack.push(node.left);
                }
            }
        }
    }

    public static void inOrderUnRecur(Node head) {
        System.out.print("in-order: ");
        if (head != null) {
            Stack<Node> stack = new Stack<>();
            while (!stack.isEmpty() || head != null) {
                if (head != null) {
                    stack.push(head.left);
                    head = head.left;
                } else {
                    head = stack.pop();
                    System.out.println(head.value + " ");
                    head = head.right;
                }
            }
        }
        System.out.println();
    }

    public static void posOrderUnRecur1(Node head) {
        System.out.print("pos-order: ");
        if (head != null) {
            Stack<Node> s1 = new Stack<>();
            Stack<Node> s2 = new Stack<>();
            s1.push(head);
            while (!s1.isEmpty()) {
                head = s1.pop();
                s2.push(head);
                if (head.left != null) {
                    s1.push(head.left);
                }
                if (head.right != null) {
                    s1.push(head.right);
                }
                while (!s2.isEmpty()) {
                    System.out.print(s2.pop().value + " ");
                }
            }
        }
        System.out.println();
    }

3. Morris遍历

Morris遍历法,能以时间复杂度O(N),空间复杂度O(1)实现二叉树的遍历。

程序流程:
假设指针cur指向当前节点,cur从头结点开始。

  1. 如果cur无左孩子,则cur = cur.right;
  2. 如果cur有左孩子,则找到cur左子树上最右的节点,记为mostRight,分为以下两种情况:
    1. 若mostRight的right指针为null,则让其指向cur,且cur = cur.left;
    2. 若mostRight的right指针指向cur,则让其指回null,且cur = cur.right。

假设二叉树如下:

           1
      2        3
   4    5   6    7

则Morris遍历顺序为:1,2,4,2,5,1,3,6,3,7

Morris 遍历代码实现:

public static void morrisIn(Node head) {
		if (head == null) {
			return;
		}
		Node cur = head;
		Node mostRight = null;
		while (cur != null) {
			mostRight = cur.left;
			if (mostRight != null) {
				while (mostRight.right != null && mostRight.right != cur) {
					mostRight = mostRight.right;
				}
				if (mostRight.right == null) {
					mostRight.right = cur;
					cur = cur.left;
					continue;
				} else {
					mostRight.right = null;
				}
			}
			cur = cur.right;
		}
	}

特点:

  1. 当某个节点有左子树,则会到达该节点两次,如果没有左子树,只会到达一次。
  2. 当第二次回到某个节点时,它的左子树已遍历完。

实质:
Morris遍历是利用左子树最右节点的指针指向null或指向自己来标记是第一次来到该节点还是第二次来到该节点。


Morris遍历改先序遍历

在以下两种情况下打印节点:

  1. 当节点没有左子树时,打印当前节点;
  2. 当节点有左子树时并且第一次访问时打印该节点,就是当mosrRight的右指针为null时。
public static void morrisPre(Node head) {
        if (head == null) {
            return;
        }
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    mostRight.right = cur;
                    System.out.print(cur.value + " ");
                    cur = cur.left;
                    continue;
                } else {
                    mostRight.right = null;
                }
            } else {
                System.out.print(cur.value + " ");
            }
            cur = cur.right;
        }
        System.out.println();
    }

Morris遍历改中序遍历

在以下两种情况下打印节点:

  1. 当节点没有左子树时,说明第一次和第二次是重合在一起的,打印当前节点即可。
  2. 当节点有左子树时,那么需要处理完左子树再打印,即当前节点准备右移时打印。
public static void morrisIn(Node head) {
        if (head == null) {
            return;
        }
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
                    mostRight.right = null;
                }
            }
            System.out.print(cur.value + " ");
            cur = cur.right;
        }
        System.out.println();
    }

Morris遍历改后序遍历

在以下两种情况下打印节点:

  1. 只有当到达某个节点两次时逆序打印该节点左子树的右边界;
  2. 在代码的最后逆序打印整棵树的右边界,而逆序的过程就和单向链表的反转过程类似。
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;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
二叉树非递归遍历常用的方法有两种,一种是使用栈,一种是使用 Morris 遍历。下面我将分别给出两种方法的 Python 代码实现。 ## 栈实现非递归遍历 ```python class TreeNode: def __init__(self, val): self.val = val self.left = None self.right = None def preorderTraversal(root): if not root: return [] stack, res = [root], [] while stack: node = stack.pop() res.append(node.val) if node.right: stack.append(node.right) if node.left: stack.append(node.left) return res def inorderTraversal(root): if not root: return [] stack, res = [], [] while stack or root: if root: stack.append(root) root = root.left else: node = stack.pop() res.append(node.val) root = node.right return res def postorderTraversal(root): if not root: return [] stack, res = [root], [] while stack: node = stack.pop() res.append(node.val) if node.left: stack.append(node.left) if node.right: stack.append(node.right) return res[::-1] ``` 上述代码实现了二叉树的前序遍历、中序遍历和后序遍历,分别用 `preorderTraversal`、`inorderTraversal` 和 `postorderTraversal` 表示。这些函数的实现都是基于栈的非递归遍历方法,使用栈保存待访问的节点。在遍历时先将根节点入栈,然后循环执行以下操作: - 弹出栈顶节点,记录其值; - 将栈顶节点的右子节点(如果有)入栈; - 将栈顶节点的左子节点(如果有)入栈。 由于栈的特性,上述循环会先访问左子树,然后是右子树,符合二叉树遍历的先左后右的规则。针对后序遍历,需要将结果数组反转。 ## Morris 遍历实现非递归遍历 ```python class TreeNode: def __init__(self, val): self.val = val self.left = None self.right = None def inorderTraversal(root): if not root: return [] cur, res = root, [] while cur: if not cur.left: res.append(cur.val) cur = cur.right else: pre = cur.left while pre.right and pre.right is not cur: pre = pre.right if not pre.right: pre.right = cur cur = cur.left else: pre.right = None res.append(cur.val) cur = cur.right return res ``` 上述代码实现了二叉树的中序遍历,使用 Morris 遍历方法。Morris 遍历方法基于线索二叉树的思想,利用空闲的指针保存访问过的节点,从而避免使用栈或者递归占用额外的空间。具体实现方法如下: - 如果当前节点的左子节点为空,记录当前节点的值,然后将当前节点指向其右子节点; - 如果当前节点的左子节点不为空,找到其左子树的最右节点,记为 `pre`; - 如果 `pre` 的右指针为空,将其右指针指向当前节点,然后将当前节点指向其左子节点; - 如果 `pre` 的右指针不为空,说明当前节点的左子树已经访问完毕,记录当前节点的值,然后将 `pre` 的右指针置为空,将当前节点指向其右子节点。 由于 Morris 遍历方法只使用了常数级别的空间,因此在空间复杂度上优于基于栈的遍历方法。但是,由于需要修改树的结构,因此在时间复杂度上可能会劣于基于栈的遍历方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值