二叉树的遍历

import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 先序遍历:按照“根左右”的顺序,先遍历根节点,再遍历左子树,再遍历右子树
 * 中序遍历:按照“左根右“的顺序,先遍历左子树,再遍历根节点,最后遍历右子树
 * 后续遍历:按照“左右根”的顺序,先遍历左子树,再遍历右子树,最后遍历根节点
 * <p>
 * 说明:
 *		1)这里的'先/中/后'是指每次遍历的时候,根节点被遍历的顺序。
 * 		2)每个节点都可以看做一个根节点。
 * 		3)遍历的时候,我们会将当前节点作为一个根节点来看待。
 */
public class BinaryTree {

    Integer value;
    BinaryTree leftChild;
    BinaryTree rightChild;

    public BinaryTree(Integer value, BinaryTree leftChild, BinaryTree rightChild) {
        this.value = value;
        this.leftChild = leftChild;
        this.rightChild = rightChild;
    }


    // 存放遍历后的节点
    static ArrayList<BinaryTree> list = new ArrayList<BinaryTree>();

    /**
     * 先序遍历
     */
    public static void preOrder(BinaryTree root) {

        if (root == null) return;

        list.add(root);                 // 1.将当前节点(根节点)存入list
        if (root.leftChild != null) {   // 2.当前节点的左子树不为空时,继续往左找
            preOrder(root.leftChild);
        }                               // 3.当前节点的左子树为空时,说明根节点和左孩子已经遍历完毕了,则接下来遍历右孩子

        if (root.rightChild != null) {
            preOrder(root.rightChild);
        }
    }

    /**
     * 中序遍历
     */
    public static void inOrder(BinaryTree root) {

        if (root == null) return;

        if (root.leftChild != null) {
            inOrder(root.leftChild);
        }
        list.add(root);
        if (root.rightChild != null) {
            inOrder(root.rightChild);
        }
    }

    /**
     * 后序遍历
     */
    public static void postOrder(BinaryTree root) {

        if (root == null) return;

        if (root.leftChild != null) {
            postOrder(root.leftChild);
        }
        if (root.rightChild != null) {
            postOrder(root.rightChild);
        }
        list.add(root);

    }


    /**
     * 先序遍历 非递归
     *
     * @param root
     */
    public static void preOrderNonRecursion(BinaryTree root) {

        if (root == null) return;

        Stack<BinaryTree> stack = new Stack<BinaryTree>();

        BinaryTree currentNode = root;

        while (!stack.empty() || currentNode != null) {

            while (currentNode != null) {
                list.add(currentNode);
                stack.push(currentNode);                // 1.将当前节点(根节点)存在栈中
                currentNode = currentNode.leftChild;    // 2.当前节点的左子树不为空时,将当前节点的左子树作为根节点,继续执行循环。 注:这里与递归式的先序遍历类似。
            }                                           // 3.当前节点的左子树为空时,说明根节点和左孩子已经遍历完毕了,则接下来遍历右孩子

            if (!stack.empty()) {
                currentNode = stack.pop();
                currentNode = currentNode.rightChild;
            }

        }
    }

    /**
     * 中序遍历 非递归
     *
     * @param root
     */
    public static void inOrderNonRecursion(BinaryTree root) {

        if (root == null) return;

        Stack<BinaryTree> stack = new Stack<BinaryTree>();

        BinaryTree currentNode = root;

        while (!stack.empty() || currentNode != null) {

            while (currentNode != null) {
                stack.push(currentNode);
                currentNode = currentNode.leftChild;
            }

            // 当root为空时,说明已经到达左子树最下边,这时需要出栈了
            if (!stack.empty()) {
                currentNode = stack.pop();
                list.add(currentNode);
                currentNode = currentNode.rightChild;
            }

        }
    }

