树与二叉树数据结构详解

文章目录

  • 一、树的基本概念
    • 1.树的知识框架
    • 1.树的定义
    • 3.树的基本术语
    • 4.树的性质
    • 5.树的存储结构
  • 二、二叉树的操作
    • 1.二叉树的遍历
    • 2.二叉树的基本操作
  • 三、基础面试题
    • 1.二叉树的前序遍历
    • 2.二叉树的中序遍历
    • 3.二叉树的后序遍历
    • 4.检查两颗树是否相同
    • 5.另一颗树的子树
    • 6.二叉树最大深度
    • 7.判断一颗二叉树是否是平衡二叉树
    • 8.对称二叉树
    • 9.二叉树的镜像
  • 四、进阶面试题
    • 1.二叉树的构建及遍历
    • 2.二叉树的分层遍历
    • 3.给定一个二叉树, 找到该树中两个指定节点的最近公共祖先(LCA问题)
    • 4.二叉树搜索树转换成排序双向链表
    • 5.根据一棵树的前序遍历与中序遍历构造二叉树
    • 6.根据一棵树的中序遍历与后序遍历构造二叉树
    • 7. 二叉树创建字符串


一、树的基本概念

1.树的知识框架

树与二叉树数据结构详解_第1张图片

1.树的定义

树是n(n>=0)个结点的有限集。当n = 0时,称为空树。在任意一棵非空树中应满足:

  1. 有且仅有一个特定的称为根的结点。
  2. 当n>1时,其余节点可分为m(m>0)个互不相交的有限集T1,T2,…,Tm,其中每个集合本身又是一棵树,并且称为根的子树。

显然,树的定义是递归的,即在树的定义中又用到了自身,树是一种递归的数据结构。树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点:

  1. 树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
  2. 树中所有结点可以有零个或多个后继。

因此n个结点的树中有n-1条边。

3.树的基本术语

结合图示来说明一下树的一些基本术语和概念。
树与二叉树数据结构详解_第2张图片

  1. 考虑结点K。根A到结点K的唯一路径上的任意结点,称为结点K的祖先。如结点B是结点K的祖先,而结点K是结点B的子孙。路径上最接近结点K的结点E称为K的双亲,而K为结点E的孩子。根A是树中唯一没有双亲的结点。有相同双亲的结点称为兄弟,如结点K和结点L有相同的双亲E,即K和L为兄弟。
  2. 树中其中某个结点的孩子个数称为该结点的度,树中结点的最大度数称为树的度。如结点B的度为2,结点D的度为3(此结点度最大),树的度为3。
  3. 度大于0的结点称为分支结点(又称非终端结点);度为0(没有孩子结点)的结点称为叶子结点(又称终端结点)。在分支结点中,每个结点的分支数就是该结点的度
  4. 结点的深度、高度和层次。
    结点的层次从树根开始定义,根结点为第1层,它的子结点为第2层,以此类推。双亲在同一层的结点互为堂兄弟,图中结点G与E,F,H,I,J互为堂兄弟。
    结点的深度是从根结点开始自顶向下逐层累加的。它可以是某一层,例如结点F所在的深度为3,而L结点所在的深度为4。
    结点的高度是从叶结点开始自底向上逐层累加的。高度只讲的是树的高度,树的高度为4。
    树的高度(或深度)是树中结点的最大层数。图中树的高度为4。
  5. 有序树和无序树。**树中结点的各子树从左到右是有次序的,不能互换,称该树为有序树,否则称为无序树。**假设图为有序树,若将子结点位置互换,则变成一棵不同的树。
  6. 路径和路径长度。树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的,而路径长度是路径上所经过的边的个数。
    注意:由于树中的分支是有向的,即从双亲指向孩子,所以树中的路径是从上向下的,同一双亲的两个孩子之间不存在路径。
  7. 森林。森林是m (m≥0)棵互不相交的树的集合。森林的概念与树的概念十分相近,因为只要把树的根结点删去就成了森林。反之,只要给m棵独立的树加上一个结点,并把这m棵树作为该结点的子树,则森林就变成了树。一棵树也可以称作一个森林。

