本题来自左神《程序员代码面试指南》“如何较为直观地打印二叉树”题目。

题目

二叉树可以用常规的三种遍历结果来描述其结构,但是不够直观,尤其是二叉树中有重复值的时候,仅通过三种遍历的结果来构造二叉树的真实结构更是难上加难,有时则根本不可能。

给定一棵二叉树的头节点head,已知二叉树节点值的类型为32 位整型,请实现一个打印二叉树的函数,可以直观地展示树的形状,也便于画出真实的结构。

题解

这是一道较开放的题目,实现者不仅要设计出符合要求且不会产生歧义的打印方式,还要考虑实现难度,在面试时仅仅写出思路必然是不满足代码面试要求的。本书给出一种符合要求且代码量不大的实现,希望读者也能实现并优化自己的设计。具体过程如下:

1.设计打印的样式。实现者首先应该解决的问题是用什么样的方式来无歧义地打印二叉树。比如,二叉树如图 3-4 所示。


对如图 3-4 所示的二叉树,本文设计的打印样式如图 3-5 所示。

下面解释一下如何看打印的结果。

首先,二叉树大概的样子是把打印结果顺时针旋转90°,读者可以把图3-4 的打印结果(也就是图3-5 顺时针旋转90°之后)做对比,两幅图是存在明显对应关系的;

接下来,怎么清晰地确定任何一个节点的父节点呢?

  • 如果一个节点打印结果的前缀与后缀都有“H”(如图3-5 中的“H1H”),则说明这个节点是头节点,当然就不存在父节点。
  • 如果一个节点打印结果的前缀与后缀都有“v”,则表示父节点在该节点所在列的前一列,在该节点所在行的下方,并且是离该节点最近的节点。
    比如,图3-5 中的“v3v”“v6v”和“v7v”,父节点分别为“H1H”“v3v”和“^4^”。
  • 如果一个节点打印结果的前缀与后缀都有“^”,则表示父节点在该节点所在列的前一列,在该节点所在行的上方,并且是离该节点最近的节点。比如,图3-5 中的“^5^”“^2^”和“^4^”,父节点分别为“v3v”“H1H”和“^2^

2.一个需要重点考虑的问题——规定节点打印时占用的统一长度。我们必须规定一个节点在打印时到底占多长。

试想一下,如果有些节点的值本身的长度很短,如“1” “2”等,而有些节点的值本身的长度很长,如“43323232” “78787237”等,那么如果不规定一个统一的长度,则在打印一个长短值交替的二叉树时必然会出现格式对不齐的问题,进而产生歧义。

在 Java 中,整型值占用长度最长的值是Integer.MIN_VALUE(-2147483648),占用的长度为 11,加上前缀和后缀(“H”“v”或“^”)之后占用长度为 13。为了在打印之后更好地区分,再把前面加上两个空格,后面加上两个空格,总共占用长度为 17。也就是说,长度为 17 的空间必然可以放下任何一个 32 位整数,同时样式还不错。

至此,我们约定,打印每一个节点时,必须让每一个节点在打印时占用长度都为17,如果不足,则前后都用空格补齐。示例如下:

比如,节点值为8,假设这个节点加上“v”作为前后缀,那么实质内容为“v8v”,长度才为 3,在打印时在“v8v”的前面补 7 个空格,后面也补 7 个空格,让总长度为 17。再如,节点值为 66,假设这个节点加上“v”作为前后缀,那么实质内容为“v66v”,长度才为 4,在打印时在“v66v”的前面补 6 个空格,后面补 7 个空格,让总长度为 17。总之,如果长度不足,则前后贴上几乎数量相等的空格来补齐。

3.确定了打印的样式,规定了占用长度的标准,最后来解释具体的实现。