    /**
     * 后序遍历 非递归
     *  要点:
     *      1)后序遍历需要判断上次访问的节点是位于左子树,还是右子树。
     *      2)  若是位于左子树,则需跳过根节点,先进入右子树,再回头访问根节点;
     *      3)  若是位于右子树,则直接访问根节点。
     *
     * @param root
     */
    public static void postOrderNonRecursion(BinaryTree root) {

        if (root == null) return;

        Stack<BinaryTree> stack = new Stack<BinaryTree>();

        BinaryTree currentNode = root;   // 当前访问的节点
        BinaryTree lastVisitNode = null; // 上次访问的节点

        // 把currentNode移到当前节点的左子树的最左边
        while (currentNode != null) {
            stack.push(currentNode);
            currentNode = currentNode.leftChild;
        }

        while (!stack.empty()) {
            currentNode = stack.pop();

            // 后续遍历中,一个根节点被访问的前提是:当前节点(可以看成根节点)无右子树 或 当前节点的右子树刚刚被访问过。
            if (currentNode.rightChild != null && currentNode.rightChild != lastVisitNode) {

                stack.push(currentNode); // 当前节点的右子树不为空且没有被访问过,故根节点(当前节点)再次入栈。

                // 进入右子树,把currentNode移到当前节点的右子树的最左边
                currentNode = currentNode.rightChild;
                while (currentNode != null) {
                    stack.push(currentNode);
                    currentNode = currentNode.leftChild;
                }
            } else {
                // 访问
                list.add(currentNode);
                lastVisitNode = currentNode;
            }
        }

    }


    /**
     * 广度优先遍历二叉树 - 按层遍历二叉树
     *
     * 思路:
     *  1)新建一个队列,将根节点添加到队列中。
     *  2)若队列不为空,则将队列的头元素出列并打印头元素的值,若头元素有子节点,则依次将头元素的左右子节点添加到队列中。
     *
     * @param root
     */
    public static void breadthFirstSearch(BinaryTree root) {

        if (root == null) return;

        LinkedBlockingQueue<BinaryTree> queue = new LinkedBlockingQueue<BinaryTree>();
        queue.offer(root);

        while (!queue.isEmpty()) {

            BinaryTree node = queue.poll();
            printNode(node);

            if (null != node.leftChild) {
                queue.offer(node.leftChild);
            }

            if (null != node.rightChild) {
                queue.offer(node.rightChild);
            }
        }
    }


    /**
     * 返回当前数的深度:
     *  1)如果一棵树只有一个结点,它的深度为1
     *  2)如果根结点只有左子树而没有右子树,那么树的深度是其左子树的深度加1
     *  3)如果根结点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1
     *  4)如果既有右子树又有左子树,那该树的深度就是其左、右子树深度的较大值加1
     *
     * @param root
     * @return
     */
    public static int getTreeDepth(BinaryTree root) {
        int left = 0, right = 0;

        if (root.leftChild == null && root.rightChild == null) {
            return 1;
        }
        if (root.leftChild != null) {
            left = getTreeDepth(root.leftChild);
        }
        if (root.rightChild != null) {
            right = getTreeDepth(root.rightChild);
        }
        return left > right ? left + 1 : right + 1;
    }


    /**
     * 打印二叉树中指定大小的路径。
     *
     * 概念:
     *  1)树的路径:根节点到叶子节点之间路径。
     *  2)路径的大小:路径上所有节点的值的和。
     *
     * 思路:
     *  1)先序遍历二叉树,在遍历的时候将当前遍历的节点添加到路径上,并且累加该节点的值。
     *  2)若遍历的节点不是叶子结点,则继续遍历它的子节点。
     *  3)若遍历的节点是叶子结点 且 路径中结点值的和等于期望的值,则将路径上的节点的值打印出来。
     *  4)当前节点访问结束后,将当前节点从路径中删除。
     *
     * 重要:
     *  递归的本质就是一个入栈和出栈的过程。
     *
     * @param root
     * @param expectedSum
     */
    public static void printPath(BinaryTree root, int expectedSum) {

        if (null == root) return;

        Stack<BinaryTree> pathStack = new Stack<>();
        Integer currentSum = 0;
        findPath(root, expectedSum, pathStack, currentSum);
    }