4.树的性质

二叉树的性质

注意其中两个性质:
1、
在这里插入图片描述
在这里插入图片描述
其中这两个性质是可以互相转化的,只要记住其中一个性质就行。

2、
在这里插入图片描述
这个性质可以推为:
1.已经父亲结点的下标为i,则其左孩子的结点为2i+1,右孩子的结点为2i+2。
2.已知孩子结点n,推父亲节点:(n-1)/2 。

面试题:

比如:假设一棵完全二叉树中总共有1000个节点,则该二叉树中多少个叶子节点,多少个非叶子节点,多少个节点只有左孩子,多少个节点只有右孩子。

答:因为该二叉树是一棵完全二叉树,所以不可能有结点是只有右孩子的,因此最后一个空为0。

再求出该二叉树一共有多少层,因为有1000个结点,又由log2的(n+1)向上取整得深度为10,但是第十层还没放满(放满的结点数为2的10次方-1)。我们还可以求出10层前的结点一共有多少个,由2的9次方-1得511,所以第十层放的是1000-511=489个结点。

因此第十层的489个结点都为叶子结点,但是不要忘了第十层是未放满的,因此推出第九层中也是有叶子结点的。将第十层的叶子结点除2,得第9层的非叶子结点为:489/2=244.5,说明有第九层非叶子结点中有一个结点是只有左节点的。第九层结点个数:2的(9-1)次方=256个。因此非叶子结点有245个。求得第九层的叶子结点为256-245=11个。所以一共加起来的叶子结点有11+489=500个。

又因为二叉树的结点数是由叶子结点和非叶子结点组成的,所以非叶子结点有1000-500==500个,叶子结点有500个,只有左孩子的只有1个,没有只有右孩子的结点。

5.树的存储结构

二叉树的链式存储是通过一个一个的节点引用起来的,常见的表示方式有二叉和三叉表示方式,具体如下:

// 孩子表示法
class Node {
 int val; // 数据域
 Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
 Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
}
// 孩子双亲表示法
class Node {
 int val; // 数据域
 Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
 Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
 Node parent; // 当前节点的根节点
}

其实二叉树如何表示,主要是看创建的TreeNode结点类是如何设置来存储二叉树中的结点的。不过大多数情况下都是采取孩子表示法来构建二叉树。

二、二叉树的操作

1.二叉树的遍历

所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问题(比如:打印节点内容、节点内容加1)。 遍历是二叉树上最重要的操作之一,是二叉树上进行其它运算之基础。
树与二叉树数据结构详解_第3张图片

在遍历二叉树时,如果没有进行某种约定,每个人都按照自己的方式遍历,得出的结果就比较混乱,如果按照某种规则进行约定,则每个人对于同一棵树的遍历结果肯定是相同的。如果N代表根节点,L代表根节点的左子树,R代表根节点的右子树,则根据遍历根节点的先后次序有以下遍历方式:

二叉树的遍历分为:
1.NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点—>根的左子树—>根的右子树。
2.LNR:中序遍历(Inorder Traversal)——根的左子树—>根节点—>根的右子树。
3.LRN:后序遍历(Postorder Traversal)——根的左子树—>根的右子树—>根节点。

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

树与二叉树数据结构详解_第4张图片

2.二叉树的基本操作

import java.util.*;

class TreeNode {
    public char val;
    public TreeNode left;
    public TreeNode right;

    public TreeNode(char val) {
        this.val = val;
    }
}
public class BinaryTree {
    public TreeNode creatTree() {
        TreeNode A = new TreeNode('A');
        TreeNode B = new TreeNode('B');
        TreeNode C = new TreeNode('C');
        TreeNode D = new TreeNode('D');
        TreeNode E = new TreeNode('E');
        TreeNode F = new TreeNode('F');
        TreeNode G = new TreeNode('G');
        TreeNode H = new TreeNode('H');
        A.left = B;
        B.left = D;
        B.right = E;
        E.right = H;
        A.right = C;
        C.left = F;
        C.right = G;
        return A;
    }

