如何根据数组创建二叉树和打印二叉树?

2023-11-11

***By Long Luo***

之前的[如何根据数组或者字符串创建链表?](http://www.longluo.me/blog/2020/12/10/Construct-A-LinkedList-From-An-Array-Or-String/)详述了[Leetcode](https://leetcode-cn.com/problemset/all/)中[链表](https://leetcode-cn.com/tag/linked-list/)相关算法题的测试方法。在Leetcode中关于[树](https://leetcode-cn.com/tag/tree/)的算法题中,很多树的题目,测试用例都是一个数组,比如[102. 二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/)中所示:

```txt
给定二叉树: [3,9,20,null,null,15,7]
    3
   / \
  9  20
    /  \
   15   7
```

那么问题来了,如何根据数组构造一颗树呢?
为了加快刷题,我们需要一个工具来实现构造树和打印树结构这2个问题。

# 树

树是一种抽象数据类型(ADT)或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由 n(n>0)个有限节点组成一个具有层次关系的集合。

![Tree](https://upload-images.jianshu.io/upload_images/577271-d0d70afb1a3a65cc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

如上图所示,把它叫做「树」是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

树具有以下的特点:
- 每个节点都只有有限个子节点或无子节点;
- 没有父节点的节点称为根节点;
- 每一个非根节点有且只有一个父节点;
- 除了根节点外,每个子节点可以分为多个不相交的子树;
- 树里面没有环路。

当我们完成一棵树的构建之后,虽然我们已经有树的前序、中序和后序遍历这种可以遍历树,但是如果我们如上图一样展示这棵树的结构,如何才能直观地打印出来呢?

<!--more-->

# 如何打印一棵树?
这里我们借用Leetcode中二叉树的数据结构定义:

```java
/**
 * Definition for a binary tree node.
 */
public class TreeNode {
    public int val;

    public TreeNode left;
    public TreeNode right;

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

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

## 思路
树的展示方式有2种,水平展示和竖直展示。竖直展示比较直观,水平展示更适合用于节点元素大小长短不一致的情况,Linux下展示文件结构就是水平展示。

## 水平树

代码如下所示:

```java
    public static int getTreeDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }

        return 1 + Math.max(getTreeDepth(root.left), getTreeDepth(root.right));
    }

    private static String traversePreOrder(TreeNode root) {
        if (root == null) {
            return "";
        }

        StringBuilder sb = new StringBuilder();
        sb.append(root.val);

        String pointerRight = "└──";
        String pointerLeft;
        if (root.right != null) {
            pointerLeft = "├──";
        } else {
            pointerLeft = "└──";
        }

        traverseNodes(sb, "", pointerLeft, root.left, root.right != null);
        traverseNodes(sb, "", pointerRight, root.right, false);

        return sb.toString();
    }

    private static void traverseNodes(StringBuilder sb, String padding, String pointer, TreeNode node,
                                      boolean hasRightSibling) {
        if (node == null) {
            return;
        }

        sb.append("\n");
        sb.append(padding);
        sb.append(pointer);
        sb.append(node.val);

        StringBuilder paddingBuilder = new StringBuilder(padding);
        if (hasRightSibling) {
            paddingBuilder.append("│  ");
        } else {
            paddingBuilder.append("   ");
        }

        String paddingForBoth = paddingBuilder.toString();
        String pointerRight = "└──";
        String pointerLeft = (node.right != null) ? "├──" : "└──";

        traverseNodes(sb, paddingForBoth, pointerLeft, node.left, node.right != null);
        traverseNodes(sb, paddingForBoth, pointerRight, node.right, false);
    }

    public static void printTreeHorizontal(TreeNode root) {
        System.out.print(traversePreOrder(root));
    }
