二叉树总结(JAVA)

来源:互联网 发布:python str.format 编辑:程序博客网 时间:2024/06/05 16:33


import java.util.ArrayList;import java.util.LinkedList;import java.util.List;import java.util.Queue;import java.util.Stack;public class BinTree {public static void main(String[] args) {// TODO Auto-generated method stub/** *      1 *          / \ *         2   3 *        / \  /\ *       4  5  6 7 *      / \ / \ *     8  9 10 11 */int[] a = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };TreeNode root = createBinTree(a);//完全二叉树//levelTravers(root);//层次遍历//int levelNum = getLevelNodeNum(root, 4);//K层的节点数//List<TreeNode> path = new ArrayList<TreeNode>();//getNodePath(root, new TreeNode(5), path);//获取根结点到指定节点之间路径的所有节点//getNodePathSenior(root, new TreeNode(11), new TreeNode(6), path);//获取任意2个节点之间路径的所有节点//for(TreeNode node : path){//System.out.println(node.getData()+" ");//}//TreeNode lastCommonParent =  findLastCommonParentRec(root, new TreeNode(11), new TreeNode(6));//查找任意2个节点最近的共同祖先//System.out.println(lastCommonParent.getData());//int maxDistance = getMaxDistance(root);//计算树中2个节点的最远距离//System.out.println(maxDistance);//干掉4的右孩子//root.getLeft().getLeft().setRight(null);boolean flag = isCompleteBinaryTree(root);//判断是否为完全二叉树System.out.println(flag);//恢复二叉树//int[] pre  =  {1, 2, 4, 8, 9, 5, 10, 11, 3, 6 ,7};//int[] min  =  {8, 4, 9, 2, 10, 5, 11, 1, 6, 3, 7};//int[] post =  {8, 9, 4, 10, 11, 5, 2, 6, 7, 3, 1};//List<TreeNode> preOrder = new ArrayList<TreeNode>();//for(int i : pre){//preOrder.add(new TreeNode(i));//}//List<TreeNode> minOrder = new ArrayList<TreeNode>();//for(int i : min){//minOrder.add(new TreeNode(i));//}//List<TreeNode> postOrder = new ArrayList<TreeNode>();//for(int i : post){//postOrder.add(new TreeNode(i));//}//TreeNode root = rebuildBinaryTreeRec(preOrder, minOrder);//先序+中序//TreeNode root = rebuildBinaryTreeRec(minOrder, postOrder, 1);//中序+后序//levelTravers(root);//判断是否为平衡二叉树//TreeNode head = new TreeNode(5);//head.setLeft(new TreeNode(4));//head.getLeft().setLeft(new TreeNode(1));//head.setRight(new TreeNode(6));//System.out.print(isAVLTreeRec(head));}/** * 非递归构建完全二叉树 * 这里默认数组是二叉树层次遍历的结果,即由层次遍历的结果恢复二叉树 * 这样的数组有一个特点: * 数组下标范围在 [0,treeNodes.length/2 - 1]里的元素全部都是父节点,我们可以根据父节点与子节点的位置关系创建二叉树 * 父节点与子节点位置关系: * 左孩子位置:leftChildIndex = parentIndex*2+1; * 右孩子位置: rightChildIndex = parentIndex*2+2 * 这里需要注意的一个点是最后一个父节点: * 因为数组个数为奇数的时候,所有的父节点都有左右孩子,而当数组个数为偶数的时候,最后一个父节点没有有孩子。所以我们需要对自后一个父节点左特殊处理,所以我们先构建[0,length/2 - 1)区间的父节点 * 因为这些节点肯定是有左右孩子的 * @param treeNodes * @return */public static TreeNode createBinTree(int[] treeNodes){//将数组所有的元素构建成节点存放在List中List<TreeNode> nodeList = new ArrayList<TreeNode>();for(int i : treeNodes){nodeList.add(new TreeNode(i));}//修改节点之间的关系创建二叉树,先处理一般情况下的父节点for(int parentIndex = 0; parentIndex < treeNodes.length/2 - 1; parentIndex++){//构建父亲节点的左孩子nodeList.get(parentIndex).setLeft(nodeList.get(parentIndex*2+1));//构建父亲节点的右孩子nodeList.get(parentIndex).setRight(nodeList.get(parentIndex*2+2));}//特殊处理最后一个父亲节点,因为只有数组个数为奇数的时候,最后一个父亲节点才有右孩子int lastParentIndex = treeNodes.length/2-1;if(treeNodes.length%2 == 0){//偶数,没有右孩子nodeList.get(lastParentIndex).setLeft(nodeList.get(lastParentIndex*2+1));}else{//奇数,有右孩子nodeList.get(lastParentIndex).setLeft(nodeList.get(lastParentIndex*2+1));nodeList.get(lastParentIndex).setRight(nodeList.get(lastParentIndex*2+2));}//返回第一个节点,即二叉树的根节点return nodeList.get(0);}/** * 计算二叉树节点的个数 * 递归 * O(n) */public static int getNumRec(TreeNode root){if(root == null) return 0;return (getNumRec(root.getLeft())+getNumRec(root.getRight())+1);}/** * 计算二叉树节点的个数 * 层次遍历 * O(n) */public static int getNum(TreeNode root){int num = 0;if(root == null) return num;num = 1;Queue<TreeNode> queue = new LinkedList<TreeNode>();queue.add(root);while(!queue.isEmpty()){TreeNode node = queue.remove();if(node.getLeft() != null){queue.add(node.getLeft());num++;}if(node.getRight() != null){queue.add(node.getRight());num++;}}return num;}/** * 求树的深度(高度) * 递归 * O(n) */public static int getDepthRec(TreeNode root){if( root == null) return 0;int leftDepth = getDepth(root.getLeft());int rightDepth = getDepth(root.getRight());return (1+Math.max(leftDepth, rightDepth));}/** * 层次遍历 * O(n) */public static int getDepth(TreeNode root){if(root == null) return 0;int depth = 0;int currLevelNum = 1;int nextLevelNum = 0;Queue<TreeNode> queue = new LinkedList<TreeNode>();queue.add(root);while(!queue.isEmpty()){TreeNode node = queue.remove();currLevelNum--;if(node.getLeft()!= null){queue.add(node.getLeft());nextLevelNum++;}if(node.getRight() != null){queue.add(node.getRight());nextLevelNum++;}if(currLevelNum == 0){depth++;currLevelNum = nextLevelNum;nextLevelNum = 0;}}return depth;}/** * 先序遍历二叉树 * 递归 */public static void preOrderRec(TreeNode node){if(node == null) return;    System.out.println(node.getData());    preOrderRec(node.getLeft());    preOrderRec(node.getRight());}/** * 使用栈进行先序遍历 */public static void preOrder(TreeNode root){if(root == null) return ;Stack<TreeNode> stack = new Stack<TreeNode>();stack.push(root);while(!stack.isEmpty()){TreeNode node = stack.pop();System.out.println(node.getData());//关键点,先入栈右节点if(node.getRight() != null) stack.push(node.getRight());if(node.getLeft() != null) stack.push(node.getRight());}}/** * 中序遍历二叉树 * 递归 */public static void inOrderRec(TreeNode root){if(root == null) return;inOrderRec(root.getLeft());System.out.println(root.getData());inOrderRec(root.getRight());}/** * 后序遍历二叉树 * 递归 */public static void postOrder(TreeNode root){if(root == null) return;postOrder(root.getLeft());postOrder(root.getRight());System.out.println(root.getData());}/** * 查找二叉树 * 任何一个节点,其左孩子的值<改节点的值,其右孩子的值>该节点的值,是一个特殊的二叉树 * 查找功能 * 递归 */public static TreeNode searchRec(TreeNode root, int key){if(root == null) return null;int temp = key - root.getData();if(temp < 0){return searchRec(root.getLeft(), key);}else if(temp > 0){return searchRec(root.getRight(), key);}else{return root;}}/** * 查找二叉树 * 任何一个节点,其左孩子的值<改节点的值,其右孩子的值>该节点的值,是一个特殊的二叉树 * 查找功能 * 非递归实现 */public static TreeNode search(TreeNode root, int key){if(root == null) return null;while(root != null){if(key < root.getData()){root = root.getLeft();}else if(key < root.getData()){root = root.getRight();}else{return root;}}return root;}/** * 将查找二叉树转换成有序双向链表,不允许创建新节点,只允许修改指针 * 递归 */public static TreeNode convertBTSToDLL(TreeNode root){root = convertBTSToDLLSub(root);//需要手动处理root引用,将其移动至最左边while(root.getLeft() != null){root = root.getLeft();}return root;}/** * 返回的链表指针指向了链表的中部 * @param root * @return */public static TreeNode convertBTSToDLLSub(TreeNode root){if(root == null || (root.getLeft() == null && root.getRight() == null)) return root;//处理左子树TreeNode temp = convertBTSToDLLSub(root.getLeft());while(temp.getRight() != null){temp = temp.getRight();}//将左子树的最右边和根结点拼接起来temp.setRight(root);root.setLeft(temp);//处理右子树,同上 TreeNode temp2 = convertBTSToDLLSub(root.getRight()); while(temp2.getLeft() != null){ temp2 = temp.getLeft(); } temp2.setLeft(root); root.setRight(temp2);  return root;}/** * 层次遍历二叉树 * 辅助队列 * @param root */public static void levelTravers(TreeNode root){if(root == null) return;Queue<TreeNode> queue = new LinkedList<TreeNode>();queue.add(root);while(!queue.isEmpty()){TreeNode node = queue.remove();System.out.print(node.getData()+" ");if(node.getLeft() != null) queue.add(node.getLeft());if(node.getRight() != null) queue.add(node.getRight());}}/** * 计算K层的节点的个数 * 层次遍历,输出第K层的节点个数 * @param depth * @return */public static int getLevelNodeNum(TreeNode root, int depth){if(depth == 1) return 1;int curLevelN = 1;//二叉树当前层节点从1开始int nextLevelN = 0;int curDepth = 1;//当前层Queue<TreeNode> queue = new LinkedList<TreeNode>();queue.add(root);while(!queue.isEmpty()){TreeNode node = queue.remove();curLevelN--;if(node.getLeft() != null){//下一层的节点入队列的时候,进行累加以记录下一层节点的数量queue.add(node.getLeft());nextLevelN++;}if(node.getRight() != null){queue.add(node.getRight());nextLevelN++;}//当一个节点从对头出队后,判断当前层的是否还有节点,如果没有的话,那就遍历下一层了if(curLevelN == 0){curLevelN = nextLevelN;//下一层需要遍历的节点个数curDepth++;//下一层的深度if(curDepth == depth) return curLevelN;nextLevelN = 0;}}return 0;//无意义}/** * 计算二叉树叶子节点的个数 * @param root * @return */public static int getLeafNodeNum(TreeNode root){//思路:层次遍历的过程中,记录既没有左孩子,也没右孩子的节点数,那就是叶子节点数啦。代码编写与层次遍历差不太多return 0;}/** * 判断2个二叉树是否相等 * 思路:同时遍历2个二叉树,判断当前节点是否相等,出现不等立即放回false;一直到遍历完为止 * 可以先序、中序、后序遍历,可以递归、非递归遍历、层次遍历等 * @param firstRoot * @param secondRoot * @return */public static boolean isSame(TreeNode firstRoot, TreeNode secondRoot){return true;}/** * 判断是否为平衡二叉树 * 平衡二叉树: * 1)左右子树的深度相差不能大于1 * 2)左右子树都为平衡二叉树 * 3)满足查二叉树的特性 * 递归 * @param root * @return */public static boolean isAVLTreeRec(TreeNode root){if(root == null) return true;//左右子树的深度差不得超过1if(Math.abs(getDepthRec(root.getLeft()) - getDepthRec(root.getRight())) > 1){return false;}//根结点的值大于左孩子if(root.getLeft() != null && root.getLeft().getData() >= root.getData()){return false;}//根结点的值必须小于右孩子if(root.getRight() != null && root.getRight().getData() <= root.getData()){return false;}//递归判断左右子树也得满足上述特性return isAVLTreeRec(root.getLeft()) && isAVLTreeRec(root.getRight());}/** * 求二叉树的镜像 * 镜像: * 镜像二叉树的左子树为原二叉树的右子树 * 右子树同理 * 递归比较简单 * @param root * @return */public static TreeNode mirrorRec(TreeNode root){if(root == null) return null;TreeNode mirrorRoot = new TreeNode(root.getData());mirrorRoot.setLeft(mirrorRec(root.getRight()));mirrorRoot.setRight(mirrorRec(root.getLeft()));return mirrorRoot;}/** * 判断2棵树是否互相为镜像 * 递归,其实任何2棵树是否相等除了遍历去比较之外,也可以用此法判定 * @param firstRoot * @param secondRoot * @return */public static boolean isMirrorRec(TreeNode firstRoot, TreeNode secondRoot){if(firstRoot == null && secondRoot == null) return true;if(firstRoot == null || secondRoot == null) return false;if(firstRoot.getData() != secondRoot.getData()) return false;return isMirrorRec(firstRoot.getLeft(), secondRoot.getRight()) && isMirrorRec(firstRoot.getRight(), secondRoot.getLeft());}/** * 查找二叉树中2个节点最近的共同祖先节点 * 递归 * 思想: * 1)如果2个节点分别在根结点的2侧,则直接返回根结点, * 2)如果2个节点都在根结点的左侧,那么对根结点的左子树递归处理 * 3)如果2个节点都在根结点的右侧,那么对根结点的右子树递归处理 * @param root * @param n1 * @param n2 * @return */public static TreeNode findLastCommonParentRec(TreeNode root, TreeNode n1, TreeNode n2){if(root == null || n1 == null || n2 == null) return null;if(findNodeRec(root.getLeft(), n1)){//n1在左子树if(findNodeRec(root.getRight(), n2)){//n2在右子树return root;}else{//n2在左子树,则在左子树递归处理2个节点return findLastCommonParentRec(root.getLeft(), n1, n2);}}else{//n1在右子树if(findNodeRec(root.getLeft(), n2)){//n2在左子树return root;}else{//n2在右子树,则在右子树中对2个节点递归处理return findLastCommonParentRec(root.getRight(), n1, n2);}}}/** * 递归查找二叉树中是否存在node节点 * 也可以遍历去查找对应的节点,效率也会更高一些 * 上一个方法的辅助方法 * @param root * @param node * @return */public static boolean findNodeRec(TreeNode root, TreeNode node){if(root == null || node == null) return false;if(root.getData() == node.getData()) return true;if(findNodeRec(root.getLeft(), node)){//递归左子树OKreturn true;}else if(findNodeRec(root.getRight(), node)){//递归右子树okreturn true;}//没有找到return false;}/** * 递归查找二叉树中是否存在node节点 * @param root * @param node * @param arg * @return 查找到的node节点 */public static TreeNode findNodeRec(TreeNode root, TreeNode node, boolean arg){if(root == null || node == null) return null;if(root.getData() == node.getData()) return root;if(findNodeRec(root.getLeft(), node)){//能在左子树找到node节点,继续递归左子树查找节点return findNodeRec(root.getLeft(), node, true);}else if(findNodeRec(root.getRight(), node)){//能在右子树找到节点,继续递归右子树查找节点return findNodeRec(root.getRight(), node, true);}else{//左右子树都找不到节点return null;}}/** * 从根结点到指定节点路径之间所有的节点放入集合中 * 递归 * 思路:将递归函数设计为:将当前节点到指定节点之间路径的所有节点全部放入路径中,能放则放回true,否则放回false * 递归函数实现: * 1)首先从根结点开始,将根结点放入路径中 * 2)判定是否已经到了指定节点 * 3)若没有到指定节点,递归从左孩子开始放,能放则放回true,否则放回false * 4)若左孩子不能放,那么就放右孩子,递归从右孩子开始放,能放则放回true,否则放回false * 5)左右孩子都不能放,那么表示根结点没有到指定节点的路径,那么从路径中移除根结点,返回false * @param root * @param node * @param path * @return  */public static boolean getNodePath(TreeNode root, TreeNode node, List<TreeNode> path){if(root == null || node == null) return false;//将当前节点放入path中去path.add(root);//当前节点就是指定节点,那么肯定能放入路径,放回trueif(root.getData() == node.getData()) return true;//判别从当前节点是到指定节点路径之间的节点是否能全部放入路径中//从当前节点的左子树开始试探if(getNodePath(root.getLeft(), node, path)){//能成功return true;}else if(getNodePath(root.getRight(), node, path)){//左子树不行,那从右子树开始试探//能成功return true;}else{//都不成功,表明当前节点不在路径中,将当前节点从path中移除path.remove(root);return false;}}/** * 引申: * 二叉树中任意2个节点,求两个节点路径之间的所有节点  * 思路:将其中一个节点当成根结点,尝试获取路径看是够能成功,如果都不能成功,表明2个节点在一个最近祖先节点的2侧, * 那么找到最近的祖先节点,以祖先节点为根节点,找到2条路径拼接起来就是我们需要的路径啦 * @param n1 * @param n2 * @param path * @return */public static boolean getNodePathSenior(TreeNode root, TreeNode n1, TreeNode n2, List<TreeNode> path){if(n1 == null || n2 == null) return false;//查找树中对应的n1n1 = findNodeRec(root, n1, true);//查找树中对应的n2n2 = findNodeRec(root, n2, true);if(getNodePath(n1, n2, path)){//以n1节点为根节点能找到路径return true;}else if(getNodePath(n2, n1, path)){//以n2为根结点能找到路径return true;}else{//寻找最近的祖先节点TreeNode lastCommonParent = findLastCommonParentRec(root, n1, n2);//拼接2个路径List<TreeNode> path1 = new ArrayList<TreeNode>();List<TreeNode> path2 = new ArrayList<TreeNode>();getNodePath(lastCommonParent, n1, path1);getNodePath(lastCommonParent, n2, path2);path1.remove(0);//最近祖先节点存在于2条路径当中,所以会重复,去重//将path1中的节点反序,为了使结果好看,从一个节点到另一个节点的顺序List<TreeNode> temp = new ArrayList<>();for(int i = path1.size() - 1; i >= 0; i--){temp.add(path1.get(i));}//拼接path.addAll(temp);path.addAll(path2);return true;}}/** * 计算树的2个节点的最大距离 * @param root * @return * 思路: * 返回下列3个值中的最大值 * 1)计算右子树的深度+1 * 2)计算左子树的深度+1 * 3)左子树的深度+右子树的深度+1+1(这里需要加2个1) */public static int getMaxDistance(TreeNode root){if(root == null) return 0;int lDepth = getDepth(root.getLeft());int rDepth = getDepth(root.getRight());return Math.max((lDepth+rDepth+1+1), Math.max(lDepth, rDepth));}/** * 判断一个树是否为完全二叉树 * 完全二叉树:树的深度为n,那么n-1层的节点必须是满的,n层的节点集中在左侧 * 思路:非递归比较简单 * 层次遍历二叉树,当发现一个节点没有左孩子,那么该节点的右孩子一定要为空,并且后序节点的都没有左右孩子,否则不为完全二叉树 * 当发现一个节点没有右孩子,那么后序节点就不能有左右孩子了 * @param root * @return */public static boolean isCompleteBinaryTree(TreeNode root){if(root == null) return false;Queue<TreeNode> queue  = new LinkedList<TreeNode>();queue.add(root);boolean leftNull = false;//当发现没有左孩子的第一个节点是,将其设置为true;然后开始判断该节点是是否有右孩子,后序节点是否有左右孩子boolean rightNull = false; //当发现第一个右孩子为空时,触发一个判定,去判定有序节点都不应该有左右孩子while(!queue.isEmpty()){TreeNode node = queue.remove();if(leftNull || rightNull){ //后序节点不应该有左右孩子节点if(node.getLeft() != null || node.getRight() != null) return false;}if(node.getLeft() != null) queue.add(node.getLeft());if(node.getRight() != null) queue.add(node.getRight());if(node.getLeft() == null){leftNull = true;//该节点不能有右孩子,后序节点不能有左右孩子}if(leftNull){ //当发现没有左孩子的第一个节点,开始判断该节点是是否有右孩子if(node.getRight() != null) return false;}//只去用左孩子为空去触发判定是不够的,还需要用右孩子去判定触发if(node.getRight() == null){rightNull = true;}}return true;}/** * 由先序和中序序列重建二叉树 * 思路: * 不断确定根结点,分割出根结点的左子树的先序和中序序列递归重建根结点的左子树,和分割右子树的先序和中序序列递归重建根结点的右子树 * 由先序序列的第一个节点确定根结点,然后由根结点在中序序列中找到分割点,分割点左边就是根结点的左子树的中序序列,分割点的右边就是右子树的中序序列 * 因为左右子树的长度已经确定,所以可以在先序序列中进行分割,从根结点后面分割出左子树的长度的序列即左子树的先序序列,从根结点后面的左子树长度+1的位置开始分割,得到右子树的先序序列 * 然后根据左子树的先序和中序序列递归重建根结点的左子树 * 然后根据右子树的先序和中序序列递归重建根结点的右子树 * @param preOrder * @param minOrder * @return */public static TreeNode rebuildBinaryTreeRec(List<TreeNode> preOrder, List<TreeNode> minOrder){TreeNode root = null;List<TreeNode> leftPreOrder;List<TreeNode> rightPreOrder;List<TreeNode> leftMinOrder;List<TreeNode> rightMinOrder;int prePos;//先序序列分割点int minPos;//中序序列分割点if(preOrder.size() > 0 && minOrder.size() > 0){//构建根结点root = new TreeNode(preOrder.get(0));//先序序列的第一个节点为根结点//划分中序序列的左右子树,即左子树和右子树的中序序列minPos = minOrder.indexOf(preOrder.get(0));leftMinOrder = minOrder.subList(0, minPos);rightMinOrder = minOrder.subList(minPos+1,minOrder.size());//划分先序序列的左右子树,即左子树和右子树的先序序列prePos = leftMinOrder.size();//左子树的长度leftPreOrder = preOrder.subList(1, prePos+1);rightPreOrder = preOrder.subList(prePos+1, preOrder.size());//递归构建左子树,右子树root.setLeft(rebuildBinaryTreeRec(leftPreOrder, leftMinOrder));root.setRight(rebuildBinaryTreeRec(rightPreOrder, rightMinOrder));}return root;}/** * 中序+后序恢复二叉树 * 思路和上边差不多 * @param minOrder * @param postOrder * @param op * @return */public static TreeNode rebuildBinaryTreeRec(List<TreeNode> minOrder, List<TreeNode> postOrder, int op){TreeNode root = null;List<TreeNode> leftMinOrder = null;List<TreeNode> leftPostOrder = null;List<TreeNode> rightMinOrder = null;List<TreeNode> rightPostOrder = null;int minPos = 0;int postPos = 0;if(minOrder.size() > 0 && postOrder.size() > 0){//后序遍历的最后一个节点是根结点root = new TreeNode(postOrder.get(postOrder.size()-1));//分割左右子树的中序序列minPos = minOrder.indexOf(root);leftMinOrder = minOrder.subList(0, minPos);rightMinOrder = minOrder.subList(minPos+1, minOrder.size());//分割左右子树的后序序列postPos = leftMinOrder.size();leftPostOrder = postOrder.subList(0, postPos);rightPostOrder = postOrder.subList(postPos, postOrder.size()-1);//递归重建左子树,右子树root.setLeft(rebuildBinaryTreeRec(leftMinOrder, leftPostOrder, 1));root.setRight(rebuildBinaryTreeRec(rightMinOrder, rightPostOrder, 1));}return root;}}/** * 树的节点 */class TreeNode{private TreeNode left;private TreeNode right;    private int data;    private String id = "id";    TreeNode(){};TreeNode(int data){this.data = data;};TreeNode(TreeNode node){this.left = node.left;this.right = node.right;this.data = node.data;}public void setLeft(TreeNode left){this.left = left;}public TreeNode getLeft(){return this.left;}public void setRight(TreeNode right){this.right = right;}public TreeNode getRight(){return this.right;}public void setData(int data){this.data = data;}public int getData(){return this.data;}public boolean equals(Object obj){if(obj instanceof TreeNode){TreeNode node = (TreeNode) obj;return (this.data == node.data);}return super.equals(obj);}/** * 需要重新复写hashCode方法,因为在散列集合中,是根据equals和hashCode()共同来决定2者是否相等; * 规则:2者相等,equals返回true,hashCode相等;2者不相等,equals返回false, 但是hashCode不一定不相等,因为不同对象可能散列出相等的hashCode; * 这里使用的是id 的hashCode; 表示data相等,因为hashCode一定行等,那么2者就相等 */public int hashCode(){return this.id.hashCode();}}