1二叉树的相关遍历操作:
递归遍历思路:二叉树是通过递归的方式定义的,如根---左---右,左和右又可能是另外一颗树的子树的根,然后继续递归下去,那么有关二叉树的遍历我们都可以通过递归来实现-也就是深度优先的dfs算法--->递推
所有的遍历操作都是单独得将-->根这个节点+左子树+右子树三者拆分出来分别操作;
1.1二叉树的前序遍历:
二叉树我们可以使用整体的思想---将一颗完整的二叉树划分为三大块---根--左子树---右子树,需要对整个二叉树进行某个操作就只需要对这三大块操作就行了---而且每块操作的逻辑都是一样的,要前序遍历整个树,我们知道前序遍历的逻辑是先遍历根节点--再遍历做孩子节点---再遍历右孩子节点那么我们就可以采用递归遍历的思想就等价于先遍历单独的前序遍历根这一个节点(将这一个节点看成一个树,然后前序遍历该树,由于该树只有根节点一个节点,无左右孩子节点,所以前序遍历结果就是根节点的val值)--------->然后前序遍历左子树------>最后前序遍历右孩子右子树.
代码实现:
public void preOrder(TreeNode root) {
if(root==null) return;
//先前序前序遍历根节点---根树---由于根树只有一个节点所以前序遍历结果就是这个节点
System.out.println(root.val);
//然后前序遍历左子树--左边这颗树可不敢直接打印--因为左 右树节点个数不一定是1个
//遍历一颗树的方法我们已经有了吗??---有了preOrder方法就是专门用来前序遍历一棵树的
preOrder(root.left);
//最后右子树
preOrder(root.right);
}
递归的每一步代码解析:
1.2中序遍历:
思想和前序遍历一样
//中序遍历
public void inOrder(TreeNode root){
if(root==null) return;
//先处理左子树
inOrder(root.left);
//在处理根
System.out.print(root.val);
//最后处理右子树
inOrder(root.right);
}
1.3后序遍历:
思想和前序遍历一样:
//后续遍历
public void postOrder(TreeNode root){
if (root==null) return;
postOrder(root.left);
postOrder(root.right);
System.out.println(root.val);
}
2求节点个数:
思路:两种思路核心都是递归实现
2遍历思路:通过遍历方式-每遍历得到一个就size++,返回最后的size即可
1子问题思路:树的节点个数=根节点(1)+左子树节点数+右子树节点数
代码实现:
2:
public void sizedemo(TreeNode root){
if(root==null) return;
size++;
sizedemo(root.left);
sizedemo(root.right);
}
public int size(TreeNode root){
sizedemo(root);
return size;
}
1:
public static int size =0;
public int size1(TreeNode root){
if(root==null) return 0;
return size1(root.left)+size1(root.right)+1;
}
3求叶子节点个数
叶子结点的left和right都为null,我们可以遍历的去寻找,当遍历到一个位置就去判断一下这个位置是否为叶子结点,如果是count++,也可以拆分为子问题思路--等价于求根树+左树+右树的叶子节点个数
代码:
/**
* 遍历思路
* @param root
* @return
*/
int count=0;
public void getLeafNodeCount(TreeNode root){
if(root==null) return;
if (root.left==null&&root.right==null) count++;
getLeafNodeCount(root.left);
getLeafNodeCount(root.right);
}
/**
* 子问题思路
*/
public int getLeafNodeCount1(TreeNode root){
if(root==null) return 0;
if(root.left==null&&root.right==null) return 1;
else return getLeafNodeCount1(root.left)+getLeafNodeCount1(root.right);
}
4求某层的节点数:
我们知道一棵树除了第一层是固定的一个节点外,其他的层的节点个数是随机不固定的,不好求,所以这里我们想着怎么样让求某层节点的问题统一转换成求某棵树第一层节点个数的问题,这样就是固定的1个节点,然后全部相加起来即可,首先来观察现象:
总结出来的规律就是: 也就是说求root的k层节点个数可转化成求左右孩子k-1层节点个数和的问题
很明显这样的一个将问题不断深入不断转换,求一棵树又得依赖求另外一棵树的过程就是递归的过程,所以我们可以通过递归思路来解决这个问题:
问题就转换成求左子树的第k-1层+右子树的第k-1层
/**
* 获取第k层节点个数
* @param root
* @param k
* @return
*/
public int getKLevelNodeCount(TreeNode root,int k){
int ret=0;
if (root==null) return 0;
if(k==1) return 1;
return getKLevelNodeCount(root.left,k-1)+getKLevelNodeCount(root.right,k-1);
}
5获取二叉树的高度:
思路:求左子树的高度和右子树的高度然后比个大小得出最大值然后+1就可得出输的高度
public int getHeight(TreeNode root) {
//转换成 左子树的高度vs右子树的高度--->最大值+1
if (root==null) return 0;
int left=getHeight(root.left);//左子树的高度
int right=getHeight(root.right);//右子树的高度
int maxHeight=Math.max(left,right);
return maxHeight+1;
}
6检测value值是否存在
可以使用前序遍历的思想:根左右的去遍历整个树,遍历到一个位置判断一下该位置是否是val,是就返回
// 检测值为value的元素是否存在
/**
* 相当于前序遍历--遍历到一个位置判断是否是val是就返回
* @param root
* @param val
* @return
*/
public TreeNode find(TreeNode root, int val){
if(root==null) return null;
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;
}
7层序遍历:
思想:层序遍历的要求是从上到下,然后每层从左到右,这里我们层序遍历需要借助的容器是队列
层序遍历顾名思义就是一层一层的遍历,我们的队列当中存着某层的元素,在我们正式poll这些元素之前我们需要先统计一下这一层的的元素个数(也就是队列poll之前的个数),因为我们在后续是边poll边把其下一层的元素也入队列,如果不标记一下个数,那么就无法知道poll多个元素然后收拾,导致遍历得到的某层元素多了或者少了,就是同层元素就得在一次性全部出队列
public List<List<Integer>> levelOrder(TreeNode root) {
//借助队列完成二叉树的层序遍历
List<List<Integer>> ret=new ArrayList<>();
if(root==null) return ret;
Queue<TreeNode> q=new LinkedList<>();
q.add(root);
while(!q.isEmpty()){//每一个while循环就是遍历了一层
int sz=q.size();//标记好这一层有多少个元素待会可以指定数量出队防止少出多出
List<Integer> tmp=new ArrayList<>();//当前容器存储当前层的结果
while(sz-->0){//一次循环出一个元素也就是遍历到该层的一个元素
TreeNode t=q.poll();
// tmp.add(t.val);
if(t.left!=null) q.add(t.left);
if(t.right!=null) q.add(t.right);
}
//tmp当中存的就是当前层的遍历结果
ret.add(tmp);
}
return ret;
}
8判断一棵树是不是完全二叉树:
完全二叉树: 除了最后一层外,其他的所有层节点都是满的,即抛开最后一层叶子结点看其他所有层的树都是满二叉树,就是说每一层都是满的,然后最后一层节点必须连续且靠左

