五、二叉树的(前序、中序、后序、层次等遍历的)递归和非递归解法

本文详细介绍了二叉树的基本概念及其多种遍历方法,包括递归与非递归实现,并提供了具体代码示例。

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

二叉树是一种非常重要的数据结构,其他很多数据结构都是基于二叉树基础演变而来的。

在我刷 剑指offer 中,碰到了  “ 二叉搜索树与双向链表 ” 这样的题目,对于里面的非递归解法很不理解,所以我就又重新复习了一遍二叉树的前序、中序、后序和层次遍历,以及求树的深度,广度等。

 

闲话少说,直接上代码,如果代码看不太懂,就自己画一颗二叉树,自己按照程序走一遍,多走几遍,然后看看相关文章的注释,就能弄懂了。

比如:

                   10

           7                 12

   6          8       11         14

前序遍历:  10 ,7,6,8,12,11, 14, 10

后序遍历:   6, 8, 7, 11, 14, 12, 10

中序遍历:   6,7, 8, 9, 10, 11, 12, 14

层次遍历:  10, 7, 12, 6 , 8 , 11 , 14

深度: 3

广度:  4

 

我这里只给出类似的代码,具体的相应修改,需要大家自己动手啦^_^

节点定义:

public static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int x) {
            val = x;
        }
    }

 

1,前序遍历 (递归解法)

    public static void preOrder(TreeNode treeNode) {
        if (treeNode != null) {
            System.out.print(treeNode.val + " ");
            preOrder(treeNode.left);
            preOrder(treeNode.right);
        }
    }

 

2.1, 前序遍历(非递归解法①)

    /**
     * 先序非递归:
     * 这种实现类似于图的深度优先遍历(DFS)。
     * 维护一个栈,将根节点入栈,然后只要栈不为空,出栈并访问,
     * 接着依次将访问节点的右节点、左节点入栈。
     * 这种方式应该是对先序遍历的一种特殊实现(看上去简单明了),
     * 但是不具备很好的扩展性,在中序和后序方式中不适用
     *
     * @param root
     */
    public static void preOrderStack(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode treeNode = stack.pop();
            System.out.print(treeNode.val + " ");
            if (treeNode.right != null) {
                stack.push(treeNode.right);
            }
            if (treeNode.left != null) {
                stack.push(treeNode.left);
            }
        }
    }

 

2.2 先序遍历(非递归解法②)

/**
     * 先序非递归2:
     * 利用栈模拟递归过程实现循环先序遍历二叉树。
     * 这种方式具备扩展性,它模拟递归的过程,将左子树点不断的压入栈,直到null,
     * 然后处理栈顶节点的右子树。
     *
     * @param root
     */
    public static void preOrderStack2(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        TreeNode treeNode = root;
        while (treeNode != null || !stack.isEmpty()) {
            //将左子树点不断的压入栈
            while (treeNode != null) {
                //先访问再入栈
                System.out.print(treeNode.val + " ");
                stack.push(treeNode);
                treeNode = treeNode.left;
            }
            //出栈并处理右子树
            if (!stack.isEmpty()) {
                treeNode = stack.pop();
                treeNode = treeNode.right;
            }

        }

    }

 

3,中序遍历(递归解法)

    public static void inOrder(TreeNode treeNode) {
        if (treeNode != null) {
            inOrder(treeNode.left);
            System.out.print(treeNode.val + " ");
            inOrder(treeNode.right);
        }
    }

 

4,中序遍历(非递归解法)

    /**
     * 中序非递归:
     * 利用栈模拟递归过程实现循环中序遍历二叉树。
     * 思想和上面的先序非递归2相同,
     * 只是访问的时间是在左子树都处理完直到null的时候出栈并访问。
     *
     * @param treeNode
     */
    public static void inOrderStack(TreeNode treeNode) {
        Stack<TreeNode> stack = new Stack<>();
        while (treeNode != null || !stack.isEmpty()) {
            while (treeNode != null) {
                stack.push(treeNode);
                treeNode = treeNode.left;
            }
            //左子树进栈完毕
            if (!stack.isEmpty()) {
                treeNode = stack.pop();
                System.out.print(treeNode.val + " ");
                treeNode = treeNode.right;
            }
        }
    }

 

5,后序遍历(递归解法)

    public static void postOrder(TreeNode treeNode) {
        if (treeNode != null) {
            postOrder(treeNode.left);
            postOrder(treeNode.right);
            System.out.print(treeNode.val + " ");
        }
    }

 

6,后序遍历(非递归解法①)

