赞
踩
满二叉树:深度为k,有2^k-1个节点的二叉树
完全二叉树:除了最底层可能没满,且都在靠左侧
二叉搜索树:二叉搜索树是一个有序树
平衡二叉搜索树(AVL树):一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
TreeSet集合是基于TreeMap的实现,而TreeMap是基于二叉树(红黑树)结构,也就是说TreeSet集合的底层使用的二叉树(红黑树)结构。
数组存储二叉树的遍历:如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
有两种遍历方式:
前中后,其实指的就是中间节点的遍历顺序。左和右指的是左子树和右子树!
栈其实就是递归的一种实现结构,前中后序遍历的逻辑其实都可以借助栈使用递归的方式来实现的。
广度优先遍历的实现一般使用队列来实现。
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
视频链接:https://www.bilibili.com/video/BV1Wh411S7xt/?vd_source=80cf8293f27c076730af6c32ceeb2689
讲解连接:https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%80%92%E5%BD%92%E9%81%8D%E5%8E%86.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE
对应题目:
这类问题一般都是传入一个根节点,要求返回一个存放遍历顺序的list。
递归问题按照三步走:
确定递归函数的参数和返回值
递归函数:void traversal(cur, list)
确定终止条件
什么时候终止遍历开始返回?遇到null
if(cur==null) return;
确定单层递归的逻辑
中:list.add(cur)
左:traversal(cur.left, list)
右:traversal(cur.right, list)
//144.前序遍历 class Solution { void transfer (TreeNode cur, List<Integer> res) { if(cur == null) return; res.add(cur.val); transfer(cur.left, res); transfer(cur.right, res); } public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); transfer(root, res); return res; } } //145.后序遍历 class Solution { void transfer (TreeNode cur, List<Integer> res) { if(cur == null) return; transfer(cur.left, res); transfer(cur.right, res); res.add(cur.val); } public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); transfer(root, res); return res; } } //94.中序遍历 class Solution { void transfer (TreeNode cur, List<Integer> res) { if(cur == null) return; transfer(cur.left, res); res.add(cur.val); transfer(cur.right, res); } public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); transfer(root, res); return res; } }
我们用栈来模拟迭代遍历。
一共分为两步操作:访问节点+处理节点
前序:中 左 右
注意空节点不入栈!
首先我们把5入栈。然后把5弹出,放进数组里。
然后把6 4入栈。为什么按照右 左的顺序入栈?–因为栈先进后出,要保证出栈的顺序是左 右!
然后把4弹出,(把4当做中)再处理4的左和右:把2 1入栈。然后弹出1,弹出2,弹出6。
//前序:中左右 入栈顺序:中右左 class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Stack<TreeNode> st = new Stack<>(); if(root == null) return res; st.push(root); //先把根节点放进去 while(!st.empty()) { //一次循环就是一次中左右的处理 TreeNode node = st.peek(); //保存一下栈顶元素(中) st.pop(); //先把中 出栈 res.add(node.val); if (node.right != null){ st.push(node.right); } if (node.left != null){ st.push(node.left); } } return res; } }
先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了,如下图:
//前序中左右--反转左右--变成中右左---整个res反转--左右中(后序) class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Stack<TreeNode> st = new Stack<>(); if(root == null) return res; st.push(root); //先把根节点放进去 while(!st.empty()) { //一次循环就是一次中左右的处理 TreeNode node = st.peek(); //保存一下栈顶元素(中) st.pop(); //先把中 出栈 res.add(node.val); if (node.left != null){ st.push(node.left); } if (node.right != null){ st.push(node.right); } } Collections.reverse(res); return res; } }
中序是左中右,先访问中,但是要先处理的是左。这就造成了访问顺序和处理顺序不一样!
那怎么办?
我们需要一个指针cur
帮助我们遍历二叉树,处理的时候把元素加入到res数组里。
要用栈来记录我们遍历过的顺序。因为在处理元素的时候其实是按遍历的顺序逆向输出的!
非空就入栈,指针往左走,当前为空了就出栈 同时输出到数组 往右走!
while(cur==null || stk.empty())
右孩子不为空,此时右孩子的这个点就当作根节点一样处理,是一棵新树,然后右孩子要入栈,接着像最开始一样继续找左,这个是迭代循环的关键
// 中序遍历顺序: 左-中-右 入栈顺序: 左-右 class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null){ return result; } Stack<TreeNode> stack = new Stack<>(); TreeNode cur = root; //cur从根节点开始 while (cur != null || !stack.isEmpty()){ //循环终止条件 if (cur != null){ //如果cur为空就入栈,同时往左 stack.push(cur); cur = cur.left; }else{ //cur不为空就出栈一个 同时令cur=出栈的 把val加入数组 同时往右遍历(把它当作根 开始新一轮) cur = stack.pop(); result.add(cur.val); cur = cur.right; } } return result; } }
中序遍历里,使用栈的话,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。
那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。
如何标记呢,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。
//中
st.push(node);
st.push(null);
//右
if (node.right!=null) st.push(node.right);
//左
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> result = new LinkedList<>(); Stack<TreeNode> st = new Stack<>(); if (root != null) st.push(root); while (!st.empty()) { TreeNode node = st.peek(); if (node != null) { st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中 if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈) if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈) st.push(node); // 添加中节点 st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。 } else { // 只有遇到空节点的时候,才将下一个节点放进结果集 st.pop(); // 将空节点弹出 node = st.peek(); // 重新取出栈中元素 st.pop(); result.add(node.val); // 加入到结果集 } }class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> result = new LinkedList<>(); Stack<TreeNode> st = new Stack<>(); if (root != null) st.push(root); while (!st.empty()) { TreeNode node = st.peek(); if (node != null) { st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中 if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈) st.push(node); // 添加中节点 st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。 if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈) } else { // 只有遇到空节点的时候,才将下一个节点放进结果集 st.pop(); // 将空节点弹出 node = st.peek(); // 重新取出栈中元素 st.pop(); result.add(node.val); // 加入到结果集 } } return result; } } return result; } }
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> result = new LinkedList<>(); Stack<TreeNode> st = new Stack<>(); if (root != null) st.push(root); while (!st.empty()) { TreeNode node = st.peek(); if (node != null) { st.pop(); // 将该节点弹出,避免重复操作,下面再将右左中节点添加到栈中 if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈) if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈) st.push(node); // 添加中节点 st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。 } else { // 只有遇到空节点的时候,才将下一个节点放进结果集 st.pop(); // 将空节点弹出 node = st.peek(); // 重新取出栈中元素 st.pop(); result.add(node.val); // 加入到结果集 } } return result; } }
class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> result = new LinkedList<>(); Stack<TreeNode> st = new Stack<>(); if (root != null) st.push(root); while (!st.empty()) { TreeNode node = st.peek(); if (node != null) { st.pop(); // 将该节点弹出,避免重复操作,下面再将中右左节点添加到栈中 st.push(node); // 添加中节点 st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。 if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈) if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈) } else { // 只有遇到空节点的时候,才将下一个节点放进结果集 st.pop(); // 将空节点弹出 node = st.peek(); // 重新取出栈中元素 st.pop(); result.add(node.val); // 加入到结果集 } } return result; } }
翻转数组:Collections.reverse(传一个list)
递归遍历按照三步走:
迭代遍历,先看前序。用栈来模拟迭代遍历。
一共分为两步操作:访问节点+处理节点
前序:中 左 右。但是按照右-左顺序入栈
后序就是交换左右顺序,然后整个res反转。
迭代遍历中序:需要一个指针cur
帮助我们遍历二叉树,处理的时候把元素加入到res数组里。
非空就入栈,指针往左走,当前为空了就出栈 同时输出到数组 往右走!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。