载一棵小树苗,精心培育,总有一天会长成参天大树
好比查找二叉、AVL、B + *、红黑……
可是,今天不种树,改为画树……java
事情时这样的:在搞懂简单二叉树的过程当中,常常须要验证本身的代码有没有问题,我以前的作法是“断点+肉眼观察”大法。随着节点的增多,断点还好,肉眼愈来愈扛不住,遂决定把树打印出来。把树的各类操做(新增/删除节点)先后进行比对,是非一目了然!node
对于上面的树,咱们已经能够从根节点遍历它了。(若是对树的基本操做还不清楚的话,可参看【树结构1】查找二叉树)segmentfault
直接给出遍历方式:app
public void treeIterator(TwoForkTree tree){ if(tree==null){ return ; } treeIterator(tree.leftNode); System.out.print(tree.getId()+"\t"); //打印节点,这个位置是“中序遍历” treeIterator(tree.rightNode); }
既然咱们已经能够遍历它,那有没有方式能够记录下当前节点在第几层呢?也就是,第一层:32;第二层:20、40;第三层:3五、41;第四层:38。若是能够作到,咱们再按层级,一层一层的输出,不就把树打印出来了嘛!学习
怎么记录当前层级呢?对遍历方法稍加变更便可测试
public void record(TwoForkTree tree,int index){ if(tree==null){ return ; } index++; record(tree.leftNode,index); System.out.println(index+":"+tree.getId()+"\t"); record(tree.rightNode,index); }
执行结果:ui
接下来的事情简单了,咱们把上述控制台输出的内容,用Map
保存下来,再逐行输出便可。this
//按层级存储节点的值 @Getter Map<Integer,List<Integer>> layerTree = new HashMap<>(); public void record(TwoForkTree tree,int index){ if(tree==null){ return ; } index++; record(tree.leftNode,index); List<Integer> layerData = layerTree.get(index); if(CollectionUtils.isEmpty(layerData)){ layerData = new LinkedList<>(); layerTree.put(index,layerData); } layerData.add(tree.id); record(tree.rightNode,index); }
测试以及逐行输出便可:spa
@Test public void testRecord(){ tree.record(tree,0); SimpleNode simpleNode = (SimpleNode) tree; Map<Integer,List<Integer>> layerTree = simpleNode.layerTree; int layerIndex=0; while (layerIndex<layerTree.size()){ layerIndex++; List<Integer> layerData = layerTree.get(layerIndex); for (Integer data:layerData){ System.out.print(data+"\t"); } System.out.println(); } }
执行结果:3d
网上的资料大部分到这里就结束了,但看看这个产物,虽然是把树按层级打印出来了,但不少部分还须要你脑补才行。留白太大,对艺术做品还好,但学习研究仍是尽量精准的好。我想要的是,带着枝杈的树!
# 目标 32 / \ 20 40 / \ 35 41 \ 38
怎么实现呢?遍历节点过程当中,像Map中存储节点的时候,咱们彻底能够知道,它的子节点状况——若是有左子节点,记录一个/
;若是有右子节点,记录一个\
。由此,咱们能够封装一个Bean。
class Printer{ private Integer id; private int index; private String leftChildLink; private String rightChildLink; }
对代码进行调整后,效果变成这样:
虽然还要进行脑补,但彷佛容易了些?固然,这不是结束,其实距离目标效果就差最后一步了。咱们须要对数值和子节点链接符(“/”、“\”
)分别存储,输出时根据上一层的位置作调整!
给出完整实现:
/** * 树打印 */ public void printTree(){ Map<Integer,List<Printer>> printMap = printTree(this,0); int layerIndex = 1; StringBuilder idBu = new StringBuilder(); StringBuilder linkBu = new StringBuilder(); LinkedList<Integer> nextLineIdPositions = new LinkedList<>(); while (layerIndex<=layerTreeMap.size()){ List<Printer> printers = printMap.get(layerIndex); int lastIdLen = 0; int lastIdPosition = 0; for(Printer printer:printers){ int position; if(CollectionUtils.isEmpty(nextLineIdPositions)){ position = 20; }else { position = nextLineIdPositions.removeFirst()-idLen(printer.getId())/2; if(position<=lastIdPosition+lastIdLen){ position+=idLen(printer.getId())/2; } } lastIdPosition = position; lastIdLen = idLen(printer.getId()); appendAt(idBu,position,printer.getId()+"`"); if(!Strings.isNullOrEmpty(printer.getLeftChildLink()) || !Strings.isNullOrEmpty(printer.getRightChildLink())){ int linkPosition = idBu.length()-idLen(printer.getId()); if(!Strings.isNullOrEmpty(printer.getLeftChildLink())){ appendAt(linkBu,linkPosition-idLen(printer.getId())/2,printer.getLeftChildLink()); nextLineIdPositions.add(linkPosition-idLen(printer.getId())/2); } if(!Strings.isNullOrEmpty(printer.getRightChildLink())){ // if(Strings.isNullOrEmpty(printer.getLeftChildLink())){ // linkPosition+=2; // } appendAt(linkBu,linkPosition+idLen(printer.getId()),printer.getRightChildLink()); nextLineIdPositions.add(linkPosition+idLen(printer.getId())+1); } } } System.out.println(idBu.toString()); System.out.println(linkBu.toString()); idBu.setLength(0); linkBu.setLength(0); layerIndex++; } // 数据还原 layerTreeMap.clear(); } private int idLen(Integer id){ return (id+"").length(); } private StringBuilder appendAt(StringBuilder bu,int position,String param){ while (bu.length()<position){ bu.append(" "); } return bu.append(param); } private String createSpace(int num){ StringBuilder spaceBu = new StringBuilder(); for(int k=0;k<num;k++){ spaceBu.append(" "); } return spaceBu.toString(); } //栈,用来记录路径 Map<Integer,List<Printer>> layerTreeMap = new HashMap<>(); private Map<Integer,List<Printer>> printTree(TwoForkTree node,int index){ if(node==null){ return null; } index++; List tempList = layerTreeMap.get(index); if(CollectionUtils.isEmpty(tempList)){ tempList = new LinkedList(); layerTreeMap.put(index,tempList); } Printer printer = new Printer(); tempList.add(printer); printer.setId(node.getId()); printer.setIndex(index); if(node.leftNode!=null){ printer.setLeftChildLink("/"); } if(node.rightNode!=null){ printer.setRightChildLink("\\"); } printTree(node.leftNode,index); printTree(node.rightNode,index); return layerTreeMap; } @Setter @Getter public class Printer{ private Integer id; private int index; private String leftChildLink; private String rightChildLink; }
最终效果:
注:之因此加了分割符( ' 32` '后面的符号),是由于打印方法还略有不足——有时两个节点会连在一块儿,没办法看出具体的节点值。分割符的存在算是投机取巧。