public static class TagNode {
        TreeNode treeNode;
        boolean isFirst;
    }

    /**
     * 后序非递归:
     * 后序遍历不同于先序和中序,它是要先处理完左右子树,
     * 然后再处理根(回溯)。
     * <p>
     * <p>
     * 对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,
     * 此时该结点出现在栈顶,但是此时不能将其出栈并访问,因此其右孩子还为被访问。
     * 所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,
     * 此时可以将其出栈并访问。这样就保证了正确的访问顺序。
     * 可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。
     * 因此需要多设置一个变量标识该结点是否是第一次出现在栈顶,这里是在树结构里面加一个标记,然后合成一个新的TagNode。
     *
     * @param treeNode
     */
    public static void postOrderStack(TreeNode treeNode) {
        Stack<TagNode> stack = new Stack<>();
        TagNode tagNode;
        while (treeNode != null || !stack.isEmpty()) {
            //沿左子树一直往下搜索,直至出现没有左子树的结点
            while (treeNode != null) {
                tagNode = new TagNode();
                tagNode.treeNode = treeNode;
                tagNode.isFirst = true;
                stack.push(tagNode);
                treeNode = treeNode.left;
            }

            if (!stack.isEmpty()) {
                tagNode = stack.pop();
                //表示是第一次出现在栈顶
                if (tagNode.isFirst == true) {
                    tagNode.isFirst = false;
                    stack.push(tagNode);
                    treeNode = tagNode.treeNode.right;
                } else {
                    //第二次出现在栈顶
                    System.out.print(tagNode.treeNode.val + " ");
                    treeNode = null;
                }
            }
        }
    }

 

7,后序遍历(非递归解法②)

    /**
     * 后序非递归2:
     * 要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;
     * 或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。
     * 若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,
     * 左孩子和右孩子都在根结点前面被访问。
     *
     * @param treeNode
     */
    public static void postOrderStack2(TreeNode treeNode) {
        Stack<TreeNode> stack = new Stack<>();
        TreeNode currentTreeNode;
        TreeNode preTreeNode = null;
        stack.push(treeNode);

        while (!stack.isEmpty()) {
            currentTreeNode = stack.peek();
            //如果当前结点没有孩子结点或者孩子节点都已被访问过
            if ((currentTreeNode.left == null && currentTreeNode.right == null) ||
                    (preTreeNode != null && (preTreeNode == currentTreeNode.left || preTreeNode == currentTreeNode.right))) {
                System.out.print(currentTreeNode.val + " ");
                stack.pop();
                preTreeNode = currentTreeNode;
            } else {
                if (currentTreeNode.right != null) {
                    stack.push(currentTreeNode.right);
                }
                if (currentTreeNode.left != null) {
                    stack.push(currentTreeNode.left);
                }
            }
        }
    }
}

 

 8,层次遍历(递归解法)

public void levelOrder(BinaryNode<AnyType> Node) {
        if (Node == null) {
            return;
        }

        int depth = depth(Node);

        for (int i = 1; i <= depth; i++) {
            levelOrder(Node, i);
        }
    }

    private void levelOrder(BinaryNode<AnyType> Node, int level) {
        if (Node == null || level < 1) {
            return;
        }

        if (level == 1) {
            System.out.print(Node.element + "  ");
            return;
        }

        // 左子树
        levelOrder(Node.left, level - 1);

        // 右子树
        levelOrder(Node.right, level - 1);
    }

    public int depth(BinaryNode<AnyType> Node) {
        if (Node == null) {
            return 0;
        }

        int l = depth(Node.left);
        int r = depth(Node.right);
        if (l > r) {
            return l + 1;
        } else {
            return r + 1;
        }
    }

 

9,层序遍历(非递归解法)

public void levelOrder(BinaryTreeNode root){
	 Queue<BinaryTreeNode> queue=new LinkedList<BinaryTreeNode>();
	 queue.offer(root);
	 while(!queue.isEmpty()){
		 temp=queue.poll();
		 System.out.print(temp.getData()+"\t");
		 if(null!=temp.getLeft()) 
			 queue.offer(temp.getLeft());
		 if(null!=temp.getRight()){
			 queue.offer(temp.getRight());
		}
	}
}

 

10,求二叉树深度

// 获取最大深度
    public static int getMaxDepth(TreeNode root) {
        if (root == null)
            return 0;
        else {
            int left = getMaxDepth(root.left);
            int right = getMaxDepth(root.right);
            return 1 + Math.max(left, right);
        }
    }

 

11, 求二叉树广度

