載一棵小樹苗,精心培育,總有一天會長成參天大樹
比如查找二叉、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` '後面的符號),是因爲打印方法還略有不足——有時兩個節點會連在一起,沒辦法看出具體的節點值。分割符的存在算是投機取巧。