二叉树深度优先遍历的非递归实现

本文介绍了深度优先遍历的基本概念及其三种方式(先序、中序、后序),并分别提供了递归和非递归(迭代)的代码实现,帮助读者理解和掌握二叉树遍历技巧。
摘要由CSDN通过智能技术生成

👏作者简介:大家好,我是笙一,java大二练习生,喜欢算法和java相关知识。

📕正进行的系列:算法

第一次写博客,有所欠缺感谢指正。

目录

前言

深度优先遍历

递归方式实现

先序遍历(递归版)

中序遍历(递归版)

后续遍历(递归版)

非递归方式(迭代)实现

先序遍历(非递归版)

中序遍历(非递归版)

后序遍历(非递归版)

总结


前言

最近写算法题的过程中,忽然对基础的深度优先遍历记忆模糊。想到可能对于新手来说,对基础的二叉树深度优先遍历非递归版本实现理解不是很深刻,所以写下这篇博客。

深度优先遍历

深度优先遍历是一个非常经典的算法,新手经常在二叉树相关题目中遇到它。而最基本的就其三种遍历方式:先序遍历,中序遍历,后序遍历。对于一个二叉树,包含其根节点,左子树,右子树。而这三种遍历方式的区别就是遍历顺序的不同。一般规定:先序遍历顺序为根、左、右,中序遍历顺序为左、根、右,后序遍历顺序为左、右、根。

递归方式实现

递归方式实现非常简单,我们这里不做叙述,直接看代码:

先序遍历(递归版)
class Node {
    public int value;
    public Node left;
    public Node right;

    public Node(int val) {
        this.value = val;
    }
}

public void preTraversal(Node head) {
    if(head == null) {
        return;
    }
    System.out.print(head.value + " ");
    preTraversal(head.left);
    preTraversal(head.right);
}
中序遍历(递归版)
    public void midTraversal(Node head) {
        if(head == null) {
            return;
        }
        midTraversal(head.left);
        System.out.print(head.value + " ");
        midTraversal(head.right);
    }
后续遍历(递归版)
    public void posTraversal(Node head) {
        if(head == null) {
            return;
        }
        posTraversal(head.left);
        posTraversal(head.right);
        System.out.print(head.value + " ");
    }

非递归方式(迭代)实现

众所周知,所有的递归代码都是利用栈来存信息,所以都可以改成非递归代码,无非采用自己压栈的方式。对于二叉树的遍历当然也不例外。

先序遍历(非递归版)

采取以下步骤实现:

1. 创建一个新的栈 stack,将头节点head压栈。

2. 从stack中弹出栈顶节点 cur,弹出就打印其值,然后如果 cur 有右孩子节点先压入右孩子,如果 cur 有左孩子节点再压入左孩子。

3. 重复过程2,直到栈 stack 内为空。

分析一下这个过程:我们已知栈可以将信息逆序,而先序遍历的顺序是根,左, 右,意思就是,对于每一个子树,我们要先遍历根节点,然后遍历其左子树和右子树。所以我们在弹出节点的同时,在栈内是先压右,后压左,这样在每次弹出节点时的顺序才会正确。

代码实现:

    public void preTraversalWithUnRecur(Node head) {
        if (head == null) {
            return;
        }
        Stack<Node> stack = new Stack<>();
        stack.push(head);
        while (!stack.isEmpty()) {
            head = stack.pop();
            System.out.print(head.value + " ");
            if (head.right != null) {
                stack.push(head.right);
            }
            if (head.left != null) {
                stack.push(head.left);
            }
        }
    }
中序遍历(非递归版)

中序遍历的实现是我认为三种遍历方式中较为有趣的一种,我们先讲它的实现步骤,再来聊聊为什么要这么做。

采取以下步骤实现:

1. 创建一个新的栈 stack,将头节点 head 压栈。

2. 在 stack 中压入从头节点开始,所有的左孩子节点(将整棵树的左边界压入栈中)。并且每次压入时使此时 head = head.left 。

3. 当 head 为空时,stack 弹出一个节点node,弹出就打印,并且让此时 head = node.right, 继续重复步骤2 。

4. 直到 stack 为空, 所有过程完成。

理解一下:

