数据结构(三)——二叉树的相关操作

前言

这里同样不介绍二叉树的相关概念,毕竟数据结构的基础已经讲过很多了,这里不再赘述。至于一些平衡二叉树,完全二叉树,红黑树,B+树等相关结构,这个已经有很多博客介绍了,这里只是介绍一下二叉树的一些基础操作。

定义

这个应该见过多次

/**
 * autor:liman
 * createtime:2020/2/6
 * comment:二叉树节点的实现
 */
public class TreeNode {

    public String value;
    public TreeNode left;
    public TreeNode right;

    public TreeNode(String value) {
        this.value = value;
    }
}

遍历的非递归实现

递归的实现非常简单,估计很多人都会,这里就介绍非递归的遍历实现

基本的遍历操作如下:这里示意的输出一下表示遍历操作

/**
 * 做遍历的操作。
 *
 * @param node
 */
public static void doTraverse(TreeNode node) {
    System.out.print(node.value + " ");
}

先序遍历

/**
 * 非递归实现的先序遍历,这里用到了栈的操作
 * 
 * @param root
 */
public static void preOrderTraverse(TreeNode root) {
    if (root != null) {//如果根节点不为空
        Stack<TreeNode> stack = new Stack<>();//非递归要用到栈
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            doTraverse(node);
            //先判断右子树,再判断左子树
            if (node.right != null) {//判断右子树,如果右子树不为空,入栈
                stack.push(node.right);
            }
            if (node.left != null) {//判断左子树,如果左子树不空,则入栈
                stack.push(node.left);
            }
        }
    }
}

中序遍历

/**
 * 非递归实现中序遍历
 *
 * @param root
 */
public static void innerOrderTraverse(TreeNode root) {
    if (root != null) {
        Stack<TreeNode> stack = new Stack<>();
        //不断的入栈左子树,找到整个树中最左端的一个节点(中序遍历的头结点)
        while (!stack.isEmpty() || root != null) {
            if (root != null) {
                stack.push(root);
                root = root.left;
            } else {//如果节点的左子树为空了,则直接弹出,遍历,然后指向右子树。
                root = stack.pop();
                doTraverse(root);
                root = root.right;
            }
        }

    }
}

后续遍历

/**
 * 非递归实现后序遍历,一个栈比较麻烦,两个栈相对简单
 *
 * @param root
 */
public static void postOrderTraverse(TreeNode root) {
    if (root != null) {
        Stack<TreeNode> stack1 = new Stack<>();
        Stack<TreeNode> stack2 = new Stack<>();//用于存放待遍历的元素
        stack1.push(root);
        while (!stack1.isEmpty()) {
            TreeNode pop = stack1.pop();
            stack2.push(pop);
            if (pop.left != null) {
                stack1.push(pop.left);
            }
            if (pop.right != null) {
                stack1.push(pop.right);
            }
        }
        while (!stack2.isEmpty()) {
            doTraverse(stack2.pop());
        }
    }
}

层次遍历

/**
 * 层次遍历,非递归实现
 * 利用队列
 * @param root
 */
public static void levelOrderTraverse(TreeNode root) {
    if (root != null) {
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.offer(root);//root入队列
        while (!queue.isEmpty()) {
            //获取当前层次的节点个数
            int levelNum = queue.size();
            for (int i = 0; i < levelNum; i++) {
                TreeNode node = queue.poll();
                doTraverse(node);
                if (node.left != null) {//入队左子树
                    queue.offer(node.left);
                }

                if (node.right != null) {//入队右子树
                    queue.offer(node.right);
                }
            }
        }
    }
}

计算二叉树的深度(最大深度和最小深度)

最大深度

递归实现

递归实现最大深度,代码是最为简单的

/**
 * 递归计算最大深度
 *
 * @param root
 * @return
 */
public int maxDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }
    int left = maxDepth(root.left);
    int right = maxDepth(root.right);
    return Math.max(left, right) + 1;//毕竟根节点还是要算一层高度的。
}

非递归实现

/**
 * 非递归实现最大深度,要用到层序遍历
 *
 * @param root
 * @return
 */
public int maxDepthLevel(TreeNode root) {
    if (root == null) {
        return 0;
    }

    Queue<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root);
    int level = 0;
    while (!queue.isEmpty()) {
        level++; //这里统计层高
        int levelNum = queue.size();
        for (int i = 0; i < levelNum; i++) {
            TreeNode node = queue.poll();
            if (node.left != null) {
                queue.offer(node.left);
            }

            if (node.right != null) {
                queue.offer(node.right);
            }
        }
    }
    return level;
}

