载一棵小树苗,精心培育,总有一天会长成参天大树
比如查找二叉、AVL、B + *、红黑……
但是,今天不种树,改成画树……
事情时这样的:在搞懂简单二叉树的过程中,经常需要验证自己的代码有没有问题,我之前的做法是“断点+肉眼观察”大法。随着节点的增多,断点还好,肉眼越来越扛不住,遂决定把树打印出来。把树的各种操作(新增/删除节点)前后进行比对,是非一目了然!
思路
对于上面的树,我们已经可以从根节点遍历它了。(如果对树的基本操作还不清楚的话,可参看【树结构1】查找二叉树)
直接给出遍历方式:
public void treeIterator(TwoForkTree tree){
if(tree==null){
return ;
}
treeIterator(tree.leftNode);
System.out.print(tree.getId()+"\t"); //打印节点,这个位置是“中序遍历”
treeIterator(tree.rightNode);
}
既然我们已经可以遍历它,那有没有方式可以记录下当前节点在第几层呢?也就是,第一层:32;第二层:20、40;第三层:35、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);
}
执行结果:
代码实现
接下来的事情简单了,我们把上述控制台输出的内容,用Map
保存下来,再逐行输出即可。
//按层级存储节点的值
@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);
}
测试以及逐行输出即可:
@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();
}
}
执行结果:
改进
网上的资料大部分到这里就结束了,但看看这个产物,虽然是把树按层级打印出来了,但很多部分还需要你脑补才行。留白太大,对艺术作品还好,但学习研究还是尽可能精准的好。我想要的是,带着枝杈的树!
# 目标
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` '后面的符号),是因为打印方法还略有不足——有时两个节点会连在一起,没办法看出具体的节点值。分割符的存在算是投机取巧。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。