往期传送门:
目录
初识迭代
什么是迭代?迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。换句话说就是不断的用变量的旧值推出新值的过程。和递归有很大的区别。
下面各题都是是利用迭代的方式来解题,很多是我们前面使用递归方式实现过的,小伙伴们可以对比看看哪个方法更适合你哟
利用迭代解题
使用迭代实现层序遍历—力扣102题
我们借助队列来操作,先将根节点入队
当队列为空时,所有节点就处理完毕了,队列中保存的都是下一层要处理的元素
注意!每次进入内循环之前,要先计算队列的长度,因为在将节点的左右节点入队时,队列的长度是发生变化的
代码实现:
public class Num102 {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ret = new ArrayList<>();
if(root == null){
return ret;
}
//借助队列实现遍历
Deque<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
//保存当前层的元素
List<Integer> curlist = new ArrayList<>();
//开始遍历取出当前层的所有元素添加到 curlist中
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode cur = queue.poll();
curlist.add(cur.val);
if(cur.left != null){
queue.offer(cur.left);
}
if(cur.right != null){
queue.offer(cur.right);
}
}
ret.add(curlist);
}
return ret;
}
}
计算二叉树的节点个数
//使用非递归找节点个数
public static int getNodesNonRecursion(TreeNode root){
if(root == null){
return 0;
}
int count = 0;
Deque<TreeNode> deque = new LinkedList<>();
deque.offer(root);
while (!deque.isEmpty()){
TreeNode cur = deque.poll();
count ++;
if(cur.right != null){
deque.offer(cur.right);
}
if(cur.left != null){
deque.offer(cur.left);
}
}
return count;
}
计算叶子节点个数
//使用非递归计算叶子节点个数
public static int getleafNodesNC(TreeNode root){
if(root == null){
return 0;
}
int ret = 0;
Deque<TreeNode> deque = new LinkedList<>();
deque.offer(root);
while (!deque.isEmpty()){
TreeNode cur = deque.poll();
if(cur.left == null && cur.right == null){
ret ++;
continue;
}
if(cur.right != null){
deque.offer(cur.right);
}
if(cur.left != null){
deque.offer(cur.left);
}
}
return ret;
}
判断是否为对称二叉树—力扣101题
public boolean isSymmetric(TreeNode root) {
if(root == null){
return true;
}
Deque<TreeNode> deque = new LinkedList<>();
deque.offer(root.left);
deque.offer(root.right);
while (!deque.isEmpty()){
//一次取出两个节点,这两个节点就是两颗子树的根节点
TreeNode t1 = deque.poll();
TreeNode t2 = deque.poll();
if(t1 == null && t2 == null){
//左右两棵树都为空,继续后面的判断
continue;
}
if(t1 == null || t2 == null){
//只有一个为空
return false;
}
if(t1.val != t2.val){
return false;
}
//左右两棵树都不为空,且节点值相同
//继续判断 t1.left 和 t2.right 以及 t1.right 和 t2.left
deque.offer(t1.left);
deque.offer(t2.right);
deque.offer(t1.right);
deque.offer(t2.left);
}
return true;
}
}
判断一棵树是否是完全二叉树—力扣958题
我们知道,完全二叉树的叶子节点只出现在最下层或者次下次,当出现第一个叶子节点,或者第一个只有左子树没有右子树的节点时,该节点后面的所有节点都是叶子节点
因此,我们使用层序遍历,并且可以引入一个标记位,来区分二叉树的两种状态:
- 状态一:每个节点都有左右子树
- 状态二:每个节点都是叶子节点
切换状态的条件:
- 当碰到第一个只有左树没有右树的节点时
- 当碰到第一个叶子节点时
代码实现:
public class Num958 {
public boolean isCompleteTree(TreeNode root) {
Deque<TreeNode> deque = new LinkedList<>();
deque.offer(root);
//标记位,用来判断当前的状态
boolean isSecondStep = false;
while (!deque.isEmpty()){
TreeNode cur = deque.poll();
if(!isSecondStep){
//第一种状态
if(cur.left == null && cur.right != null){
//只有右孩子,错误
return false;
} else if(cur.left != null && cur.right != null) {
deque.offer(cur.left);
deque.offer(cur.right);
} else if(cur.left != null ) {
//只有左孩子,没有右孩子
isSecondStep = true;
deque.offer(cur.left);
}else {
//此时既没有左孩子,也没有右孩子,既叶子节点
isSecondStep = true;
}
}else {
//此时为第二状态
if(cur.left != null || cur.right != null){
return false;
}
}
}
return true;
}
}
求二叉树最大宽度—力扣662题
力扣https://leetcode-cn.com/problems/maximum-width-of-binary-tree/每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的
null
节点也计入长度)之间的长度。
思路:我们借助完全二叉树的编号来做题,若根节点从1开始编号,父节点编号为K,则左孩子的编号为 2K,右孩子的编号为 2K + 1
可以得出结论:每层宽度恰好等于该层最右侧节点编号 - 最左侧节点编号 + 1
代码实现:
public class Num662 {
public int widthOfBinaryTree(TreeNode root) {
if(root == null){
return 0;
}
int maxWidth = 0;
Queue<NodeNum> queue = new LinkedList<>();
queue.offer(new NodeNum(root,1));
while (!queue.isEmpty()){
//当前层宽度
int levelWidth = 0;
//当前层元素个数
int size =queue.size();
//最左侧节点编号
int L = 0;
//最右侧节点编号
int R = 0;
for (int i = 0; i < size; i++) {
NodeNum cur = queue.poll();
if(i == 0){
//node就是最左侧节点
L = cur.num;
}
if(i == size - 1){
//node就是最右侧节点
R = cur.num;
}
if(cur.node.left != null){
queue.offer(new NodeNum(cur.node.left,cur.num * 2));
}
if(cur.node.right != null){
queue.offer(new NodeNum(cur.node.right,cur.num * 2 +1));
}
}
levelWidth = R - L +1;
maxWidth = Math.max(maxWidth,levelWidth);
}
return maxWidth;
}
//此时我们在层序遍历中,会存储每个出现的节点和它对应的编号
//因此我们创建一个类,将TreeNode二次包装,新增一个节点的编号
private class NodeNum{
TreeNode node;
//节点编号
int num;
public NodeNum(TreeNode node,int num){
this.node = node;
this.num = num;
}
}
}
前中后序的迭代实现
1.前序遍历—力扣144题
前序遍历是根左右,借助栈来实现,先将根节点压入栈中,由于栈是先进后出,所以先压入右子树,再压入左子树。
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ret = new ArrayList<>();
if(root == null){
return ret;
}
Deque<TreeNode> stack = new ArrayDeque<>();
stack.push(root);
while ((!stack.isEmpty())){
TreeNode cur = stack.pop();
//先访问根节点
ret.add(cur.val);
//先压入右孩子
if(cur.right != null){
stack.push(cur.right);
}
//再压入左孩子
if(cur.left != null){
stack.push(cur.left);
}
}
return ret;
}
2.中序遍历—力扣94题
中序遍历是左根右,如图搭配代码展示了元素入栈出栈的过程,要理解,元素出栈是作为“根节点”出栈的,意思就是当访问它和它的左树之后,再次回头访问这个“根节点”时,这个节点才输出,也就是访问两次才输出
代码实现:
public class Num94_NonRrecurion {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ret = new ArrayList<>();
if(root == null){
return ret;
}
//当前走到的节点
TreeNode cur = root;
Deque<TreeNode> stack = new ArrayDeque<>();
while (cur != null || !stack.isEmpty()){
//一路向左走到底
while (cur != null){
stack.push(cur);
cur = cur.left;
}
//此时栈顶就是最左侧的节点
cur = stack.pop();
ret.add(cur.val);
//继续访问右子树
cur = cur.right;
}
return ret;
}
}
3.后序遍历—力扣145题
后序遍历是左根右,和中序遍历类似,元素出栈是作为“根节点”出栈的,和中序遍历不同的是,后序遍历元素要三次访问“根节点”时才能输出。
代码实现:
public class Num145 {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ret = new ArrayList<>();
if(root == null){
return ret;
}
TreeNode cur = root;
//上一个完全处理过的节点,也就说左右根全部处理完毕的节点
TreeNode prev = null;
Deque<TreeNode> stack = new ArrayDeque<>();
while (!stack.isEmpty() || cur != null){
//先一路向左走到底
while (cur != null){
stack.push(cur);
cur = cur.left;
}
//此时左树为空,cur取出栈顶元素,第二次访问
cur = stack.pop();
//判断右树是否为空或者被访问过
if(cur.right == null || prev == cur.right){
ret.add(cur.val);
//当前节点cur就是最后处理的根节点,更新prev引用,变为cur
prev = cur;
cur = null;
}else {
//此时右树不为空且没有处理过,就需要把根节点再压入栈中,继续处理右子树
stack.push(cur);
cur = cur.right;
}
}
return ret;
}
}