前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >算法练习(11)-二叉树的各种遍历

算法练习(11)-二叉树的各种遍历

作者头像
菩提树下的杨过
发布2021-10-29 11:29:09
2920
发布2021-10-29 11:29:09
举报

二叉树的节点结构如下:

代码语言:javascript
复制
public class TreeNode {

    public TreeNode left;
    public TreeNode right;
    public int val;

    public TreeNode(int val) {
        this.val = val;
    }

    public TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

一、递归序

二叉树的三种经典遍历: 前序/中序/后序 可参考先前的文章:数据结构C#版笔记--树与二叉树, 不过今天换一种角度来理解"前序/中序/后序"(来自左程云大佬的视频分享), 假设有一个递归方法, 可以遍历二叉树:

代码语言:javascript
复制
public static void foo(TreeNode n1) {
    if (n1 == null) {
        return;
    }

    System.out.printf("(1):" + n1.val + "  ");

    foo(n1.left);
    System.out.printf("(2):" + n1.val + "  ");

    foo(n1.right);
    System.out.printf("(3):" + n1.val + "  ");

}

如上图,可以看到,每个节点有3次被访问到的时机,第1次是递归压入堆栈,另外2次是左、右子节点处理完毕,函数返回。

如果在这3个时机,均打印节点的值,会发现:第1次打印的值(上图底部的红色输出),就是前序遍历(头-左-右),第2次打印的值(上图底部的蓝色输出),就是中间遍历(左-头-右),第3次打印的值(上图底部的黑色输出),就是后序遍历(左-右-头).这3次打印结果的全集, 也称为"递归序".

二、前序/中序/ 后序遍历的非递归实现

代码语言:javascript
复制
/**
 * 前序遍历(非递归版): root-left-right
 *
 * @param root
 */
static void preOrderUnRecur(TreeNode root) {
    if (root == null) {
        return;
    }
    Stack<TreeNode> stack = new Stack<>();
    stack.add(root);
    while (!stack.isEmpty()) {
        TreeNode n = stack.pop();
        System.out.print(n.val + " ");
        if (n.right != null) {
            stack.add(n.right);
        }
        if (n.left != null) {
            stack.add(n.left);
        }
    }
}

/**
 * 中序遍历(非递归版): left-root-right
 * 思路: 不停压入左边界(即:头-左),直到null,
 *       然后弹出打印过程中,发现有右孩子,则压栈
 *       然后再对右孩子,不停压入左边界
 * @param n
 */
static void inOrderUnRecur(TreeNode n) {
    Stack<TreeNode> stack = new Stack<>();
    while (n != null || !stack.isEmpty()) {
        if (n != null) {
            //左边界进栈,直到最末端
            stack.push(n);
            n = n.left;
        } else {
            //跳到右边,压入右节点(压完后,n不为空,会重新进入上面的左边界处理)
            n = stack.pop();
            System.out.print(n.val + " ");
            n = n.right;
        }
    }
}

/**
 * 后序遍历(非递归版): left-right-root
 *
 * @param root
 */
static void postOrderUnRecur(TreeNode root) {
    if (root == null) {
        return;
    }
    Stack<TreeNode> stack = new Stack<>();
    //用于收集最后所有"排好序"的节点
    Stack<TreeNode> result = new Stack<>();
    stack.add(root);
    while (!stack.isEmpty()) {
        TreeNode n = stack.pop();
        result.add(n);
        if (n.left != null) {
            stack.add(n.left);
        }
        if (n.right != null) {
            stack.add(n.right);
        }
    }
    while (!result.isEmpty()) {
        System.out.print(result.pop().val + " ");
    }
}

三、层序遍历

即按一层层遍历所有节点, 直接按头-左-右, 放到队列即可

代码语言:javascript
复制
public static void levelOrder(TreeNode n1) {
    if (n1 == null) {
        return;
    }
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(n1);
    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        System.out.printf(node.val + " ");
        if (node.left != null) {
            queue.add(node.left);
        }
        if (node.right != null) {
            queue.add(node.right);
        }
    }
}

还是这颗树,层序遍历输出结果为 1 2 3 4 5,如果想输出结果更友好点,一层输出一行, 可以改进一下,搞一个Map<Node, Integer> 记录每个节点所在的层

代码语言:javascript
复制
static void levelOrder2(TreeNode n) {
    if (n == null) {
        return;
    }
    int currLevel = 1;

    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(n);

    //弄1个map,记录每个元素所在的层
    Map<TreeNode, Integer> levelMap = new HashMap<>();
    levelMap.put(n, 1);
    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        //从map取查找出队元素所在的层
        int nodeLevel = levelMap.get(node);
        //如果与当前层不一样,说明来到了下一层(关键!)
        if (currLevel != nodeLevel) {
            currLevel += 1;
            //输出换行符
            System.out.println();
        }
        System.out.print(node.val + " ");
        if (node.left != null) {
            //左节点入队,说明取到了下层,把下层元素提前放入map
            levelMap.put(node.left, currLevel + 1);
            queue.add(node.left);
        }
        if (node.right != null) {
            //右节点入队,说明取到了下层,把下层元素提前放入map
            levelMap.put(node.right, currLevel + 1);
            queue.add(node.right);
        }
    }
}

输出为:

1

2 3

4 5

这个版本还可以继续优化, 仔细想想, 其实只需要知道什么时候进入下一层就可以了, 没必要搞个Map记录所有节点在第几层, 按头-左-右的顺序层层入队, 然后不断出队, queue中同时最多也只会有3个元素.

代码语言:javascript
复制
    static void levelOrder3(TreeNode n) {
        if (n == null) {
            return;
        }
        //curEnd:本层最后1个节点
        //nextEnd:下层最后1个节点
        TreeNode curEnd = n, nextEnd = null;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(n);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            System.out.printf(node.val + " ");
            //逐层入队
            //注:queue中,最多只会有头-左-右 3个节点
            //入队过程中,nextEnd最终肯定会指向本层最后1个节点
            if (node.left != null) {
                queue.add(node.left);
                nextEnd = node.left;
            }
            if (node.right != null) {
                queue.add(node.right);
                nextEnd = node.right;
            }
            if (node == curEnd) {
                //如果出队的元素, 已经是本层最后1个,说明这层到头了
                System.out.printf("\n");
                //进入下一层后,重新标识curEnd
                curEnd = nextEnd;
            }
        }
    }

输出效果不变

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-10-27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档