咱們構建一個以下圖所示的二叉樹:c++
咱們使用下面的數據結構來描繪出這個二叉樹git
1 public class Node { 2 private String name = ""; 3 public Node leftChild; 4 public Node rightChild; 5 6 public Node(String name) { 7 this.name = name; 8 } 9 10 public Node() { 11 } 12 13 public void setName(String name) { 14 this.name = name; 15 }
前序遍歷:github
1 /** 2 * 前序遍歷 3 */ 4 public String readPre() { 5 StringBuilder result = new StringBuilder(); 6 result.append(name); //前序遍歷 7 if (leftChild != null) { 8 result.append(leftChild.readPre()); 9 } 10 if (rightChild != null) { 11 result.append(rightChild.readPre()); 12 } 13 return result.toString(); 14 }
中序遍歷:算法
1 /** 2 * 中序遍歷 3 */ 4 public String readMid() { 5 StringBuilder result = new StringBuilder(); 6 if (leftChild != null) { 7 result.append(leftChild.readMid()); 8 } 9 result.append(name); //中序遍歷 10 if (rightChild != null) { 11 result.append(rightChild.readMid()); 12 } 13 return result.toString(); 14 }
後序遍歷:數據結構
1 /** 2 * 後序遍歷 3 */ 4 public String readEnd() { 5 StringBuilder result = new StringBuilder(); 6 if (leftChild != null) { 7 result.append(leftChild.readEnd()); 8 } 9 if (rightChild != null) { 10 result.append(rightChild.readEnd()); 11 } 12 result.append(name); //後序遍歷 13 return result.toString(); 14 }
從上面能夠看到,前序、中序、後序遍歷的算法基本上差很少,其主要是在對根節點的訪問順序不一樣,而後利用遞歸的方式來進行實現。app
層序遍歷:ui
1 /** 2 * 層序遍歷 3 */ 4 public String readLevel() { 5 Queue<Node> queue = new LinkedList<>(); 6 StringBuilder result = new StringBuilder(); 7 queue.offer(this); 8 while (!queue.isEmpty()) { 9 Node curNode = queue.poll(); 10 result.append(curNode.name); 11 if (curNode.leftChild != null) { 12 queue.offer(curNode.leftChild); 13 } 14 if (curNode.rightChild != null) { 15 queue.offer(curNode.rightChild); 16 } 17 } 18 return result.toString(); 19 }
跟其餘遍歷不一樣,層序遍歷須要藉助隊列來進行實現。首先將根節點放到隊列中,而後遍歷循環,依次將左孩子和右孩子放置到隊列中。this
在第二章節中,得到到前序、中序、後序、層序的結果依次以下:spa
1 String pre = "ABDGHCEIF"; //前序遍歷 2 String mid = "GDHBAEICF"; //中序遍歷 3 String end = "GHDBIEFCA"; //後序遍歷 4 String level = "ABCDEFGHI"; //層序遍歷
那可否經過上面的字符串還原出二叉樹的的形狀呢?這個分狀況討論code
前序+中序:
思路:經過前序得到根節點的位置,利用根節點將中序序列分爲左子樹和右子樹,而後不斷的遞歸劃分便可。
代碼:
1 /** 2 * 根據前序和中序排序表獲取樹 3 */ 4 private static Node buildTreeByPreMid(char[] pre, int preBegin, int preEnd, char[] mid, int midBegin, int midEnd) { 5 Node root = new Node(); 6 root.setName(pre[preBegin] + ""); 7 8 int midRootLoc = 0; 9 for (int i = midBegin; i <= midEnd; i++) { 10 if (mid[i] == pre[preBegin]) { 11 midRootLoc = i; 12 break; 13 } 14 } 15 16 //遞歸獲得左子樹 17 if (preBegin + (midRootLoc - midBegin) >= preBegin + 1 && (midRootLoc - 1) >= midBegin) { 18 Node leftChild = buildTreeByPreMid(pre, preBegin + 1, preBegin + (midRootLoc - midBegin), 19 mid, midBegin, midRootLoc - 1); 20 root.leftChild = leftChild; 21 } 22 23 //遞歸獲得右子樹 24 if (preEnd >= (preEnd - (midEnd - midRootLoc) + 1) && (midEnd >= midRootLoc + 1)) { 25 Node rightChild = buildTreeByPreMid(pre, preEnd - (midEnd - midRootLoc) + 1, preEnd, 26 mid, midRootLoc + 1, midEnd); 27 root.rightChild = rightChild; 28 } 29 30 return root; 31 }
後序+中序:
思路:經過後序獲取根節點的位置,而後在中序中劃分左子樹和右子樹,而後遞歸劃分便可。
代碼:
1 /** 2 * 根據後序和中序遍歷還原樹 3 */ 4 private static Node buildTreeByMidEnd(char[] mid, int midBegin, int midEnd, char[] end, int endBegin, int endEnd) { 5 Node root = new Node(); 6 root.setName(end[endEnd] + ""); 7 int midRootLoc = 0; 8 for (int i = midEnd; i >= midBegin; i--) { 9 if (mid[i] == end[endEnd]) { 10 midRootLoc = i; 11 break; 12 } 13 } 14 15 //還原左子樹 16 if (midRootLoc - 1 >= midBegin && (endBegin + (midRootLoc - midBegin) - 1 >= endBegin)) { 17 Node leftChild = buildTreeByMidEnd(mid, midBegin, midRootLoc - 1, end, endBegin, endBegin + (midRootLoc - midBegin) - 1); 18 root.leftChild = leftChild; 19 } 20 21 //還原右子樹 22 if (midEnd >= midRootLoc + 1 && (endEnd - 1 >= endEnd - (midEnd - midRootLoc))) { 23 Node rightChild = buildTreeByMidEnd(mid, midRootLoc + 1, midEnd, end, endEnd - (midEnd - midRootLoc), endEnd - 1); 24 root.rightChild = rightChild; 25 } 26 27 return root; 28 }
層序+中序:
思路:根據層序遍歷獲取根節點的位置,而後將中序劃分爲左子樹和右子樹,而後根據劃分出的左子樹和右子樹分別在層序遍歷中獲取其對應的層序順序,而後遞歸調用劃分便可。
代碼以下:
1 /** 2 * 根據層序遍歷和中序遍歷獲得結果 3 * @return 4 */ 5 private static Node buildTreeByMidLevel(char[] mid, char[] level, int midBegin, int midEnd) { 6 Node root = new Node(level[0] + ""); 7 8 int midLoc = -1; 9 for (int i = midBegin; i <= midEnd; i++) { 10 if (mid[i] == level[0]) { 11 midLoc = i; 12 break; 13 } 14 } 15 16 if (level.length >= 2) { 17 if (isLeft(mid, level[0], level[1])) { 18 Node left = buildTreeByMidLevel(mid, getLevelStr(mid, midBegin, midLoc - 1, level), midBegin, midLoc - 1); 19 root.leftChild = left; 20 if (level.length >= 3 && !isLeft(mid, level[0], level[2])) { 21 Node right = buildTreeByMidLevel(mid, getLevelStr(mid, midLoc + 1, midEnd, level), midLoc + 1, midEnd); 22 root.rightChild = right; 23 } 24 } else { 25 Node right = buildTreeByMidLevel(mid, getLevelStr(mid, midLoc + 1, midEnd, level), midLoc + 1, midEnd); 26 root.rightChild = right; 27 } 28 } 29 return root; 30 } 31 32 /** 33 * 將中序序列中midBegin與MidEnd的字符依次從level中提取出來,保持level中的字符順序不變 34 */ 35 private static char[] getLevelStr(char[] mid, int midBegin, int midEnd, char[] level) { 36 char[] result = new char[midEnd - midBegin + 1]; 37 int curLoc = 0; 38 for (int i = 0; i < level.length; i++) { 39 if (contains(mid, level[i], midBegin, midEnd)) { 40 result[curLoc++] = level[i]; 41 } 42 } 43 return result; 44 } 45 46 /** 47 * 若是str字符串的begin和end位置之間(包括begin和end)含有字符target,則返回true。 48 */ 49 private static boolean contains(char[] str, char target, int begin, int end) { 50 for (int i = begin; i <= end; i++) { 51 if (str[i] == target) { 52 return true; 53 } 54 } 55 return false; 56 }
其餘的遍歷組合均不能還原出二叉樹的形狀,由於沒法確認其左右孩子。例如,前序爲AB,後序爲AB,則沒法確認出,B節點是A節點的左孩子仍是右孩子,所以沒法還原。
完整代碼已經上傳至GitHub: