一、树的基本概念
1、树的定义
树是一种非线性的数据结构,它是由n ( n>=0)个有限结点组成一个具有层次关系的集合。 把它叫做树是因为它看 起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
它具有以下的特点:
- 有一个特殊的结点,称为根结点,根结点没有前驱结点
- 除根结点外,其余结点被分成M(M > 0)个互不相交的集合T1、T2、…、Tm,其中每一个集合 <= m) 又是一棵与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
- 树是递归定义的。
- 树形结构中,子树之间不能有交集,否则就不是树形结构
2、树的概念
- 结点的度:一个结点含有子树的个数称为该结点的度; 如上图: A的度为6
- 树的度:一棵树中,所有结点度的最大值称为树的度; 如上图:树的度为6
- 叶子结点或终端结点:度为0的结点称为叶结点; 如上图: B、C、 H、 I…等节点为叶结点
- 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图: A是B的父结点
- 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图: B是A的孩子结点 - 根结点:一棵树中,没有双亲结点的结点;如上图: A
- 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推
- 树的高度或深度:树中结点的最大层次; 如上图:树的高度为4
–以下了解即可–
- 非终端结点或分支结点:度不为0的结点; 如上图: D、 E、 F、G…等节点为分支结点
- 兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图: B、C是兄弟结点
- 堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图: H、 I互为兄弟结点
- 结点的祖先:从根到该结点所经分支上的所有结点;如上图: A是所有结点的祖先
- 子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙
- 森林:由m ( m>=0)棵互不相交的树组成的集合称为森林
3、树的表示形式
树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,实际中树有很多种表示方式,如: 双亲表示法,孩子表示法、孩子双亲表示法、孩子兄弟表示法等等。
我们这里就简单的了解其中最常用的孩子兄弟表示法。
class Node {
int value; // 树中存储的数据
Node firstChild; // 第一个孩子引用
Node nextBrother; // 下一个兄弟引用
}
4、树的应用
文件系统管理(目录和文件)
5、深度与广度
- 1、层序遍历–广度优先
- 2、前序遍历–深度优先
6、树的种类
- 1、二叉树(Binary Tree):每个节点最多含有两个子节点。
- 2、满二叉树(Full Binary Tree):每个不是尾节点的节点都有两个子节点。
- 3、完全二叉树(Complete Binary Tree):假设一个二叉树深度为d,除了d层外,其他各层的节点数量均已达到最大值,且第d层所有节点从左向右紧密排列。
- 4、排序二叉树(二叉搜索树 Binary Search Tree):在此树中,每个节点的数值比左子树上的每个节点都大,比所有右子树上的节点都小。
- 5、平衡二叉树(AVL Tree):任何节点的两棵子树的高度差不大于1的排序二叉树。
- 6、B树(B-Tree):B树和平衡二叉树一样,只不过它是一种多叉树。(一个节点的子节点数量可以超过2)
- 7、红黑树:一种自平衡二叉寻找树。
二、二叉树概念及特性
1、二叉树的概念
一棵二叉树是结点的一个有限集合,该集合:
- 或者为空
- 或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
从上图可以看出:
- 二叉树不存在度大于2的结点
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
注意:对于任意的二叉树都是由以下几种情况复合而成的:
2、两种特殊二叉树
- 满二叉树: 一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树。也就是说, 如果一棵 二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
- 完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完 全二叉树。
- 要注意的是:满二叉树是一种特殊的完全二叉树。
3、二叉树的性质
- 1、若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有(i>0)个结点
- 2、 若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 (k>=0)
- 3、对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0 =n2+1
- 4、具有n个结点的完全二叉树的深度k为log2(n+1)上取整
- 5、对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i 的结点有:
- 若i>0 ,双亲序号: (i-1)/2;i=0 ,i为根结点编号,无双亲结点
- 若2i+1<n,左孩子序号: 2i+1,否则无左孩子
- 若2i+2<n,右孩子序号: 2i+2,否则无右孩子
- 6、已知父亲节点的下标为i,左孩子节点下标:2i+1,右孩子下标:2i+2
- 7、已知孩子【左孩子/右孩子】节点下标是i,父亲节点的下标是(i-1)/2
证明度为0的节点比度为2的节点个数多一个:
- 1、一颗二叉树假设有N个节点,度为0的节点有n0,度为1的节点有n1,度为2的节点有n2,则:N=n0+n1+n2。
- 2、一颗二叉树总共有N-1=n1+2*n2条边。
- n0+n1+n2=1+n1+2*n2 ===》 n0=1+n2
4、二叉树的存储
二叉树的存储结构分为: 顺序存储和类似于链表的链式存储。
二叉树的链式存储是通过一个一个的节点引用起来的,常见的表示方式有二叉和三叉表示方式,具体如下:
// 孩子表示法
class Node {
int val; // 数据域
Node left; // 左孩子的引用 ,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用 ,常常代表右孩子为根的整棵右子树
}
// 孩子双亲表示法
class Node {
int val; // 数据域
Node left; // 左孩子的引用 ,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用 ,常常代表右孩子为根的整棵右子树
Node parent; // 当前节点的根节点
}
5、二叉树的遍历
遍历(Traversal):指沿着某条搜索路线,依次对树中每个结 点均做一次且仅做一次访问。
- 1、前序遍历:根左右
- 2、中序遍历:左根右
- 3、后序遍历:左右根
- 4、层序遍历:从左到右遍历每一层
// 前序遍历
void preOrder(Node root);
// 中序遍历
void inOrder(Node root);
// 后序遍历
void postOrde(Node root);
6、二叉树的构建
根据上图创建一个二叉树:
public class BinaryTree {
static class TreeNode{
public char val;
public TreeNode left;
public TreeNode right;
public TreeNode(char val){
this.val=val;
}
}
//这个二叉树的根节点
public TreeNode root;
//创建一颗树
public void createTree(){
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;
A.right=C;
B.left=D;
C.left=F;
C.right=G;
E.right=H;
this.root=A;
}
}
7、二叉搜索树
三、二叉树的基本操纵
1、二叉树的前序遍历
//前序遍历
void preOrder(TreeNode root){
if(root==null) return;
System.out.println(root.val+" ");
preOrder(root.left);
preOrder(root.right);
}
1、下面的代码是题目的解答,需要注意的是,要把List定义成全局的,而不能定义在方法当中,因为如果定义在方法当中,则递归的时候会重新创建List
2、这里不能返回null,必须返回ret,由测试用例可知
class Solution {
List<Integer> ret=new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
if(root==null)return ret;//这里不能返回null,必须返回ret,由测试用例可知
ret.add(root.val);
preorderTraversal(root.left);
preorderTraversal(root.right);
return ret;
}
}
2、二叉树的中序遍历
//中序遍历
void inOrder(TreeNode root){
if(root==null) return;
inOrder(root.left);
System.out.println(root.val+" ");
inOrder(root.right);
}
class Solution {
List<Integer> ret=new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root==null)return ret;
inorderTraversal(root.left);
ret.add(root.val);
inorderTraversal(root.right);
return ret;
}
}
3、二叉树的后序遍历
//后序遍历
void postOrder(TreeNode root){
if(root==null) return;
postOrder(root.left);
postOrder(root.right);
System.out.println(root.val+" ");
}
class Solution {
List<Integer> ret=new ArrayList<>();
public List<Integer> postorderTraversal(TreeNode root) {
if(root==null)return ret;
postorderTraversal(root.left);
postorderTraversal(root.right);
ret.add(root.val);
return ret;
}
}
4、二叉树的层序遍历
-
本题采用非递归的方式进行层序遍历。使用队列来实现。
-
先判断根节点是否为空,如果不为空就入队,接着再把根节点的左右子树入队,然后都出队,
//层序遍历
//可以引伸出的题目
//求树的最大宽度,求树的左视图
void levelOrder2(TreeNode root){
if(root==null)return;
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode cur=queue.poll();
System.out.println(cur.val+" ");//如果不为空,就放到队列里面,然后弹出去,在打印它
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.right!=null){
queue.offer(cur.right);
}
}
}
- 层序遍历的特殊用法:求一棵二叉树的左视图
【1】求二叉树的左视图
class Solution{
public List<Integer> rightSideView(TreeNode root){
List<Integer> res=new ArrayList<>();//res里面存放右视图的节点
if(root==null){
return res;//如果结点是空,直接返回null的res
}
Queue<TreeNode>queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size=queue.size();
for(int i=0;i<size;i++){ //遍历当前层的所有节点
TreeNode node=queue.poll();
if(node.left!=null){
queue.offer(node.left);//将当前节点的左孩子入队
}
if(node.right!=null){
queue.offer(node.right);//将当前节点的右孩子入队
}
if(i==size-1){ //将当前层的最后一个节点放入结果列表
res.add(node.val);
}
}
}
return res;
}
}
【2】求二叉树的最大宽度
在这里插入代码片
【3】层序遍历
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ret = new ArrayList<>();
if (root == null) return ret;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
List<Integer> row = new ArrayList<>();
while (size > 0) {
TreeNode cur = queue.poll();
size--;
row.add(cur.val);
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
ret.add(row);
}
return ret;
}
}
5、获取树中节点的个数
//子问题思路 获取树中节点个数
int size(TreeNode root){
if(root==null) return 0;
return size(root.left)+size(root.right)+1;
}
//获取树当中节点的个数:只要遍历到了节点就++
public static int nodeSize;
void size2(TreeNode root){
if(root==null) return;
nodeSize++;
size2(root.left);
size2(root.right);
}
6、求叶子节点个数
- 1、子问题思路:左树的叶子+右树的叶子=整棵树的叶子
- 2、遍历问题思路:遍历到叶子节点就++
//子问题 获取叶子节点的个数
int getLeafNodeCount(TreeNode root){
if(root==null)return 0;//说明无叶子节点
if(root.left==null && root.right==null)return 1;
return getLeafNodeCount(root.right)+getLeafNodeCount(root.left);
}
//获取树当中叶子节点的个数
public static int leafsize;
void getLeafNodeCount2(TreeNode root){
if(root==null) return;
if(root.left==null && root.right==null)leafsize++;
getLeafNodeCount2(root.left);
getLeafNodeCount2(root.right);
}
7、求第K层节点的个数
//求第K层节点的个数
int getKLevelNodeCount(TreeNode root,int k){
if(root==null)return 0;
if(k==1)return 1;
return getKLevelNodeCount(root.left,k-1)+getKLevelNodeCount(root.right,k-1);
}
8、获取二叉树的高度(最大深度)
分析:树的高度是【左树和右树高度的最大值】+1
//获取二叉树的高度
int getHeight(TreeNode root){
if(root==null)return 0;
return 1+getHeight(root.right)>getHeight(root.left)?getHeight(root.right):getHeight(root.left);
}
但是上面的代码在OS题目上面无法通过,超出了时间限制。改成一下代码即可:
//获取二叉树的高度
int getHeight(TreeNode root){
if(root==null)return 0;
int leftHeight=getHeight(root.left);
int rightHeight=getHeight(root.right);
return 1+leftHeight > rightHeight ? leftHeight : rightHeight;
}
9、查找当前树当做是否存在该节点
找根节点和左右子树中是否存在该节点,如果没有的话就返回null
//查找当前树当做是否存在该节点
TreeNode find(TreeNode root,int val){
if(root==null)return null;
if(root.val==val)return root;
TreeNode ret1=find(root.left,val);//用一个节点接收查找左子树得到的返回值
if(ret1!=null) return ret1;//说明在左子树当中找到这个值了
TreeNode ret2=find(root.right,val);//用一个节点接收查找右子树得到的返回值
if(ret2!=null) return ret2;//说明在右子树当中找到这个值了
return null;//说明在根节点和左右子树都没有找到这个结点
}
10、 判断一棵树是不是完全二叉树
// 判断一棵树是不是完全二叉树
boolean isCompleteTree(TreeNode root) {
if(root==null)return true;//空树也是一棵完全二叉树
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode cur=queue.poll();
if(cur!=null){
//这里和层序遍历不同,只要cur不是空的,则左右子节点都入队
queue.offer(cur.left);
queue.offer(cur.right);
}else{
break;
}
}
while(!queue.isEmpty()){
TreeNode cur=queue.peek();
if(cur!=null){
return false;
}else{
queue.poll();
}
}
return true;
}
11、总结
import com.sun.source.tree.Tree;
import java.util.LinkedList;
import java.util.Queue;
/**
* @author Susie-Wen
* @version 1.0
* @description:
* @date 2022/7/13 12:30
*/
public class BinaryTree {
static class TreeNode {
public char val;
public TreeNode left;//左孩子的引用
public TreeNode right;//右孩子的引用
public TreeNode(char val) {
this.val = val;
}
}
/**
* 创建一棵二叉树 返回这棵树的根节点
*
* @return
*/
public TreeNode createTree() {
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('H');
TreeNode G=new TreeNode('G');
TreeNode H=new TreeNode('H');
A.left=B;
A.right=C;
B.left=D;
B.right=E;
C.left=F;
C.right=G;
E.right=H;
return A;
}
// 前序遍历
public void preOrder(TreeNode root) {
if(root==null)return;
System.out.print(root.val+" ");
preOrder(root.left);
preOrder(root.right);
}
// 中序遍历
void inOrder(TreeNode root) {
if(root==null)return;
preOrder(root.left);
System.out.print(root.val+" ");
preOrder(root.right);
}
// 后序遍历
void postOrder(TreeNode root) {
if(root==null)return;
preOrder(root.left);
preOrder(root.right);
System.out.print(root.val+" ");
}
public static int nodeSize;
/**
* 获取树中节点的个数:遍历思路
*/
void size(TreeNode root) {
if(root==null)return;
nodeSize++;
size(root.left);
size(root.right);
}
/**
* 获取节点的个数:子问题的思路
*
* @param root
* @return
*/
int size2(TreeNode root) {
if(root==null)return 0;
return size2(root.left)+size2(root.right)+1;
}
/*
获取叶子节点的个数:遍历思路
*/
public static int leafSize = 0;
void getLeafNodeCount1(TreeNode root) {
if(root==null)return;
if(root.left==null&&root.right==null) {
leafSize++;
}
getLeafNodeCount1(root.left);
getLeafNodeCount1(root.right);
}
/*
获取叶子节点的个数:子问题
*/
int getLeafNodeCount2(TreeNode root) {
if(root==null)return 0;
if(root.left==null&&root.right==null)return 1;
return getLeafNodeCount2(root.left)+getLeafNodeCount2(root.right);
}
/*
获取第K层节点的个数
*/
int getKLevelNodeCount(TreeNode root, int k) {
if(root==null)return 0;
if(k==1)return 1;
return getKLevelNodeCount(root.left,k-1)+getKLevelNodeCount(root.right,k-1);
}
/*
获取二叉树的高度
时间复杂度:O(N)
*/
int getHeight(TreeNode root) {
if(root==null)return 0;
int leftHeight=getHeight(root.left);
int rightHeight=getHeight(root.right);
return 1+Math.max(leftHeight,rightHeight);
}
// 检测值为value的元素是否存在
TreeNode find(TreeNode root, char val) {
if(root.val==val)return root;
TreeNode leftNode=find(root.left,val);
if(leftNode!=null)return leftNode;
TreeNode rightNode=find(root.right,val);
if(rightNode!=null)return rightNode;
return null;
}
//层序遍历
void levelOrder(TreeNode root) {
if(root==null)return;
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode cur=queue.poll();
System.out.println(cur.val+" ");
if(cur.left!=null)queue.offer(cur.left);
if(cur.right!=null)queue.offer(cur.right);
}
}
// 判断一棵树是不是完全二叉树
boolean isCompleteTree(TreeNode root) {
if(root==null)return true;//空树也是一棵完全二叉树
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode cur=queue.poll();
if(cur!=null){
queue.offer(cur.left);
queue.offer(cur.right);
}else{
break;
}
}
while(!queue.isEmpty()){
TreeNode cur=queue.peek();
if(cur!=null){
return false;
}else{
queue.poll();
}
}
return true;
}
}
public class Test02 {
public static void main(String[] args) {
BinaryTree binaryTree=new BinaryTree();
BinaryTree.TreeNode root=binaryTree.createTree();
System.out.println("前序遍历");
binaryTree.preOrder(root);
System.out.println();
System.out.println("中序遍历");
binaryTree.inOrder(root);
System.out.println();
System.out.println("后序遍历");
binaryTree.postOrder(root);
System.out.println();
System.out.println("节点个数1:"+binaryTree.size2(root));
System.out.println("高度:"+binaryTree.getHeight(root));
System.out.println("层序遍历");
binaryTree.levelOrder(root);
System.out.println("是否是完全二叉树");
System.out.println(binaryTree.isCompleteTree(root));
}
}
四、二叉树相关的面试题练习
1、有关二叉树性质的题目
- 某二叉树共有 399 个结点 ,其中有 199 个度为 2 的结点 ,则该二叉树中的叶子结点数为 ( )
A 不存在这样的二叉树
B 200
C 198
D 199
解:B,对于二叉树来说,叶子节点的个数是度为2节点个数+1。
- 在具有 2n 个结点的完全二叉树中 ,叶子结点个数为 ( )
A n
B n+1
C n-1
D n/2
解:A,n0+n1+n2=2n,n0+n1+n0-1=2n,2*n0+n1=2n+1,则n0=n。
- 一个具有767个节点的完全二叉树 ,其叶子节点个数为 ()
A 383
B 384
C 385
D 386
解:B,n0+n1+n2=767,n0+n1+n0-1=767,n0*2+n1=768==>n1=0,n0=384
- 一棵完全二叉树的节点数为531个 ,那么这棵树的高度为 ( )
A 11
B 10
C 8
D 12
解:B,K=向上取整【log2(n+1)】
2、有关二叉树遍历的题目
- 某完全二叉树按层次输出 (同一层从左到右) 的序列为 ABCDEFGH 。该完全二叉树的前序序列为()
A: ABDHECFG
B: ABCDEFGH
C: HDBEAFCG
D: HDEBFGCA
解:A
- 二叉树的先序遍历和中序遍历如下:先序遍历: EFHIGJK;中序遍历: HFIEJKG.则二叉树根结点为()
A: E
B: F
C: G
D: H
解:A
- 设一课二叉树的中序遍历序列: badce ,后序遍历序列: bdeca ,则二叉树前序遍历序列为()
A: adbce
B: decab
C: debac
D: abcde
解:D
- 某二叉树的后序遍历序列与中序遍历序列相同 ,均为 ABCDEF ,则按层次输出(同一层从左到右)的序列为()
A: FEDCBA
B: CBAFED
C: DEFCBA
D: ABCDEF
解:A
3、检查两颗树是否相同
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
分析:可能出现的情况
- 1、递归的过程当中,一个为空,一个不为空。
- 2、都不为空,但两个值不一样。
使用前序遍历来检查:当根节点一样,并且左右子树也相同,则两棵树就相同。
这里使用的前序遍历,就是深度优先算法。
下面的if语句不能交换,如果第二个if和第三个if交换的话会出现空指针异常。
时间复杂度:O(min (m,n) ),因为当有一个节点不同的时候,就返回false了。
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p==null && q!=null || p!=null && q==null)return false;
if(p==null && q==null)return true;
if(p.val != q.val)return false;
// p!=null && q!=null && p.val!=q.val
return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
}
4、另一棵树的子树
分析:情况如下
- 1、先判断subRoot和root是不是两棵相同的树。
- 2、如果本身不是两棵相同的子树,那么判断当前的subRoot是不是root的左子树的子树。
- 3、判断是不是root.left的左子树的子树
时间复杂度:O(m*n)因为root的每一个节点都需要和subRoot的节点进行比较。
isSubtree方法当中,需要最先判断两棵树是否有一个是null,只要有一个为空,就返回false。
这里使用的方法isSameTree就是上一题当中的代码。
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(root==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;//都不满足
}
public boolean isSameTree(TreeNode p, TreeNode q){
if(p==null && q!=null || p!=null && q==null)return false;
if(p==null && q==null)return true;
if(p.val != q.val)return false;
// p!=null && q!=null && p.val!=q.val
return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
}
5、二叉树的最大深度
class Solution {
public int maxDepth(TreeNode root) {
if(root==null)return 0;
int leftHeight=maxDepth(root.left);
int rightHeight=maxDepth(root.right);
return leftHeight>rightHeight?leftHeight+1:rightHeight+1;
}
}
6、平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过 1 。
分析:
- root左树高度-root右树高度<=1 并且root的左树是平衡的,右树是平衡的。
- 遍历这棵二叉树的每个节点,判断每个节点的左右子树的高度差是否<=1
时间复杂度:O(n^2)
class Solution {
public boolean isBalanced(TreeNode root) {
if(root==null)return true;
if(Math.abs(maxDepth(root.left)-maxDepth(root.right))<=1
&& isBalanced(root.left) && isBalanced(root.right))return true;
return false;
}
public int maxDepth(TreeNode root) {
if(root==null)return 0;
int leftHeight=maxDepth(root.left);
int rightHeight=maxDepth(root.right);
return leftHeight>rightHeight?leftHeight+1:rightHeight+1;
//return Math.max(leftHeight,rightHeight)+1;
}
}
上面代码的复杂度太大,在面试的时候肯定不行的,因此需要改进。
时间复杂度:O(n)
class Solution {
public boolean isBalanced(TreeNode root) {
if(root==null)return true;
return maxDepth(root)>=0;
}
public int maxDepth(TreeNode root) {
if(root==null)return 0;
int leftHeight=maxDepth(root.left);
int rightHeight=maxDepth(root.right);
if(leftHeight>=0 && rightHeight>=0 &&
Math.abs(leftHeight-rightHeight)<=1)return Math.max(leftHeight,rightHeight)+1;
else return -1;//出现不平衡则最终高度会返回-1
}
}
7、对称二叉树
分析:要判断root这棵树是否对称,我们需要判断root的左树和右树是否的对称的。都要判断null。
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null)return true;
return isSymmetricChild(root.left,root.right);
}
private boolean isSymmetricChild(TreeNode leftTree,TreeNode rightTree){
if(leftTree==null && rightTree!=null || leftTree!=null && rightTree==null)return false;
if(leftTree==null && rightTree==null)return true;
if(leftTree.val!=rightTree.val)return false;
return isSymmetricChild(leftTree.left,rightTree.right) &&
isSymmetricChild(leftTree.right,rightTree.left);
}
}
8、二叉树遍历
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
- 先创建根节点,在创建左子树,最后创建右子树
import java.util.*;
class TreeNode{
public char val;
public TreeNode left;
public TreeNode right;
public TreeNode(char val){this.val=val;}
}
public class Main{
public int i=0;
//创建二叉树
public TreeNode createTree(String s){
TreeNode root=null;
if(s.charAt(i)!='#'){
root=new TreeNode(s.charAt(i));
i++;
root.left=createTree(s);
root.right=createTree(s);
}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);
while(scan.hasNextLine()){
String s=scan.nextLine();
Main m=new Main();
TreeNode root=m.createTree(s);
m.inorder(root);
}
}
}
9、公共祖先问题
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
分析:
- 1、假设这棵树,每个节点还包含双亲的信息。此时如果需要求最近公共祖先,实际上求的是两个链表的交点。
- 2、假设这是一颗二叉搜索树(排序二叉树)
【1】思路一
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null)return null;
if(root==p || root==q)return root;
TreeNode retleft=lowestCommonAncestor(root.left,p,q);
TreeNode retright=lowestCommonAncestor(root.right,p,q);
if(retleft !=null && retright !=null)return root;
else if(retleft!=null)return retleft;
else return retright;
}
}
【2】思路二
把从根节点到指定节点的路径存储到栈中: 遇到节点就放到栈里面,然后判断放到栈中的元素是否是指定节点,如果不是指定节点的话,就递归左子树和右子树,如果都没有的话就都出栈。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null || p==null || q==null)return null;
Stack<TreeNode> stack1=new Stack<>();
getPath(root,p,stack1);
Stack<TreeNode> stack2=new Stack<>();
getPath(root,q,stack2);
int size1=stack1.size();
int size2=stack2.size();//比较两个栈谁大谁小,多元素的那个栈要先出栈一些元素,直到所有的元素都相等
if(size1>size2){
int tmp=size1-size2;
while(tmp!=0){
stack1.pop();
tmp--;
}
}else{
int tmp=size2-size1;
while(tmp!=0){
stack2.pop();
tmp--;
}
}
//此时两个栈当中,元素个数一样的
while(!stack1.empty() && !stack2.empty()){
if(stack1.peek() == stack2.peek()){
return stack1.peek();
}else{
stack1.pop();
stack2.pop();
}
}
return null;//没有公共祖先
}
//找到根节点到指定节点node之间路径上的所有节点,存储到stack当中。
private boolean getPath(TreeNode root,TreeNode node,Stack<TreeNode> stack){
if(root==null || node==null)return false;
stack.push(root);
if(root==node)return true;
boolean ret1=getPath(root.left,node,stack);
//此时还不能直接判断false,因为此时只能证明左边不存在
if(ret1==true)return true;
boolean ret2=getPath(root.right,node,stack);
if(ret2==true)return true;
//根节点和左右子树都不是,因此出栈
stack.pop();
return false;
}
}
10、二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个 排序 的双向链表。
分析:
- 1、排序问题:要使得二叉搜索树有序,则进行中序遍历,因为中序遍历可以使得二叉搜索树是有序的。因此本题的解法就是使用中序遍历。
- 2、变成双向链表:前驱和后继。
- 3、空间复杂度O(1)(即在原树上操作),时间复杂度 O(n)
- 4、返回值是双向链表的头结点,而最终得到的
pRootOfTree
指向的是二叉搜索树的根节点,因此还要向左遍历,直到找到头结点。
public class Solution {
public TreeNode prev=null;
public void ConvertChild(TreeNode root){
//中序遍历
if(root==null)return;
ConvertChild(root.left);
// System.out.println(root.val+" ");
root.left=prev;
if(prev!=null){
prev.right=root;
}
prev=root;
ConvertChild(root.right);
}
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree==null)return null;
ConvertChild(pRootOfTree);
//向左遍历找到双向链表的头结点
TreeNode head=pRootOfTree;
while(head.left!=null){
head=head.left;
}
return head;
}
}
11、从前序与中序遍历序列构造二叉树
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
题目》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》
分析:
- 1、把前序遍历的值先构建根节点。
- 2、在中序遍历数组中,找到根节点的位置ri。
- 3、分别构建root的左树和右树。
class Solution {
public int preIndex=0;
private TreeNode buildTreeChild(int[] preorder,int[] inorder,int inbegin,int inend){
if(inbegin>inend)return null;//没有左树或者没有右树
TreeNode root=new TreeNode(preorder[preIndex]);//构建根节点
//找到当前根节点在中序遍历中的位置
int rootIndex=findInorderIndex(inorder,preorder[preIndex],inbegin,inend);
preIndex++;
root.left=buildTreeChild(preorder,inorder,inbegin,rootIndex-1);
root.right=buildTreeChild(preorder,inorder,rootIndex+1,inend);
return root;
}
private int findInorderIndex(int[] inorder,int val,int inbegin,int inend){
for(int i=inbegin;i<=inend;i++){
if(inorder[i]==val)return i;
}
return -1;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
return buildTreeChild(preorder,inorder,0,inorder.length-1);
}
}
12、从中序与后序遍历序列构造二叉树
class Solution {
public int postIndex=0;
private TreeNode buildTreeChild(int[] postorder,int[] inorder,int inbegin,int inend){
if(inbegin>inend)return null;//没有左树或者没有右树
TreeNode root=new TreeNode(postorder[postIndex]);//构建根节点
//找到当前根节点在中序遍历中的位置
int rootIndex=findInorderIndex(inorder,postorder[postIndex],inbegin,inend);
postIndex--;
root.right=buildTreeChild(postorder,inorder,rootIndex+1,inend);
root.left=buildTreeChild(postorder,inorder,inbegin,rootIndex-1);
return root;
}
private int findInorderIndex(int[] inorder,int val,int inbegin,int inend){
for(int i=inbegin;i<=inend;i++){
if(inorder[i]==val)return i;
}
return -1;
}
public TreeNode buildTree(int[] inorder, int[] postorder) {
postIndex=postorder.length-1;
return buildTreeChild(postorder,inorder,0,inorder.length-1);
}
}
13、二叉树创建字符串
- 给你二叉树的根节点 root ,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。
- 空节点使用一对空括号对 “()” 表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。
class Solution {
public String tree2str(TreeNode root) {
StringBuilder sb=new StringBuilder();
tree2strChild(root,sb);
return sb.toString();
}
private void tree2strChild(TreeNode t,StringBuilder sb){
if(t==null)return;
sb.append(t.val);//把根节点放到字符串里面
if(t.left!=null){
sb.append("(");
//递归左树
tree2strChild(t.left,sb);
sb.append(")");
}else{
if(t.right==null){
return;
}else{
sb.append("()");//左边没有子树,右边有子树的情况
}
}
if(t.right==null){
return;
}else{
sb.append("(");
//递归右树
tree2strChild(t.right,sb);
sb.append(")");
}
}
}
14、二叉树前序非递归遍历实现
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
Stack<TreeNode> stack=new Stack<>();
List<Integer> ret=new ArrayList<>();
TreeNode cur=root;
while(cur!=null || !stack.empty()){
while(cur!=null){
stack.push(cur);
System.out.print(cur.val+" ");
ret.add(cur.val);
cur=cur.left;
}
//如果cur为空,就从栈当中弹出一个元素,赋给top,并遍历top的右边
TreeNode top=stack.pop();
cur=top.right;
}
return ret;
}
}
15、二叉树中序非递归遍历实现
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ret=new ArrayList<>();
Stack<TreeNode> stack=new Stack<>();
TreeNode cur=root;
while(cur!=null || !stack.empty()){
while(cur!=null){
stack.push(cur);
cur=cur.left;
}
//如果cur为空,就从栈当中弹出一个元素,赋给top,并遍历top的右边
TreeNode top=stack.pop();
ret.add(top.val);
cur=top.right;
}
return ret;
}
}
16、二叉树后序非递归遍历实现
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ret=new ArrayList<>();
Stack<TreeNode> stack=new Stack<>();
TreeNode prev=null;
TreeNode cur=root;
while(cur!=null || !stack.empty()){
while(cur!=null){
stack.push(cur);
cur=cur.left;
}
//如果cur为空,就从栈当中弹出一个元素,赋给top,并遍历top的右边
TreeNode top=stack.peek();
if(top.right==null || top.right==prev){//右边为空,或者右边已经打印过了
stack.pop();
ret.add(top.val);
prev=top;
}else{
cur=top.right;
}
}
return ret;
}
}
17、反转二叉树
可以参考第七题的对称二叉树
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root==null)return null;
return reverseTree(root);
}
public TreeNode reverseTree(TreeNode root){
if(root==null)return null;
TreeNode t=root.left;
root.left=root.right;
root.right=t;
reverseTree(root.left);
reverseTree(root.right);
return root;
}
}