    /**
     * 打印大小等于expectedSum的路径
     *
     * @param root
     * @param expectedSum   路径期望的大小
     * @param pathStack     辅助栈
     * @param currentSum    辅助栈中节点值的和
     */
    public static void findPath(BinaryTree root, int expectedSum, Stack<BinaryTree> pathStack, int currentSum) {

        pathStack.push(root);
        currentSum += root.value;

        // 如果不是叶子节点,说明没有到达路径的终点,故需要继续向下走
        if (null != root.leftChild) {
            findPath(root.leftChild, expectedSum, pathStack, currentSum);
        }
        if (null != root.rightChild) {
            findPath(root.rightChild, expectedSum, pathStack, currentSum);
        }

        // 如果是叶子节点 且 路径的大小为期望的大小,则打印路径上的节点
        if ((null == root.leftChild && null == root.rightChild) && currentSum == expectedSum) {
            pathStack.forEach(node -> {
                printNode(node);
            });
        }

        // 在返回到父节点前,将路径上的当前节点删除。
        pathStack.pop();
    }


    /**
     * 根据前序遍历的序列和中序遍历的序列,使用递归的方式来重建二叉树:
     *
     * @param preOrderArray         先序遍历得到的数组
     * @param startIndexOfPreOrder  先序遍历得到的数组的起始下标
     * @param endIndexOfPreOrder    先序遍历得到的数组的末尾下标
     *
     * @param inOrderArray          中序遍历得到的数组
     * @param startIndexOfInOrder   中序遍历得到的数组的起始下标
     * @param endIndexOfInOrder     中序遍历得到的数组的末尾下标
     * @return
     */
    public static BinaryTree rebuildBinaryTree(int[] preOrderArray,
                                               int startIndexOfPreOrder,
                                               int endIndexOfPreOrder,
                                               int[] inOrderArray,
                                               int startIndexOfInOrder,
                                               int endIndexOfInOrder) {

        if (null == preOrderArray || null == inOrderArray || preOrderArray.length == 0 || preOrderArray.length != inOrderArray.length) {
            return null;
        }

        // 先序遍历的第一个节点即根节点
        int rootValue = preOrderArray[startIndexOfPreOrder];
        BinaryTree root = new BinaryTree(rootValue,null,null);

        // 若先序遍历得到的序列的起始下标和末尾下标相同(即该序列只有一个节点),说明该节点是一个叶子节点。
        if (startIndexOfPreOrder == endIndexOfPreOrder) {
            // 叶子节点,无论先序遍历还是中序遍历,结果都是一样的。
            if (startIndexOfInOrder == endIndexOfInOrder && preOrderArray[startIndexOfPreOrder] == inOrderArray[startIndexOfInOrder]) {
                return root;
            } else {
                throw new IllegalArgumentException();
            }
        }

        // 【要点】在中序遍历中找到根节点的索引,从而可以得到左子树上的节点 和 右子树上的节点。
        int rootIndexOfInOrder = startIndexOfInOrder;
        for (int i = startIndexOfInOrder; i <= endIndexOfInOrder; i++) {
            if (rootValue == inOrderArray[i]) {
                break;
            }
            rootIndexOfInOrder++;
        }

        // 左子树的长度
        int leftLengthOfInOrder = rootIndexOfInOrder - startIndexOfInOrder;

        int newEndIndexOfPreOrder = startIndexOfPreOrder + leftLengthOfInOrder;

        // 右子树的长度
        int rightLengthOfInOrder = endIndexOfInOrder - rootIndexOfInOrder;

        if (leftLengthOfInOrder > 0) {
            // 构建左子树
            root.leftChild = rebuildBinaryTree(
                    preOrderArray,
                    startIndexOfPreOrder + 1,   // 先序遍历左子树得到的数组的起始下标
                    newEndIndexOfPreOrder,      // 先序遍历左子树得到的数组的末尾下标
                    inOrderArray,
                    startIndexOfInOrder,        // 中序遍历左子树得到的数组的起始下标
                    rootIndexOfInOrder - 1);    // 中序遍历左子树得到的数组的末尾下标
        }

        if (rightLengthOfInOrder > 0) {
            // 构建右子树
            root.rightChild = rebuildBinaryTree(
                    preOrderArray,
                    newEndIndexOfPreOrder + 1,
                    endIndexOfPreOrder,
                    inOrderArray,
                    rootIndexOfInOrder + 1,
                    endIndexOfInOrder);
        }
        return root;
    }

