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