    // 前序遍历
    void preOrderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        System.out.print(root.val + " ");
        preOrderTraversal(root.left);
        preOrderTraversal(root.right);
    }

    // 中序遍历
    void inOrderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        inOrderTraversal(root.left);
        System.out.print(root.val + " ");
        inOrderTraversal(root.right);
    }

    // 后序遍历
    void postOrderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        postOrderTraversal(root.left);
        postOrderTraversal(root.right);
        System.out.print(root.val + " ");
    }

    // 遍历思路-求结点个数
    static int size = 0;

    void getSize1(TreeNode root) {
        if (root == null) {
            return;
        }
        getSize1(root.left);
        getSize1(root.right);
        size++;
    }

    // 子问题思路-求结点个数
    int getSize2(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return getSize2(root.left) + getSize2(root.right) + 1;
    }

    // 遍历思路-求叶子结点个数
    static int leafSize = 0;

    void getLeafSize1(TreeNode root) {
        if (root == null) {
            return;
        }
        if (root.left == null && root.right == null) {
            leafSize++;
        }
        getLeafSize1(root.left);
        getLeafSize1(root.right);
    }

    // 子问题思路-求叶子结点个数
    int getLeafSize2(TreeNode root) {
        if (root == null) {
            return 0;
        }
        if (root.left == null && root.right == null) {
            return 1;
        }
        return getLeafSize2(root.left) + getLeafSize2(root.right);
    }

    // 子问题思路-求第 k 层结点个数
    int getKLevelSize(TreeNode root, int k) {
        if (root == null) {
            return 0;
        }
        if (k == 1) {
            return 1;
        }
        return getKLevelSize(root.left, k - 1) + getKLevelSize(root.right, k - 1);
    }

    // 获取二叉树的高度
    int getHeight(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftNode = getHeight(root.left);
        int rightNode = getHeight(root.right);
        return Math.max(rightNode, leftNode) + 1;
    }

    // 查找 val 所在结点,没有找到返回 null
// 按照 根 -> 左子树 -> 右子树的顺序进行查找
// 一旦找到,立即返回,不需要继续在其他位置查找
    TreeNode find(TreeNode root, char val) {
        if (root == null) {
            return null;
        }
        if (root.val == val) {
            return root;
        }
        TreeNode ret = find(root.left, val);
        if (ret != null) {
            return ret;
        }
        ret = find(root.right, val);
        if (ret != null) {
            return ret;
        }
        return null;
    }

    // 层序遍历
    void levelOrderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode top = queue.poll();
            System.out.print(top.val + " ");
            if (top.left != null) {
                queue.offer(top.left);
            }
            if (top.right != null) {
                queue.offer(top.right);
            }
        }
        System.out.println();
    }
    //层序遍历
    public List<List<Character>> levelOrder(TreeNode root) {
        List<List<Character>> list = new ArrayList<>();
        if (root == null) {
            return list;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            List<Character> list1 = new ArrayList<>();
            int size = queue.size();
            while (size != 0) {
                TreeNode top = queue.poll();
                list1.add(top.val);
                if (top.left != null) {
                    queue.add(top.left);
                }
                if (top.right != null) {
                    queue.add(top.right);
                }
                size--;
            }
            list.add(list1);
        }
        return list;
    }

    // 判断一棵树是不是完全二叉树
    boolean isCompleteTree(TreeNode root) {
        if (root == null) {
            return true;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode top = queue.poll();
            if (top != null) {
                queue.offer(top.left);
                queue.offer(top.right);
            } else {
                break;
            }
        }
        while(!queue.isEmpty()) {
            TreeNode cur = queue.peek();
            if(cur==null) {
                queue.poll();
            }else {
                return false;
            }
        }
        return true;
    }

    //求二叉树的左视图
    public void leftScenery(TreeNode root) {
        if(root==null) {
            return ;
        }
        List<List<TreeNode>> list = new LinkedList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()) {
            List<TreeNode> list1 = new LinkedList<>();
            int size = queue.size();
            while(size!=0) {
                TreeNode top = queue.poll();
                list1.add(top);
                if(top.left!=null) {
                    queue.offer(top.left);
                }
                if(top.right!=null) {
                    queue.offer(top.right);
                }
                size--;
            }
            list.add(list1);
        }
        for (List<TreeNode> e:list) {
            for (TreeNode p: e) {
                System.out.print(p.val+" ");
                break;
            }
        }
    }

    //二叉树的非递归先序遍历
    public void preOrderTraversalNot(TreeNode root) {
        if(root==null) {
            return;
        }
        TreeNode cur = root;
        Stack<TreeNode> stack = new Stack<>();
        while(cur!=null||!stack.empty()) {
            while(cur!=null) {
                stack.push(cur);
                System.out.print(cur.val+" ");
                cur=cur.left;
            }
            TreeNode top = stack.pop();
            cur=top.right;
        }
    }

    // 中序遍历
    public void inOrderTraversalNot(TreeNode root) {
        if(root==null) {
            return;
        }
        TreeNode cur = root;
        Stack<TreeNode> stack = new Stack<>();
        while(cur!=null||!stack.empty()) {
            while(cur!=null) {
                stack.push(cur);
                cur=cur.left;
            }
            TreeNode top = stack.pop();
            System.out.print(top.val+" ");
            cur=top.right;
        }
    }

    // 后序遍历非递归
    public void postOrderTraversalNot(TreeNode root) {
        if(root==null) {
            return;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        TreeNode pre = null;
        while(cur!=null||!stack.empty()) {
            while(cur!=null) {
                stack.push(cur);
                cur=cur.left;
            }
            cur=stack.peek();
            if(cur.right==null||cur.right==pre) {
                TreeNode top = stack.pop();
                System.out.print(top.val+" ");
                pre=cur;
                cur=null;
            }else {
                cur=cur.right;
            }
        }
    }
}