    // 二叉搜索树

    /**
     * 判断指定序列是否为某一个二叉搜索树的后续遍历序列,假设指定序列中没有重复的数字。
     *
     * 思路:
     *  1)后续遍历序列的最后一个元素即根节点,除最后一个元素外,前面的元素可以分为两部分:第一部分为左子树的节点,它们都小于根节点;第二部分为右子树的节点,它们都大于根节点。
     *  2)遍历序列,找到第一个大于根节点的元素,则在该元素之前的元素即左子树的后序遍历序列;该元素到倒数第二个元素之间的元素即右子树的后续遍历序列。
     *  3)若没有找到大于根节点的元素,则说明该二叉搜索树只有左子树,且左子树的根节点为序列中倒数第二个元素。
     *
     * @param seq
     * @return
     */
    public static boolean isPostOrderOfBinarySearchTree(int[] seq, int startIndex, int endIndex) {

        if (null == seq || seq.length == 0 || startIndex < 0 || endIndex > seq.length - 1 || (endIndex - startIndex) < 0)
            return false;

        int root = seq[endIndex];

        // 左子树根节点的下标
        int leftChildRootIndex = 0;
        for (int i = startIndex; i < endIndex; i++) {

            if (seq[i] > root) break;
            leftChildRootIndex = i;
        }


        // 右子树根节点的下标
        int rightChildRootIndex = leftChildRootIndex + 1;
        for (int i = leftChildRootIndex + 1; i < endIndex; i++) {

            if (seq[i] < root) return false;
            rightChildRootIndex = i;
        }

        // 判断左子树是否为二叉搜索树
        boolean leftIsBST;
        if (leftChildRootIndex == startIndex) { // 左子树只有一个节点,即 左子树的根节点没有子节点
            leftIsBST = true;
        } else {                                // leftChildRootIndex > 0 即 左子树的根节点有子节点
            leftIsBST = isPostOrderOfBinarySearchTree(seq, startIndex, leftChildRootIndex);
        }

        // 判断右子树是否为二叉搜索树
        boolean rightIsBST;
        if (leftChildRootIndex + 1 == rightChildRootIndex) {    // 右子树只有一个节点,即 右子树的根节点没有子节点
            rightIsBST = true;
        } else {                                                // rightChildRootIndex > leftChildRootIndex + 1 即 右子树的根节点有子节点
            rightIsBST = isPostOrderOfBinarySearchTree(seq, leftChildRootIndex + 1, rightChildRootIndex);
        }

        return leftIsBST && rightIsBST;
    }


    /**
     * 将二叉搜索树转换为一个排序的双向链表,且不可以创建任何新的节点,只能调整树中节点指针的指向。
     * 思路:
     *  1)在转换成排序双向链表时,原先指向左子结点的指针调整为链表中指向前一个结点的指针,原先指向右子结点的指针调整为链表中指向后一个结点指针。
     *  2)中序遍历算法的特点是按照从小到大的顺序遍历二叉树的每一个结点。故在遍历的过程中进行转换。
     *
     * @param root
     */
    public static BinaryTree convertToLinkedListWithBST(BinaryTree root) {

        // 用于保存递归处理过程中,双向链表的尾节点
        BinaryTree[] lastNodeContainer = new BinaryTree[1];
        convertNode(root, lastNodeContainer);

        // 根据双向链表的尾节点得到双向链表的头节点
        BinaryTree head = lastNodeContainer[0];
        while (null != head && head.leftChild != null) {
            head = head.leftChild;
        }
        return head;
    }