打印的整体过程结合了二叉树 先右子树、再根节点、最后左子树 的递归遍历过程。(之所以选择逆中序遍历,而不使用前序/后序,是因为中序遍历的顺序就是将二叉树直接 “拍扁” 得到的顺序,因此90°旋转后,正好是按行打印的顺序)如果递归到一个节点,则首先遍历它的右子树。右子树遍历结束后,回到这个节点。

  • 如果这个节点所在层为 l,那么先打印 l×17 个空格(不换行),然后开始制作该节点的打印内容,这个内容当然包括节点的值,以及确定的前后缀字符。
  • 如果该节点是其父节点的右孩子节点,则前后缀为“v”
  • 如果该节点是其父节点的左孩子节点,则前后缀为“^”
  • 如果是头节点,则前后缀为“H”。
  • 最后,在前后分别贴上数量几乎一致的空格,占用长度为 17 的打印内容就制作完成,打印这个内容后换行。
  • 最后进行左子树的遍历过程。

直观地打印二叉树的所有过程请参看如下代码中的 printTree 方法。

代码

package chapter_3_binarytreeproblem;public class Problem_03_PrintBinaryTree {public static class Node {public int value;public Node left;public Node right;public Node(int data) {this.value = data;}}public static void printTree(Node head) {System.out.println("Binary Tree:");printInOrder(head, 0, "H", 17);System.out.println();}/*** 相当于逆向的中序遍历(右->中->左)* (之所以选择中序遍历,而不是前序/后序,是因为中序遍历的顺序就是将二叉树直接"拍扁"得到的顺序,因此90°旋转后,正好是按行打印的顺序)*/public static void printInOrder(Node head, int height, String to, int len) {if (head == null) {return;}printInOrder(head.right, height + 1, "v", len); // 递归遍历右子树String val = to + head.value + to; // 处理并打印根节点int lenM = val.length();int lenL = (len - lenM) / 2;int lenR = len - lenM - lenL;val = getSpace(lenL) + val + getSpace(lenR);System.out.println(getSpace(height * len) + val);printInOrder(head.left, height + 1, "^", len); // 递归遍历左子树}public static String getSpace(int num) {String space = " ";StringBuffer buf = new StringBuffer("");for (int i = 0; i < num; i++) {buf.append(space);}return buf.toString();}public static void main(String[] args) {Node head = new Node(1);head.left = new Node(-222222222);head.right = new Node(3);head.left.left = new Node(Integer.MIN_VALUE);head.right.left = new Node(55555555);head.right.right = new Node(66);head.left.left.right = new Node(777);printTree(head);head = new Node(1);head.left = new Node(2);head.right = new Node(3);head.left.left = new Node(4);head.right.left = new Node(5);head.right.right = new Node(6);head.left.left.right = new Node(7);printTree(head);head = new Node(1);head.left = new Node(1);head.right = new Node(1);head.left.left = new Node(1);head.right.left = new Node(1);head.right.right = new Node(1);head.left.left.right = new Node(1);printTree(head);}
}

输出结果:

Binary Tree:v66v       v3v       ^55555555^    H1H       ^-222222222^   v777v      ^-2147483648^  Binary Tree:v6v       v3v       ^5^       H1H       ^2^       v7v       ^4^       Binary Tree:v1v       v1v       ^1^       H1H       ^1^       v1v       ^1^       

左神算法:如何较为直观地打印二叉树(Java版)相关推荐

  1. 左神算法:反转单向和双向链表(Java版)

    本题来自左神<程序员面试代码指南>"反转单向和双向链表"题目. 题目 分别实现反转单向链表和反转双向链表的函数. 如果链表长度为N,时间复杂度要求为O(N),额外空间复 ...

  2. 左神算法:生成窗口最大值数组(Java版)

    本题来自左神<程序员面试代码指南>"生成窗口最大值数组"题目. 题目 有一个整型数组 arr 和一个大小为 w 的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置 ...

  3. 左神算法:加强堆的实现(Java)

    为什么要有加强堆? Java中的PriorityQueue(优先级队列)就是系统提供的堆实现,那么为什么还要手动去实现? 假如现在你手里有一个堆,里面存着一些元素,用户此时说要改变元素的排序指标且要求 ...

  4. 左神算法:最大值减去最小值小于或等于num的子数组的数量(Java版)

    本题来自左神<程序员面试代码指南>"最大值减去最小值小于或等于num的子数组的数量"题目. 题目 给定数组 arr 和整数 num,共返回有多少个子数组满足如下情况: ...

  5. 左神算法:求最大子矩阵的大小(Java版)

    本题来自左神<程序员面试代码指南>"求最大子矩阵的大小"题目. 题目 给定一个整型矩阵 map,其中的值只有0和1两种,求其中全是1的所有矩形区域中,最大的矩形区域为1 ...

  6. 左神算法:二叉树的按层打印与ZigZag打印(Java版)

    本题来自左神<程序员代码面试指南>"二叉树的按层打印与ZigZag打印"题目. 题目 给定一棵二叉树的头节点 head,分别实现 按层 和 ZigZag 打印 二叉树的 ...

  7. 左神算法:判断 t1 树中是否有与 t2 树拓扑结构完全相同的子树(Java版)

    本题来自左神<程序员代码面试指南>"判断 t1 树中是否有与 t2 树拓扑结构完全相同的子树"题目. 题目 给定彼此独立的两棵树头节点分别为 t1 和 t2,判断 t1 ...

  8. 左神算法:分别用递归和非递归方式实现二叉树先序、中序和后序遍历(Java版)

    本题来自左神<程序员代码面试指南>"分别用递归和非递归方式实现二叉树先序.中序和后序遍历"题目. 题目 用递归和非递归方式,分别按照二叉树先序.中序和后序打印所有的节点 ...

  9. 左神算法:两个单链表相交的一系列问题(链表是否有环 / 两无环链表是否相交 / 两有环链表是否相交)

    本题来自左神<程序员代码面试指南>"两个单链表相交的一系列问题"题目. 题目 在本题中,单链表可能有环,也可能无环.给定两个单链表的头节点 head1 和 head2, ...

最新文章

  1. Android 使用摄像头拍照
  2. 电脑开机老是显示explorer.exe文件损坏
  3. 使用JMS实现请求/应答程序
  4. Altium Designer 资料整理
  5. Electron中实现通过webview实现内嵌网页并嵌入css样式和js脚本等
  6. Oracle 查看 对象 持有锁的情况
  7. 修改Mysql默认 编码
  8. Vue中浏览器的的前进和后退
  9. python矩阵运算与线形代数_[译] 线性代数:矩阵基本运算
  10. SpringBoot 整合Shiro Ehcache
  11. (文中有惊喜)走进云时代的数据库
  12. python的dropna 和notna的性能_python数据分析学习(7)数据清洗与准备(1)
  13. 使用sersync实现多台服务器实时同步文件
  14. curl实现发送Get和Post请求(PHP)
  15. USB摄像头CMOS图像传感器PCB原理图
  16. webgis、gis学习技巧总结
  17. 170815 WarGames-Krypton(0-6)
  18. 【SQLSERVER 2000】由于数据移动,未能继续以 NOLOCK 方式扫描
  19. 金士顿固态硬盘不认盘修复_上海金士顿固态硬盘维修 上海台电固态硬盘不识别数据恢复中心...
  20. 【软件工程】交付和维护

热门文章

  1. LTE中的RSRP、RSSI、RSRQ、SINR、MCS介绍
  2. HFDS的Shell操作
  3. HashMap原理以及TreeMap和Collections工具类(2022.6.10)
  4. xxl-job源码解析(技术分享)
  5. pca图解读_PCA 图像识别 详解(一)
  6. PySide2学习总结(十二)打开文件对话框--FileDialog
  7. 阿里少壮派上位:70后、80后全面掌舵阿里
  8. sklearn 纠偏
  9. css3 实现图片闪过一道光效果 亲测可用
  10. 计算机英语性考任务答案,国开电大理工英语1单元自测2形考任务答案