樹是數據結構中很重要的一部分,也是各大公司面試常考部分。
繼樹的各種遍歷算法之后,今天又整理一下樹的常見算法操作。
本文包括:
1.求節點的最近公共祖先
2.樹的序列化與反序列化
3.已知先序遍歷和中序遍歷構造二叉樹
4.已知中序遍歷和后序遍歷構造二叉樹
1.求節點最近的公共祖先
此題不同的要求有不同的解法
如果已知樹中的每一個結點有指向父節點的指針:
思路:從給定節點遍歷到根節點,當父節點相等時返回。
解法1
private ArrayList<TreeNode> getPath2Root(TreeNode node) { ArrayList<TreeNode> list = new ArrayList<TreeNode>(); while (node != null) { list.add(node); node = node.parent; } return list; } public TreeNode lowestCommonAncestor(TreeNode node1, TreeNode node2) { ArrayList<TreeNode> list1 = getPath2Root(node1); ArrayList<TreeNode> list2 = getPath2Root(node2); int i, j; for (i = list1.size() - 1, j = list2.size() - 1; i >= 0 && j >= 0; i--, j--) { if (list1.get(i) != list2.get(j)) { return list1.get(i).parent; } } return list1.get(i+1); }
解法2:
private TreeNode getRoot(node) { while (node.parent != null) { node = node.parent; } return node; } private TreeNode getAncestor(TreeNode root, TreeNode node1, TreeNode node2) { if (root == null || root == node1 || root == node2) { return root; } TreeNode left = getAncestor(root.left, node1, node2); TreeNode right = getAncestor(root.right, node1, node2); if (left != null && right != null) { return root; } if (left != null) { return left; } if (right != null) { return right; } return null; } public TreeNode lowestCommonAncestor(TreeNode node1, TreeNode node2) { if (node1 == null || node2 == null) { return null; } TreeNode root = getRoot(node1); return getAncestor(root, node1, node2); }
如果樹中的節點不帶有指向父節點的指針:
思路利用先序遍歷和中序遍歷的特點,利用先序遍歷分辨根節點,根據根節點和給定節點AB的位置關系來確定公共祖先,根據中序遍歷結果,兩個節點中間的節點即為所求祖先,若中間沒有節點,則中序遍歷中下標較小的即為祖先。
TreeNode node =null; public TreeNode lowestCommonAncestor(TreeNode root, TreeNode A, TreeNode B) { // write your code here TreeNode result =null; ArrayList<TreeNode> inorder = new ArrayList<TreeNode>(); ArrayList<TreeNode> preorder = new ArrayList<TreeNode>(); inorderto(root,inorder); preorderto(root,preorder); //得到AB節點在中序遍歷中的下標 int aValue = inorder.indexOf(A); int bValue = inorder.indexOf(B); int lastindex = 0 ; for(int i=0;i<preorder.size();){ int rootValue = inorder.indexOf(preorder.get(i)); if((aValue<=rootValue && bValue>=rootValue)||(aValue>=rootValue && bValue<=rootValue)){ result = preorder.get(i); break; } else i++; } return result; } public void inorderto(TreeNode root,ArrayList<TreeNode> list){ if(root==null) return; else{ inorderto(root.left,list); list.add(root); inorderto(root.right,list); } } public void preorderto(TreeNode root,ArrayList<TreeNode> list){ if(root==null) return; else{ list.add(root); preorderto(root.left,list); preorderto(root.right,list); } }
2.樹的序列化與反序列化
將樹的節點存入文件,再從文件讀出構造出樹稱為樹的序列化與反序列化。
思路:利用先序遍歷(其他遍歷亦可),將樹的節點值存入字符串,若節點為空則存入#,每個節點之間用','隔開用來區分數字。
讀出時亦用先序遍歷的方法。
public String serialize(TreeNode root) { // write your code here String str = ""; Stack<TreeNode> stack = new Stack<TreeNode>(); if(root!=null) stack.push(root); while(!stack.isEmpty()){ TreeNode node = stack.pop(); if(node!=null){ str += node.val + ","; stack.push(node.right); stack.push(node.left); } else str += "#,"; } return str; } int index =0 ; public TreeNode deserialize(String data) { if(data=="") return null; String val = outValue(index,data); index += 1; if(val.equals("#")) return null; else{ TreeNode node = new TreeNode(Integer.parseInt(val)); node.left = deserialize(data); node.right = deserialize(data); return node; } } public String outValue(int index,String str){ String res = ""; for(int i=0,j=0;i<str.length();i++){ if(str.charAt(i)==',') j++; else if(j==index) res += str.charAt(i); if(j>index) break; } return res; }
3.已知先序遍歷和中序遍歷構造二叉樹
思路:利用先序遍歷區分根節點,利用中序遍歷區分左右子樹
private int findPosition(int[] arr, int start, int end, int key) { int i; for (i = start; i <= end; i++) { if (arr[i] == key) { return i; } } return -1; } private TreeNode myBuildTree(int[] inorder, int instart, int inend, int[] preorder, int prestart, int preend) { if (instart > inend) { return null; } TreeNode root = new TreeNode(preorder[prestart]); int position = findPosition(inorder, instart, inend, preorder[prestart]); root.left = myBuildTree(inorder, instart, position - 1, preorder, prestart + 1, prestart + position - instart); root.right = myBuildTree(inorder, position + 1, inend, preorder, position - inend + preend + 1, preend); return root; } public TreeNode buildTree(int[] preorder, int[] inorder) { if (inorder.length != preorder.length) { return null; } return myBuildTree(inorder, 0, inorder.length - 1, preorder, 0, preorder.length - 1); }
4.已知中序遍歷和后序遍歷構造二叉樹
思路:和前者思路差不多,利用后序遍歷來尋找根節點,利用中序遍歷分辨左右子樹
public TreeNode buildTree(int[] inorder, int[] postorder) { // write your code here if(inorder.length != postorder.length) return null; return myTree(inorder,0,inorder.length-1,postorder,0,postorder.length-1); } public TreeNode myTree(int[]inorder,int instart,int inend, int[] postorder,int poststart,int postend){ if(instart>inend) return null; TreeNode root = new TreeNode(postorder[postend]); int position = findPosition(inorder,instart,inend,postorder[postend]); root.left = myTree(inorder,instart,position-1,postorder,poststart,poststart+position-1-instart); root.right = myTree(inorder,position+1,inend,postorder,position - inend + postend ,postend-1); return root; } private int findPosition(int[] arr, int start, int end, int key) { int i; for (i = start; i <= end; i++) { if (arr[i] == key) { return i; } } return -1; }