java二叉树遍历递归和非递归_基于Java的二叉树的三种遍历方式的递归与非递归实现...

二叉树的遍历方式包括前序遍历、中序遍历和后序遍历,其实现方式包括递归实现和非递归实现。

前序遍历:根节点 | 左子树 | 右子树

中序遍历:左子树 | 根节点 | 右子树

后序遍历:左子树 | 右子树 | 根节点

1. 递归实现

递归方式实现代码十分简洁,三种遍历方式的递归实现代码结构相同,只是执行顺序有所区别。

前序遍历:

public class preOrderRecur {

List res = new ArrayList<>();

public List preOrderTraversal(TreeNode root) {

if (root != null) {

res.add(root.val); // 根节点

preOrderTraversal(root.left); // 左子树

preOrderTraversal(root.right); // 右子树

}

return res;

}

}

中序遍历:

public class inOrderRecur {

List res = new ArrayList<>();

public List inOrderTraversal(TreeNode root) {

if (root != null) {

inOrderTraversal(root.left); // 左子树

res.add(root.val); // 根节点

inOrderTraversal(root.right); // 右子树

}

return res;

}

}

后序遍历:

public class inOrderRecur {

List res = new ArrayList<>();

public List inOrderTraversal(TreeNode root) {

if (root != null) {

inOrderTraversal(root.left); // 左子树

inOrderTraversal(root.right); // 右子树

res.add(root.val); // 根节点

}

return res;

}

}

2. 迭代实现

2.1 使用辅助栈——空间复杂度O(N)

2.1.1 中序遍历

从当前结点一直向其最左孩子搜索,直到没有左孩子了停止,这个过程中将路程中的所有结点入栈;

弹出栈顶元素,将其记录在答案中,并把当前结点置为弹出元素的右孩子并重复第一步过程。

public class inOrderIterator {

List res = new ArrayList<>();

public List inOrderTraversal(TreeNode root) {

Stack stack = new Stack<>();

while (root != null || !stack.isEmpty()) {

if (root != null) {

stack.push(root);

root = root.left;

} else {

TreeNode node = stack.pop();

res.add(node.val);

root = node.right;

}

}

return res;

}

}

2.1.2 前序遍历

方法1:因为前序遍历访问顺序是“中-左-右”,所以可以先将根结点压栈,然后按照下列步骤执行。

如果栈不为空,则弹出栈顶元素存入结果中;

如果弹出元素的右孩子不为空则将右孩子压栈,然后如果其左孩子也不为空将其左孩子压栈(因为栈是后入先出的,所以为了达到“中-左-右”的顺序,需要先压入右孩子,再压入左孩子)。

public class preOrderIterator {

List res = new ArrayList<>();

public List inOrderTraversal(TreeNode root) {

if (root == null) return res;

Stack stack = new Stack<>();

stack.push(root);

while (!stack.isEmpty()) {

root = stack.pop();

res.add(root.val);

// 右孩子压栈

if (root.right != null) stack.push(root.right);

// 左孩子压栈

if (root.left != null) stack.push(root.left);

}

return res;

}

}

方法2:根据中序遍历进行微调:

public class preOrderIterator {

List res = new ArrayList<>();

public List inOrderTraversal(TreeNode root) {

Stack stack = new Stack<>();

while (root != null || !stack.isEmpty()) {

if (root != null) {

res.add(root.val);

stack.push(root);

root = root.left;

} else {

TreeNode node = stack.pop();

root = node.right;

}

}

return res;

}

}

2.1.3 后序遍历

因为前序遍历的顺序是“左-中-右”,而后序遍历顺序是“左-右-中”,不考虑左结点,区别只是在于中结点和右结点的顺序进行了反向而已,因此可以使用前序遍历的代码进行调整,只需要将前序遍历对左右孩子压栈的顺序反向即可,即先压入左孩子,再压入右孩子。除此之外,因为按照这种方法调整得到的遍历顺序为“中-右-左”,正好是后序遍历的反向顺序,因此在获得遍历序列后还需进行逆序操作。

public class postOrderIterator {

List res = new LinkedList<>();

public List postOrderTraversal(TreeNode root) {

if (root == null) return res;

Stack stack = new Stack<>();

stack.push(root);

while (!stack.isEmpty()) {

root = stack.pop();

// 头插法

res.add(0, root.val);

// 左孩子压栈

if (root.left != null) stack.push(root.left);

// 右孩子压栈

if (root.right != null) stack.push(root.right);

}

return res;

}

}

2.2 Morris遍历——空间复杂度O(1)

该方法的思路简单说就是,对于每一个结点,找到它左孩子的最右子结点,因为按照正常访问顺序,其左孩子的最有子节点访问完后就应该访问其本身了,因此将其左孩子最右子节点的右指针指向它。基本步骤如下:

如果当前结点左孩子为空,说明最左边访问完毕,将其置为其右孩子

如果当前结点左孩子不为空,那么开始尝试找到该结点左孩子的最右子节点,建立连接关系

如果找到的当前结点的左孩子的最右子节点右指针为空,说明还未建立连接关系,是首次访问当前结点,那么将该最右结点的右指针指向当前结点,然后当前结点向左孩子走一步继续重复所有步骤。

如果找到的当前结点的左孩子的最右子节点右指针不为空,说明已建立过连接关系,是第二次访问当前结点,这意味着当前结点的左子树应该已经全部遍历完了,此时应恢复连接关系重新置为空,然后当前结点向右孩子走一步继续重复所有步骤。

该方法虽然保证了O(1)的空间复杂度,但在遍历过程中改变了部分结点的指向,破坏了树的结构。

2.2.1 中序遍历

public class inOrderMorris {

List res = new ArrayList<>();

public List inOrderTraversal(TreeNode root) {

TreeNode pre = null;

TreeNode cur = root;

while (cur != null) {

if (cur.left == null) {

res.add(cur.val);

cur = cur.right;

} else {

pre = cur.left;

while (pre.right != null && pre.right != cur) pre = pre.right;

if (pre.right == null) {

pre.right = cur;

cur = cur.left;

} else {

res.add(cur.val);

pre.right = null;

cur = cur.right;

}

}

}

return res;

}

}

2.2.2 前序遍历

public class preOrderMorris {

List res = new ArrayList<>();

public List preOrderTraversal(TreeNode root) {

TreeNode pre = null;

TreeNode cur = root;

while (cur != null) {

if (cur.left == null) {

res.add(cur.val);

cur = cur.right;

} else {

pre = cur.left;

while (pre.right != null && pre.right != cur) pre = pre.right;

if (pre.right == null) {

res.add(cur.val);

pre.right = cur;

cur = cur.left;

} else {

pre.right = null;

cur = cur.right;

}

}

}

return res;

}

}

2.2.3 后序遍历

前序遍历反向的思想

public class postOrderMorris {

List res = new LinkedList<>();

public List postOrderTraversal(TreeNode root) {

TreeNode pre = null;

TreeNode cur = root;

while (cur != null) {

if (cur.right == null) {

res.add(0, cur.val);

cur = cur.left;

} else {

pre = cur.right;

while (pre.left != null && pre.left != cur) pre = pre.left;

if (pre.left == null) {

res.add(0, cur.val);

pre.left = cur;

cur = cur.right;

} else {

pre.left = null;

cur = cur.left;

}

}

}

return res;

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值