前言
本文主要是自己的一个学习记录,代码并未详细解释,如有错误或疑问欢迎留言讨论。
一. 依据完整中序创建二叉树
先创建二叉树,才方便验证下面的遍历方法是否正确。思想就是采用队列先进先出的方式,每个节点离开队列的同时,将其左右子节点加入队列中,直至队列中无节点。直接给出代码:
数据结构如下:
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
创建方法如下:输入格式为:Integer[] list = {1, 2, 3, null, null, 6, 7}(LinkedList的用法见下一节预备知识)
public static TreeNode buildTree(Integer[] list) {
if (list == null || list.length == 0 || list[0] == null)
return null;
TreeNode root = new TreeNode(list[0]);
int length = list.length;
LinkedList<TreeNode> link = new LinkedList<TreeNode>();
link.offer(root);
for (int i = 1; i < length; i += 2) {
TreeNode node = link.removeFirst();
Integer left = list[i];
Integer right = list[i + 1];
if (left != null)
node.left = new TreeNode(left);
else
node.left = null;
if (right != null)
node.right = new TreeNode(right);
else
node.right = null;
link.offer(node.left);
link.offer(node.right);
}
return root;
}
二. 递归版前中后序遍历
递归版比较简单,此处直接给出代码:
// 递归方式先序遍历
public static void pOrder(TreeNode root, List<Integer> list) {
if (root == null)
return;
list.add(root.val);
pOrder(root.left, list);
pOrder(root.right, list);
}
// 递归方式中序遍历
public static void iOrder(TreeNode root, List<Integer> list) {
if (root == null)
return;
iOrder(root.left, list);
list.add(root.val);
iOrder(root.right, list);
}
// 递归方式后续遍历
public static void aOrder(TreeNode root, List<Integer> list) {
if (root == null)
return;
aOrder(root.left, list);
aOrder(root.right, list);
list.add(root.val);
}
三. 预备知识:Java中的队列实现
Java的队列有两个常用实现类:LinkedList和ArrayDeque,其中LinkedList实现了List接口和Deque接口,ArrayDeque实现了Deque接口;而Deque是Queue的子接口。
Queue有如下方法:
在队尾添加元素:add(), offer()
在队头删除或获取元素:remove(), poll(), peek()<只获取不删除>,element()<只获取不删除>
Deuqe可以用作双端队列,还可以用作栈,下面列举几个Deque常用方法:
当作队列使用时的常用方法:
在开头添加元素:void addFirst(Object e), boolean offerFirst(Object e),
在开头移除元素: Object removeFirst(), Object pollFirst(),Object peekFirst()<获取但不删除第一个元素>,
在末尾添加元素:void addLast(Object e), boolean offerLast(Object e)
在末尾移除元素:Object removeLast(), Object pollLast(), Object peekLast()<获取但不删除最后一个元素>,
当作栈使用时的常用方法:
在栈顶添加元素:void push(Object e),
在栈顶移除元素:Object pop(Object e)
当然也包括Queue的方法。
四. 层次遍历
层次遍历的思想也是采用队列,先进先出,与第一节一模一样,下面给出代码:
// 层次遍历:采用队列的方式
public static List<Integer> layerOrder(TreeNode root) {
List<Integer> list = new ArrayList<>();
Deque<TreeNode> queue = new ArrayDeque<>();
queue.addLast(root);
while (queue != null && queue.size() > 0) {
addLR(queue, list);
}
return list;
}
/**
* 完成三个功能:添加左右节点;出队 队首节点;将队首节点添加进list
* @param queue
* @param list
*/
static void addLR (Deque<TreeNode> queue, List<Integer> list) {
TreeNode currentNode = queue.removeFirst();
if (currentNode != null) {
list.add(currentNode.val);
if (currentNode.left != null)
queue.addLast(currentNode.left);
if (currentNode.right != null)
queue.addLast(currentNode.right);
}
}
五. 非递归版前中后序遍历
一般来说,递归的非递归实现都是用栈实现的,仔细想一下递归的过程,就能得出栈实现的程序。
/**
* 前序
*思想:记录元素入栈顺序;先进行入栈操作,将每个左节点入栈;然后出栈,出栈时将右子树根节点入栈。
*/
public static List<Integer> preOrderStack(TreeNode root) {
if (root == null)
return null;
/**
* 初始化栈和队列;
*/
List<Integer> list = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
stack.add(root);
list.add(root.val);
TreeNode currentNode = null;
while (stack.size() > 0) {
//入栈,一直将左子树根节点入栈
currentNode = stack.peekFirst().left; // 获取栈顶元素的左子树节点
while (currentNode != null) {
stack.push(currentNode);
list.add(currentNode.val); // 记录入栈节点值
currentNode = currentNode.left;
}
// 出栈栈顶元素,并将右子树根节点入栈;
// 需要说明的是,出栈的目的是为了将右子树根节点入栈,所以此步必须要真正入栈一个节点
while (currentNode == null && stack.size() > 0) {
currentNode = stack.pop().right;
}
if (currentNode != null) {
list.add(currentNode.val); // 记录入栈节点值
stack.push(currentNode);
}
}
return list;
}
/**
* 中序;
* 思想:出入栈顺序与前序遍历相同,不同的是中序在出栈的时候记录元素值。
* @param root
* @return
*/
public static List<Integer> inOrderStack(TreeNode root) {
if (root == null)
return null;
List<Integer> list = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
stack.push(root);
TreeNode currentNode = root;
while (stack.size() > 0){
// 入栈左子树
currentNode = stack.peekFirst().left;
while (currentNode != null) {
stack.push(currentNode);
currentNode = currentNode.left;
}
// 出栈,入栈右子树
while (currentNode == null && stack.size() > 0) {
currentNode = stack.pop();
list.add(currentNode.val); // 记录出栈节点值
currentNode = currentNode.right;
}
if (currentNode != null)
stack.push(currentNode);
}
return list;
}
/**
* 后序
* 思想:还是先入左子树根节点再入右子树根节点,出栈时记录;
* 需要注意的是:对于一颗树来说,左结点和右节点都不存在或者出栈了才进行出栈操作。
* @param root
* @return
*/
public static List<Integer> afterOrderStack(TreeNode root) {
if (root == null) {
return null;
}
List<Integer> list = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
stack.add(root);
TreeNode currentNode = null;
while (stack.size() > 0) {
// 入栈左子树
currentNode = stack.getFirst().left;
stack.getFirst().left = null; // 断掉与左结点的联系
while (currentNode != null) {
stack.push(currentNode);
currentNode = currentNode.left;
stack.getFirst().left = null; // 断掉与左结点的联系
}
// 入栈右子树
currentNode = stack.getFirst().right;
stack.getFirst().right = null; // 断掉与右节点的联系
if (currentNode != null) {
stack.push(currentNode);
}
else { // 左右节点都为null,出栈
list.add(stack.pop().val);
}
}
return list;
}
总结
理解递归具体的运行过程后再实现非递归方式就会游刃有余了。本文写的简陋,如有错误欢迎指出。