三、基础面试题

1.二叉树的前序遍历

对应leetcode题

思路1:递归:注意此题的返回值为List< Integer >,因此首先要创建一个该类型的list,不过结点有可能为空,则直接返回空的list。为了能够使用到方法的返回值,我们在进行递归的时候可以利用list的addAll方法将左和右递归所添加到新的list1和list2当中的数字。

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root==null) {
            return list;
        }
        list.add(root.val);
        List<Integer> list1 = preorderTraversal(root.left);
        list.addAll(list1);
        List<Integer> list2 = preorderTraversal(root.right);
        list.addAll(list2);
        return list;
    }
}

思路2:迭代:我们可以用栈来实现。先创建好list,判断root是否为空,为空则直接返回list;不为空则将root的元素添加到stack中。先直接让root到其二叉树的最左结点,如果一直不为空依次将经过的元素添加到栈当中。当root已经为空,则将top栈顶元素出栈,并且让root结点指向top结点的右子树。最外层循环的判断条件为root不为空和栈不为空。

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root==null) {
            return list;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while(cur!=null||!stack.empty()) {
            while(cur!=null) {
                stack.push(cur);
                list.add(cur.val);
                cur=cur.left;
            }
            TreeNode top = stack.pop();
            cur=top.right;
        }
        return list;
    }
}

2.二叉树的中序遍历

对应leetcode题
思路:和前序遍历的思路差不多。

代码一:递归

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root==null) {
            return list;
        }
        List<Integer> list1 = inorderTraversal(root.left);
        list.addAll(list1);
        list.add(root.val);
        List<Integer> list2 = inorderTraversal(root.right);
        list.addAll(list2);
        return list;
    }
}

代码二:迭代

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root==null) {
            return list;
        }
        Stack<TreeNode> stack = new Stack<>();
        while(root!=null||!stack.empty()) {
            while(root!=null) {
                stack.push(root);
                root=root.left;
            }
            TreeNode top = stack.pop();
            list.add(top.val);
            root=top.right;
        }
        return list;
    }
}

3.二叉树的后序遍历

对应leetcode题