```

## 垂直树

代码如下所示:

```java
    public static void printTree(TreeNode root) {
        int maxLevel = getTreeDepth(root);
        printNodeInternal(Collections.singletonList(root), 1, maxLevel);
    }

    private static void printNodeInternal(List<TreeNode> nodes, int level, int maxLevel) {
        if (nodes == null || nodes.isEmpty() || isAllElementsNull(nodes)) {
            return;
        }

        int floor = maxLevel - level;
        int endgeLines = (int) Math.pow(2, (Math.max(floor - 1, 0)));
        int firstSpaces = (int) Math.pow(2, (floor)) - 1;
        int betweenSpaces = (int) Math.pow(2, (floor + 1)) - 1;

        printWhitespaces(firstSpaces);

        List<TreeNode> newNodes = new ArrayList<TreeNode>();
        for (TreeNode node : nodes) {
            if (node != null) {
                System.out.print(node.val);
                newNodes.add(node.left);
                newNodes.add(node.right);
            } else {
                newNodes.add(null);
                newNodes.add(null);
                System.out.print(" ");
            }

            printWhitespaces(betweenSpaces);
        }
        System.out.println("");

        for (int i = 1; i <= endgeLines; i++) {
            for (int j = 0; j < nodes.size(); j++) {
                printWhitespaces(firstSpaces - i);
                if (nodes.get(j) == null) {
                    printWhitespaces(endgeLines + endgeLines + i + 1);
                    continue;
                }

                if (nodes.get(j).left != null) {
                    System.out.print("/");
                } else {
                    printWhitespaces(1);
                }

                printWhitespaces(i + i - 1);
                if (nodes.get(j).right != null) {
                    System.out.print("\\");
                } else {
                    printWhitespaces(1);
                }
                printWhitespaces(endgeLines + endgeLines - i);
            }

            System.out.println("");
        }

        printNodeInternal(newNodes, level + 1, maxLevel);
    }

    private static void printWhitespaces(int count) {
        for (int i = 0; i < count; i++) {
            System.out.print(" ");
        }
    }

    private static <T> boolean isAllElementsNull(List<T> list) {
        for (Object object : list) {
            if (object != null) {
                return false;
            }
        }

        return true;
    }
```

# 从数组构建一棵二叉树

代码如下所示:

```java
    public static TreeNode constructTree(Integer[] array) {
        if (array == null || array.length == 0 || array[0] == null) {
            return null;
        }

        int index = 0;
        int length = array.length;

        TreeNode root = new TreeNode(array[0]);
        Deque<TreeNode> nodeQueue = new LinkedList<>();
        nodeQueue.offer(root);
        TreeNode currNode;
        while (index < length) {
            index++;
            if (index >= length) {
                return root;
            }
            currNode = nodeQueue.poll();
            Integer leftChild = array[index];
            if (leftChild != null) {
                currNode.left = new TreeNode(leftChild);
                nodeQueue.offer(currNode.left);
            }
            index++;
            if (index >= length) {
                return root;
            }
            Integer rightChild = array[index];
            if (rightChild != null) {
                currNode.right = new TreeNode(rightChild);
                nodeQueue.offer(currNode.right);
            }
        }

        return root;
    }
```

# 测试

下面就来测试下代码吧:

```java
    public static void main(String[] args) {
        Integer[] tstData1 = {1, null, 2, 2, 32, 31, 3, 23, 1, 23, 123, 12, 3, 12, 31, 23, 2};
        TreeNode tstNode1 = constructTree(tstData1);
        System.out.println("\nTree:");
        printTree(tstNode1);
        System.out.println("\nHorizontal Tree:");
        printTreeHorizontal(tstNode1);
        System.out.println("\nPreOrder:");
        preOrderPrint(tstNode1);

        Integer[] tstData2 = {1, 2, 3, null, 4, 5, 6, 7, null};
        TreeNode tstNode2 = constructTree(tstData2);
        System.out.println("\nTree:");
        printTree(tstNode2);
        System.out.println("\nHorizontal Tree:");
        printTreeHorizontal(tstNode2);
        System.out.println("\nPreOrder:");
        preOrderPrint(tstNode2);
        System.out.println("\nInOrder:");
        inOrderPrint(tstNode2);
        System.out.println("\nPostOrder:");
        postOrderPrint(tstNode2);
    }