// 获取最大宽度
    public static int getMaxWidth(TreeNode root) {
        if (root == null)
            return 0;

        Queue<TreeNode> queue = new ArrayDeque<TreeNode>();
        int maxWitdth = 1; // 最大宽度
        queue.add(root); // 入队

        while (true) {
            int len = queue.size(); // 当前层的节点个数
            if (len == 0)
                break;
            while (len > 0) {// 如果当前层,还有节点
                TreeNode t = queue.poll();
                len--;
                if (t.left != null)
                    queue.add(t.left); // 下一层节点入队
                if (t.right != null)
                    queue.add(t.right);// 下一层节点入队
            }
            maxWitdth = Math.max(maxWitdth, queue.size());
        }
        return maxWitdth;
    }

 

 

 

 

 

参考文章:

1, http://www.cnblogs.com/dolphin0520/archive/2011/08/25/2153720.html

2, https://www.cnblogs.com/wylwyl/p/10477528.html

3, https://blog.youkuaiyun.com/lr131425/article/details/60755706

4, https://blog.youkuaiyun.com/apandi_/article/details/52916523

5, https://www.cnblogs.com/qiuyong/p/6675492.html
6,
https://www.cnblogs.com/morethink/p/7265817.html

7,https://www.cnblogs.com/liuyang0/p/6271324.html

8, https://blog.youkuaiyun.com/zhangxiangdavaid/article/details/37115355

9,https://www.cnblogs.com/xudong-bupt/p/4036190.html

10, https://blog.youkuaiyun.com/lr131425/article/details/60755706

 

 

 