代码1:递归

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root==null) {
            return list;
        }
        List<Integer> list1 = postorderTraversal(root.left);
        list.addAll(list1);
        List<Integer> list2 = postorderTraversal(root.right);
        list.addAll(list2);
        list.add(root.val);
        return list;
    }
}

代码2:迭代

思路:后序遍历的迭代跟前序和中序遍历的迭代不相同。因为后序遍历的顺序为左->右->根 。因此在root到达最左边时,不能急于直接将top栈顶元素出栈,而是当左树为空时将root变为栈顶元素,要判断该位置的右边是否为空和当右数不为空当出栈的元素为B时,它还会再次去循环上次的操作,会一直在B和E中死循环。具体可以下面这个例子来看。

当B右树不为空,则可以设置一个prev来标记。先将栈顶元素出栈,出栈的为E,则将E的值存起来,prev指向现在的cur结点处,即E。并且将cur置为空,避免了结点在B和E处的死循环。当然,如果右树为空的情况下,也可以是上面相同的操作,只不过是将两个操作步骤合并在一起。

树与二叉树数据结构详解_第5张图片

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }
        TreeNode cur = root;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode prev = null;
        while (cur != null || !stack.empty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            cur = stack.peek();
            if (cur.right == null || cur.right == prev) {
                stack.pop();
                res.add(cur.val);
                prev = cur;
                cur = null;
            } else {
                cur = cur.right;
            }
        }
        return res;
    }
}

4.检查两颗树是否相同

对应leetcode题

主要有三种情况:
第一:当两棵树的根均为空,则相同;
第二:当两棵树其中一个根为空,则不同;
第三:判断两棵树的根结点的值是否相同,不同则返回false

每一个子树都是相同的判断方法,因此只要将两棵树的左树对比、右树对比即可。

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p==null&&q==null) {
            return true;
        }
        if(p==null||q==null) {
            return false;
        }
        if(p.val!=q.val) {
            return false;
        }
        return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);
    }
}

5.另一颗树的子树

对应leetcode题

思路:在第四题判断两棵树是否相同的基础上来判断另一棵树是否是子树的问题。无非就是在该树的左树与另一棵树作对比,如果相同则返回true,若false则判断该树的右数是否有另一棵树,有则返回true;到最后判断了左树和右树都没有该子树,则返回false。

class Solution {
    public boolean isSametree(TreeNode p,TreeNode q) {
        if(p==null&&q==null) {
            return true;
        }
        if(p==null||q==null) {
            return false;
        }
        if(p.val!=q.val) {
            return false;
        }
        return isSametree(p.left,q.left)&&isSametree(p.right,q.right);
    }
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if(root==null&&subRoot==null) {
            return true;
        }
        if(root==null||subRoot==null) {
            return false;
        }
        if(isSametree(root,subRoot)) {
            return true;
        }
        if(isSubtree(root.left,subRoot)) {
            return true;
        }
        if(isSubtree(root.right,subRoot)) {
            return true;
        }
        return false;
    }
}

6.二叉树最大深度

思路:求左树的高度后,再求右树的高度,最后取其中最高的高度的树加上根的那一个高度则为整棵二叉树的高度。

class Solution {
    public int maxDepth(TreeNode root) {
        if(root==null) {
            return 0;
        }
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);
        return (leftDepth>rightDepth)?(leftDepth+1):(rightDepth+1);
    }
}

注意:一定要创建leftDepth和rightDepth变量来记录左右子树的高度,不要在三目运算符当中用直接递归来判断,否则会用到多次递归,时间复杂度高。

7.判断一颗二叉树是否是平衡二叉树

对应leetcode题
平衡二叉树条件:如果一棵树是平衡二叉树,那么它的子树均是平衡二叉树。平衡二叉树的定义:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

此题有两种思路。分别是向下递归O(N^2)和向上递归O(N)。

向下递归:
思路:首先从根开始判断,如果从根开始就不满足一棵平衡二叉树的定义(判断左树的高度和右树高度相减的绝对值是否小于2。),那么此树一定不是一棵平衡二叉树。满足则递归它的左右子树,并且是一样的判断方法。

