理论基础
1.二叉树的种类:
- 满二叉树
如果一棵二叉树只有度为0的节点和度为2的节点,并且度为0的节点在同一层上,则这棵二叉树为满二叉树。
这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节点的二叉树。
- 完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第h层,则该层包含1~2^(h-1)个节点。
二叉搜索树:
二叉搜索树是一个有序树。
- 若它左子树不为空,则左子树上所有节点的值均小于它的根节点的值。
- 若它右子树不为空,则右子树上所有节点的值均大于它的根节点的值。
- 它的左右子树也分别为二叉排序树。
下面这二颗树都是搜索树。
平衡二叉搜索树:
他是一棵空树或它的左右两个子树的高度差绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
二叉树的存储方式:
- 二叉树可以链式存储,也可以顺序存储。链式就是指针,顺序就是数组。
数组存储二叉树遍历:如果父节点下标为i,那么它的左孩子就是i*2+1,右孩子为i*2+2。
二叉树的遍历方式:
- 深度优先遍历:先往深走,遇到叶子节点再往回走。
前序遍历,中序遍历,后序遍历。
- 广度优先遍历:一层一层的去遍历。
层次遍历。
补充:前中后序遍历的逻辑其实都可以借助栈使用非递归的方式来实现。广度优先遍历一般使用队列来实现,因为先进先出的结构特点,才能一层一层的来遍历。
二叉树的递归遍历
递归三要素:
- 确定递归函数的参数和返回值:确定哪些参数是递归过程中需要处理的,那么就在递归函数里加上这个参数,并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
- 确定终止条件:写完了递归算法,运行的时候经常会出现栈溢出的问题,就是没写终止条件或者终止条件没写对,操作系统也是用一个栈结构来保存每一层递归信息,如果递归没有终止,操作系统必然栈溢出。
- 确定单层递归的逻辑。
前序递归:
public class Solution {
public IList<int> PreorderTraversal(TreeNode root) {
List<int> l1 = new List<int>();
Preorder(root,l1);
return l1;
}
public void Preorder(TreeNode root,List<int> res){ //参数及返回值
if(root == null){ //终止条件
return;
}
//单次执行逻辑
res.Add(root.val); //只记录根节点的数值
Preorder(root.left,res);//跳转下个左子树,并且记录左子树的根节点值,直到遇到null
Preorder(root.right,res);//最后记录右子树的根节点值
}
}
中后序递归,仅仅是变换下记录的顺序。
二叉树的迭代遍历
前序遍历:
- 最开始的时候将根节点添加入栈中。记录根节点的值并弹出,按顺序存入右子树和左子树的根节点。判断条件是栈中没有元素。并且每次存入左右子树是要判断存入节点是否为空,只有不为空才能存入。
public class Solution {
public IList<int> PreorderTraversal(TreeNode root) {
Stack<TreeNode> s1 = new Stack<TreeNode>();
List<int> l1 = new List<int>();
if(root == null){
return l1;
}
s1.Push(root);
while(s1.Count >0){
TreeNode temp = s1.Pop();
l1.Add(temp.val);
if (temp.right != null) s1.Push(temp.right);
if (temp.left != null) s1.Push(temp.left);
}
return l1;
}
}
中序遍历:
- 因为遍历的顺序和存入的顺序不同,导致代码有不同。对于中序遍历(左中右),我们首先要找到最左的节点。使用指针记录当前指针位置,当当前指针的左节点为空时,说明找到了目标节点。我们只需要弹出最后记录的节点,并记录其数。然后,我们目前已经解决了该节点下的左节点和该节点,所以将指针指向该节点的右节点进行上述的重复操作。
public class Solution {
public IList<int> InorderTraversal(TreeNode root) {
Stack<TreeNode> s1 = new Stack<TreeNode>();
List<int> l1 = new List<int>();
TreeNode cur = root;
while(cur != null || s1.Count >0){
//分两步,先是找到最左边
if(cur != null){
s1.Push(cur); //存入根节点
cur = cur.left;// 移动指针到左节点
}else{ //当左节点不存在的时候
TreeNode temp = s1.Pop(); //将存在的节点弹出
l1.Add(temp.val); //开始记录该节点值
cur = temp.right;//移动指针指向弹出节点的右节点
}
}
return l1;
}
}
后序遍历:
- 后续遍历是遍历左右中,所以在先序遍历的基础上,我们只需记录中右左,最后反转即可解决问题。
public class Solution {
public IList<int> PostorderTraversal(TreeNode root) {
Stack<TreeNode> s1 = new Stack<TreeNode>();
List<int> l1 = new List<int>();
if(root == null){
return l1;
}
s1.Push(root);
while(s1.Count > 0){
TreeNode temp = s1.Pop();
l1.Add(temp.val);
if(temp.left != null) s1.Push(temp.left);
if(temp.right != null) s1.Push(temp.right);
}
l1.Reverse();
return l1;
}
}
统一迭代法
有点难理解,就先跳过了。(以后有时间补充)