package BinaryTreeSummary; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Stack; /** * http://blog.csdn.net/luckyxiaoqiang/article/details/7518888 輕鬆搞定面試中的二叉樹題目 * http://www.cnblogs.com/Jax/archive/2009/12/28/1633691.html 算法大全(3) 二叉樹 * * TODO: 必定要能熟練地寫出全部問題的遞歸和非遞歸作法! * * 1. 求二叉樹中的節點個數: getNodeNumRec(遞歸),getNodeNum(迭代) * 2. 求二叉樹的深度: getDepthRec(遞歸),getDepth * 3. 前序遍歷,中序遍歷,後序遍歷: preorderTraversalRec, preorderTraversal, inorderTraversalRec, postorderTraversalRec * (https://en.wikipedia.org/wiki/Tree_traversal#Pre-order_2) * 4.分層遍歷二叉樹(按層次從上往下,從左往右): levelTraversal, levelTraversalRec(遞歸解法!) * 5. 將二叉查找樹變爲有序的雙向鏈表: convertBST2DLLRec, convertBST2DLL * 6. 求二叉樹第K層的節點個數:getNodeNumKthLevelRec, getNodeNumKthLevel * 7. 求二叉樹中葉子節點的個數:getNodeNumLeafRec, getNodeNumLeaf * 8. 判斷兩棵二叉樹是否相同的樹:isSameRec, isSame * 9. 判斷二叉樹是否是平衡二叉樹:isAVLRec * 10. 求二叉樹的鏡像(破壞和不破壞原來的樹兩種狀況):mirrorRec, mirrorCopyRec * 10.1 判斷兩個樹是否互相鏡像:isMirrorRec * 11. 求二叉樹中兩個節點的最低公共祖先節點:getLastCommonParent, getLastCommonParentRec, getLastCommonParentRec2 * 12. 求二叉樹中節點的最大距離:getMaxDistanceRec * 13. 由前序遍歷序列和中序遍歷序列重建二叉樹:rebuildBinaryTreeRec * 14.判斷二叉樹是否是徹底二叉樹:isCompleteBinaryTree, isCompleteBinaryTreeRec * */ public class Demo { /* 1 / \ 2 3 / \ \ 4 5 6 */ public static void main(String[] args) { TreeNode r1 = new TreeNode(1); TreeNode r2 = new TreeNode(2); TreeNode r3 = new TreeNode(3); TreeNode r4 = new TreeNode(4); TreeNode r5 = new TreeNode(5); TreeNode r6 = new TreeNode(6); r1.left = r2; r1.right = r3; r2.left = r4; r2.right = r5; r3.right = r6; // System.out.println(getNodeNumRec(r1)); // System.out.println(getNodeNum(r1)); // System.out.println(getDepthRec(r1)); // System.out.println(getDepth(r1)); // preorderTraversalRec(r1); // System.out.println(); // preorderTraversal(r1); // System.out.println(); // inorderTraversalRec(r1); // System.out.println(); // inorderTraversal(r1); // System.out.println(); // postorderTraversalRec(r1); // System.out.println(); // postorderTraversal(r1); // System.out.println(); // levelTraversal(r1); // System.out.println(); // levelTraversalRec(r1); // System.out.println(); // TreeNode tmp = convertBSTRec(r1); // while(true){ // if(tmp == null){ // break; // } // System.out.print(tmp.val + " "); // if(tmp.right == null){ // break; // } // tmp = tmp.right; // } // System.out.println(); // while(true){ // if(tmp == null){ // break; // } // System.out.print(tmp.val + " "); // if(tmp.left == null){ // break; // } // tmp = tmp.left; // } // TreeNode tmp = convertBST2DLL(r1); // while(true){ // if(tmp == null){ // break; // } // System.out.print(tmp.val + " "); // if(tmp.right == null){ // break; // } // tmp = tmp.right; // } // System.out.println(getNodeNumKthLevelRec(r1, 2)); // System.out.println(getNodeNumKthLevel(r1, 2)); // System.out.println(getNodeNumLeafRec(r1)); // System.out.println(getNodeNumLeaf(r1)); // System.out.println(isSame(r1, r1)); // inorderTraversal(r1); // System.out.println(); // mirror(r1); // TreeNode mirrorRoot = mirrorCopy(r1); // inorderTraversal(mirrorRoot); System.out.println(isCompleteBinaryTree(r1)); System.out.println(isCompleteBinaryTreeRec(r1)); } private static class TreeNode { int val; TreeNode left; TreeNode right; public TreeNode(int val) { this.val = val; } } /** * 求二叉樹中的節點個數遞歸解法: O(n) * (1)若是二叉樹爲空,節點個數爲0 * (2)若是二叉樹不爲空,二叉樹節點個數 = 左子樹節點個數 + * 右子樹節點個數 + 1 */ public static int getNodeNumRec(TreeNode root) { if (root == null) { return 0; } else { return getNodeNumRec(root.left) + getNodeNumRec(root.right) + 1; } } /** * 求二叉樹中的節點個數迭代解法O(n):基本思想同LevelOrderTraversal, * 即用一個Queue,在Java裏面能夠用LinkedList來模擬 */ public static int getNodeNum(TreeNode root) { if(root == null){ return 0; } int count = 1; Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.add(root); while(!queue.isEmpty()){ TreeNode cur = queue.remove(); // 從隊頭位置移除 if(cur.left != null){ // 若是有左孩子,加到隊尾 queue.add(cur.left); count++; } if(cur.right != null){ // 若是有右孩子,加到隊尾 queue.add(cur.right); count++; } } return count; } /** * 求二叉樹的深度(高度) 遞歸解法: O(n) * (1)若是二叉樹爲空,二叉樹的深度爲0 * (2)若是二叉樹不爲空,二叉樹的深度 = max(左子樹深度, 右子樹深度) + 1 */ public static int getDepthRec(TreeNode root) { if (root == null) { return 0; } int leftDepth = getDepthRec(root.left); int rightDepth = getDepthRec(root.right); return Math.max(leftDepth, rightDepth) + 1; } /** * 求二叉樹的深度(高度) 迭代解法: O(n) * 基本思想同LevelOrderTraversal,仍是用一個Queue */ public static int getDepth(TreeNode root) { if(root == null){ return 0; } int depth = 0; // 深度 int currentLevelNodes = 1; // 當前Level,node的數量 int nextLevelNodes = 0; // 下一層Level,node的數量 LinkedList<TreeNode> queue = new LinkedList<TreeNode>(); queue.add(root); while( !queue.isEmpty() ){ TreeNode cur = queue.remove(); // 從隊頭位置移除 currentLevelNodes--; // 減小當前Level node的數量 if(cur.left != null){ // 若是有左孩子,加到隊尾 queue.add(cur.left); nextLevelNodes++; // 並增長下一層Level node的數量 } if(cur.right != null){ // 若是有右孩子,加到隊尾 queue.add(cur.right); nextLevelNodes++; } if(currentLevelNodes == 0){ // 說明已經遍歷完當前層的全部節點 depth++; // 增長高度 currentLevelNodes = nextLevelNodes; // 初始化下一層的遍歷 nextLevelNodes = 0; } } return depth; } /** * 前序遍歷,中序遍歷,後序遍歷 前序遍歷遞歸解法: * (1)若是二叉樹爲空,空操做 * (2)若是二叉樹不爲空,訪問根節點,前序遍歷左子樹,前序遍歷右子樹 */ public static void preorderTraversalRec(TreeNode root) { if (root == null) { return; } System.out.print(root.val + " "); preorderTraversalRec(root.left); preorderTraversalRec(root.right); } /** * 前序遍歷迭代解法:用一個輔助stack,老是把右孩子放進棧 * http://www.youtube.com/watch?v=uPTCbdHSFg4 */ public static void preorderTraversal(TreeNode root) { if(root == null){ return; } Stack<TreeNode> stack = new Stack<TreeNode>(); // 輔助stack stack.push(root); while( !stack.isEmpty() ){ TreeNode cur = stack.pop(); // 出棧棧頂元素 System.out.print(cur.val + " "); // 關鍵點:要先壓入右孩子,再壓入左孩子,這樣在出棧時會先打印左孩子再打印右孩子 if(cur.right != null){ stack.push(cur.right); } if(cur.left != null){ stack.push(cur.left); } } } /** * 中序遍歷遞歸解法 * (1)若是二叉樹爲空,空操做。 * (2)若是二叉樹不爲空,中序遍歷左子樹,訪問根節點,中序遍歷右子樹 */ public static void inorderTraversalRec(TreeNode root) { if (root == null) { return; } inorderTraversalRec(root.left); System.out.print(root.val + " "); inorderTraversalRec(root.right); } /** * 中序遍歷迭代解法 ,用棧先把根節點的全部左孩子都添加到棧內, * 而後輸出棧頂元素,再處理棧頂元素的右子樹 * http://www.youtube.com/watch?v=50v1sJkjxoc * * 還有一種方法能不用遞歸和棧,基於線索二叉樹的方法,較麻煩之後補上 * http://www.geeksforgeeks.org/inorder-tree-traversal-without-recursion-and-without-stack/ */ public static void inorderTraversal(TreeNode root){ if(root == null){ return; } Stack<TreeNode> stack = new Stack<TreeNode>(); TreeNode cur = root; while( true ){ while(cur != null){ // 先添加一個非空節點全部的左孩子到棧 stack.push(cur); cur = cur.left; } if(stack.isEmpty()){ break; } // 由於此時已經沒有左孩子了,因此輸出棧頂元素 cur = stack.pop(); System.out.print(cur.val + " "); cur = cur.right; // 準備處理右子樹 } } /** * 後序遍歷遞歸解法 * (1)若是二叉樹爲空,空操做 * (2)若是二叉樹不爲空,後序遍歷左子樹,後序遍歷右子樹,訪問根節點 */ public static void postorderTraversalRec(TreeNode root) { if (root == null) { return; } postorderTraversalRec(root.left); postorderTraversalRec(root.right); System.out.print(root.val + " "); } /** * 後序遍歷迭代解法 * http://www.youtube.com/watch?v=hv-mJUs5mvU * */ public static void postorderTraversal(TreeNode root) { if (root == null) { return; } Stack<TreeNode> s = new Stack<TreeNode>(); // 第一個stack用於添加node和它的左右孩子 Stack<TreeNode> output = new Stack<TreeNode>();// 第二個stack用於翻轉第一個stack輸出 s.push(root); while( !s.isEmpty() ){ // 確保全部元素都被翻轉轉移到第二個stack TreeNode cur = s.pop(); // 把棧頂元素添加到第二個stack output.push(cur); if(cur.left != null){ // 把棧頂元素的左孩子和右孩子分別添加入第一個stack s.push(cur.left); } if(cur.right != null){ s.push(cur.right); } } while( !output.isEmpty() ){ // 遍歷輸出第二個stack,即爲後序遍歷 System.out.print(output.pop().val + " "); } } /** * 分層遍歷二叉樹(按層次從上往下,從左往右)迭代 * 至關於廣度優先搜索,使用隊列實現。隊列初始化,將根節點壓入隊列。當隊列不爲空,進行以下操做:彈出一個節點 * ,訪問,若左子節點或右子節點不爲空,將其壓入隊列 */ public static void levelTraversal(TreeNode root) { if (root == null) { return; } LinkedList<TreeNode> queue = new LinkedList<TreeNode>(); queue.push(root); while (!queue.isEmpty()) { TreeNode cur = queue.removeFirst(); System.out.print(cur.val + " "); if (cur.left != null) { queue.add(cur.left); } if (cur.right != null) { queue.add(cur.right); } } } /** * 分層遍歷二叉樹(遞歸) * 不多有人會用遞歸去作level traversal * 基本思想是用一個大的ArrayList,裏面包含了每一層的ArrayList。 * 大的ArrayList的size和level有關係 * * 這是我目前見到的最好的遞歸解法! * http://discuss.leetcode.com/questions/49/binary-tree-level-order-traversal#answer-container-2543 */ public static void levelTraversalRec(TreeNode root) { ArrayList<ArrayList<Integer>> ret = new ArrayList<ArrayList<Integer>>(); dfs(root, 0, ret); System.out.println(ret); } private static void dfs(TreeNode root, int level, ArrayList<ArrayList<Integer>> ret){ if(root == null){ return; } // 添加一個新的ArrayList表示新的一層 if(level >= ret.size()){ ret.add(new ArrayList<Integer>()); } ret.get(level).add(root.val); // 把節點添加到表示那一層的ArrayList裏 dfs(root.left, level+1, ret); // 遞歸處理下一層的左子樹和右子樹 dfs(root.right, level+1, ret); } /** * 將二叉查找樹變爲有序的雙向鏈表 要求不能建立新節點,只調整指針。 * 遞歸解法: * 參考了http://stackoverflow.com/questions/11511898/converting-a-binary-search-tree-to-doubly-linked-list#answer-11530016 * 感受是最清晰的遞歸解法,但要注意遞歸完,root會在鏈表的中間位置,所以要手動 * 把root移到鏈表頭或鏈表尾 */ public static TreeNode convertBST2DLLRec(TreeNode root) { root = convertBST2DLLSubRec(root); // root會在鏈表的中間位置,所以要手動把root移到鏈表頭 while(root.left != null){ root = root.left; } return root; } /** * 遞歸轉換BST爲雙向鏈表(DLL) */ public static TreeNode convertBST2DLLSubRec(TreeNode root){ if(root==null || (root.left==null && root.right==null)){ return root; } TreeNode tmp = null; if(root.left != null){ // 處理左子樹 tmp = convertBST2DLLSubRec(root.left); while(tmp.right != null){ // 尋找最右節點 tmp = tmp.right; } tmp.right = root; // 把左子樹處理後結果和root鏈接 root.left = tmp; } if(root.right != null){ // 處理右子樹 tmp = convertBST2DLLSubRec(root.right); while(tmp.left != null){ // 尋找最左節點 tmp = tmp.left; } tmp.left = root; // 把右子樹處理後結果和root鏈接 root.right = tmp; } return root; } /** * 將二叉查找樹變爲有序的雙向鏈表 迭代解法 // * 相似inorder traversal的作法 */ public static TreeNode convertBST2DLL(TreeNode root) { if(root == null){ return null; } Stack<TreeNode> stack = new Stack<TreeNode>(); TreeNode cur = root; // 指向當前處理節點 TreeNode old = null; // 指向前一個處理的節點 TreeNode head = null; // 鏈表頭 while( true ){ while(cur != null){ // 先添加一個非空節點全部的左孩子到棧 stack.push(cur); cur = cur.left; } if(stack.isEmpty()){ break; } // 由於此時已經沒有左孩子了,因此輸出棧頂元素 cur = stack.pop(); if(old != null){ old.right = cur; } if(head == null){ // /第一個節點爲雙向鏈表頭節點 head = cur; } old = cur; // 更新old cur = cur.right; // 準備處理右子樹 } return head; } /** * 求二叉樹第K層的節點個數 遞歸解法: * (1)若是二叉樹爲空或者k<1返回0 * (2)若是二叉樹不爲空而且k==1,返回1 * (3)若是二叉樹不爲空且k>1,返回root左子樹中k-1層的節點個數與root右子樹k-1層節點個數之和 * * 求以root爲根的k層節點數目 等價於 求以root左孩子爲根的k-1層(由於少了root那一層)節點數目 加上 * 以root右孩子爲根的k-1層(由於少了root那一層)節點數目 * * 因此遇到樹,先把它拆成左子樹和右子樹,把問題降解 * */ public static int getNodeNumKthLevelRec(TreeNode root, int k) { if (root == null || k < 1) { return 0; } if (k == 1) { return 1; } int numLeft = getNodeNumKthLevelRec(root.left, k - 1); // 求root左子樹的k-1層節點數 int numRight = getNodeNumKthLevelRec(root.right, k - 1); // 求root右子樹的k-1層節點數 return numLeft + numRight; } /** * 求二叉樹第K層的節點個數 迭代解法: * 同getDepth的迭代解法 */ public static int getNodeNumKthLevel(TreeNode root, int k){ if(root == null){ return 0; } Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.add(root); int i = 1; int currentLevelNodes = 1; // 當前Level,node的數量 int nextLevelNodes = 0; // 下一層Level,node的數量 while( !queue.isEmpty() && i<k){ TreeNode cur = queue.remove(); // 從隊頭位置移除 currentLevelNodes--; // 減小當前Level node的數量 if(cur.left != null){ // 若是有左孩子,加到隊尾 queue.add(cur.left); nextLevelNodes++; // 並增長下一層Level node的數量 } if(cur.right != null){ // 若是有右孩子,加到隊尾 queue.add(cur.right); nextLevelNodes++; } if(currentLevelNodes == 0){ // 說明已經遍歷完當前層的全部節點 currentLevelNodes = nextLevelNodes; // 初始化下一層的遍歷 nextLevelNodes = 0; i++; // 進入到下一層 } } return currentLevelNodes; } /** * 求二叉樹中葉子節點的個數(遞歸) */ public static int getNodeNumLeafRec(TreeNode root) { // 當root不存在,返回空 if (root == null) { return 0; } // 當爲葉子節點時返回1 if (root.left == null && root.right == null) { return 1; } // 把一個樹拆成左子樹和右子樹之和,原理同上一題 return getNodeNumLeafRec(root.left) + getNodeNumLeafRec(root.right); } /** * 求二叉樹中葉子節點的個數(迭代) * 仍是基於Level order traversal */ public static int getNodeNumLeaf(TreeNode root) { if(root == null){ return 0; } Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.add(root); int leafNodes = 0; // 記錄上一個Level,node的數量 while( !queue.isEmpty() ){ TreeNode cur = queue.remove(); // 從隊頭位置移除 if(cur.left != null){ // 若是有左孩子,加到隊尾 queue.add(cur.left); } if(cur.right != null){ // 若是有右孩子,加到隊尾 queue.add(cur.right); } if(cur.left==null && cur.right==null){ // 葉子節點 leafNodes++; } } return leafNodes; } /** * 判斷兩棵二叉樹是否相同的樹。 * 遞歸解法: * (1)若是兩棵二叉樹都爲空,返回真 * (2)若是兩棵二叉樹一棵爲空,另外一棵不爲空,返回假 * (3)若是兩棵二叉樹都不爲空,若是對應的左子樹和右子樹都同構返回真,其餘返回假 */ public static boolean isSameRec(TreeNode r1, TreeNode r2) { // 若是兩棵二叉樹都爲空,返回真 if (r1 == null && r2 == null) { return true; } // 若是兩棵二叉樹一棵爲空,另外一棵不爲空,返回假 else if (r1 == null || r2 == null) { return false; } if(r1.val != r2.val){ return false; } boolean leftRes = isSameRec(r1.left, r2.left); // 比較對應左子樹 boolean rightRes = isSameRec(r1.right, r2.right); // 比較對應右子樹 return leftRes && rightRes; } /** * 判斷兩棵二叉樹是否相同的樹(迭代) * 遍歷一遍便可,這裏用preorder */ public static boolean isSame(TreeNode r1, TreeNode r2) { // 若是兩個樹都是空樹,則返回true if(r1==null && r2==null){ return true; } // 若是有一棵樹是空樹,另外一顆不是,則返回false if(r1==null || r2==null){ return false; } Stack<TreeNode> s1 = new Stack<TreeNode>(); Stack<TreeNode> s2 = new Stack<TreeNode>(); s1.push(r1); s2.push(r2); while(!s1.isEmpty() && !s2.isEmpty()){ TreeNode n1 = s1.pop(); TreeNode n2 = s2.pop(); if(n1==null && n2==null){ continue; }else if(n1!=null && n2!=null && n1.val==n2.val){ s1.push(n1.right); s1.push(n1.left); s2.push(n2.right); s2.push(n2.left); }else{ return false; } } return true; } /** * 判斷二叉樹是否是平衡二叉樹 遞歸解法: * (1)若是二叉樹爲空,返回真 * (2)若是二叉樹不爲空,若是左子樹和右子樹都是AVL樹而且左子樹和右子樹高度相差不大於1,返回真,其餘返回假 */ public static boolean isAVLRec(TreeNode root) { if(root == null){ // 若是二叉樹爲空,返回真 return true; } // 若是左子樹和右子樹高度相差大於1,則非平衡二叉樹, getDepthRec()是前面實現過的求樹高度的方法 if(Math.abs(getDepthRec(root.left) - getDepthRec(root.right)) > 1){ return false; } // 遞歸判斷左子樹和右子樹是否爲平衡二叉樹 return isAVLRec(root.left) && isAVLRec(root.right); } /** * 求二叉樹的鏡像 遞歸解法: * (1)若是二叉樹爲空,返回空 * (2)若是二叉樹不爲空,求左子樹和右子樹的鏡像,而後交換左子樹和右子樹 */ // 1. 破壞原來的樹,把原來的樹改爲其鏡像 public static TreeNode mirrorRec(TreeNode root) { if (root == null) { return null; } TreeNode left = mirrorRec(root.left); TreeNode right = mirrorRec(root.right); root.left = right; root.right = left; return root; } // 2. 不能破壞原來的樹,返回一個新的鏡像樹 public static TreeNode mirrorCopyRec(TreeNode root){ if(root == null){ return null; } TreeNode newNode = new TreeNode(root.val); newNode.left = mirrorCopyRec(root.right); newNode.right = mirrorCopyRec(root.left); return newNode; } // 3. 判斷兩個樹是否互相鏡像 public static boolean isMirrorRec(TreeNode r1, TreeNode r2){ // 若是兩個樹都是空樹,則返回true if(r1==null && r2==null){ return true; } // 若是有一棵樹是空樹,另外一顆不是,則返回false if(r1==null || r2==null){ return false; } // 若是兩個樹都非空樹,則先比較根節點 if(r1.val != r2.val){ return false; } // 遞歸比較r1的左子樹的鏡像是否是r2右子樹 和 // r1的右子樹的鏡像是否是r2左子樹 return isMirrorRec(r1.left, r2.right) && isMirrorRec(r1.right, r2.left); } // 1. 破壞原來的樹,把原來的樹改爲其鏡像 public static void mirror(TreeNode root) { if(root == null){ return; } Stack<TreeNode> stack = new Stack<TreeNode>(); stack.push(root); while( !stack.isEmpty() ){ TreeNode cur = stack.pop(); // 交換左右孩子 TreeNode tmp = cur.right; cur.right = cur.left; cur.left = tmp; if(cur.right != null){ stack.push(cur.right); } if(cur.left != null){ stack.push(cur.left); } } } // 2. 不能破壞原來的樹,返回一個新的鏡像樹 public static TreeNode mirrorCopy(TreeNode root){ if(root == null){ return null; } Stack<TreeNode> stack = new Stack<TreeNode>(); Stack<TreeNode> newStack = new Stack<TreeNode>(); stack.push(root); TreeNode newRoot = new TreeNode(root.val); newStack.push(newRoot); while( !stack.isEmpty() ){ TreeNode cur = stack.pop(); TreeNode newCur = newStack.pop(); if(cur.right != null){ stack.push(cur.right); newCur.left = new TreeNode(cur.right.val); newStack.push(newCur.left); } if(cur.left != null){ stack.push(cur.left); newCur.right = new TreeNode(cur.left.val); newStack.push(newCur.right); } } return newRoot; } /** * 求二叉樹中兩個節點的最低公共祖先節點 * 遞歸解法: * (1)若是兩個節點分別在根節點的左子樹和右子樹,則返回根節點 * (2)若是兩個節點都在左子樹,則遞歸處理左子樹;若是兩個節點都在右子樹,則遞歸處理右子樹 */ public static TreeNode getLastCommonParentRec(TreeNode root, TreeNode n1, TreeNode n2) { if (findNodeRec(root.left, n1)) { // 若是n1在樹的左子樹 if (findNodeRec(root.right, n2)) { // 若是n2在樹的右子樹 return root; // 返回根節點 } else { // 若是n2也在樹的左子樹 return getLastCommonParentRec(root.left, n1, n2); // 遞歸處理 } } else { // 若是n1在樹的右子樹 if (findNodeRec(root.left, n2)) { // 若是n2在左子樹 return root; } else { // 若是n2在右子樹 return getLastCommonParentRec(root.right, n1, n2); // 遞歸處理 } } } // 幫助方法,遞歸判斷一個點是否在樹裏 private static boolean findNodeRec(TreeNode root, TreeNode node) { if (root == null || node == null) { return false; } if (root == node) { return true; } // 先嚐試在左子樹中查找 boolean found = findNodeRec(root.left, node); if (!found) { // 若是查找不到,再在右子樹中查找 found = findNodeRec(root.right, node); } return found; } // 求二叉樹中兩個節點的最低公共祖先節點 (更加簡潔版的遞歸) public static TreeNode getLastCommonParentRec2(TreeNode root, TreeNode n1, TreeNode n2) { if(root == null){ return null; } // 若是有一個match,則說明當前node就是要找的最低公共祖先 if(root.equals(n1) || root.equals(n2)){ return root; } TreeNode commonInLeft = getLastCommonParentRec2(root.left, n1, n2); TreeNode commonInRight = getLastCommonParentRec2(root.right, n1, n2); // 若是一個左子樹找到,一個在右子樹找到,則說明root是惟一可能的最低公共祖先 if(commonInLeft!=null && commonInRight!=null){ return root; } // 其餘狀況是要否則在左子樹要否則在右子樹 if(commonInLeft != null){ return commonInLeft; } return commonInRight; } /** * 非遞歸解法: * 先求從根節點到兩個節點的路徑,而後再比較對應路徑的節點就行,最後一個相同的節點也就是他們在二叉樹中的最低公共祖先節點 */ public static TreeNode getLastCommonParent(TreeNode root, TreeNode n1, TreeNode n2) { if (root == null || n1 == null || n2 == null) { return null; } ArrayList<TreeNode> p1 = new ArrayList<TreeNode>(); boolean res1 = getNodePath(root, n1, p1); ArrayList<TreeNode> p2 = new ArrayList<TreeNode>(); boolean res2 = getNodePath(root, n2, p2); if (!res1 || !res2) { return null; } TreeNode last = null; Iterator<TreeNode> iter1 = p1.iterator(); Iterator<TreeNode> iter2 = p2.iterator(); while (iter1.hasNext() && iter2.hasNext()) { TreeNode tmp1 = iter1.next(); TreeNode tmp2 = iter2.next(); if (tmp1 == tmp2) { last = tmp1; } else { // 直到遇到非公共節點 break; } } return last; } // 把從根節點到node路徑上全部的點都添加到path中 private static boolean getNodePath(TreeNode root, TreeNode node, ArrayList<TreeNode> path) { if (root == null) { return false; } path.add(root); // 把這個節點加到路徑中 if (root == node) { return true; } boolean found = false; found = getNodePath(root.left, node, path); // 先在左子樹中找 if (!found) { // 若是沒找到,再在右子樹找 found = getNodePath(root.right, node, path); } if (!found) { // 若是實在沒找到證實這個節點不在路徑中,說明剛纔添加進去的不是路徑上的節點,刪掉! path.remove(root); } return found; } /** * 求二叉樹中節點的最大距離 即二叉樹中相距最遠的兩個節點之間的距離。 (distance / diameter) * 遞歸解法: * (1)若是二叉樹爲空,返回0,同時記錄左子樹和右子樹的深度,都爲0 * (2)若是二叉樹不爲空,最大距離要麼是左子樹中的最大距離,要麼是右子樹中的最大距離, * 要麼是左子樹節點中到根節點的最大距離+右子樹節點中到根節點的最大距離, * 同時記錄左子樹和右子樹節點中到根節點的最大距離。 * * http://www.cnblogs.com/miloyip/archive/2010/02/25/1673114.html * * 計算一個二叉樹的最大距離有兩個狀況: 狀況A: 路徑通過左子樹的最深節點,經過根節點,再到右子樹的最深節點。 狀況B: 路徑不穿過根節點,而是左子樹或右子樹的最大距離路徑,取其大者。 只須要計算這兩個狀況的路徑距離,並取其大者,就是該二叉樹的最大距離 */ public static Result getMaxDistanceRec(TreeNode root){ if(root == null){ Result empty = new Result(0, -1); // 目的是讓調用方 +1 後,把當前的不存在的 (NULL) 子樹當成最大深度爲 0 return empty; } // 計算出左右子樹分別最大距離 Result lmd = getMaxDistanceRec(root.left); Result rmd = getMaxDistanceRec(root.right); Result res = new Result(); res.maxDepth = Math.max(lmd.maxDepth, rmd.maxDepth) + 1; // 當前最大深度 // 取狀況A和狀況B中較大值 res.maxDistance = Math.max( lmd.maxDepth+rmd.maxDepth, Math.max(lmd.maxDistance, rmd.maxDistance) ); return res; } private static class Result{ int maxDistance; int maxDepth; public Result() { } public Result(int maxDistance, int maxDepth) { this.maxDistance = maxDistance; this.maxDepth = maxDepth; } } /** * 13. 由前序遍歷序列和中序遍歷序列重建二叉樹(遞歸) * 感受這篇是講的最爲清晰的: * http://crackinterviewtoday.wordpress.com/2010/03/15/rebuild-a-binary-tree-from-inorder-and-preorder-traversals/ * 文中還提到一種避免開額外空間的方法,等下次補上 */ public static TreeNode rebuildBinaryTreeRec(List<Integer> preOrder, List<Integer> inOrder){ TreeNode root = null; List<Integer> leftPreOrder; List<Integer> rightPreOrder; List<Integer> leftInorder; List<Integer> rightInorder; int inorderPos; int preorderPos; if ((preOrder.size() != 0) && (inOrder.size() != 0)) { // 把preorder的第一個元素做爲root root = new TreeNode(preOrder.get(0)); // Based upon the current node data seperate the traversals into leftPreorder, rightPreorder, // leftInorder, rightInorder lists // 由於知道root節點了,因此根據root節點位置,把preorder,inorder分別劃分爲 root左側 和 右側 的兩個子區間 inorderPos = inOrder.indexOf(preOrder.get(0)); // inorder序列的分割點 leftInorder = inOrder.subList(0, inorderPos); rightInorder = inOrder.subList(inorderPos + 1, inOrder.size()); preorderPos = leftInorder.size(); // preorder序列的分割點 leftPreOrder = preOrder.subList(1, preorderPos + 1); rightPreOrder = preOrder.subList(preorderPos + 1, preOrder.size()); root.left = rebuildBinaryTreeRec(leftPreOrder, leftInorder); // root的左子樹就是preorder和inorder的左側區間而造成的樹 root.right = rebuildBinaryTreeRec(rightPreOrder, rightInorder); // root的右子樹就是preorder和inorder的右側區間而造成的樹 } return root; } /** 14. 判斷二叉樹是否是徹底二叉樹(迭代) 若設二叉樹的深度爲h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數, 第 h 層全部的結點都連續集中在最左邊,這就是徹底二叉樹。 有以下算法,按層次(從上到下,從左到右)遍歷二叉樹,當遇到一個節點的左子樹爲空時, 則該節點右子樹必須爲空,且後面遍歷的節點左右子樹都必須爲空,不然不是徹底二叉樹。 */ public static boolean isCompleteBinaryTree(TreeNode root){ if(root == null){ return false; } Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.add(root); boolean mustHaveNoChild = false; boolean result = true; while( !queue.isEmpty() ){ TreeNode cur = queue.remove(); if(mustHaveNoChild){ // 已經出現了有空子樹的節點了,後面出現的必須爲葉節點(左右子樹都爲空) if(cur.left!=null || cur.right!=null){ result = false; break; } } else { if(cur.left!=null && cur.right!=null){ // 若是左子樹和右子樹都非空,則繼續遍歷 queue.add(cur.left); queue.add(cur.right); }else if(cur.left!=null && cur.right==null){ // 若是左子樹非空但右子樹爲空,說明已經出現空節點,以後必須都爲空子樹 mustHaveNoChild = true; queue.add(cur.left); }else if(cur.left==null && cur.right!=null){ // 若是左子樹爲空但右子樹非空,說明這棵樹已經不是徹底二叉徹底樹! result = false; break; }else{ // 若是左右子樹都爲空,則後面的必須也都爲空子樹 mustHaveNoChild = true; } } } return result; } /** * 14. 判斷二叉樹是否是徹底二叉樹(遞歸) * http://stackoverflow.com/questions/1442674/how-to-determine-whether-a-binary-tree-is-complete * */ public static boolean isCompleteBinaryTreeRec(TreeNode root){ // Pair notComplete = new Pair(-1, false); // return !isCompleteBinaryTreeSubRec(root).equalsTo(notComplete); return isCompleteBinaryTreeSubRec(root).height != -1; } // 遞歸判斷是否滿樹(完美) public static boolean isPerfectBinaryTreeRec(TreeNode root){ return isCompleteBinaryTreeSubRec(root).isFull; } // 遞歸,要建立一個Pair class來保存樹的高度和是否已滿的信息 public static Pair isCompleteBinaryTreeSubRec(TreeNode root){ if(root == null){ return new Pair(0, true); } Pair left = isCompleteBinaryTreeSubRec(root.left); Pair right = isCompleteBinaryTreeSubRec(root.right); // 左樹滿節點,並且左右樹相同高度,則是惟一可能造成滿樹(若右樹也是滿節點)的狀況 if(left.isFull && left.height==right.height){ return new Pair(1+left.height, right.isFull); } // 左樹非滿,但右樹是滿節點,且左樹高度比右樹高一 // 注意到若是其左樹爲非徹底樹,則它的高度已經被設置成-1, // 所以不可能知足第二個條件! if(right.isFull && left.height==right.height+1){ return new Pair(1+left.height, false); } // 其餘狀況都是非徹底樹,直接設置高度爲-1 return new Pair(-1, false); } private static class Pair{ int height; // 樹的高度 boolean isFull; // 是不是個滿樹 public Pair(int height, boolean isFull) { this.height = height; this.isFull = isFull; } public boolean equalsTo(Pair obj){ return this.height==obj.height && this.isFull==obj.isFull; } } }
一,問題描述html
在控制檯上輸入一組數據,請按照輸入的數據的格式來構造一棵二叉樹,並打印出二叉樹的高度。java
輸入的數據格式以下:node
第一行爲一個整數N(實際上是二叉樹中邊的數目),表示接下來一共有N行輸入,每行輸入有兩個數,左邊的數表示父結點,右邊的數表示父結點的孩子結點。示例以下:面試
6算法
0 1wordpress
0 2post
1 3ui
2 4this
2 5spa
4 6
從上面的輸入能夠看出:①根結點0 的左孩子爲1,右孩子爲2 。②結點1 只有一個孩子,即左孩子3
二,問題分析
問題的關鍵是根據上面的輸入數據 構造一棵二叉樹。
首先用一個Map<Integer, List<Integer>>保存上面的輸入的數據。其中Key爲父結點,Value爲父結點的孩子結點。對於二叉樹而言,父結點的孩子結點最多隻有2個,故List長度最大爲2.
而後,根據Map來構造二叉樹便可。
對於Map中的每個Entry,Entry的Key爲父結點,找到父結點在樹中的位置(findNode方法)。
Entry的Value爲父結點的左右孩子,遍歷Value,構造孩子結點。已知了父結點在樹中的位置,又構造了孩子結點,只須要將父結點的左右指針指向左右孩子便可。
三,代碼實現
import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Scanner; import java.util.Set; //按要求構造二叉樹,假設頭結點爲0 public class BuildTree { public static void main(String[] args) { Scanner sc = new Scanner(System.in); //<parent, childList> Map<Integer, List<Integer>> datas = new LinkedHashMap<Integer, List<Integer>>(); while(sc.hasNextInt()) { int edges = sc.nextInt(); //將樹的信息保存到hashmap<parent, childList>中 for(int i = 0; i < edges; i++) { int parent = sc.nextInt(); int child = sc.nextInt(); if(!datas.containsKey(parent)){ List<Integer> childs = new ArrayList<Integer>(); childs.add(child); datas.put(parent, childs); }else{ List<Integer> childs = datas.get(parent); childs.add(child); } }//end for BinaryNode root = buildTree(datas); int height = height(root); System.out.println(height); } sc.close(); } //求二叉樹的高度 private static int height(BinaryNode root){ if(root == null) return 0; int leftHeight = height(root.left); int rightHeight = height(root.right); return 1 + (leftHeight > rightHeight ? leftHeight : rightHeight); } //構造二叉樹 private static BinaryNode buildTree(Map<Integer, List<Integer>> datas){ BinaryNode root = null; BinaryNode current = null; List<Integer> childs = null; Set<Entry<Integer, List<Integer>>> entrySet = datas.entrySet(); for (Entry<Integer, List<Integer>> entry : entrySet) { int parent = entry.getKey(); current = findNode(parent, root); childs = datas.get(parent); if(current == null){//說明parent是根結點 root = new BinaryNode(parent); createNode(root, childs); }else{ createNode(current, childs); } } return root; } //建立parent結點的左右孩子結點 private static void createNode(BinaryNode parent, List<Integer> childs){ if(childs.size() == 2){//說明有左右孩子 BinaryNode leftChild = new BinaryNode(childs.get(0)); BinaryNode rightChild = new BinaryNode(childs.get(1)); parent.left = leftChild; parent.right = rightChild; } if(childs.size() == 1){//說明只有左孩子 BinaryNode leftChild = new BinaryNode(childs.get(0)); parent.left = leftChild; } } //查找樹根爲root的二叉樹中 值爲 nodeVal 的結點 private static BinaryNode findNode(int nodeVal, BinaryNode root){ //先序遞歸遍歷查找 值爲 nodeVal的結點 BinaryNode target = null; if(root == null) return null; if(root.val == nodeVal) return root; target = findNode(nodeVal, root.left);//先在左子樹中查找 if(target == null) target = findNode(nodeVal, root.right);//左子樹中未找到,則在右子樹中查找 return target; } private static class BinaryNode{ int val; BinaryNode left; BinaryNode right; public BinaryNode(int val){ this.val = val; left = right = null; } } }
複雜度分析:因爲 當構造父結點的左右孩子時,須要先查找父結點在二叉樹中的位置,這個查找是用「先序遍歷的思路」實現的。
故構造樹的時間複雜度爲O(1+2+3+……+N)=O(N^2)。有點大。