总结:
1.求当前根的左树和右树的高度,计算相减后的绝对值。
2.如果相减后的绝对值没有超过2,只能暂时证明此树是平衡的。
3.继续判断根的左树和右树。

class Solution {
    public int maxDepth(TreeNode root) {
        if(root==null) {
            return 0;
        }
        int leftHeight=maxDepth(root.left);
        int rightHeight=maxDepth(root.right);
        return Math.max(leftHeight,rightHeight)+1;
    }
    public boolean isBalanced(TreeNode root) {
        if(root==null) {
            return true;
        }
        int leftHeight = maxDepth(root.left);
        int rightHeight = maxDepth(root.right);
        return 
        Math.abs(leftHeight-rightHeight)<2 &&isBalanced(root.left)&&isBalanced(root.right);
    }
}

向上递归:

思路:在Depth方法内部,由int leftDepth = Depth(root.left);递归到了树的最左结点并求出该结点的左树高度。再由int rightDepth = Depth(root.right);计算到了它右树的高度。最左结点的左树和右树一定为空,则加上该结点它的高度为1。以此类推,如果其中有一棵子树的左树和右树的高度差大于等于2,则整棵树一定不是一棵平衡二叉树,直接返回-1。因此在isBalanced方法里面直接判断最后的返回值是否大于等于0即可,为负则不是平衡二叉树。

class Solution {
    public int Depth(TreeNode root) {
        if(root==null) {
            return 0;
        }
        int leftDepth = Depth(root.left);
        int rightDepth = Depth(root.right);
        if(leftDepth>=0&&rightDepth>=0&&Math.abs(leftDepth-rightDepth)<2) {
            return (leftDepth>rightDepth)?(leftDepth+1):(rightDepth+1);
        }else {
            return -1;
        }
    }
    public boolean isBalanced(TreeNode root) {
        if(root==null) {
            return true;
        }
        return Depth(root)>=0;
    }
}

8.对称二叉树

对应leetcode题
思路跟判断两棵树是否相等相似,只不过此处换为判断子树的左子树的左和右子树的右、左子树的右和右子的左是否相等。

class Solution {
    public boolean isSametree(TreeNode p,TreeNode q) {
        if(p==null&&q==null) {
            return true;
        }
        if(p==null||q==null) {
            return false;
        }
        if(p.val!=q.val) {
            return false;
        }
        return isSametree(p.left,q.right)&&isSametree(p.right,q.left);
    }
    public boolean isSymmetric(TreeNode root) {
        if(root==null) {
            return true;
        }
        return isSametree(root.left,root.right);
    }
}

9.二叉树的镜像

对应leetcode题

思路:要将二叉树的结点进行交换,不要交换结点的值。再根处交换完左结点和右结点后,依次递归下去进行相同的步骤,并且交换后左右子树都要进行结点交换。

public class Solution {
    public TreeNode convert(TreeNode pCur) {
        if(pCur==null) {
            return null;
        }
        if(pCur.left==null&&pCur.right==null) {
            return pCur;
        }
        TreeNode tmp = pCur.left;
        pCur.left=pCur.right;
        pCur.right=tmp;
        convert(pCur.left);
        convert(pCur.right);
        return pCur;
    }
    public TreeNode Mirror (TreeNode pRoot) {
        // write code here
        if(pRoot==null) {
            return null;
        }
        if(pRoot.left==null&&pRoot.right==null) {
            return pRoot;
        }
        TreeNode root = convert(pRoot);
        return root;
    }
}

四、进阶面试题

1.二叉树的构建及遍历

树与二叉树数据结构详解_第6张图片

思路:根据先序遍历的字符串来构建出二叉树,再进行中序遍历来输出遍历结果。无非就是先根据先序遍历来一个个创建结点串联起来,并且用i来遍历字符串。
对应leetcode题
注:是一个ACM模式的题

import java.util.Scanner;
class TreeNode {
    public char val ;
    public TreeNode left;
    public TreeNode right;
    
    public TreeNode(char val) {
        this.val=val;
    }
}

