二叉树的入门实战

引言

由于自己的算法基础比较薄弱,特此记录一下学习过程中遇见的算法题,从简单算法题开始做了一个梳理,希望我的梳理能对跟我一样的小白起到一点作用,写的不好的地方请在评论区多多提建议。本节记录的是有关二叉树算法题,我们先从最基础的地方开始入门:

  1. 将链表转换成二叉树(根左右顺序、层序)
  2. 对二叉树进行先序遍历、中序遍历、后序遍历。

常规题

0.基础数据准备
  1. 二叉树的基本结构
/**
 * @author 欢迎关注【编程开发分享者】公众号,领取新人编程大礼包
 * @Date 2020/12/18 18:31
 * @Description
 */
public class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;
    TreeNode(int x) { val = x; }
}
  1. 准备一个链表,作为二叉树的每个节点的值,如果值为空,则使用null表示
/**
 * 构建一个链表
 *
 * @return
 */
public LinkedList<String> generateList() {
    String[] array = {"3", "8", "7", null, null, "9", null, null, "5", null, "1"};
    LinkedList<String> linkedList = new LinkedList<>(Arrays.asList(array));
    return linkedList;
}
1.将链表转换成二叉树

如果没有做特殊要求,将链表转换成二叉树可以有很多种办法,你想怎么转就怎么转。

第一种:按照根、左、右的顺序构建

我们可以采用先序的方法,按照根节点、左节点、右节点的顺序依次添加。如下图所示,红色节点是按照顺序进行增加节点。
在这里插入图片描述
对应的代码如下,主要采用了递归的思想,每次先取出链表的头结点,然后插入到左节点和右节点中。

/**
 * 先序生成一个树,根节点、左节点、右节点的顺序
 *
 * @param list
 * @return
 */
public TreeNode generateTree(LinkedList<String> list) {
    TreeNode node;
    if (list == null || list.size() == 0 || list.isEmpty()) return null;
    String val = list.removeFirst();
    if (val != null) {
        node = new TreeNode(Integer.parseInt(val));
        node.left = generateTree(list);
        node.right = generateTree(list);
        return node;
    }
    return null;
}
第二种:层序构建(这种方式比较常用):

按照层次的顺序依次添加,为null的节点是不能添加左右节点的,看效果图。
层序
这种方式是最常用的构建二叉树的方式,学过二叉树知识的同学们应该都知道,确定一棵二叉树,最少需要两种遍历的方式(先序+中序或者后序+中序)。所以第一种方式并不能唯一的确定一个二叉树。这种层序的构建是可以做到的,虽然看着代码很多,但其实非常好理解,其核心思想是,从链表中取值封装成TreeNode对象,放入到队列中,然后从队列中取出TreeNode,判断一下是不是要给其添加左右节点。对应的代码如下,

/**
 * 先序生成一个树,层序
 * 使用一个队列来保存已经遍历过的节点。
 *
 * @param list
 * @return
 */
public TreeNode generateTreeLayer(LinkedList<String> list) {
    if (list == null && list.size() == 0) return null;
    Queue<TreeNode> queue = new LinkedList<>();
    //取出第一个值,封装成TreeNode后放入到队列中
    TreeNode root = new TreeNode(Integer.parseInt(list.removeFirst()));
    queue.add(root);
    while (queue != null && queue.size() != 0) {
        //取出队列中的第一个值
        TreeNode node = queue.poll();
        //给当前节点添加左节点
        if (list != null && list.size() != 0) {
            String val = list.removeFirst();
            if (val != null) {
                node.left = new TreeNode(Integer.parseInt(val));
                queue.add(node.left);
            }
        }
        //给当前节点添加右节点
        if (list != null && list.size() != 0) {
            String val = list.removeFirst();
            if (val != null) {
                node.right = new TreeNode(Integer.parseInt(val));
                queue.add(node.right);
            }
        }
    }
    return root;
}
小小练习题

学会了如何构建一个二叉树我们就可以试着做一下力扣的第297道题:传送门,这虽然是一道hard题,但不要被其表象所迷惑,你可以的!这道题是要对一颗二叉树进行序列化和反序列化,通俗的讲就是将一棵二叉树转换成一个字符串,然后在通过字符串可以转换成二叉树。

