层级遍历
代码实现的每一步解释
public List<T> levelOrder(){
List<T> list = new LinkedList<>();
MyArrayQueue<Node> queue1 = new MyArrayQueue<>();
queue1.enQueue(root); // 将根节点入队列
while (!queue1.isEmpty()){
Node node = queue1.deQueue(); // 从队列中取出一个节点
list.add(node.value); // 将节点的值添加到结果列表
if(node.left != null){
queue1.enQueue(node.left); // 左孩子入队列
}
if(node.right != null){
queue1.enQueue(node.right); // 右孩子入队列
}
}
return list; // 返回结果列表
}
这个代码实现了层级遍历,通过使用队列来记录待处理的节点,从而逐层遍历整个二叉树。
上面的层级遍历算法实现了广度优先搜索 (BFS),并使用了队列 (Queue) 数据结构。具体步骤如下:
- 初始化队列:创建一个空队列并将根节点入队列。
- 循环遍历:当队列不为空时,重复以下步骤:
- 从队列中取出一个节点。
- 将该节点的值添加到结果列表中。
- 如果该节点的左子节点不为空,则将左子节点入队列。
- 如果该节点的右子节点不为空,则将右子节点入队列。
我们可以用一个简单的二叉树作为例子:
A
/ \
B C
/ \ \
D E F
让我们一步一步地看这个算法是如何工作的:
初始化
- 根节点
A
入队列。 - 队列:
[A]
循环遍历
-
从队列中取出
A
,将A
的值添加到结果列表,左右孩子B
和C
入队列。- 结果列表:
[A]
- 队列:
[B, C]
- 结果列表:
-
从队列中取出
B
,将B
的值添加到结果列表,左右孩子D
和E
入队列。- 结果列表:
[A, B]
- 队列:
[C, D, E]
- 结果列表:
-
从队列中取出
C
,将C
的值添加到结果列表,右孩子F
入队列。- 结果列表:
[A, B, C]
- 队列:
[D, E, F]
- 结果列表:
-
从队列中取出
D
,将D
的值添加到结果列表,没有孩子。- 结果列表:
[A, B, C, D]
- 队列:
[E, F]
- 结果列表:
-
从队列中取出
E
,将E
的值添加到结果列表,没有孩子。- 结果列表:
[A, B, C, D, E]
- 队列:
[F]
- 结果列表:
-
从队列中取出
F
,将F
的值添加到结果列表,没有孩子。- 结果列表:
[A, B, C, D, E, F]
- 队列:
[]
- 结果列表:
队列为空,算法结束,最终结果列表为 [A, B, C, D, E, F]
。
后序遍历
代码实现的每一步解释
public List<T> postOrder(){
List<T> list = new LinkedList<>(); // 存储遍历结果的容器
MyArrayStack<Node> nodeMyArrayStack = new MyArrayStack<>(); // 创建一个栈
nodeMyArrayStack.push(root); // 将根节点入栈
while(!nodeMyArrayStack.isEmpty()){
Node pop = nodeMyArrayStack.pop(); // 从栈中取出一个节点
list.add(0, pop.value); // 使用头插法将节点的值插入到结果列表的开头
if(pop.left != null){
nodeMyArrayStack.push(pop.left); // 左孩子入栈
}
if(pop.right != null){
nodeMyArrayStack.push(pop.right); // 右孩子入栈
}
}
return list; // 返回结果列表
}
这个代码实现了二叉树的后序遍历,通过使用栈和头插法来确保节点值按后序遍历的顺序排列。
这段代码实现了二叉树的后序遍历(Post-order Traversal),但与传统的递归方法不同,这里使用了栈 (Stack) 数据结构来进行非递归的后序遍历。后序遍历的顺序是:先访问左子树,再访问右子树,最后访问根节点。
代码的具体步骤如下:
- 初始化栈:创建一个空栈并将根节点入栈。
- 循环遍历:当栈不为空时,重复以下步骤:
- 从栈中取出一个节点。
- 将该节点的值插入到结果列表的开头(使用头插法)。
- 如果该节点的左子节点不为空,则将左子节点入栈。
- 如果该节点的右子节点不为空,则将右子节点入栈。
让我们用一个简单的二叉树作为例子来说明这个算法:
A
/ \
B C
/ \ \
D E F
初始化
- 根节点
A
入栈。 - 栈:
[A]
- 结果列表:
[]
循环遍历
-
从栈中取出
A
,将A
的值插入到结果列表的开头,左右孩子B
和C
入栈。- 结果列表:
[A]
- 栈:
[B, C]
- 结果列表:
-
从栈中取出
C
,将C
的值插入到结果列表的开头,右孩子F
入栈。- 结果列表:
[C, A]
- 栈:
[B, F]
- 结果列表:
-
从栈中取出
F
,将F
的值插入到结果列表的开头,没有孩子。- 结果列表:
[F, C, A]
- 栈:
[B]
- 结果列表:
-
从栈中取出
B
,将B
的值插入到结果列表的开头,左右孩子D
和E
入栈。- 结果列表:
[B, F, C, A]
- 栈:
[D, E]
- 结果列表:
-
从栈中取出
E
,将E
的值插入到结果列表的开头,没有孩子。- 结果列表:
[E, B, F, C, A]
- 栈:
[D]
- 结果列表:
-
从栈中取出
D
,将D
的值插入到结果列表的开头,没有孩子。- 结果列表:
[D, E, B, F, C, A]
- 栈:
[]
- 结果列表:
栈为空,算法结束,最终结果列表为 [D, E, B, F, C, A]
,这就是后序遍历的顺序。
后序遍历(递归实现)
递归是一种解决问题的编程技术,它涉及一个函数调用自身,以便逐步解决问题的各个子部分。在你的代码中,postOrder2
方法使用递归来实现二叉树的后序遍历。让我们一步步来分析它是如何工作的。
递归后序遍历的执行步骤
后序遍历的顺序是:左子树 -> 右子树 -> 根节点。下面是详细的步骤:
-
递归出口:如果当前节点 (
node
) 为空,直接返回。这是递归的结束条件,确保我们在遇到叶子节点的孩子(即null
)时不再继续递归。 -
递归调用:对于每个节点,先递归遍历其左子树,然后递归遍历其右子树,最后处理当前节点(将其值添加到列表中)。
我们用一个简单的二叉树作为例子来说明这个算法:
A
/ \
B C
/ \ \
D E F
代码实现的每一步解释
private void postOrder2(Node node, List<T> list){
if (node == null){
return; // 递归出口
}
postOrder2(node.left, list); // 遍历左子树
postOrder2(node.right, list); // 遍历右子树
list.add(node.value); // 处理当前节点
}
public List<T> postOrder2(){
LinkedList<T> list = new LinkedList<>();
postOrder2(root, list); // 从根节点开始递归
return list; // 返回结果列表
}
递归过程示例
-
初始调用:
postOrder2(root, list)
,其中root
是节点A
。
-
递归遍历左子树:
- 对
A
的左子节点B
调用postOrder2(B, list)
。
- 对
-
递归遍历左子树的左子树:
- 对
B
的左子节点D
调用postOrder2(D, list)
。 D
没有左子树和右子树,直接将D
添加到列表:list = [D]
。
- 对
-
递归遍历左子树的右子树:
- 对
B
的右子节点E
调用postOrder2(E, list)
。 E
没有左子树和右子树,直接将E
添加到列表:list = [D, E]
。
- 对
-
处理左子树的根节点:
- 将
B
添加到列表:list = [D, E, B]
。
- 将
-
递归遍历右子树:
- 对
A
的右子节点C
调用postOrder2(C, list)
。
- 对
-
递归遍历右子树的左子树:
C
没有左子树,跳过。
-
递归遍历右子树的右子树:
- 对
C
的右子节点F
调用postOrder2(F, list)
。 F
没有左子树和右子树,直接将F
添加到列表:list = [D, E, B, F]
。
- 对
-
处理右子树的根节点:
- 将
C
添加到列表:list = [D, E, B, F, C]
。
- 将
-
处理根节点:
- 将
A
添加到列表:list = [D, E, B, F, C, A]
。
- 将
递归的关键点
- 递归出口:确保递归终止。
- 递归调用:逐步处理左子树和右子树,再处理根节点。
- 头插法:在处理根节点时将节点值添加到列表的末尾。
通过递归调用和递归出口的配合,递归函数能够遍历整个二叉树并按照后序遍历的顺序处理每个节点。