    /**
     * 将指向左子结点的指针调整为链表中指向前一个结点的指针,将指向右子结点的指针调整为链表中指向后一个结点指针。
     *
     * 注意:
     *  1)这里使用了一个容量为1的数组来存储在递归处理过程中不断更新的双向链表的尾节点。
     *  2)不能使用 BinaryTree lastNode 代替 BinaryTree[] lastNodeContainer 来存储不断更新的双向链表的尾节点。
     *      1>每调用一次方法,都会在栈中新建一个局部变量(即该方法的形参),方法调用结束后这个局部变量(即形参)就被回收了。
     *      2>使用数组的好处是,方法调用结束后,数组的引用(即形参)虽然被回收了,但是堆中的数组对象并没有被回收,并且数组对象的内容已经被更新过了。
     *
     * @param root
     * @param lastNodeContainer
     */
    public static void convertNode(BinaryTree root, BinaryTree[] lastNodeContainer) {

        if (root != null) {

            // 如果左子树不为空则先将左子树转换为双向链表
            if (null != root.leftChild) {
                convertNode(root.leftChild, lastNodeContainer);
            }

            // 将当前节点的前驱节点指向已经转换完成的双向链表。
            root.leftChild = lastNodeContainer[0];

            // 如果左子树不为空,则设置(由左子树转换而来的)双向链表的后继节点。
            if (null != lastNodeContainer[0]) {
                lastNodeContainer[0].rightChild = root;
            }

            // 将当前节点设为双向链表的尾节点
            lastNodeContainer[0] = root;

            // 如果右子树不为空,则将右子树转换为双向链表
            if (null != root.rightChild) {
                convertNode(root.rightChild, lastNodeContainer);
            }
        }
    }



    /**
     * 判断指定的二叉搜索树是否是平衡二叉树
     *
     * 思路:
     *  根据平衡二叉树的特点来实现该方法:
     *      1)平衡二叉树是二叉查找树的一种。
     *      2)每个结点的左子树和右子树都是平衡二叉树。
     *      3)每个结点的左子树和右子树之间的高度差不超过1,即每个结点的平衡因子的值只能是-1、0或1。
     *
     * @param root  BST的根节点
     * @return
     */
    public static boolean isBalance(BinaryTree root) {

        // 用于保存递归处理过程中,当前节点的高度。
        int[] height = new int[1];

        return isBalanceByRecursion(root, height);
    }

    /**
     * 思路:
     *  1)使用后续遍历的方式来遍历二叉树,同时记录当前节点的高度。
     *  2)因为是后续遍历,故我们在遍历当前节点前就已经知道:左子树和右子树是否是平衡的 以及 左子树和右子树的高度分别是多少。
     *
     * @param root
     * @param height
     * @return
     */
    public static boolean isBalanceByRecursion(BinaryTree root, int[] height) {

        if (null == root) {
            height[0] = 0;
            return true;
        }

        int[] leftHeight = new int[1];      // 用于保存递归处理过程中,当前节点左子树的高度。
        int[] rightHeight = new int[1];     // 用于保存递归处理过程中,当前节右子树点的高度。

        // 若当前结点的左子树和右子树都是平衡二叉树,且左子树和右子树之间的高度差不超过1,则以当前节点为根节点的二叉树是平衡的,返回true
        if (isBalanceByRecursion(root.leftChild, leftHeight)
                && isBalanceByRecursion(root.rightChild, rightHeight)
                && Math.abs(leftHeight[0] - rightHeight[0]) <= 1) {
            height[0] = leftHeight[0] > rightHeight[0] ? leftHeight[0] + 1 : rightHeight[0] + 1;
            return true;
        } else {
            return false;
        }
    }


    public static void printNode(BinaryTree node) {
        System.out.println(node.value);
    }


