二叉树的递归套路
Posted Mozartto
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉树的递归套路相关的知识,希望对你有一定的参考价值。
二叉树的递归套路
woc...现在在图书馆二楼,脚冻僵了...法克...
好,下面我来讲一下如何打印一个二叉树(不是你们想像的那种写个递归遍历代码两三行)
首先来看一下打印出来的二叉树是什么样子,横着的,每个孩子结点左右有箭头,箭头朝下代表右结点,箭头朝上代表左结点。H代表头结点
打印顺序是右头左。
public static void printTree(Node head) {
System.out.println("Binary Tree:")
printInOrder(head, 0, "H", 17);
System.out.println();
}
//String to参数的意思是当前结点的那个箭头
//height的意思是横着打印的时候结点前面预留的空格长度,空格长度与结点在树中的高度呈正相关
//len的意思是,不管长度是多少,我都搞出一个len长度的字符串打印出来,即统一认为值是17个空间
//我自己占多少位我预留好,前面空多少个空格出来跟我的高度相关
public static void printInOrder(Node head, int height, String to, int len) {
if(head == null) {
return;
}
printInorder(head.right, height + 1, "v", len);
String val = to + head.value + to;
int lenM = val.length(); //中间部分数字占的长度
int lenL = (len - lenM) / 2; //左侧部分占的长度
int lenR = len - lenM - lenL; //右侧部分占的长度
val = getSpace(lenL) + val + getSpace(lenR); //我这个值要占的总长度,左+值+右
System.out.println(getSpace(height * len) + val); //根据高度先在前面输出空格,再在后面补上我这个值(左+值+右)
//好累。。。不想写注释 //2021.1.21注释:啊这,你也太懒了吧,我现在写注释
//getSpace函数自己实现
}
题目:二叉树结构如下定义:
Class Node {
V value;
Node left;
Node right;
Node parent;
}
给你二叉树的某个结点,返回该结点的的后继结点(红黑树就是在普通二叉树上多加了一个指向父结点的指针)
后继结点:中序遍历结果中后面的那个结点,最后一个结点的后继结点是null
普遍的做法:先根据parent指针找到整棵树的头结点,然后根据头结点对整棵树进行中序遍历来寻找结点X的后继结点
不管是先序中序还是后序,本质上都是递归序(上一讲讲过),都是每一个结点到达三次,时间复杂度都是O(N)
假如我们有一种方法,能够直接找到一个结点的后继结点,如果此结点到其后继结点的距离为k,如果能把时间复杂度控制在O(k)那岂不是妙哉!
x的后继结点是其右树上的最左结点
找某个结点的中序遍历中的后继:
一直往上找,如果此结点是上一个结点的右孩子,就继续往上找,直到变成上一个结点的左孩子,上一个结点此时就是我们要找的后继
好困啊...不想更这个代码了。找中序遍历中的前驱结点也是这个思路,自己写。
下面来讲一个有意思的题:在国外流行了一年多,从微软开始流行,到Facebook等FLAG大厂也喜欢问。
请把一段纸条竖着放在桌子上,然后从纸条的下面向上方对折一次,压出折痕后展开。此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。如果从纸条的下边向上方连续对折两次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。
给定一个输入参数N,代表纸条从下边向上方连续对折N次,请从上到下打印所有折痕的方向。
有一个规律,你每次对折之后,在你前一次对折的折痕上方是凹折痕,下方是凸折痕
所以你想从上到下打印所有折痕,就是下图中那棵树的中序遍历
例如:N = 1时,打印down。N = 2时,打印down down up
就是一个二叉树的中序遍历,代码很简单,如下:
二叉树的递归套路
可以解决面试中绝大多数的二叉树问他尤其是树型dp问题,绝大多数大到什么程度,95%以上
本质是利用二叉树的便利性。本质就是在树上做动态规划。
给定一个二叉树的头结点head,返回这颗二叉树是不是平衡二叉树
public static class Info{
public Boolean isBalanced;
public int height;
public Info(bollean b, int h) {
isBalanced = b;
height = h;
}
}
//以X为头结点的树
public static Info process2(Node X) {
if(X == null) {
return new Info(true, 0); //空树直接返回(空树是平衡二叉树)
}
Info leftInfo = process2(X.left);
Info rightInfo = process2(X.right);
//左树高度是多少,左树是否平衡;右树高度是多少,右树是否平衡;两个信息都拽在手里
//接下来只需要再生成出我的两个信息再返回整个函数就结束了
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
boolean isBalanced = true;
if(!leftInfo.isBalanced || !rightInfo.isBalanced || Math.abs(leftInfo.height - right.height) > 1) {
isBalanced = false; //左树或右树不平衡,或左右两子树高度差超过1,将当前树改为不平衡
}
}
二叉树的递归套路
假设以X结点为头,假设可以向X左树和X右树要任何信息
在上一步的假设情况下,讨论以X为头结点的树,得到答案的可能性(很重要)
列出所有可能性后,确定到底需要向左树和右树要什么样的信息
把左树信息和右树信息求全集,就是任何一颗子树都需要返回的信息S
递归函数都返回S,每一棵子树都这么要求
写代码,在代码中考虑如何把左树的信息和右树的信息整合出整棵树的信息
例题:给定一棵二叉树的头结点head,任何两个结点之间都存在距离,返回整棵二叉树的最大距离
两个节点之间的距离就是两个结点之间的结点数
得到答案的可能性:
X左树上离自己最远的点(左树的高度)经过X到X右树上离自己最远的点,即左高+X自己+右高
左树上的最大距离与右树上的最大距离两个取其中之一的max
最大距离与头结点X无关
最大距离与头结点X有关
需要左右树分别提供其高度以及其最大距离
public static int maxDistance2(Node head) {
return process(head).maxDistance; //主函数只要你整棵树的最大距离
}
public static class Info {
public int maxDistance;
public int height;
public info(int dis, int h) {
maxDistance = dis;
height = h; //需要提供的1就这两个信息
}
}
//返回的是左右子树提供的信息
public static Info process(Node X) {
if(X == null) {
return new Info(0, 0);
}
Info leftInfo = process(X.left);
Info rightInfo = process(X.right);
//返回完了左右子树的信息,开始加工我自己的信息
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
int maxDistance = Math.max(
Math.max(leftInfo.maxDistance, rightInfo.maxDistance),
leftInfo.height + rightInfo.height + 1);
return new Info(maxDistance, height);
//加工完我自己的信息,就可以把左右子树的信息丢掉了。下一次之间给我的父结点返回我自己的信息。这就是动态规划的本质。跟跑个递归是一样的
}
例题:给定一棵二叉树的头结点head,返回这棵二叉树中最大的二叉搜索子树的头结点
搜索二叉树(BST):整棵树上没有重复值,左子树结点永远比当前结点值小,右子树结点永远比当前结点值大
大分类:
与X无关
就是你最终找的这棵搜索二叉树不以X为头
就是在左右子树上单独找满足条件的最大二叉搜索树
与X有关
左树和右树都是搜索二叉树
左max < x,右min > x,这些条件都必须全部满足,有一点破坏,就只存在情况1
需要左右子树提供的信息
左子树:最大搜索子树的size、boolean isAllBst、左子树上的最大值max
右子树:最大搜索子树的size、boolean isAllBst、有子树上的最小值min
这种属于左树上的要求和右树上的要求不一样的,但是我想一劳永逸,就是不用区分左右子树,所以你需要给我返回四个信息:
最大搜索子树的size
整体是否为搜索二叉树
整棵子树的最大值
整棵子树的最小值
所以求全集:
public static class Info {
public boolean isAllBst;
public int maxSubBstSize;
public int min;
public int max;
public Info(boolean isAllBst, int size, int min, int max) {
this.isAllBst = isAllBst;
this.maxSubBstSize = size;
this.min = min;
this.max = max;
}
}
public static Info process(Node head) {
if(X == null) {
return null;
}
Info leftInfo = process(X.left);
Info rightInfo = process(X.right);
//接下来加工完我自己的四个信息就可以返回了
int min = X.value;
int max = X.value;
if(leftInfo != null) {
min = Math.min(min, leftInfo.min);
max = Math.max(max, leftInfo.max);
}
if(rightInfo != null) {
min = Math.min(min, rightInfo.min);
max = Math.max(max, rightInfo.max);
}
//上面两个if是在左右子树中分别找出最大值和最小值,令其成为当前树的最大/最小值
//有关min和max的加工就加工完了
//先说满足条件1,与x无关的情况(不满足条件2则必满足条件1
int maxSubBstSize = 0;//先默认整棵树的最大二叉搜索树的大小是0
if(leftInfo != null) {
maxSubBstSize = leftInfo.maxSubSize;//先把左树的最大搜索子树值赋给当前树
}
if(rightInfo != null) {
maxSubBstSize = Math.max(maxSubBstSize, right.maxSubBstSize);
//比较右树的最大值和刚才取得的最大值
}
boolean isAllBst = false; //满足这个条件证明其满足条件2,否则满足条件1
//什么样的情况会成立条件2
if(
//左树整体是搜索二叉树
(leftInfo == null ? true : leftInfo.isAllBst) //这个地方容易误解,注意这个括号里只有判断条件,没有赋值语句
&&
//右树整体是搜索二叉树
(rightInfo == null ? true : rightInfo.isAllBst)
&&
//左树最大值小于X的值
(leftInfo == null ? true : leftInfo.max < X.value)
&&
//右树最大值大于X的值
(rightInfo == null ? true : rightInfo.max > X.value)
//以上四个条件头成立的情况下
) {
maxSubBstSize =
(leftInfo == null ? 0 : leftInfo.maxSubBstSize)
+
( rightInfo == null ? 0 : rightInfo.maxSubBstSize)
+
1;
isAllBst = true;
}
return new Info(isAllBst, maxSubBstSize, min, max)ñ
}
例题:派对的最大快乐值问题
//员工的信息定义如下(这是一棵多叉树)
class Employee {
public int happy; //这名员工可以带来的快乐值
List<Employee> nexts; //这名员工有哪些直接下级
}
//规定好不存在一个上级是另一个下级的下级的情况
//每一个人都只有唯一的一个直接上级
//所以整棵树可以写成一个多叉树结构
现在,公司要办party,你可以决定哪些员工来,哪些员工不来,规则:
如果某个员工来了,那么这个员工的所有直接下级都不能来
派对整体的快乐值是所有到场员工快乐值的累加
你的目标是让派对的整体快乐值尽量大
给定一棵多叉树的头结点boss,请返回派对的最大快乐值
假设以X为头结点,可以跟我的子树要信息,得到答案的可能性是什么?——X来和X不来。即不管你底下的树是什么样子,你发请柬的答案都可以分为两类,发了和没发。所有的答案都只能分为这两类
X来
需要获得的信息:x的happy值,以x的子结点为根的树的所有快乐值的总和
X不来
0 +
max(x子树a来的最大快乐值,a不来情况下的最大快乐值)
+
max(x子树b来的最大快乐值,b不来情况下的最大快乐值)
+
max(x子树c来的最大快乐值,c不来情况下的最大快乐值)
所以所有的子树需要返回两个信息
这棵子树头结点来的情况下它能带来的最大快乐值
这棵子树头结点不来的情况下它能带来的最大快乐值
public static info {
public int yes; //来时的最大快乐值
public int no; //不来时的最大快乐值
public Info(int yes, int no) {
this.yes = yes;
this.no = no;
}
//需要返回的信息:这个结点来和不来时能带来的快乐值
}
public static Info process2(Employee x) {
if(x.nexts.isEmpty()) { //最基层的员工
return new Info(x.happy, 0);
}
int yes = x.happy;
int no = 0;
for(Employee next : x.nexts) {
Info nextInfo = process2(next); //对每一个下级员工,我都调用递归函数要来它的信息
yes += nextInfo.no; //如果我的头结点来了,对每棵子树,我只能获得它不来状况下的最大快乐值
no += Math.max(nextInfo.yes, nextInfo.no);
//如果我不来,就把每棵子树来和不来都做max,累加到我的no上
//no的时候我的子树可来,可不来
}
return new Info(yes, no);
}
//复杂度是O(n),递归套路一个结点最多经过3次
高度结构化的coding,让你即便是个新手,也能把这个套路写出来。
本篇结束。
以上是关于二叉树的递归套路的主要内容,如果未能解决你的问题,请参考以下文章