学习目标:
60天训练营打卡计划!
二叉树种类:
满二叉树
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,且最底层的填充是从左到右填充。
二叉搜索树
- 若左子树不为空,则左子树上所有的节点均小于他根节点的值。
- 若右子树不为空,则右子树上所有的节点均大于他根节点的值。
平衡二叉搜索树(AVL树)
- 对于树中的每一个节点,它的左子树和右子树的高度差不超过 1。
- 对于树中的每一个节点,其左子树中的所有节点的值小于该节点的值,右子树中的所有节点的值大于该节点的值。
二叉树存储方式:
那么链式存储方式就用指针, 顺序存储的方式就是用数组。
数组存储二叉树
如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
二叉树遍历方式:
深度优先遍历(递归法,迭代法)
借助栈使用递归的方式来实现(只要记得住遍历的顺序即可)
- 前序遍历 中 左(子树)右(子树)
- 中序遍历 左(子树)中 右(子树)
- 后序遍历 左(子树)右(子树)中
广度优先遍历(迭代法)
一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
- 层序遍历
上述的部分内容引用自代码随想录
学习内容:
递归法
144. 二叉树的前序遍历
- java中新建数组的方法 :List res = new ArrayList();
class Solution {
private void traversal(TreeNode cur, List<Integer> res){
if(cur == null){
return;
}
res.add(cur.val);
traversal(cur.left, res);
traversal(cur.right, res);
}
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
traversal(root, res);
return res;
}
}
145. 二叉树的后序遍历
- java中新建数组的方法 :List res = new ArrayList();
class Solution {
private void traversal(TreeNode cur, List<Integer> res){
if(cur == null){
return;
}
traversal(cur.left, res);
traversal(cur.right, res);
res.add(cur.val);
}
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
traversal(root, res);
return res;
}
}
94. 二叉树的中序遍历
- java中新建数组的方法 :List res = new ArrayList();
class Solution {
private void traversal(TreeNode cur, List<Integer> res){
if(cur == null){
return;
}
traversal(cur.left, res);
res.add(cur.val);
traversal(cur.right, res);
}
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
traversal(root, res);
return res;
}
}
迭代法(不是很熟悉)
整个过程可以分为访问节点和处理节点两个过程。其中,访问节点指的是将二叉树的节点放入栈的过程,而处理节点指的是将栈中的节点弹出到数组(打存储输出顺序的数组)的过程。
144. 二叉树的前序遍历
- java中新建数组的方法 :List res = new ArrayList();
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if(root == null)
return res;
Stack<TreeNode> stk = new Stack<>();
stk.push(root);
while(!stk.empty()){
// 每次先向数组中存入中间节点
TreeNode node = stk.pop();
res.add(node.val);
// 再按照左子树和右子树的逆序
// 先向数组中存放右节点(右子树的中间节点),
// 再存放左节点(左子树的中间节点)。
if(node.right != null){
stk.push(node.right);
}
if(node.left != null){
stk.push(node.left);
}
}
return res;
}
}
145. 二叉树的后序遍历
- java中新建数组的方法 :List res = new ArrayList();
- 和上述的前序遍历很相似,前序遍历是中间节点先出现,后序遍历是中间节点后出现。只要更换进栈顺序和数组翻转即可实现后序遍历。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null){
return res;
}
Stack<TreeNode> stk = new Stack<>();
stk.push(root);
// 入栈顺序:中-左-右
// 出栈顺序:中-右-左,
// 必须要先进行中间节点的处理。
while(!stk.empty()){
TreeNode node = stk.pop();
res.add(node.val);
if (node.left != null){
stk.push(node.left);
}
if (node.right != null){
stk.push(node.right);
}
}
// 最后翻转结果
Collections.reverse(res);
return res;
}
}
94. 二叉树的中序遍历
- java中新建数组的方法 :List res = new ArrayList();
- 分为访问节点和处理节点两个过程:
一定会优先访问到中间节点,这样就没法实现左中右的顺序输出。
指针从根节点一直向左子树遍历,遇到null就从栈中取元素,栈中存的是之前访问过的元素(中),就要处理该节点的右子节点。
/**
* Definition for a binary tree node.
* 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;
* }
* }
*/
// 迭代法 当指针遇到树的null节点时就从栈中取元素
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> stk = new Stack<>();
List<Integer> res = new ArrayList<>();
TreeNode tmp = root;
while(tmp != null || !stk.empty()){
// 若节点左侧有节点,就向栈添加节点,并向左移动指针
if(tmp != null){
stk.push(tmp);
tmp = tmp.left;
}
// 若遇到节点为null,就从栈取值,操作,右移指针。
else{
tmp = stk.pop();
res.add(tmp.val);
tmp = tmp.right;
}
}
return res;
}
}
统一迭代的学习心得
将已访问但未处理的节点前加一个空指针。如图所示
下列的程序引用自
代码随想录pdf文件
迭代法前序遍历代码如下:
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> 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;
}
}
迭代法后序遍历代码如下:
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;
}
}
学习时间:
- 上午半小时,下午两个半小时(实验室工作比较多),整理文档半小时。
- 11月13日(day20)重新学习迭代法
- 12月22日三刷,迭代法实现中序遍历仍不熟悉