public class Main {
    public static int i = 0 ;
    public static TreeNode inorderChild(String str) {
        if(str==null) {
            return null;
        }
        char ch = str.charAt(i);
        TreeNode root = null;
        if(ch!='#') {
            root = new TreeNode(ch);
            i++;
            root.left=inorderChild(str);
            root.right=inorderChild(str);
        }else {
            i++;
        }
        return root;
    }
    public static void inorder(TreeNode root) {
        if(root==null) {
            return ;
        }
        inorder(root.left);
        System.out.print(root.val+" ");
        inorder(root.right);
    }
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String str = scan.nextLine();
        TreeNode root = inorderChild(str);
        inorder(root);
    }
}

2.二叉树的分层遍历

对应leetcode题

思路:因为此题的返回类型为List>,那么我们就首先要对它进行创建list。如果root为空就直接返回。此题要用到队列,到此处后先将root入队,再将root出队后将其左右子树的根入队,除非左结点或者右节点为空不入队。又因为每一层的结点都要保存到List< Integer >类型的list1变量中。因此还要有一个while循环依次将每一层的元素入到List< Integer >类型的变量中。最外层循环判断条件为队列不为空,每一层循环结束后都要将list1加入到list当中。

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> list = new ArrayList<>();
        if(root==null) {
            return list;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()) {
            List<Integer> list1 = new ArrayList<>();
            int size = queue.size();
            while(size!=0) {
                TreeNode top = queue.poll();
                list1.add(top.val);
                if(top.left!=null) {
                   queue.offer(top.left);
                }
                if(top.right!=null) {
                   queue.offer(top.right);
                }
                size--;
            }
            list.add(list1);
        }
        return list;
    }
}

3.给定一个二叉树, 找到该树中两个指定节点的最近公共祖先(LCA问题)

对应leetcode题

思路:
情况1:
左右子树都有p和q,则它们的最近公共祖先就是root。
树与二叉树数据结构详解_第7张图片
情况2:
因为此题用递归,如果p和q都在根结点的左树中,则哪个先找到的就直接返回,并且作为它们的公共祖先。直接返回root。
树与二叉树数据结构详解_第8张图片

情况3:如果p和q都在根的右树,则判断方法跟情况1相同,哪个先找到的就直接返回root。
树与二叉树数据结构详解_第9张图片

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null) {
            return null;
        }
        if(root.val==p.val||root.val==q.val) {
            return root;
        }
        TreeNode leftNode = lowestCommonAncestor(root.left,p,q);
        TreeNode rightNode = lowestCommonAncestor(root.right,p,q);
        if(leftNode!=null&&rightNode!=null) {//左右树都有p和q
            return root;
        }
        if(leftNode!=null) {//在左树先找到的,右树找不到的,情况2
            return leftNode;
        }
        if(rightNode!=null) {//在右树找不到,左树找到,情况3
            return rightNode;
        }
        return null;//都不满足返回空,即没有p和q
    }
}

4.二叉树搜索树转换成排序双向链表

对应leetcode题
树与二叉树数据结构详解_第10张图片
搜索二叉树特点:
1.若它的左子树不为空,则左子树上所有结点的值都小于根结点。
2.若它的右子树不为空,则左子树上所有结点的值都大于根结点。
3.它的左右子树分别为二叉搜索树。
4.二叉搜索树的中序遍历是有序的。

思路:先遍历到二叉树的最左结点,另每个结点的左子树指向前一个结点,右子树指向后一个结点。此处要设置prev来指向前驱结点。若prev不为空,prev的右子树指向pCur,并且让prev指向pCur,再去处理pCur的右子树。也是相同的处理方式,则采用递归。

public class Solution {
    public TreeNode prev = null;
    public TreeNode convertTree(TreeNode pCur) {
        if(pCur==null) {
            return null;
        }
        convertTree(pCur.left);
        pCur.left = prev;
        if(prev!=null) {
            prev.right=pCur;
        }
        prev=pCur;
        convertTree(pCur.right);
        return pCur;
    }
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree==null) {
            return null;
        }
        TreeNode root = convertTree(pRootOfTree);
        while(root.left!=null) {
            root=root.left;
        }
        return root;
    }
}