```

输出如下所示:

```txt
Tree:
               1                               
                \               
                 \              
                  \             
                   \            
                    \           
                     \          
                      \         
                       \        
                       2               
                      / \       
                     /   \      
                    /     \     
                   /       \    
                   2       32       
                  / \     / \   
                 /   \   /   \  
                 31   3   23   1   
                / \ / \ / \ / \ 
                23 123 12 3 12 31 23 2                                                   
Horizontal Tree:
1
└──2
   ├──2
   │  ├──31
   │  │  ├──23
   │  │  └──123
   │  └──3
   │     ├──12
   │     └──3
   └──32
      ├──23
      │  ├──12
      │  └──31
      └──1
         ├──23
         └──2
PreOrder:
1,2,2,31,23,123,3,12,3,32,23,12,31,1,23,2,

Tree:
       1               
      / \       
     /   \      
    /     \     
   /       \    
   2       3       
    \     / \   
     \   /   \  
     4   5   6   
    /           
    7                                     
Horizontal Tree:
1
├──2
│  └──4
│     └──7
└──3
   ├──5
   └──6
PreOrder:
1,2,4,7,3,5,6,
InOrder:
2,7,4,1,5,3,6,
PostOrder:
7,4,2,5,6,3,1,
```

# 小结
通过上述,我们最终就完成了我们的任务。

本文来源:[如何根据数组创建二叉树?](http://www.longluo.me/blog/2020/12/23/Construct-A-Tree-From-An-Array-And-Print-A-Tree/)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何根据数组创建二叉树和打印二叉树? 的相关文章

  • Eclipse 中的 Java 构建路径问题

    在 Eclipse 中 我有一个与我的构建路径相关的错误 错误 Project XX is missing required library middlegen 2 1 jar 但该库在构建路径配置之前被删除 是不是缓存或者其他方面有问题
  • 无法禁用 Firestore 中的离线数据

    从我的数据中删除数据后Firestore Database 这需要我的Android app一段时间后才意识到数据已被删除 我认为这是由于自动数据缓存而发生的 我的应用程序与离线使用无关 我想禁用此功能 我已将其添加到我的自定义中Appli
  • 如何使用 Jsoup 获取包含非 ASCII 字符(ą、ś ...)的 URL?

    我正在使用 jsoup 解析一些波兰网站 但我对 URL 中的 等特殊字符有问题example com k t读起来像example com k 每个没有这个特殊字符的查询都可以完美运行 我努力了Document doc Jsoup par
  • 浏览时 Java Applet 不会被终止

    当用户离开加载小程序的页面时 如何停止 Java 小程序的进程 我正在使用 Chrome 现在要杀死小程序 我必须使用窗口的任务栏并杀死进程 java exe Java applet 具有生命周期方法 那些是init start stop
  • Android 服务 START_STICKY START_NOT_STICKY

    我需要让我的服务始终在后台运行 并使用 startService 函数启动我的服务 无论应用程序的状态如何 我都不想重新启动服务 这是我的观察 START STICKY gt 如果应用程序启动 则服务正在重新启动 当应用程序关闭时 服务也会
  • 在 Java 中将字符串复制到文件的开头

    我想将一个字符串写入文件的开头 我该怎么做 我根本不知道如何添加字符串 这就是我到目前为止所做的 public static void prepend String filename String data throws IOExcepti
  • 如何在休眠中持久保存实体期间验证实体的约束

    我有一个带有字段名称的实体 我希望它不超过255 所以我这样定义它 Entity public class A implements Serializable NotNull Size max 255 private String name
  • 如何修复运行 Android 模拟器时出现 GPU Driver Issue 错误

    我的 Android 模拟器几周前运行良好 但现在出现错误 当我运行代码时 GPU 驱动程序问题错误对话框与模拟器一起弹出 当我单击 确定 时 Android 模拟器不会按预期运行应用程序 错误如下 Your GPU driver info
  • 不带破折号的 CliBuilder 参数

    使用 Groovy CliBuilder 理想情况下我希望有一个命令行 如下所示 MyProgram groovy CommandName arg1 arg2 arg3 是否可以使用 CliBuilder 解析提取 CommandName
  • xclock 工作,X11 DISPLAY 设置但仍然 java.awt.HeadlessException:

    获取 java awt HeadlessException 似乎是一个非常常见的问题 并且 中已经讨论过 以下问题 没有 X11 DISPLAY 变量 这是什么意思 https stackoverflow com questions 662
  • Apache Camel - 路由中的事务

    我有一个关于 Apache Camel 的一般性问题 我无法找到聚合器是否已进行交易 如果是交易 交易是如何实现的 聚合的速度有多快 将消息发送到聚合器可以在事务中运行 您需要一个带有聚合器的持久存储来让传出消息充当事务 请参阅有关持久性的
  • 如何查找类路径中具有指定名称的所有资源?

    我想列出类路径中具有特定名称的所有文件 我预计会发生多次 因此Class getResource String 不管用 基本上 我必须识别类路径中任何位置具有特定名称 例如 xyz properties 的所有文件 然后累积读取其中的元数据
  • 使用会话空闲超时进行轮询

    我对 Tomcat 中的所有应用程序使用单点登录 我的要求是 我必须轮询应从后端获取的事务状态 但它也不应该影响会话的空闲超时 有人可以建议是否可以做点什么吗 Thanx 我不知道是否有标准方法可以做到这一点 如果没有 你可以写一个过滤器
  • Android Studio错误的含义:未注释的参数覆盖@NonNull参数

    我正在尝试 Android Studio 创建新项目并添加默认值后onSaveInstanceState方法创建 MyActivity 类 当我尝试将代码提交到 Git 时 我收到一个我不明白的奇怪错误 代码是这样的 我得到的错误是这样的
  • 尝试用Java实现基于文本的Hangman游戏

    我需要检查用户输入的字母以及他们猜测的空格是否位于隐藏单词的特定位置 变量one等于用户猜测的空间索引 而letterGuess是他们猜测的字母 我的代码怎么错了 示例 秘密词是你好 hidden word is 用户猜测h 0 1 2 3
  • 不幸的是 Project_Name 已停止

    我有一个简单的应用程序 您可以在文本视图中输入文本并按提交 它会在另一个活动中显示文本 然而 当我按下提交时 给我消息 不幸的是 发送已停止 我查看了SO上的其他线程 但是不幸的是 myfirstproject 在 java 中停止工作错误
  • Java:易失性足以使类线程安全?

    我有一个关于 Java 中 volatile 语句的问题 请看这个构造的例子 class Master Foo is a class with thread safe methods public volatile Foo foo clas
  • 如何使用现代.fxml和controller.java在javafx 2.x中制作自动完成组合框[重复]

    这个问题在这里已经有答案了 如何使用现代 fxml 和controller java 在 javafx 2 x 中制作一个类似的自动完成组合框 就像制作这个一样 http blog ngopal com np 2011 07 04 auto
  • 如何在不下载子项的情况下从 Firebase 获取子项密钥?

    我有一个 Firebase 数据库 其中的节点 items 有很多子项 我想导入子项键的列表 由于每个子项都包含相当多我对此不感兴趣的数据 因此我想仅下载子项密钥 以最大程度地减少传输的数据量 为了便于说明 假设我有以下数据结构 然后我想获
  • 如何读取FTL文件中的JSONArray?

    我在我的 Java 文件中硬编码了以下 JSON 对象 JSONObject notificationInfoJson new JSONObject notificationInfoJson put title Payment Receiv

随机推荐