数据结构之二叉树
第一篇:数据结构之链表
第二篇:数据结构之栈和队列
在这篇文章里面,我们主要探讨和树相关的话题。
首先,我们来对树进行定义:树是n(n>= 0)个节点的有限集。在任何一个非空树中:(1)有且仅有一个特定的称为“根”的节点;(2)当n>1时,其余节点可分为m(m>0)个互相相关的有限集T1、T2、T3……,其中每一个集合本身又是一棵树,并且称为根的子树。
对于我们这篇文章里讨论的二叉树,它是一种特殊的树形结构,每个节点至多只有两颗子树,并且子树有左右之分,其次序不能随意颠倒。
接下来,我们使用java代码来定义一棵树:
二叉树的定义
1 public class BinNode { 2 private int m_Value; 3 private BinNode m_Left; 4 private BinNode m_Right; 5 public void setValue(int m_Value) { 6 this.m_Value = m_Value; 7 } 8 public int getValue() { 9 return m_Value; 10 } 11 public void setLeft(BinNode m_Left) { 12 this.m_Left = m_Left; 13 } 14 public BinNode getLeft() { 15 return m_Left; 16 } 17 public void setRight(BinNode m_Right) { 18 this.m_Right = m_Right; 19 } 20 public BinNode getRight() { 21 return m_Right; 22 } 23 24 public boolean isLeaf() 25 { 26 return m_Left == null && m_Right == null; 27 } 28 }
下面,开始讨论和二叉树相关的话题
- 构造二叉树,给出一个已排序的整型数组,如何根据它来构造一个BST(二叉搜索树)。
思路:二叉搜索树的特点是左子树的值<=父节点的值<=右子树的值。我们可以从下标0开始遍历数组,然后依次创建树节点,这样下来,对于树的根节点来说,只有右子树,没有左子树,整个树不是平衡二叉树。为了优化这一点,我们可以将数组的中间元素作为根节点,前半部分的值作为树的左子树,后半部分的值作为树的右子树,然后使用递归依次构建。
根据升序数组构造平衡二叉搜索树1 public static BinNode buildTree(int[] arrValue) 2 { 3 if(arrValue == null) return null; 4 BinNode root = new BinNode(); 5 buildTree(arrValue, 0, arrValue.length - 1, root); 6 return root; 7 } 8 9 private static void buildTree(int[] arrValue, int startPos, int endPos, BinNode tree) 10 { 11 if (startPos > endPos) return; 12 13 int midPos = startPos + (endPos - startPos)/2; 14 if (tree == null) 15 { 16 tree = new BinNode(); 17 } 18 tree.setValue(arrValue[midPos]); 19 20 if (midPos - 1 >= startPos) 21 { 22 BinNode left = new BinNode(); 23 tree.setLeft(left); 24 buildTree(arrValue, startPos, midPos - 1, left); 25 } 26 if (endPos >= midPos + 1) 27 { 28 BinNode right = new BinNode(); 29 tree.setRight(right); 30 buildTree(arrValue, midPos + 1, endPos, right); 31 } 32 }
- 树的遍历(前序遍历、中序遍历、后序遍历、层次遍历)
思路:可以采用递归或者非递归的方式进行遍历
前序遍历
前序遍历(递归)1 public static void preOrder(BinNode tree) 2 { 3 if (tree == null) 4 { 5 return; 6 } 7 System.out.println(tree.getValue()); 8 preOrder(tree.getLeft()); 9 preOrder(tree.getRight()); 10 }
前序遍历(非递归)1 public static void preOrder2(BinNode tree) 2 { 3 if (tree == null) return; 4 Stack stack = new Stack(10); 5 BinNode temp = tree; 6 while(temp != null) 7 { 8 System.out.println(temp.getValue()); 9 if (temp.getRight() != null) stack.push(temp.getRight()); 10 temp = temp.getLeft(); 11 } 12 while(stack.get_Count() > 0) 13 { 14 temp = (BinNode)stack.pop(); 15 System.out.println(temp.getValue()); 16 while(temp != null) 17 { 18 if (temp.getRight() != null) 19 { 20 stack.push(temp.getRight()); 21 } 22 temp = temp.getLeft(); 23 } 24 } 25 }
中序遍历
中序遍历(递归)1 public static void inOrder(BinNode tree) 2 { 3 if (tree == null) 4 { 5 return; 6 } 7 inOrder(tree.getLeft()); 8 System.out.println(tree.getValue()); 9 inOrder(tree.getRight()); 10 }
中序遍历(非递归)1 public static void inOrder2(BinNode tree) 2 { 3 if (tree == null) return; 4 Stack stack = new Stack(10); 5 BinNode temp = tree; 6 while(temp != null) 7 { 8 stack.push(temp); 9 temp = temp.getLeft(); 10 } 11 while(stack.get_Count() > 0) 12 { 13 temp = (BinNode)stack.pop(); 14 System.out.println(temp.getValue()); 15 if (temp.getRight() != null) 16 { 17 temp = temp.getRight(); 18 stack.push(temp); 19 while(temp != null) 20 { 21 if (temp.getLeft() != null) 22 { 23 stack.push(temp.getLeft()); 24 } 25 temp = temp.getLeft(); 26 } 27 } 28 } 29 }
后序遍历
后序遍历(递归)1 public static void postOrder(BinNode tree) 2 { 3 if (tree == null) 4 { 5 return; 6 } 7 postOrder(tree.getLeft()); 8 postOrder(tree.getRight()); 9 System.out.println(tree.getValue()); 10 }
后序遍历(非递归)1 public static void postOrder2(BinNode tree) 2 { 3 if (tree == null) return; 4 Stack stack = new Stack(10); 5 BinNode temp = tree; 6 while(temp != null) 7 { 8 stack.push(temp); 9 temp = temp.getLeft(); 10 } 11 12 while(stack.get_Count() > 0) 13 { 14 BinNode lastVisited = temp; 15 temp = (BinNode)stack.pop(); 16 if (temp.getRight() == null || temp.getRight() == lastVisited) 17 { 18 System.out.println(temp.getValue()); 19 } 20 else if (temp.getLeft() == lastVisited) 21 { 22 stack.push(temp); 23 temp = temp.getRight(); 24 stack.push(temp); 25 while(temp != null) 26 { 27 if (temp.getLeft() != null) 28 { 29 stack.push(temp.getLeft()); 30 } 31 temp = temp.getLeft(); 32 } 33 } 34 } 35 }
层次遍历
层次遍历1 public static void printTree(BinNode tree) 2 { 3 if (tree == null) 4 { 5 return; 6 } 7 Queue queue = new Queue(10); 8 queue.enQueue(tree); 9 while(queue.get_Count() > 0) 10 { 11 BinNode temp = (BinNode) queue.deQueue(); 12 System.out.println(temp.getValue()); 13 if (temp.getLeft() != null) queue.enQueue(temp.getLeft()); 14 if (temp.getRight() != null) queue.enQueue(temp.getRight()); 15 } 16 }
- 判断二叉树是否是平衡二叉树
思路:平衡二叉树的特点是左右子树的深度差不能大于1,采用递归的方式。
判断是否是平衡二叉树1 public static boolean isBalance(BinNode tree) 2 { 3 if (tree == null) 4 { 5 return true; 6 } 7 int lDepth = getDepth(tree.getLeft()); 8 int rDepth = getDepth(tree.getRight()); 9 if (lDepth - rDepth > 1 || lDepth - rDepth < -1) 10 { 11 return false; 12 } 13 return isBalance(tree.getRight()) && isBalance(tree.getLeft()); 14 } 15 16 17 public static int getDepth(BinNode tree) 18 { 19 if(tree == null) 20 { 21 return 0; 22 } 23 int lLength = getDepth(tree.getLeft()); 24 int rLength = getDepth(tree.getRight()); 25 26 return lLength > rLength ? lLength + 1 : rLength + 1; 27 }
- 找出任何两个节点的最近的共同父节点
思路:用1表示根节点,2、3表示第二层节点,4、5、6、7表示第三层节点,以此类推,对于编号为n的节点,它的子节点编号为(2*n和2*n+1),通过这种方式重新标记树后,我们分别找到两个节点对应的编号,然后分别进行除2操作,直到找到相同的节点,这个节点就是最近的共同父节点。
寻找最近的共同父节点1 public static BinNode findParentNode2(BinNode tree, BinNode node1, BinNode node2) 2 { 3 if (tree == null) return null; 4 BinNode result = null; 5 int pos1 = 1, pos2 = 1; 6 int i = 1; 7 java.util.HashMap<Integer, BinNode> table = new java.util.HashMap<Integer, BinNode>(); 8 BinNode temp = tree; 9 table.put(i, temp); 10 Queue queue = new Queue(10); 11 queue.enQueue(i); 12 int count = 0; 13 while(queue.get_Count() > 0) 14 { 15 i = Integer.valueOf(queue.deQueue().toString()); 16 if (table.get(i) != null && table.get(i).getValue() == node1.getValue()) 17 { 18 pos1 = i; 19 count++; 20 } 21 if (table.get(i) != null && table.get(i).getValue() == node2.getValue()) 22 { 23 pos2 = i; 24 count++; 25 } 26 if(count == 2) break; 27 28 if(table.get(i) == null) 29 { 30 table.put(2*i, null); 31 table.put(2*i + 1, null); 32 } 33 else 34 { 35 table.put(2*i, table.get(i).getLeft()); 36 table.put(2*i + 1, table.get(i).getRight()); 37 } 38 queue.enQueue(2*i); 39 queue.enQueue(2*i + 1); 40 41 } 42 if (pos1 == pos2) 43 { 44 result = table.get(pos1); 45 } 46 else if(pos1 > pos2) 47 { 48 while(pos1 > pos2) pos1 = pos1/2; 49 } 50 else 51 { 52 while(pos2 > pos1) pos2 = pos2/2; 53 } 54 if(pos1 == pos2 || pos1/2 == pos2 || pos2/2 == pos1) 55 { 56 result = table.get(pos1 > pos2 ? pos2 : pos1); 57 } 58 else 59 { 60 while(pos1 != pos2) 61 { 62 pos1 = pos1/2; 63 pos2 = pos2/2; 64 } 65 } 66 result = table.get(pos1); 67 68 return result; 69 }
如果我们已知树是二叉排序树,那么我们可以利用二叉排序树的特点,分别寻找两个节点,在寻找过程中,将扫描过的节点放入stack中,这样可以获得两个stack,然后分别对两个stack进行pop操作,得到的第一个相同的节点就是最近的共同父节点。
寻找最近的共同父节点(方案2)1 public static BinNode findParentNode(BinNode tree, BinNode node1, BinNode node2) 2 { 3 if (tree == null) 4 { 5 return null; 6 } 7 8 BinNode result = null; 9 Stack stack1 = new Stack(10); 10 Stack stack2 = new Stack(10); 11 stack1.push(tree); 12 stack2.push(tree); 13 BinNode temp = tree; 14 while(temp != null && temp.getValue() != node1.getValue()) 15 { 16 stack1.push(temp); 17 if (temp.getValue()> node1.getValue()) 18 { 19 temp = temp.getLeft(); 20 } 21 else 22 { 23 temp = temp.getRight(); 24 } 25 } 26 temp = tree; 27 while(temp != null && temp.getValue() != node2.getValue()) 28 { 29 stack2.push(temp); 30 if (temp.getValue()> node2.getValue()) 31 { 32 temp = temp.getLeft(); 33 } 34 else 35 { 36 temp = temp.getRight(); 37 } 38 } 39 if (stack1.get_Count() > stack2.get_Count()) 40 { 41 while(stack1.get_Count() > stack2.get_Count()) 42 { 43 stack1.pop(); 44 } 45 } 46 if (stack2.get_Count() > stack1.get_Count()) 47 { 48 while(stack2.get_Count()>stack1.get_Count()) 49 { 50 stack2.pop(); 51 } 52 } 53 while(stack1.get_Count() > 0 && stack2.get_Count() > 0) 54 { 55 if (stack1.peek() == stack2.peek()) 56 { 57 result = (BinNode)stack1.peek(); 58 break; 59 } 60 stack1.pop(); 61 stack2.pop(); 62 } 63 64 return result; 65 }
- 找出树中节点值得和为某一值得所有路径
思路:肯定是需要使用递归来实现,使用stack来存储扫描过的路径。
寻找和为某一值的所有路径1 public static void findSum(BinNode tree, int sum, Stack stack) 2 { 3 if (tree == null) return; 4 stack.push(tree); 5 if (tree.getValue() == sum) 6 { 7 Stack temp = new Stack(stack.get_Count()); 8 while(stack.get_Count()>0) 9 { 10 temp.push(stack.pop()); 11 } 12 StringBuffer sb = new StringBuffer(); 13 while(temp.get_Count() > 0) 14 { 15 sb.append(((BinNode)temp.peek()).getValue()).append("->"); 16 stack.push(temp.pop()); 17 } 18 System.out.println(sb.substring(0, sb.length() - 2)); 19 } 20 else if (tree.getValue() < sum) 21 { 22 if (tree.getLeft() != null) findSum(tree.getLeft(), sum - tree.getValue(), stack); 23 if (tree.getRight() != null) findSum(tree.getRight(), sum - tree.getValue(), stack); 24 } 25 stack.pop(); 26 }
- 判断给定数组是否是二叉搜索树后序遍历的结果
思路:观察二叉搜索树后序遍历的特点,根节点在最后,从下标0开始扫描,直到第一个大于根节点值得下标m,[0,(m-1)]的元素是节点的左子树,[m, n]是节点的右子树,并且右子树中任何节点的值都大于根节点。这样可以使用递归的方式以此判断。
判断给定数组是否是BST后序遍历结果 - 给定一颗BST,另f=(max+min)/2,寻找距离f最近但大于f的节点
思路:对于BST,最小值是最左边的叶子节点,最大值是最右边的叶子节点。
寻找BST中指定节点1 public static BinNode findAvgInBST(BinNode tree) 2 { 3 if (tree == null) return null; 4 BinNode temp = tree; 5 while(temp.getLeft() != null) temp = temp.getLeft(); 6 int minValue = temp.getValue(); 7 temp = tree; 8 while(temp.getRight() != null) temp = temp.getRight(); 9 int maxValue = temp.getValue(); 10 int avgValue = (minValue + maxValue)/2; 11 return findNode(tree, avgValue); 12 } 13 14 private static BinNode findNode(BinNode tree, int value) 15 { 16 if (tree == null) return null; 17 if (tree.getValue() >= value) 18 { 19 if (tree.getLeft() == null) 20 { 21 return tree; 22 } 23 else 24 { 25 BinNode temp = findNode(tree.getLeft(), value); 26 return temp.getValue() < tree.getValue() ? temp:tree; 27 } 28 } 29 else 30 { 31 if (tree.getRight() == null) 32 { 33 return tree; 34 } 35 else 36 { 37 BinNode temp = findNode(tree.getRight(), value); 38 return temp.getValue() < tree.getValue() ? temp:tree; 39 } 40 } 41 }
- 给出一棵树,得到这棵树的镜像
思路:我们可以考虑前序遍历的结果:父节点->左子树->右子树,而镜像之后的结果:父节点->右子树->左子树,我们可以采用递归前序遍历的方式来实现
镜像二叉树1 public static void imageTree(BinNode tree) 2 { 3 if (tree == null) return; 4 BinNode temp = tree.getLeft(); 5 tree.setLeft(tree.getRight()); 6 tree.setRight(temp); 7 if (tree.getLeft() != null) imageTree(tree.getLeft()); 8 if (tree.getRight() != null) imageTree(tree.getRight()); 9 }
我们也可以采用循环+栈的方式来实现
镜像二叉树(方案二)1 public static void imageTrees(BinNode tree) 2 { 3 if (tree == null) return; 4 Stack stack = new Stack(10); 5 BinNode temp = tree; 6 stack.push(temp); 7 while(stack.get_Count() > 0) 8 { 9 temp = (BinNode)stack.pop(); 10 BinNode temp1 = temp.getLeft(); 11 temp.setLeft(temp.getRight()); 12 temp.setRight(temp1); 13 if (temp.getLeft() != null) 14 { 15 stack.push(temp.getLeft()); 16 } 17 if (temp.getRight() != null) 18 { 19 stack.push(temp.getRight()); 20 } 21 } 22 }
- 将BST转换为排序的双向链表
思路:对于BST来说,中序遍历的结果就是元素按照从小到大进行排序的结果
将BST转换成排序的双向链表1 public static DoubleLink convertTreeToList(BinNode tree) 2 { 3 if (tree == null) return null; 4 5 DoubleLink list = new DoubleLink(); 6 list.setValue(null); 7 list.setPre(null); 8 DoubleLink listNode = list; 9 10 BinNode treeNode = tree; 11 Stack stack = new Stack(10); 12 13 while(treeNode != null) 14 { 15 stack.push(treeNode); 16 treeNode = treeNode.getLeft(); 17 } 18 19 while(stack.get_Count() > 0) 20 { 21 treeNode = (BinNode)stack.pop(); 22 DoubleLink tempListNode = new DoubleLink(); 23 tempListNode.setValue(treeNode); 24 listNode.setNext(tempListNode); 25 tempListNode.setPre(listNode); 26 listNode = tempListNode; 27 if (treeNode.getRight() != null) 28 { 29 treeNode = treeNode.getRight(); 30 stack.push(treeNode); 31 while(treeNode != null) 32 { 33 if (treeNode.getLeft() != null) 34 { 35 stack.push(treeNode.getLeft()); 36 } 37 treeNode = treeNode.getLeft(); 38 } 39 } 40 } 41 42 listNode.setNext(null); 43 44 return list; 45 }
其中DoubleLink的定义如下
双向链表的定义1 public class DoubleLink { 2 private Object value; 3 private DoubleLink next; 4 private DoubleLink pre; 5 public void setValue(Object value) { 6 this.value = value; 7 } 8 public Object getValue() { 9 return value; 10 } 11 public void setNext(DoubleLink next) { 12 this.next = next; 13 } 14 public DoubleLink getNext() { 15 return next; 16 } 17 public void setPre(DoubleLink pre) { 18 this.pre = pre; 19 } 20 public DoubleLink getPre() { 21 return pre; 22 } 23 }
- 向BST中插入节点
思路:对BST中插入节点,首先按照遍历BST,找出所插接点的父节点,然后创建新节点
向BST中插入节点1 public static void addValueToBST(BinNode tree, int value) 2 { 3 if (tree == null) 4 { 5 tree = new BinNode(); 6 tree.setValue(value); 7 return; 8 } 9 10 BinNode temp = tree; 11 while(true) 12 { 13 if (temp.getValue() >= value) 14 { 15 if (temp.getLeft() == null) break; 16 temp = temp.getLeft(); 17 } 18 else 19 { 20 if (temp.getRight() == null) break; 21 temp = temp.getRight(); 22 } 23 } 24 25 BinNode node = new BinNode(); 26 node.setValue(value); 27 if (temp.getValue() >= value) 28 { 29 temp.setLeft(node); 30 } 31 else 32 { 33 temp.setRight(node); 34 } 35 }
- 向BST中删除节点
思路:首先是要能够找到需要删除的节点,然后根据节点是否有子节点分情况处理,如果没有子树,那么直接删除该节点(从父节点上解除关联);如果只有左子树或者右子树,那么删除节点后将左子树或者右子树直接挂在父节点下;如果左右子树均存在,那么需要找出左子树的最大值或者右子树的最小值作为父节点的直接子节点,然后将这个节点的左右子树分别设置为删除节点的左右子树。
删除BST中的节点1 public static void delValueFromBST(BinNode tree, int value) 2 { 3 if (tree == null) return; 4 BinNode deleteNode = findNodeInBST(tree, value); 5 BinNode parentNode = findParentNodeInBST(tree, value); 6 if (parentNode == null || deleteNode == null) return; 7 if (deleteNode.isLeaf()) 8 { 9 if (parentNode.getLeft() == deleteNode) parentNode.setLeft(null); 10 else parentNode.setRight(null); 11 } 12 else if (deleteNode.getLeft() == null && deleteNode.getRight() != null) 13 { 14 if (parentNode.getValue() >= deleteNode.getRight().getValue()) parentNode.setLeft(deleteNode.getRight()); 15 else parentNode.setRight(deleteNode.getRight()); 16 deleteNode.setRight(null); 17 } 18 else if (deleteNode.getLeft() != null && deleteNode.getRight() == null) 19 { 20 if (parentNode.getValue() >= deleteNode.getLeft().getValue()) parentNode.setLeft(deleteNode.getLeft()); 21 else parentNode.setRight(deleteNode.getLeft()); 22 deleteNode.setLeft(null); 23 } 24 else 25 { 26 BinNode temp = deleteNode.getRight(); 27 BinNode parent = deleteNode.getRight(); 28 while(temp.getLeft() != null) 29 { 30 parent = temp; 31 temp = temp.getLeft(); 32 } 33 if (parent != temp) 34 { 35 parent.setLeft(null); 36 } 37 temp.setLeft(deleteNode.getLeft()); 38 temp.setRight(deleteNode.getRight()); 39 if (parentNode.getValue() >= temp.getValue()) parentNode.setLeft(temp); 40 else parentNode.setRight(temp); 41 deleteNode.setLeft(null); 42 deleteNode.setRight(null); 43 } 44 } 45 46 private static BinNode findNodeInBST(BinNode tree, int value) 47 { 48 if (tree == null) return null; 49 if (tree.getValue() == value) return tree; 50 else if (tree.getValue() > value) return findNodeInBST(tree.getLeft(), value); 51 else return findNodeInBST(tree.getRight(), value); 52 } 53 54 private static BinNode findParentNodeInBST(BinNode tree, int value) 55 { 56 if (tree == null) return null; 57 if (tree.getValue() == value) return null; 58 if ((tree.getLeft() != null && tree.getLeft().getValue() == value) || 59 (tree.getRight() != null &&tree.getRight().getValue() == value)) return tree; 60 else if (tree.getValue() > value) return findParentNodeInBST(tree.getLeft(), value); 61 else return findParentNodeInBST(tree.getRight(), value); 62 }
最后,欢迎大家提出更多和二叉树相关的面试题目,我们可以一起讨论。
作者:李潘
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。