二叉树part01
1.二叉树的遍历递归
二叉树的前序遍历
二叉树的中序遍历
二叉树的后序遍历
✅递归的一般步骤
1.确定递归函数的参数和返回值
返回值一般都是void,因为都把结果都放在参数里了。可以在写递归逻辑的过程中逐步确认需要用到的参数。
2.确定终止条件
报栈溢出:通常是终止条件没有确认好
3.确定单层递归的逻辑
在这里也就会重复调用自己来实现递归的过程。
✅“序”指的是根结点的顺序
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
✅Java函数传参,当参数为引用类型时,传递的是引用(地址),所以在方法中对形参所进行的修改,将影响到实参。
✅以前序遍历为例,其他类似
(1)终止条件是root==null,而非判断root.left或root.right,因为即使当root没有子节点,仍然需要被访问并添加;
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list=new LinkedList<>();
preorder(root,list);
return list;
}
public void preorder(TreeNode root, List<Integer> list){
if(root==null){
return;
}
list.add(root.val); //中
preorder(root.left, list); //左
preorder(root.right, list); //右
}
}
2.二叉树的迭代遍历
参考视频:清华大学邓俊辉数据结构与算法
✅前序遍历
最左侧通路(leftmost path):在二叉树T中,从根节点出发沿着左分支一直下行的那条通路(以粗线示意)。
前序遍历序列可分解为两段:
(1)沿最左侧通路自顶而下访问的各节点。
(2)以及自底而上遍历的对应右子树。
在写迭代算法时,结合直接后继的想法会更加清楚。在前序遍历中,节点的直接后继为左孩子,若不存在则为右孩子,若仍不存在则为父节点的右孩子(右兄弟节点)。----->所以才会将右孩子入栈
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list=new LinkedList<>();
LinkedList<TreeNode> stack=new LinkedList<>();
visitLefBranch(root,list,stack);
while(!stack.isEmpty()){
TreeNode node=stack.pop();
visitLefBranch(node,list,stack);
}
return list;
}
//从node节点出发,沿左分支不断深入,直至没有左分支的节点;沿途节点遇到后立即访问
public void visitLefBranch(TreeNode node, List<Integer> list, LinkedList<TreeNode> stack){
while(node!=null){
list.add(node.val);
//存储右孩子
if(node.right!=null){
stack.push(node.right);
}
//沿左侧分支向下
node=node.left;
}
}
}
✅中序遍历
沿用最左侧通路,只是略有不同。
中序遍历序列可分解为两段:
(1)沿最左侧通路自顶而下,找到最深的节点,并将沿途节点存入栈中。
(2)访问当前节点。
(3)遍历以其右孩子(若存在)为根的子树。
中序遍历的实质功能也可理解为,为所有节点赋予一个次序,从而将半线性的二叉树转化为线性结构。于是,用直接后继的方法更容易理解。在中序遍历(左根右)中,节点的直接后继为右孩子,若右孩子存在再沿该子树的最左侧通路朝左下方深入,直到抵达子树中最靠左(最小) 的节点即可。若不存在,则直接后继为节点的父节点。----将所有节点存入栈中(需要用到父节点)。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list=new LinkedList<>();
LinkedList<TreeNode> stack=new LinkedList<>();
visitLeftBranch(root,stack);
while(!stack.isEmpty()){
TreeNode node=stack.pop();
//访问当前节点
list.add(node.val);
//遍历右子树
if(node.right!=null){
visitLeftBranch(node.right,stack);
}
}
return list;
}
//深入最左侧通路
public void visitLeftBranch(TreeNode node,LinkedList<TreeNode> stack){
while(node!=null){
stack.push(node);
node=node.left;
}
}
}
✅后序遍历
最左侧可见通路(LR Branch) : 从左侧水平向右看去,未被遮挡的节点,即为后序遍历首先访问的节点。请注意,这些节点既 可能是左孩子,也可能是右孩子。
后序遍历序列可分解为三段:
(2)访问当前节点。
(3)遍历以其右孩子(若存在)为根的子树,沿最左侧可见通路自顶而下,找到最深的节点,并将沿途节点即其右孩子都存入栈中。
(4)回溯至父节点。
在后续遍历中,直接后继为右兄弟节点,若右兄弟节点存在则沿其最左侧可见通路遍历,若不存在则回溯至当前节点的父亲节点。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list=new LinkedList<>();
LinkedList<TreeNode> stack=new LinkedList<>();
visitLRBranch(root,stack);
while(!stack.isEmpty()){
TreeNode node=stack.pop();
//访问当前节点
list.add(node.val);
//栈顶不是当前元素的父节点而是右兄弟
if(!stack.isEmpty() && stack.peek().right!=node && stack.peek().left!=node){
//遍历右兄弟子树
visitLRBranch(stack.pop(),stack);
}
}
return list;
}
public void visitLRBranch(TreeNode node,LinkedList<TreeNode> stack){
while(node!=null){
stack.push(node);
//优先左孩子
if(node.left!=null){
//先push右子树(到时候访问顺序为左子树——》右子树——〉父亲节点
if(node.right!=null){
stack.push(node.right);
}
node=node.left;
}
else{
node=node.right;
}
}
}
}
在后序遍历的实现中,必须将右孩子入栈而不是依赖于父节点的右子树判断。是因为后序遍历的顺序是先访问左子树,然后是右子树,最后是根节点。如果仅依赖于父节点的右子树判断,那么在遍历过程中,我们无法准确地知道右子树是否已经被访问过,因为右子树可能已经被弹出栈外。
在我们的实现中,当栈顶元素不是当前节点的父节点时,说明当前节点已经完成了对其左子树和右子树的访问,可以访问当前节点本身了。
3.二叉树的层序递归
原题链接
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑。
巧妙利用一个整数len,来进行层级判定。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
int len;
List<List<Integer>> result=new LinkedList<>();
List<Integer> level=new LinkedList<>();
LinkedList<TreeNode> queue=new LinkedList<>();
if(root==null){
return result;
}
len=1;
queue.offer(root);
while(!queue.isEmpty()){
TreeNode node=queue.poll();
level.add(node.val);
if(node.left!=null){
queue.offer(node.left);
}
if(node.right!=null){
queue.offer(node.right);
}
len--;
if(len==0){
result.add(level);
len=queue.size();
level=new LinkedList<>();
}
}
return result;
}
}