最小深度

最小深度相比最大深度来说,我们要判断的条件会比较多,如果树为空则直接返回0,如果只有一个根节点则直接返回1,如果左子树为空右子树不为空,则需要计算右子树的最小深度。如果右子树为空左子树不为空,则需要计算左子树的最小深度

递归实现

/**
 * 递归求树的最小深度,这里需要考虑的条件更多
 *
 * @param root
 * @return
 */
public int mixDepth(TreeNode root) {
    if (root == null) {//树为空
        return 0;
    }
    if (root.left == null && root.right == null) {//只有一个根节点
        return 1;
    }
    if (root.left == null && root.right != null) {//左子树为空,右子树不为空
        return mixDepth(root.right) + 1;
    }
    if (root.right == null && root.left != null) {//右子树为空,左子树不为空
        return mixDepth(root.left) + 1;
    }

    //这个就是左子树和右子树都不为空的情况
    int left = mixDepth(root.left);
    int right = mixDepth(root.right);
    return Math.min(left,right)+1;
}

非递归实现

层序遍历的时候,遇到的第一个左右子树均为空的节点的层高,必是最小深度

public int mixDepthLevel(TreeNode root){
    if (root == null) {
        return 0;
    }

    Queue<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root);
    int level = 0;
    while (!queue.isEmpty()) {
        level++;
        int levelNum = queue.size();
        for (int i = 0; i < levelNum; i++) {
            TreeNode node = queue.poll();
            //第一个左右子树均为空的节点的层高,必是最小深度
            if(node.left== null && node.right == null){
                return level;
            }
            if (node.left != null) {
                queue.offer(node.left);
            }

            if (node.right != null) {
                queue.offer(node.right);
            }
        }
    }
    return 0;
}

计算两个节点的最近的祖先

/**
 * 递归实现两个节点最近的祖先
 * @param root
 * @param node01
 * @param node02
 * @return
 */
public TreeNode lowestCommoAncestor(TreeNode root, TreeNode node01, TreeNode node02) {

    //最近的公共祖先是root节点
    if (root == null || root == node01 || root == node02) {
        return root;
    }

    TreeNode left = lowestCommoAncestor(root.left, node01, node02);
    TreeNode right = lowestCommoAncestor(root.right, node01, node02);

    if (left != null && right != null) {//如果左右都不空,说明两边均能找到node01和node02,则公共节点只能root了
        return root;
    }

    //如果只有一个为空,则那个公共的祖先就是这个节点。
    return left != null ? left : right;
}

根据中序和先序序列构建二叉树

这个其实数据结构中接触过相关问题,无非就是从先序遍历序列中找到根节点,然后这个根节点在中序序列中的左边均为该节点的左子树,中序序列右边的节点均为该节点的右子树。

public TreeNode buildTree(int[] preOrder,int[] inOrder){
    if(preOrder == null || inOrder == null){
        return null;
    }
    HashMap<Integer,Integer> map = new HashMap<>();
    for(int i=0;i<inOrder.length;i++){
        map.put(inOrder[i],i);
    }
    return buildTree(preOrder,0,preOrder.length-1,inOrder,0,inOrder.length-1,map);
}

/**
 *  根据中序和先序遍历的数组构建一个二叉树
 * @param preOrder 先序序列数组
 * @param pStart	先序序列的起始位置
 * @param pEnd		先序序列的结束位置
 * @param inOrder	中序序列数组
 * @param iStart	中序序列的起始位置
 * @param iEnd		中序序列的结束位置
 * @param map		中序序列元素与索引位置的map,仅仅只是为了搜索方便
 * @return
 */
public TreeNode buildTree(int[] preOrder,int pStart,int pEnd,
                          int[] inOrder,int iStart,int iEnd,
                          HashMap<Integer,Integer> map){
    if(pStart>pEnd || iStart>iEnd){
        return null;
    }

    TreeNode head = new TreeNode(preOrder[pStart]);
    int index = map.get(preOrder[pStart]);//找到根节点在中序遍历中的位置
    head.left = buildTree(preOrder,pStart+1,pStart+index-iStart,inOrder,iStart,index-1,map);
    head.right = buildTree(preOrder,pStart+index-iStart+1,pEnd,inOrder,index+1,iEnd,map);
    return head;

}

这里有很多变量,关于索引的,这里直接用一张图表达(至于这个完整的树结构,各位看官可以自行转换一下,这个不是难事):

在这里插入图片描述

总结

一些基础的操作都总结了,其实之前用C实现过,但是都不太系统。

发布了129 篇原创文章 · 获赞 37 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/104222410