<think>嗯,用户想了解如何用代码实现二叉树前序、中后序遍历算法,还提到了示例。首先,我得回忆一下这三种遍历的基本概念它们的顺区别。前序遍历是根-左-右,中是左-根-右,后序是左-右-根。对吧? 接下来,用户可能需要具体的代码示例,所以得考虑用哪种编程语言。根据提供的引用,特别是引用3,里面提到了C++的递归实现,但用户可能对Python更熟悉,或者需要多种语言的例子?不过用户的问题里没有特别说明,可能默认是常见的语言,比如Python或者Java。不过原问题中的示例代码是Python的快速排,所以可能用户更倾向于Python。不过引用3是C++,需要确认。不过用户的问题里直接问的是如何用代码实现,所以可能需要提供多种方,比如递归迭代,以及不同语言的示例? 然后,根据引用3,递归算法相对简单,结构清晰。比如前序遍历递归就是先访问根节点,然后递归左子树,再递归右子树。同样,中后序调整访问顺即可。但是用户可能需要递归迭代两种方,因为有时候迭代更高效,或者避免栈溢出的问题。引用3中的示例是递归的,但LeetCode也常要求迭代解法。所以可能需要同时给出递归迭代的实现。 另外,引用14提到根据前序重建二叉树,这可能遍历有关联,但用户的问题主要是遍历的实现,而不是重建。不过可能需要简单提及遍历的应用,比如重建二叉树需要中前序后序,但用户的问题集中在遍历的实现方。 现在,结构上可能需要分递归迭代两部分,分别说明前序、中后序的实现。对于递归,代码相对简单,调整访问顺即可。对于迭代,可能需要使用栈,处理顺不同。例如,前序的迭代可以用栈,先压入根,然后循环弹出,访问后先压右再压左。中迭代则用栈指针,先走到最左,再处理节点,再转向右子树。后序迭代相对复杂,可能需要两个栈,或者逆处理。 另外,用户可能需要代码示例的注释,解释每一步的作用。比如递归的终止条件,迭代中的栈操作等。还要注意代码的正确性,比如在Python中如何定义树的结构,可能需要先定义TreeNode类。但用户的问题可能更关注遍历算法本身,而不是树的结构定义,所以示例代码中可能需要包含一个简单的树结构定义。 例如,在Python中,可以定义一个TreeNode类,包含val、left、right属性。然后编写前序、中后序递归迭代函数。比如递归前序就是先append根的值,然后递归左,递归右。中则是先递归左,append根,再递归右。后序递归左、右,最后append根。 对于迭代的实现,前序可以用栈,每次弹出节点,访问后先压右子节点,再压左子节点。中迭代需要维护当前节点指针,先走到最左,然后弹出栈顶,访问后处理右子树。后序迭代可以用一个栈,类似前序,但顺调整为根-右-左,然后反转结果得到左-右-根的顺。或者用两个栈,第二个栈用来反转顺。 此外,遍历可能不属于用户的问题范围,但引用3提到了遍历,但用户的问题明确前、中、后序,所以可能不需要涉及。 还需要注意代码的语正确性,比如Python中的缩进,函数定义等。比如在递归函数中,可能需要将结果保存在一个列表中,可以通过传递列表作为参数,或者使用类的成员变量,或者使用嵌套函数。例如,在Python中,可以在函数内部定义递归辅助函数,或者通过修改外部列表。 比如,递归前序遍历函数可以这样写: def preorderTraversal(root): res = [] def helper(node): if not node: return res.append(node.val) helper(node.left) helper(node.right) helper(root) return res 类似地,中后序调整append的位置即可。 对于迭代版本,前序的Python代码可能如下: def preorder_iterative(root): res = [] stack = [root] while stack: node = stack.pop() if node: res.append(node.val) stack.append(node.right) stack.append(node.left) return res 需要注意的是,栈中可能压入None节点,所以在弹出后需要判断是否为空。或者可以调整条件,只压入非空节点,但初始时可能root为空的情况需要处理。 中迭代: def inorder_iterative(root): res = [] stack = [] curr = root while curr or stack: while curr: stack.append(curr) curr = curr.left curr = stack.pop() res.append(curr.val) curr = curr.right return res 后序迭代的一种方: def postorder_iterative(root): res = [] stack = [root] while stack: node = stack.pop() if node: res.append(node.val) stack.append(node.left) stack.append(node.right) return res[::-1] 这里先将根压栈,然后按根-右-左的顺处理,最后反转结果得到后序的左-右-根。 这样,用户可以得到递归迭代的代码示例,并了解不同遍历的实现方式。 另外,用户可能想知道时间复杂度空间复杂度。三种遍历的时间都是O(n),空间复杂度递归是O(h)(h为树的高度,栈空间),迭代也是O(n),因为栈的大小最坏情况是O(n)(比如退化为链表)。 最后,根据用户提供的引用,特别是引用2提到后序遍历适用于表达式树,可能可以补充应用场景,但用户的问题集中在代码实现,所以可能不需要展开,但可以简要提及。 在总结时,需要确保代码示例正确,符合语,并且解释清晰。可能需要检查每个遍历的步骤是否正确,例如迭代后序的实现是否确实通过反转前序的变体得到正确结果。</think>### 二叉树前序、中后序遍历实现方 二叉树遍历是基础算法问题,以下是递归迭代两种实现方式,以Python为例(假设已定义`TreeNode`类,包含`val`、`left`、`right`属性)。 --- ### 1. 递归实现 递归通过调整节点访问顺实现不同遍历方式。 #### **前序遍历**(根→左→右) ```python def preorder_recursive(root): res = [] def helper(node): if not node: return res.append(node.val) # 先访问根节点 helper(node.left) helper(node.right) helper(root) return res ``` #### **中遍历**(左→根→右) ```python def inorder_recursive(root): res = [] def helper(node): if not node: return helper(node.left) res.append(node.val) # 中间访问根节点 helper(node.right) helper(root) return res ``` #### **后序遍历**(左→右→根) ```python def postorder_recursive(root): res = [] def helper(node): if not node: return helper(node.left) helper(node.right) res.append(node.val) # 最后访问根节点 helper(root) return res ``` --- ### 2. 迭代实现 迭代方通过栈模拟递归过程,控制节点访问顺。 #### **前序遍历** ```python def preorder_iterative(root): res = [] stack = [root] while stack: node = stack.pop() if node: res.append(node.val) stack.append(node.right) # 先压右节点(后弹出) stack.append(node.left) # 后压左节点(先弹出) return res ``` #### **中遍历** ```python def inorder_iterative(root): res = [] stack = [] curr = root while curr or stack: while curr: # 走到最左子节点 stack.append(curr) curr = curr.left curr = stack.pop() res.append(curr.val) curr = curr.right # 转向右子树 return res ``` #### **后序遍历** ```python def postorder_iterative(root): res = [] stack = [root] while stack: node = stack.pop() if node: res.append(node.val) stack.append(node.left) # 先压左节点(后弹出) stack.append(node.right) # 后压右节点(先弹出) return res[::-1] # 反转根→右→左→结果为左→右→根 ``` --- ### 关键点说明 1. **时间复杂度**:所有方均为$O(n)$,每个节点访问一次。 2. **空间复杂度**: - 递归:$O(h)$($h$为树的高度,递归栈深度)。 - 迭代方:$O(n)$(最坏情况下栈空间占用)。 3. **应用场景**: - 前序遍历:复制二叉树结构或列化[^2]。 - 中遍历:二叉搜索树的有输出。 - 后序遍历:表达式树的后缀计算[^2]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值