    /**
     * 树的初始化:先从叶子节点开始,由叶到根
     */
    public static BinaryTree getBinaryTree() {

        BinaryTree leaf1 = new BinaryTree(11, null, null);
        BinaryTree leaf2 = new BinaryTree(12, null, null);
        BinaryTree firstMidNode1 = new BinaryTree(21, leaf1, leaf2);

        BinaryTree leaf3 = new BinaryTree(13, null, null);
        BinaryTree leaf4 = new BinaryTree(14, null, null);
        BinaryTree firstMidNode2 = new BinaryTree(22, leaf3, leaf4);

        BinaryTree secondMidNode1 = new BinaryTree(31, firstMidNode1, firstMidNode2);
        BinaryTree leaf5 = new BinaryTree(32, null, null);

        BinaryTree root = new BinaryTree(41, secondMidNode1, leaf5);
        return root;
    }

    /**
     * 二叉搜索树的初始化:先从叶子节点开始,由叶到根
     */
    public static BinaryTree getBST() {

        BinaryTree leaf1 = new BinaryTree(11, null, null);
        BinaryTree leaf2 = new BinaryTree(22, null, null);
        BinaryTree firstMidNode1 = new BinaryTree(21, leaf1, leaf2);

        BinaryTree leaf3 = new BinaryTree(35, null, null);
        BinaryTree leaf4 = new BinaryTree(39, null, null);
        BinaryTree firstMidNode2 = new BinaryTree(37, leaf3, leaf4);

        BinaryTree secondMidNode1 = new BinaryTree(31, firstMidNode1, firstMidNode2);
        BinaryTree leaf5 = new BinaryTree(52, null, null);

        BinaryTree rootOfBST = new BinaryTree(41, secondMidNode1, leaf5);
        return rootOfBST;
    }


    /**
     * 二叉搜索树的初始化:先从叶子节点开始,由叶到根
     */
    public static BinaryTree getBalanceTree() {

        BinaryTree leaf1 = new BinaryTree(11, null, null);
        BinaryTree leaf2 = new BinaryTree(22, null, null);
        BinaryTree firstMidNode1 = new BinaryTree(21, leaf1, leaf2);

        BinaryTree leaf3 = new BinaryTree(35, null, null);
        BinaryTree leaf4 = new BinaryTree(39, null, null);
        BinaryTree firstMidNode2 = new BinaryTree(37, leaf3, leaf4);

        BinaryTree root = new BinaryTree(31, firstMidNode1, firstMidNode2);
        return root;
    }

    public static List<Integer> getValueList() {
        ArrayList<Integer> resultList = new ArrayList<Integer>();
        for (BinaryTree node : list) {
            resultList.add(node.value);
        }
        return resultList;
    }

    public static void main(String[] args) {

        BinaryTree root = getBinaryTree();
//        preOrder(root);
//        inOrder(root);
//        postOrder(root);
//        preOrderNonRecursion(root);
//        inOrderNonRecursion(root);
//        postOrderNonRecursion(root);

        List<Integer> resultList = getValueList();

//        System.out.println("遍历的结果:" + resultList);
//        System.out.println("树的高度:" + getTreeDepth(root));

//        int[] preOrderArray = {1, 2, 4, 7, 3, 5, 6, 8};
//        int[] inOrderArray = {4, 7, 2, 1, 5, 3, 8, 6};
//
//        BinaryTree binaryTree = rebuildBinaryTree(preOrderArray, 0, preOrderArray.length-1, inOrderArray, 0, inOrderArray.length-1);
//        System.out.println("end");

//        breadthFirstSearch(root);
//        int[] postOrderArray = {5, 7, 6, 9, 11, 10, 8};
//        int[] postOrderArray = {5, 7, 6, 8};
//        int[] postOrderArray = {5, 7, 6, 9, 11, 3, 10, 8};
//        boolean result = isPostOrderOfBinarySearchTree(postOrderArray, 0, postOrderArray.length - 1);
//        System.out.println(result);

//        printPath(root, 104);

//        BinaryTree rootOfBST = getBST();
//        BinaryTree headOfLinkedList = convertToLinkedListWithBST(rootOfBST);

        boolean balance = isBalance(getBalanceTree());
        System.out.println(balance);
        System.out.println();

    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值