5.根据一棵树的前序遍历与中序遍历构造二叉树

对应leetcode题

思路:根据前序遍历,可以确定整棵树的根为哪一个结点,就直接先创建好该结点。因为根可以在中序遍历中分为两部分,中序遍历的根的左边是根的左树,右边是根的右树。因此要先找到根在中序遍历的哪个位置。

但是要注意一个点。因为在根的左树中,每次递归下去会有rootIndex-1,inend不断减小;在根的右子树中,有rootIndex+1inbegin不断增大。因此会有inbegin>inend的情况,因此用此条件作为递归的结束条件。

class Solution {
    public int preIndex = 0 ;
    public TreeNode buildTreeChild(int[] preorder,int[] inorder,int inbegin,int inend) {
        if(inbegin>inend) {
            return null;
        }
        TreeNode root = new TreeNode(preorder[preIndex]);
        int rootIndex = findIndex(inorder,inbegin,inend,preorder[preIndex]);
        preIndex++;
        root.left=buildTreeChild(preorder,inorder,inbegin,rootIndex-1);
        root.right=buildTreeChild(preorder,inorder,rootIndex+1,inend);
        return root;
    }
    public int findIndex(int[] inorder,int inbegin,int inend,int key) {
        for(int i=0;i<=inend;i++) {
            if(inorder[i]==key) {
                return i;
            }
        }
        return -1;
    }
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder==null||inorder==null) {
            return null;
        }
        TreeNode root = buildTreeChild(preorder,inorder,0,inorder.length-1);
        return root;
    }
}

6.根据一棵树的中序遍历与后序遍历构造二叉树

对应leetcode题

class Solution {
    public int postIndex = 0 ;
    public TreeNode buildTreeChild(int[] inorder,int inbegin,int inend,int[] postorder) {
        if(inbegin>inend) {
            return null;
        }
        TreeNode root = new TreeNode(postorder[postIndex]);
        int rootIndex = findIndex(inorder,inbegin,inend,postorder[postIndex]);
        postIndex--; 
        root.right=buildTreeChild(inorder,rootIndex+1,inend,postorder);
        root.left=buildTreeChild(inorder,inbegin,rootIndex-1,postorder);
        return root;
    }
    public int findIndex(int[] inorder,int inbegin,int inend,int key) {
        for(int i=inbegin;i<=inend;i++) {
            if(inorder[i]==key) {
                return i;
            }
        }
        return -1;
    }
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(inorder==null||postorder==null) {
            return null;
        }
        postIndex = postorder.length-1;
        TreeNode root = buildTreeChild(inorder,0,inorder.length-1,postorder);
        return root;
    }
}

7. 二叉树创建字符串

对应leetcode题
树与二叉树数据结构详解_第11张图片
树与二叉树数据结构详解_第12张图片
结论:
1.当root不为空的情况下,先append它的值。
2.当左子树为空情况下,右子树不为空时,sb直接append“()”;当右子树为空时,什么都不做。当左子树不为空时,先append"(",再递归它的左子树,左子树递归完后append")" 。
3.当右子树为空,什么都不做,当右子树不为空,则先append"(",再递归它的右子树,右子树递归完后append")" 。

class Solution {
    public void tree2strTree(TreeNode root,StringBuilder sb) {
        if(root==null) {
            return;
        }
        sb.append(root.val);
        if(root.left==null) {
            if(root.right==null) {
                return;
            }else {
                sb.append("()");
            }
        }else {
            sb.append("(");
            tree2strTree(root.left,sb);
            sb.append(")");
        }
        if(root.right==null) {
            return;
        }else {
            sb.append("(");
            tree2strTree(root.right,sb);
            sb.append(")");
        }
    }
    public String tree2str(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        if(root==null) {
            return null;
        }
        tree2strTree(root,sb);
        return sb.toString();
    }
}

你可能感兴趣的:(算法,数据结构,数据结构,算法,java)