//思路:将树序列化成一个字符串,如果节点为空,则用#号表示
public class Q297 {
    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if (root == null) return null;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        String result = "";
        while (queue != null && queue.size() != 0) {
            TreeNode node = queue.poll();
            if (node.val != -9999999) {
                result = result + node.val + ",";
            } else {
                result = result + "null,";
                continue;
            }
            if (node.left != null) {
                queue.add(node.left);
            } else {
                queue.add(new TreeNode(-9999999));
            }
            if (node.right != null) {
                queue.add(node.right);
            } else {
                queue.add(new TreeNode(-9999999));
            }
        }
        return result;
    }
    
    // Decodes your encoded data to tree. [1,2,3,null,null,4,5]
    public TreeNode deserialize(String data) {
        if (data == null) return null;
        String[] datas = data.split(",");
        LinkedList<String> linkedList = new LinkedList<>(Arrays.asList(datas));
        if (linkedList == null || linkedList.size() == 0) return null;
        TreeNode root = new TreeNode(Integer.parseInt(linkedList.removeFirst()));
        Queue<TreeNode> queue = new LinkedList();
        queue.add(root);
        while (queue != null && queue.size() != 0) {
            TreeNode node = queue.poll();
            if (linkedList != null && linkedList.size() != 0) {
                String val = linkedList.removeFirst();
                if (!val.equals("null")) {
                    node.left = new TreeNode(Integer.parseInt(val));
                    queue.add(node.left);
                }
            }
            if (linkedList != null && linkedList.size() != 0) {
                String val = linkedList.removeFirst();
                if (!val.equals("null")) {
                    node.right = new TreeNode(Integer.parseInt(val));
                    queue.add(node.right);
                }
            }
        }
        return root;
    }

    @Test
    public void Test() {
        String data = "1,2,3,null,null,4,5";
        TreeNode root = deserialize(data);
        TreeOperation.show(root);
        String result = serialize(root);
        System.out.println(result);
    }
}

测试结果如下:第一个红框是我们的原始数据,第二个红框是序列化之后的数据,我们发现比原始数据多了四个null,这其实是无关紧要的,这一道题考察的不是序列化结果要跟原始数据一样,只要将二叉树变成字符串格式,然后在原封不动的变回来即可。
在这里插入图片描述

2.对二叉树进行遍历

对二叉树主要有三种遍历的方式:先序遍历、中序遍历和后序遍历。主要的区别就是对于根节点、左节点、右节点的打印顺序不同,使用了递归的思想,代码如下:

    /**
     * 对二叉树进行先序遍历
     *
     * @param node
     */
    public void preorderTraverse(TreeNode node) {
        if (node == null) return;
        System.out.print(node.val+" ");
        preorderTraverse(node.left);
        preorderTraverse(node.right);
    }

    /**
     * 对二叉树进行中序遍历
     *
     * @param node
     */
    public void inorderTraverse(TreeNode node) {
        if (node == null) return;
        inorderTraverse(node.left);
        System.out.print(node.val+" ");
        inorderTraverse(node.right);
    }

    /**
     * 对二叉树进行后序遍历
     *
     * @param node
     */
    public void postorderTraverse(TreeNode node) {
        if (node == null) return;
        postorderTraverse(node.left);
        postorderTraverse(node.right);
        System.out.print(node.val+" ");
    }

不难发现,其实只是打印的先后顺序不一样,代码结构一点没变,至于打印的有什么不一样,我们来测试一下:
在这里插入图片描述

工具

为了方便测试,大家可以将二叉树以树形图的形式打印出来,更加直观的看到自己生成的二叉树是否正确,我参考的是这一篇文章中的代码:https://www.cnblogs.com/liulaolaiu/p/11744409.html,感谢分享。

// TreeOperation.java
public class TreeOperation {
    /*
    树的结构示例:
              1
            /   \
          2       3
         / \     / \
        4   5   6   7
    */

    // 用于获得树的层数
    public static int getTreeDepth(TreeNode root) {
        return root == null ? 0 : (1 + Math.max(getTreeDepth(root.left), getTreeDepth(root.right)));
    }


    private static void writeArray(TreeNode currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
        // 保证输入的树不为空
        if (currNode == null) return;
        // 先将当前节点保存到二维数组中
        res[rowIndex][columnIndex] = String.valueOf(currNode.val);

        // 计算当前位于树的第几层
        int currLevel = ((rowIndex + 1) / 2);
        // 若到了最后一层,则返回
        if (currLevel == treeDepth) return;
        // 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
        int gap = treeDepth - currLevel - 1;

        // 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
        if (currNode.left != null) {
            res[rowIndex + 1][columnIndex - gap] = "/";
            writeArray(currNode.left, rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
        }

        // 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
        if (currNode.right != null) {
            res[rowIndex + 1][columnIndex + gap] = "\\";
            writeArray(currNode.right, rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
        }
    }


    public static void show(TreeNode root) {
        if (root == null) System.out.println("EMPTY!");
        // 得到树的深度
        int treeDepth = getTreeDepth(root);

        // 最后一行的宽度为2的(n - 1)次方乘3,再加1
        // 作为整个二维数组的宽度
        int arrayHeight = treeDepth * 2 - 1;
        int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
        // 用一个字符串数组来存储每个位置应显示的元素
        String[][] res = new String[arrayHeight][arrayWidth];
        // 对数组进行初始化,默认为一个空格
        for (int i = 0; i < arrayHeight; i ++) {
            for (int j = 0; j < arrayWidth; j ++) {
                res[i][j] = " ";
            }
        }

        // 从根节点开始,递归处理整个树
        // res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0');
        writeArray(root, 0, arrayWidth/ 2, res, treeDepth);

        // 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
        for (String[] line: res) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < line.length; i ++) {
                sb.append(line[i]);
                if (line[i].length() > 1 && i <= line.length - 1) {
                    i += line[i].length() > 4 ? 2: line[i].length() - 1;
                }
            }
            System.out.println(sb.toString());
        }
    }
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值