像这种靠右的或者不连续的都不是完全二叉树
思路:根据二叉树的定义我们能够知道,如果层序遍历都到某个空节点后,如果后续还有节点那么它一定就不是完全二叉树了,如上图遍历到红节点后面就会出现空节点,但是后面还有节点,所以就不是.
此处层序遍历的细节就是不论你的左右是否为null我们都add到队列里
/**
* 思路:层序遍历二叉树,遇到第一个 null 节点后,后续所有节点必须为 null。
* 如果违反,则不是完全二叉树。
* @param root
* @return
*/
public boolean isCompleteTree(TreeNode root){
if(root==null) return true;
Queue<TreeNode> q=new LinkedList<>();
q.add(root);
boolean mark=false;
while(!q.isEmpty()){
TreeNode node=q.poll();
if(node==null){
mark=true;
}
else{
//每次遍历到一个不为空的位置是需要先去判断下前面是否已经出现过null了
if(mark){
//进来就说明前面已经出现过null了,然而你这里后续又存在一个不为空的位置--那么就不是完全二叉树了
return false;
}
q.add(node.left);
q.add(node.right);
}
}
return true;
}
9判断两棵二叉树是否相同:
思路:我们知道二叉树是通过递归构建的,那么对于有关二叉树的80%以上的问题我们都能使用递归来解决,我们可以将一棵树拆分成三部分 根----左----右----使用整体的眼观看待问题
public boolean isSameTree(TreeNode p, TreeNode q) {
//1`判断根
if(p==null&&q==null) return true;//如果是两空树那肯定相等
if(p!=null&&q!=null){//如果不为空,然后呢根值就直接不相等的,那肯定也不是相等的.直接返回false
if(p.val!=q.val){
return false;
}
}else return false;//这里就是一棵为null一棵不为空,那怎么可能相等对吧
//2`判断左子树和右子树
//代码如果能走到这里说明根树已经是没毛的了,相等的了,然后去判断左右子树是否一样即可
//如果一样那这里两棵树p q就一样了
return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);
}
10:另一棵树的子树
思路:还是拆分然后整体思想,一棵树拆分成根树(也就是这一整棵树,只不过左子树和右子树看成两块整体了),左子树,右子树,你另外一棵树要么是根树的子树,要么就是左子树,右子树的子树,一棵树是另一棵树的子树,这个子树要么就是和这棵树完全相等,那么就是这棵树的组成部分
首先:如果要是根树的是子树,那么你就得和这棵树完全相等,你是左右子树的子树的话,那么就只要求你这棵树是整棵树的某个组成部分即可.
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(root==null) return false;
//先处理根树---判断两根树是否相同
if(isSameTree(root,subRoot)) return true;
//处理左子树
boolean left=isSubtree(root.left,subRoot);
if(left) return left;
//处理右子树
boolean right=isSubtree(root.right,subRoot);
if(right) return right;
return false;
}
//判断两棵树是否相等的函数
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p==null&&q==null) return true;
if(p!=null&&q!=null){
if(p.val!=q.val){
return false;
}
}else return false;
return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);
}
11翻转二叉树:
思路:拆分二叉树然后递归即可
public TreeNode invertTree(TreeNode root) {
if(root==null) return null;
//先翻转根
TreeNode tmp=root.left;
root.left=root.right;
root.right=tmp;
//翻转左子树
invertTree(root.left);
//翻转右子树
invertTree(root.right);
return root;
}
12判断是否是平衡二叉树:
什么平衡二叉树:
首先左右子树的高度差<=1的树才能叫做二叉树,然后一整树得每个子树都满足这样的性质即每棵子树都是平衡二叉树
解法1:时间复杂度:o(n^2)
拆分递归,根+左子树+右子树是平衡二叉树,那么就是平衡二叉树
public int getHeight(TreeNode root) {
//转换成 左子树的高度vs右子树的高度--->最大值+1
if (root==null) return 0;
int left=getHeight(root.left);//左子树的高度
int right=getHeight(root.right);//右子树的高度
int maxHeight=Math.max(left,right);
return maxHeight+1;
}
public boolean isBalanced(TreeNode root) {
//左右子树高度差<=1---平衡二叉树--每个节点的左右子树也必须是平衡二叉树。
//一棵树是由根树和子树组成,必须满足根树和子树都是平衡二叉树-->这整棵树是平衡二叉树
if(root==null) return true;
int leftH=getHeight(root.left);
int rightH=getHeight(root.right);
return Math.abs(leftH-rightH)<=1&&isBalanced(root.left)&&isBalanced(root.right);
}
解法二:时间复杂度o(n)
解法1求高度使用了递归,然后判平衡时候又递归了,n*n=n^2,如果我们想要降低时间,就得从这两方面入手,首先我们判断是否是平衡二叉树求高度是少不了的,所以想从高度入手不太行,那么我们就想能否规避一些递归呢? 先来看一个案例:

