二叉树的遍历
深度遍历
前序遍历
递归实现
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
preorder(root,res);
return res;
}
public void preorder(TreeNode root,List<Integer> res){
if(root == null){
return;
}
res.add(root.val);
preorder(root.left,res);
preorder(root.right,res);
}
迭代实现
实现思路:
使用栈模拟递归。
- 栈中压入要处理的节点,弹出要处理的节点,并将要处理的节点值加入数组中
- 判断处理节点的右孩子和左孩子是否存在,存在依次将右孩子和左孩子压栈;若左右孩子不存在,则继续弹出栈顶元素,
- 继续上述操作,直到栈空为止
实现过程:
public List<Integer> preorderTraversal1(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if(root == null){
return res;
}
stack.push(root);
TreeNode cur = new TreeNode();
while(!stack.empty()){
cur = stack.pop();
res.add(cur.val);
//右孩子先入栈,左孩子后入栈
if(cur.right!=null){
stack.push(cur.right);
}
if(cur.left!=null){
stack.push(cur.left);
}
}
return res;
}
中序遍历
递归实现
public static List<Integer> inOrderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
inOrder(root, res);
return res;
}
public static void inOrder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
inOrder(root.left, res);
res.add(root.val);
inOrder(root.right, res);
}
迭代实现
实现思路:
- 根节点不为空时入栈
- 判断当前节点的左孩子是否为空,左孩子不为空,左孩子入栈;左孩子为空时进行第三步
- 当前节点出栈,并将当前节点值存入数组中
- 判断当前节点的右孩子是否为空,右孩子不为空,右孩子入栈,然后重复第二步;右孩子为空,重复第三步
实现过程:
public static List<Integer> inOrderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.empty()) {
//入栈当前节点并判断当前节点的左孩子是否为空,不为空,入栈
if (cur != null) {
stack.push(cur);
cur = cur.left;
}
//左孩子为空,处理栈顶元素,并判断右孩子是否为空,不为空进入if中的语句,判断右孩子的左孩子是否为空
//右孩子为空,进入else,栈顶元素出栈,并进行处理
else{
cur = stack.pop();
res.add(cur.val);
cur = cur.right;
}
}
return res;
}
后序遍历
递归实现
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postOrder(root, res);
return res;
}
public void postOrder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
postOrder(root.left, res);
postOrder(root.right, res);
res.add(root.val);
}
迭代实现
实现思路:将前序遍历的迭代法中第二部步交换左孩子和右孩子的入栈顺序,然后将整个数组反转即可
- 栈中压入要处理的节点,弹出要处理的节点,并将要处理的节点值加入数组中
- 判断处理节点的右孩子和左孩子是否存在,存在依次将左孩子和右孩子压栈;若左右孩子不存在,则继续弹出栈顶元素,
- 继续上述操作,直到栈空为止
- 反转整个数组
实现过程:
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.empty()) {
TreeNode cur = stack.pop();
res.add(cur.val);
if (cur.left != null) {
stack.push(cur.left);
}
if (cur.right != null) {
stack.push(cur.right);
}
}
Collections.reverse(res);
return res;
}
深度遍历的统一格式
我们以中序遍历为例,在二叉树中递归能做的,栈也能做! 但使用栈的话,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。
那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。
如何标记呢,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法
前序遍历
遍历顺序:右左中
//前序遍历
public static List<Integer> preOrder(TreeNode root){
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root == null) {
return res;
}
stack.push(root);
while(!stack.empty()){
TreeNode cur = stack.peek();
if(cur!=null){
stack.pop();// 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if(cur.right!=null){// 添加右节点(空节点不入栈)
stack.push(cur.right);
}
if(cur.left!=null){//添加左节点(空节点不入栈)
stack.push(cur.left);
}
stack.push(cur);//添加中节点,且为要处理的节点
stack.push(null);//标记要处理的节点
}
else{// 只有遇到空节点的时候,才将下一个节点放进结果集
stack.pop();//弹出空节点
cur = stack.pop();//重新获取要处理的节点
res.add(cur.val);//将要处理的节点值加入数组中
}
}
return res;
}
中序遍历
遍历顺序:右中左
public static List<Integer> inOrder(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root == null) {
return res;
}
stack.push(root);
while(!stack.empty()){
TreeNode cur = stack.peek();
if(cur!=null){
stack.pop();// 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if(cur.right!=null){// 添加右节点(空节点不入栈)
stack.push(cur.right);
}
stack.push(cur);//添加中节点,且为要处理的节点
stack.push(null);//标记要处理的节点
if(cur.left!=null){//添加左节点(空节点不入栈)
stack.push(cur.left);
}
}
else{// 只有遇到空节点的时候,才将下一个节点放进结果集
stack.pop();//弹出空节点
cur = stack.pop();//重新获取要处理的节点
res.add(cur.val);//将要处理的节点值加入数组中
}
}
return res;
}
后序遍历
遍历顺序:中右左
public static List<Integer> postOrder(TreeNode root){
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root == null) {
return res;
}
stack.push(root);
while(!stack.empty()){
TreeNode cur = stack.peek();
if(cur != null){
stack.pop();// 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
stack.push(cur);//添加中节点,且为要处理的节点
stack.push(null);//标记要处理的节点
if(cur.right!=null){// 添加右节点(空节点不入栈)
stack.push(cur.right);
}
if(cur.left!=null){//添加左节点(空节点不入栈)
stack.push(cur.left);
}
}
else{// 只有遇到空节点的时候,才将下一个节点放进结果集
stack.pop();//弹出空节点
cur = stack.pop();//重新获取要处理的节点
res.add(cur.val);//将要处理的节点值加入数组中
}
}
return res;
}
广度遍历
迭代法遍历二叉树实现层次遍历
实现思路:
使用队列存储要遍历的节点
- 根节点不为空,入队
- 队列不空进入循环
- 记录当前队列长度
- 使用第三步记录的队列长度记录遍历本层节点的次数,遍历本层节点思路如下:
- 将当前节点值加入数组中
- 判断左节点是否为空,不为空入栈
- 判断右节点是否为空,不为空入栈
- 当前层节点数减一(之前记录的队长减一)
实现过程:
public static List<List<Integer>> levelOrder(TreeNode root){
List<List<Integer>> res = new ArrayList<>();
Queue<TreeNode> que = new ArrayDeque<>();
if(root == null){
return res;
}
que.offer(root);
int size = 0;
while(!que.isEmpty()){
List<Integer> list = new ArrayList<>();
size = que.size();
while(size>0){
TreeNode cur = que.poll();
list.add(cur.val);
if(cur.left!=null){
que.offer(cur.left);
}
if(cur.right!=null){
que.offer(cur.right);
}
size--;
}
res.add(list);
}
return res;
}