目录
- 二叉树
- 存储结构
- 前序遍历
- 中序遍历
- 后序遍历
- 层序遍历(BFS和DFS)
二叉树
二叉树的条件:
- 本身是有序树
- 树中各个节点的度不能超过2
存储结构
顺序存储结构:二叉树的顺序存储,指的是使用顺序表(数组)存储二叉树。需要注意的是,顺序存储只适用于完全二叉树。换句话说,只有完全二叉树才可以使用顺序表存储。因此,如果我们想顺序存储普通二叉树,需要提前将普通二叉树转化为完全二叉树。
链式存储结构:从树的根节点开始,将各个节点及其左右孩子使用链表存储。一般二叉树采用链式存储。
代码如下:
//Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; } //构造函数
}
前序遍历
二叉树的几种遍历方式都是以根节点遍历的先后顺序来命名的,先序遍历根节点最先遍历,中序遍历根节点在中间,后序遍历根节点在最后,三种遍历方式采用递归方式实质都是深度优先搜索,下面是运用递归方法求得遍历后的集合序列。
前序遍历首先访问根节点,然后遍历左子树,最后遍历右子树。
实例:[1,2,4,null,3,5] 前序遍历输出:[1,2,3,4,5]
代码
class Solution {
List<Integer> res =new ArrayList<Integer>();
public List<Integer> preorderTraversal(TreeNode root) {
preTraverse(root);
return res;
}
public void preTraverse(TreeNode r){
if(r == null)
return;
res.add(r.val); //先将根节点加入res集合中
preTraverse(r.left);
preTraverse(r.right);
}
}
利用非递归方式,栈来实现,以[1,2,4,null,3,5]为例,各个步骤如下:
- 首先 1 先入栈,栈s中就一个TreeNode节点,s = [1]
- 声明一个node节点,用于存放弹出的节点,由于此时 1 出栈,并将其的值加入res结果集中, node节点的右孩子节点4不为空,入栈,再将左孩子节点2入栈,此时栈中有两个元素[4,2],进入下次while循环
- 由于节点2后入栈,所以将节点2弹出放入node节点中,将节点2加入res中,此时栈中就一个元素4,由于node节点左节点为空,右节点不为空,所以将右节点3入栈,此时栈中元素为[4,3],进入下一层循环
- 同上,此时3出栈,加入res中,节点node左右孩子节点都为空,栈中就一个元素[4],进入下次循环
- 同上,此时4出栈,加入res中,node左孩子节点5入栈,右孩子节点为空,栈中一个元素[5],继续下一次循环
- 节点5出栈,加入结果res中,左右孩子节点为空,栈s也为空,while循环结束,返回结果集res
第一次循环:s = [1] -> s.pop(1) -> res.add(1) -> s.push(4),s.push(2) -> s = [4,2]
第二次循环:s = [4,2] -> s.pop(2) -> res.add(2) -> s.push(3) -> s = [4,3]
第三次循环:s = [4,3] -> s.pop(3) -> res.add(3) -> s = [4]
第四次循环:s = [4] -> s.pop(4) -> res.add(4) -> s.push(5) -> s = [5]
第五次循环:s = [5] -> s.pop(5) -> res.add(5) -> s==empty,end while, res = [1,2,3,4,5]
/**************栈 先入后出*************/
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res =new ArrayList<Integer>();
if(root == null) return res;
Stack<TreeNode> s = new Stack<TreeNode>();
s.push(root);
while(!s.isEmpty()){
TreeNode node = new TreeNode();
node = s.pop();
res.add(node.val);
if(node.right != null) //由于栈是后入先出,所以要先将每层的右边节点入栈
s.push(node.right);
if(node.left != null)
s.push(node.left);
}
return res;
}
}
中序遍历
中序遍历是先遍历左子树,然后访问根节点,然后遍历右子树。
实例:[1,2,4,null,3,5] 中序遍历输出:[2,3,1,5,4]
代码
class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
midTraverse(root);
return res;
}
public void midTraverse(TreeNode r){
if(r == null)
return;
midTraverse(r.left);
res.add(r.val); //在中间将根节点加入res集合中
midTraverse(r.right);
}
}
后序遍历
后序遍历是先遍历左子树,然后遍历右子树,最后访问树的根节点。
实例:[1,2,4,null,3,5] 后序遍历输出:[3,2,5,4,1]
代码
class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> postorderTraversal(TreeNode root) {
lastTraverse(root);
return res;
}
public void lastTraverse(TreeNode r){
if(r == null)
return;
lastTraverse(r.left);
lastTraverse(r.right);
res.add(r.val);//在最后将val加入集合res中,根节点最后加入,先左后右
}
}
层序遍历(BFS和DFS)
层序遍历就是逐层遍历树结构。
一、BFS
广度优先搜索是一种广泛运用在树或图这类数据结构中,遍历或搜索的算法。 该算法从一个根节点开始,首先访问节点本身。 然后遍历它的相邻节点,其次遍历它的二级邻节点、三级邻节点,以此类推。
当我们在树中进行广度优先搜索时,我们访问的节点的顺序是按照层序遍历顺序的。通常我们使用一个叫做队列 FIFO的数据结构来帮助我们做广度优先搜索。
下图为结果演示:
实例:[1,2,4,null,3,5] ,层序遍历输出:[[1],[2,4],[3,5]]
代码
/********** 广度优先搜索 利用队列 **********/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null) return new ArrayList<List<Integer>>(); //极端情况[]处理
List<List<Integer>> res = new ArrayList<List<Integer>>();
Queue<TreeNode> queue = new LinkedList<>(); //利用队列来处理,先进先出
queue.offer(root); //将跟节点入队列
while(!queue.isEmpty()){
List<Integer> list = new ArrayList<>(); //在每层中定义一个list集合用于存放本层的遍历结果
int size = queue.size();
for(int i = 0;i < size ;i++){ //通过每层的size将各个节点的val加入list中
TreeNode node = queue.poll(); //每层根据size依次出队列,先入先出
list.add(node.val);
if(node.left!=null)
queue.offer(node.left); //将下一层的节点入队列
if(node.right != null)
queue.offer(node.right);
}
res.add(list); //本层遍历完后,将本层的集合list放入结果集合中
}
return res;
}
}
二、DFS
也可以用深度优先搜索,利用递归来实现。下图清楚表示了递归调用的过程。
过程图
代码
/********** 深度优先搜索 **********/
class Solution{
List<List<Integer>> res = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root){
if(root == null) return new ArrayList<List<Integer>>(); //极端情况[]处理
DFSTraverse(root,0);
return res;
}
//DFS深度优先搜素,index表示层数
public void DFSTraverse(TreeNode r,int index){
if(res.size() <= index) //这一点很重要,如果res的size小于等于index 的值,说明需要增加size
res.add(new ArrayList<Integer>());
res.get(index).add(r.val); //将r的val加入index对应的层数中
if(r.left != null)
DFSTraverse(r.left,index+1); //递归非空的孩子节点
if(r.right != null)
DFSTraverse(r.right,index+1);
}
}
复杂度分析
两种方法的复杂度相同
时间复杂度:O(N),因为每个节点恰好会被运算一次。
空间复杂度:O(N),保存输出结果的数组包含 N 个节点的值。