也就是说压根就没有递归的必要了,左边求一遍高度,然后右边求一遍高度,根据返回的高度值,就可作出判断了
具体代码实现:
public boopublic boolean isSymmetric(TreeNode root){
if(root==null) return true;
//判断一棵二叉树是否对称--->看它的左右子树是否对称--->判断两棵树是否对称
return isSymmetricChild(root.left,root.right);
}
public boolean isSymmetricChild(TreeNode leftnode,TreeNode rightnode){
//判断两颗树是否对称
//先判断根是否一样
if(leftnode!=null&&rightnode==null||leftnode==null&&rightnode!=null) return false;
if (leftnode==null&&rightnode==null) return true;
if(leftnode.val!=rightnode.val) return false;
//根已经相等
//判断左右子树
return isSymmetricChild(leftnode.left,rightnode.right)&&isSymmetricChild(leftnode.right,rightnode.left);
}
13:对称二叉树:
思路:判断一棵二叉树是否对称等价于去看他的左右子树之间是否对称即可----所以我们就将判断一棵二叉树是否对称的问题转换成看两棵树之间是否对称的问题
代码:
public boolean isSymmetric(TreeNode root){
if(root==null) return true;
//判断一棵二叉树是否对称--->看它的左右子树是否对称--->判断两棵树是否对称
return isSymmetricChild(root.left,root.right);
}
public boolean isSymmetricChild(TreeNode leftnode,TreeNode rightnode){
//判断两颗树是否对称
//先判断根是否一样
if(leftnode!=null&&rightnode==null||leftnode==null&&rightnode!=null) return false;
if (leftnode==null&&rightnode==null) return true;
if(leftnode.val!=rightnode.val) return false;
//根已经相等
//判断左右子树
return isSymmetricChild(leftnode.left,rightnode.right)&&isSymmetricChild(leftnode.right,rightnode.left);
}
14创建二叉树
这个题目的关键是创建好这棵树,其他的中序遍历啥的都是次要的,创建二叉树我们也是根据递归先创建根---然后左子树----最后右子树
class TreeNode{
char val;
TreeNode left;
TreeNode right;
public TreeNode(char val){
this.val=val;
}
}
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNextLine()) { // 注意 while 处理多个 case
String str=in.nextLine();
TreeNode root=createTree(str);
//中序遍历二叉树
inOrderTree(root);
}
}
//创建二叉树---递归创建
public static int i=0;
public static TreeNode createTree(String str){
char ch=str.charAt(i);
TreeNode root=null;
if(ch!='#'){//不能拿#来创建树
//先创建根树
root=new TreeNode(ch);
//创建左树
i++;
root.left=createTree(str);//这里是递归调用方法,在方法里面有i++操作,所以这里的左右
//都不用去手动去i++了
//创建右树
root.right=createTree(str);
}else i++;//i移到新的位置--方便创建右子树和根树的时候i的更新
return root;
}
public static void inOrderTree(TreeNode root){
if(root==null) return;
inOrderTree(root.left);
System.out.print(root.val+" ");
inOrderTree(root.right);
}
}
15二叉树的最近公共祖先
解法1:
思路:拆分成 根 左 右 先判断p q是否在根处,如果其中的任意一个在根处,那么最近公共祖先就应该是这个根.-----然后处左右子树----去左边和右边分别去找找是否在p q ,存在的话返回位置,然后根据返回的情况分论讨论得出结果
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//递归判断每个节点是否是pq 是就返回该位置 -----根据返回的位置分论讨论的出结果
if(root==null||root==p||root==q){
return root;//只要pq在同一棵树,只要其中一个在该树根,那么就是树根
}
//递归终止条件---判断根是不是
//从左边找
TreeNode left =lowestCommonAncestor(root.left,p,q);
//从右边找
TreeNode right =lowestCommonAncestor(root.right,p,q);
//根据返回结果分情况讨论
if(left!=null&&right!=null) return root;//说明左右两边都有--则为root
//全部分布在根树的某一侧11
return left!=null ? left : right;
}
解法二:
思路:
先层序遍历去找p q 直到将p 和q 都找到了,在找的时候记录头节点(记录走过的路径),然后从pq 中任选一个往根走,记录走过的路径,看看该路径是否包含另外一个节点,如果包含就直接返回,不包含,就需要将将之前没被移动的节点也开始往上移动,直到遇到之前移动的节点停止,然后直接返回即可
public TreeNode lowestCommonAncestor1(TreeNode root, TreeNode p, TreeNode q) {
//先将从根到p q的链路径存到哈希表当中----找到p q位置之前一直记录当前节点的父亲节点
Map<TreeNode,TreeNode> map=new HashMap<>();
map.put(root,null);
//采用层序遍历方式寻找
Queue<TreeNode> qe=new LinkedList<>();
qe.add(root);
//只要hash表没有pq就一直找
while(!map.containsKey(p)||!map.containsKey(q)){
TreeNode node=qe.poll();
if(node.left!=null) {
qe.add(node.left);
map.put(node.left,node);
}
if(node.right!=null)
{
qe.add(node.right);
map.put(node.right,node);
}
}
//pq 任选一个往回移动并记录其走过的的路径---
Set<TreeNode> set=new HashSet<>();
while(q!=null){
set.add(q);
q=map.get(q);
}
//如果包含另外一个说明另外一个就是最近公共祖先
while(!set.contains(p))
{
//不包含的话就需要将另外一个往上继续移动---直到遇到
p=map.get(p);
}
return p;
}
16:根据二叉树的前序和中序遍历结果构建该原始的二叉树
首先我们总结一下这类题目的技巧:前 后序遍历可以推出根节点,中序遍历推出左右子树
举几个例子:
所以我们这里的思路就是模拟,将这个画的过程通过代码模拟出来即可
具体操作思路:
我们可以定义几个变量来描述我们的取值区间,构建左树的时候该去左边取值就去左边区间取值,该去右边取值的时候就去右边区间取值,避免拿错数据来构建树,先从前序遍历中取出一个数作为我们创建树的根,然后构建左子树,然后去中序遍历结果中找到这个数的位置position,那么我们构建左子树就得从这个position的左边去取数据然后建树,建右子树就得去position右边取数建树,当我们发现si>ei时候,就说明没有范围了,没有数据可取了,就说明下面的这棵树已经建好了,也就是当前节点就是叶子节点了.
public TreeNode buildTree(int[] preorder, int[] inorder) {
return buildTreeChild(preorder,inorder,0,inorder.length-1);
}
int preIndex=0;
public TreeNode buildTreeChild(int[] preorder, int[] inorder,int si,int ei){
//终止条件
TreeNode root=null;
if(ei<si) return null;
//创建根树
root=new TreeNode(preorder[preIndex]);
int ri=findval(inorder,si,ei,preorder[preIndex]);
preIndex++;
//创建左子树
root.left=buildTreeChild(preorder,inorder,si,ri-1);
//创建右子树
root.right=buildTreeChild(preorder,inorder,ri+1,ei);
return root;
}
public int findval(int[] inorder,int si,int ei,int val){
for(int i=si;i<=ei;i++){
if(inorder[i]==val) return i;
}
return -1;
}
17二叉树的前序遍历非递归实现
思路:借助栈这一容器实现,循环执行出栈,然后将出栈的左右孩子都入栈这一操作,直到栈为空结束,这里的细节就是一定得先入右子树,然后左子树,因为后进先出,左子树后进,先出左子树,达到一个根完然后左最后右的效果:
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ret=new ArrayList<>();
Stack<TreeNode> stack=new Stack<>();
stack.push(root);
if(root==null) return ret;
while(!stack.isEmpty()){
//循环出入栈操作直到栈为空
TreeNode node=stack.pop();
ret.add(node.val);
//这里注意一定是先入右边在入左边--因为后进先出--达到先左再右的前序效果
if(node.right!=null) stack.push(node.right);
if(node.left!=null) stack.push(node.left);
}
return ret;
}
18非递归实现中序遍历
思路:也是借助栈来实现,但是逻辑完全不一样,我们需要将一棵树中最左边的节点放到栈顶,才能放心的去pop元素出来,否则你pop出来的不一定正确,所以在pop之前我们需要加一层校验,如果栈顶不是最左边元素,就应该将最左边的入到栈顶为止,然后在去pop,同时需要将root维护到right位,然后循环上述操作即可,外层while循环中多加了一个条件root!=null 因为我们一开始并为向栈里加元素,栈一开始就是空的,如果少了root!=null,那么我们将无法进入while循环
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ret=new LinkedList<>();
Stack<TreeNode> stack=new Stack<>();
while(!stack.isEmpty()||root!=null){
//确保栈顶是最左边的元素
while(root!=null){
stack.push(root);
root=root.left;
}
//栈顶一定是也可以出的了
TreeNode node=stack.pop();
ret.add(node.val);
root=node.right;
}
return ret;
}
19非递归实现后序遍历
思路:双栈法:将主栈元素poll出来入到辅助栈,之后然后将该元素的左右孩子节点入到主栈,重复这样的操作即可
public List<Integer> postorderTraversal(TreeNode root) {
//思路:双栈法---一个栈存根节点---从主栈弹出节点入到辅助栈,同时将该节点的左右孩子节点入到主栈
//循环上述操作直到主栈为空.顺序pop辅助栈即可的到结果
Stack<TreeNode> stack1=new Stack<>();//主栈
Stack<TreeNode> stack2=new Stack<>();//辅助栈
stack1.push(root);
List<Integer> ret=new ArrayList<>();
if(root==null) return ret;
while(!stack1.isEmpty()){
TreeNode node=stack1.pop();
stack2.push(node);
if(node.left!=null) stack1.push(node.left);
if(node.right!=null) stack1.push(node.right);
}
//顺序的pop栈2即可
while(!stack2.isEmpty()){
ret.add(stack2.pop().val);
}
return ret;
}
20根据二叉树创建字符串
这个题目有点恶心,具体思路:还是拆分三块来研究这个问题根---左---右,就相当于从根拿出来一部分创建字符串,左右子树拿出一部分来创建左右,然后递归这个操作即可,只不过对于处理每个块的限制条件不一样,对于根没有什么条件,对于左子树只要左右子树别同时为空就行,对于右子树只要右边别为空就行,然后具体操作就是拼接"("+左边/右边+")"即可,这样操作得出来的结果就是对的,至于题目描述简直就是shi.不必纠结
public String tree2str(TreeNode root) {
if(root==null) return "";
//先处理根--对于根的处理没有任何条件--直接拼接即可
String ret=String.valueOf(root.val);
//处理左边---这个是有条件的---只要你这个根的左右子树别同时为空即可
if(root.left!=null||root.right!=null){
//处理操作
ret+="("+tree2str(root.left)+")";//相当于在处理左边
}
//处理右边---条件--->只要右边不为空就可以来处理
if(root.right!=null){
ret+="("+tree2str(root.right)+")";//相当于在处理右边
}
return ret;
}
总结:对于二叉树的问题,我们发现80%以上的问题我们都可以使用递归的思想来解决,毕竟二叉树而是通过递归来构建的,在这个递归过程中我们紧扣树的三大组成部分----根-左-右即可,结合子问题和整体思想即可

1771

被折叠的 条评论
为什么被折叠?