中序遍历需要的是按照左、中、右顺序遍历,所以需要先拿到全局最左的节点。而每次弹出最左的节点时,下一个弹出的节点是最左节点的父节点。这样就说明为什么需要将一整条左边界都压入栈中。而当弹出左节点的父节点后,如果父节点有右子树,又需要按照中序遍历去遍历整个右子树,这就是为什么步骤3我们需要获得到右节点时,重复了步骤2 。这样我们从栈中弹出的所有节点顺序,就是我们要的结果。实在理解不了也没关系,我们可以根据code来理解一下。

代码实现:

    public void midTraversalWithUnRecur(Node head) {
        if (head == null) {
            return;
        }
        Stack<Node> stack = new Stack<>();
        while (!stack.isEmpty() && head != null) {
            // head 如果不等于null, 那就说明左边界没压完
            // 如果等于 null,可以弹出栈顶节点,就是此时的最左节点
            // 而else代码块内就是步骤3 的实现,如果找到右节点(不为空),此时head不等于空,下一个循环就继续实现步骤2    
            // 如果没右节点,那么继续else,弹出下一个节点,看看它有没有右子树
            if(head != null) {
                stack.push(head);
                head = head.left;
            } else {
                head = stack.pop();
                System.out.print(head.value + " ");
                head = head.right;
            }
        }
    }

后序遍历(非递归版)

与先序遍历方式类似。我们可以思考,后序遍历的顺序是左、右、根,反过来看不就是根、右、左吗?与先序遍历的根、左、右非常类似。而要实现先序遍历,我们采取的是先压右,再压左,那么我们在实现后序遍历时只要改变压入策略就行。这样我们得到的就是想要的结果的逆序。接下来就简单了,只要在遍历的过程中将结果压入新的一个栈,最后从栈里一个个拿出来就行了。

采取以下步骤实现:

1. 创建一个新的栈 stack1,用来进行遍历,创建另一个新的栈 stack2,用来存放遍历过程中得到的节点。将头节点head压入 stack1。

2. 从stack1中弹出栈顶节点 cur,弹出就将其压入 stack2,然后如果 cur 有左孩子节点先压入左孩子,如果 cur 有右孩子节点再压入右孩子。

3. 重复步骤2,直到栈 stack1 内为空。

4. 从 stack2 中不断弹出节点,打印其值。

代码实现:

    public void posTraversalWithUnRecur(Node head) {
        if (head == null) {
            return;
        }
        Stack<Node> stack1 = new Stack<>();
        Stack<Node> stack2 = new Stack<>();
        stack1.push(head);
        while (!stack1.isEmpty()) {
            head = stack1.pop();
            stack2.push(head);
            if (head.left != null) {
                stack1.push(head.left);
            }
            if (head.right != null) {
                stack1.push(head.right);
            }
        }
        while (!stack2.isEmpty()) {
            System.out.print(stack2.pop().value + " ");
        }
    }

总结

        非递归实现二叉树的三种遍历方式还是挺重要的,在面试上也是常考题。一定要做到熟练掌握,“张口就来”。我是笙一,一个努力拼搏的人,一起进步,加油!

  • 34
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
图的深度优先遍历非递归的数据结构通常使用栈来保存中间变量。栈是一种后进先出(LIFO)的数据结构,它可以保存当前节点的信息以便后续处理。在深度优先遍历中,我们首先将起始节点入栈,然后依次将与当前节点相连的未访问过的节点入栈,直到没有未访问的相邻节点。此时,我们将栈顶节点出栈,并将其作为当前节点继续遍历。重复这个过程,直到栈为空为止。 具体实现时,我们可以使用一个辅助栈来保存未访问的节点。每次从栈中弹出一个节点时,我们将其标记为已访问,并将其未访问的相邻节点入栈。这样,我们就可以按照深度优先的顺序遍历图中的所有节点。 需要注意的是,为了避免重复遍历已经访问过的节点,我们需要在节点被访问时将其标记为已访问。这可以通过一个额外的布尔数组或哈希表来实现。 综上所述,图的深度优先遍历非递归的数据结构是栈。我们可以使用栈来保存未访问的节点,并按照深度优先的顺序遍历图中的所有节点。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [二叉树深度优先遍历非递归深度解析](https://blog.csdn.net/zhanghuaichao/article/details/124243582)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值