1、理论知识
-
二叉树的种类
- 满二叉树:
- 如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
- 二叉树的所有叶子节点都在最后一层,并且节点总数为2^n-1,n为层数【从1 开始】
- 完全二叉树:
- 二叉树的所有叶子节点都在最后一层或者倒数第二层,且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,则该二叉树为完全二叉树。
- 二叉搜索树
- 对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。如果有相同的值,可以将该节点放在左子节点或者右子节点
- 平衡二叉树(平衡二叉搜索树)
- 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
- 满二叉树:
-
二叉树的存储方式
-
二叉树可以链式存储(用指针),也可以顺序存储(用数组)。
-
链式存储
-
顺序存储
如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
-
-
-
二叉树的遍历方式
- 深度优先:先往深走,遇到叶子节点再往回走
- 前序遍历【中左右】(递归法、迭代法)
- 中序遍历【左中右】(递归法、迭代法)
- 后序遍历【左右中】(递归法、迭代法)
- 注意:前中后,指的是中间节点的位置
- 栈 其实就是递归的一种实现结构,前中后序遍历的逻辑其实都是可以借助栈使用非递归的方式来实现的。
- 广度优先:一层一层去遍历
- (迭代法)
- 层级遍历 一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
- 深度优先:先往深走,遇到叶子节点再往回走
-
二叉树的定义
class Node { int val; Node left; Node right; Node(){} Node(int val){ this.val =val; } Node(int val, Node left, Node right){ this.val = val; this.left = left; this.right = right; } }
2、递归遍历
-
递归三要素
- 确定递归函数的参数和返回值
- 不需要返回值
- 确定终止条件
- 当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return
- 确定单层递归的逻辑
- 前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值
- 确定递归函数的参数和返回值
-
前序遍历
class Solution { List<Integer> res = new ArrayList<>(); public List<Integer> preorderTraversal(TreeNode root) { preOrder(root); return res; } public void preOrder(TreeNode node) { if(node == null) { return; } res.add(node.val);//中 preOrder(node.left);//左 preOrder(node.right);//右 } }
-
中序遍历
class Solution { List<Integer> res = new ArrayList<>(); public List<Integer> inorderTraversal(TreeNode root) { inOrder(root); return res; } public void inOrder(TreeNode node) { if(node == null) { return; } inOrder(node.left); res.add(node.val); inOrder(node.right); } }
-
后序遍历
class Solution { List<Integer> res = new ArrayList<>(); public List<Integer> postorderTraversal(TreeNode root) { postOrder(root); return res; } public void postOrder(TreeNode node) { if(node == null) { return; } postOrder(node.left); postOrder(node.right); res.add(node.val); } }
3、迭代遍历【用 栈 实现】
-
前序
-
前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中【第一层,放完出栈,顺序:中】,然后将右孩子加入栈,再加入左孩子【第二层,放完出栈,先进后出,顺序:左、右】。【一层一层放】
-
有点类似层级遍历的写法
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Deque<TreeNode> stack = new ArrayDeque<>(); if(root == null) { return res; } stack.offerFirst(root); while(!stack.isEmpty()) { TreeNode node = stack.pollFirst(); res.add(node.val); if(node.right != null) { stack.offerFirst(node.right); } if(node.left != null) { stack.offerFirst(node.left); } } return res; } }
-
-
中序
借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Deque<TreeNode> stack = new ArrayDeque<>(); TreeNode cur = root; while(cur != null || !stack.isEmpty()) { if(cur != null) {// 指针来访问节点,访问到最底层 stack.offerFirst(cur);// 将访问的节点放进栈 cur = cur.left;// 左 } else { cur = stack.pollFirst();// 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据) res.add(cur.val);// 中 cur = cur.right; // 右 } } return res; } }
-
后序
-
先序遍历是中左右,后续遍历是左右中,那么只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了
-
类似于层级遍历写法
class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Deque<TreeNode> stack = new ArrayDeque<>(); if(root == null) { return res; } stack.offerFirst(root); while(!stack.isEmpty()) { TreeNode node = stack.pollFirst(); res.add(node.val); if(node.left != null) { stack.offerFirst(node.left); } if(node.right != null) { stack.offerFirst(node.right); } } Collections.reverse(res); return res; } }
-