二叉树的前中后序遍历非递归实现及层次遍历队列实现 - 没有对象也没有new方法的大白 - CSDN博客
二叉树遍历(前序、中序、后序、层次、深度优先)递归和非递归 java实现 - 维 - CSDN博客
递归像剥洋葱,假设我们剥了一层皮后还要洗干净这层皮,这一步才算完成,那么我们在剥完一次皮后,本来按步骤要去洗的,但剥完发现还有一层皮要剥,于是我们就先不洗了,而是立马把新出现的皮给剥了,之后同样看到皮就一路剥下去,每层皮洗的动作都先延后,直到剥完最后一层皮,发现没皮可剥了,那我们就把最后一层皮拿去洗,这样我们对最后一层皮的处理就彻底完成了,完成后这层皮后出现再你手里的就是倒数第二层皮,然后你那它去洗,则第二层皮的处理就完成了,紧接着去洗倒数第三层…依次类推,直到洗好第一层皮那么整个动作就完成了.
前中后序的非递归遍历算法都是依靠辅助栈来实现的,而层次遍历是通过循环队列实现的,下面依次给出操作原理
前序遍历(DLR)的非递归思想:
先定义一个栈,把二叉树的根结点入栈
然后栈顶出栈,一出栈便输出出栈结点,出栈的同时判断它是否有左右孩子,要有则入栈.注:要是左右孩子都存在则先右孩子入栈,因为先进后出嘛
最后在栈不为空的条件下一直循环执行步骤2
中序遍历(LDR)的非递归思想
先定义一个栈,把二叉树的根结点入栈
把从根结点出发的左孩子一路入栈
碰到第一个没有左孩子的结点则访问输出该结点(即栈顶出栈结点),并让它指向自己的右孩子
若上一步中该结点有右孩子,则以他的右孩子出发,它有左孩子则左孩子统统入栈,即重复步骤2,步骤3
若步骤3中的结点没有右孩子,只要栈不为空便栈顶出栈,接着继续做步骤3的判断
后序遍历(LRD)的非递归遍历思想
和前序遍历的过程一样就四点不同的地方,其他都一样
需要两个辅助栈实现
当栈1出栈顶点左右孩子都存在时,先左孩子入栈
栈1出栈结点立马压入栈2,并不像前序那样立马访问输出
最后统一让栈2一次性出栈,每个结点一出栈就访问输出
后序遍历能这么干的依据是:逆后序遍历就是把先序遍历中对左右子树的遍历顺序交换的结果
二叉树的层次遍历实现思想(和图的广度优先遍历思想一致)
先创建一个二叉树结点类型队列,根结点若不为空则入队
紧接着对头出队,马上访问输出出队结点的数据域,出队的同时判断该结点是否有左右孩子,有的话则统统入队,注:左右孩子都有时先左孩子入队,先进先出嘛
在队不为空的情况下循环执行以上两步
二叉树是一种非常重要的数据结构,非常多其他数据结构都是基于二叉树的基础演变而来的。对于二叉树,有深度遍历和广度遍历,深度遍历有前序、中序以及后序三种遍历方法,广度遍历即我们寻常所说的层次遍历。由于树的定义本身就是递归定义,因此採用递归的方法去实现树的三种遍历不仅easy理解并且代码非常简洁,而对于广度遍历来说,须要其他数据结构的支撑。比方堆了。
四种基本的遍历思想为:
前序遍历:根结点 —> 左子树 —> 右子树
中序遍历:左子树—> 根结点 —> 右子树
后序遍历:左子树 —> 右子树 —> 根结点
层次遍历:仅仅需按层次遍历就可以
比如。求以下二叉树的各种遍历
1
/ \
2 3
/ \ /
4 5 6
/ \
7 8
前序遍历:1 2 4 5 7 8 3 6
中序遍历:4 2 7 5 8 1 3 6
后序遍历:4 7 8 5 2 6 3 1
层次遍历:1 2 3 4 5 6 7 8
二叉树的代码
public class TreeNode {
public int val;
public TreeNode left, right;
public TreeNode(int val) {
this.val = val;
this.left = this.right = null;
}
}
一、前序遍历
1)依据上文提到的遍历思路:根结点 —> 左子树 —> 右子树,非常easy写出递归版本号:
public static void preOrderRecur(TreeNode head) {
if (head == null) {
return;
}
System.out.print(head.val + " ");
preOrderRecur(head.left);
preOrderRecur(head.right);
}
2)如今讨论非递归的版本号:
依据前序遍历的顺序,优先訪问根结点。然后在訪问左子树和右子树。所以。对于随意结点node。第一部分即直接訪问之,之后在推断左子树是否为空,不为空时即反复上面的步骤,直到其为空。若为空。则须要訪问右子树。注意。在訪问过左孩子之后。须要反过来訪问其右孩子。所以,须要栈这样的数据结构的支持。对于随意一个结点node,详细过程例如以下:
a)訪问之,并把结点node入栈。当前结点置为左孩子;
b)推断结点node是否为空,若为空。则取出栈顶结点并出栈,将右孩子置为当前结点;否则反复a)步直到当前结点为空或者栈为空(能够发现栈中的结点就是为了訪问右孩子才存储的)
代码例如以下:
//先序遍历的非递归的写法,反着它的顺序写
// 1.先放中节点
// 2.有右节点放右节点
// 3.有左节点放左节点
public static void preOrderUnRecur(TreeNode head) {
if (head == null) {
return;
}
System.out.print("pre-order: ");
Stack<TreeNode> stack = new Stack<>();
stack.add(head);
while (!stack.isEmpty()) {
TreeNode pop = stack.pop();
System.out.print(pop.val + " ");
if (pop.right != null) {
stack.add(pop.right);
}
if (pop.left != null) {
stack.add(pop.left);
}
}
}
二、中序遍历
1)依据上文提到的遍历思路:左子树 —> 根结点 —> 右子树,非常easy写出递归版本号:
public static void inOrderRecur(TreeNode head) {
if (head == null) {
return;
}
inOrderRecur(head.left);
System.out.print(head.val + " ");
inOrderRecur(head.right);
}
2)非递归实现,有了上面前序的解释,中序也就比較简单了。同样的道理。仅仅只是訪问的顺序移到出栈时。代码例如以下:
//中序遍历的非递归的写法,
// 1.左节点不为null则压入左节点
// 2.左节点为null时,pop并打印,左节点
// 3.在往右,右节点为null时,pop并打印
// 4.右节点不为null时,压入右节点
//还是背下来吧
public static void inOrderUnRecur(TreeNode head) {
if (head == null) {
return;
}
System.out.print("in-order: ");
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() || head != null) {
if (head != null) {
stack.add(head);
head = head.left;
} else {
head = stack.pop();
System.out.print(head.val + " ");
head = head.right;
}
}
}
三、后序遍历
1)依据上文提到的遍历思路:左子树 —> 右子树 —> 根结点。非常easy写出递归版本号:
public static void posOrderRecur(TreeNode head) {
if (head == null) {
return;
}
posOrderRecur(head.left);
posOrderRecur(head.right);
System.out.print(head.val + " ");
}
2)非递归实现
//和前序遍历一样的只不过是使用了两个栈
//在前序遍历的时候将 中 右 左 节点压栈
//在原来是打印的地方不打印,将本该打印的节点压到第二个栈中
//这样第二个栈的出栈顺序就是 右 左 中了
public static void posOrderUnRecur1(TreeNode head) {
if (head == null) {
return;
}
System.out.print("pos-order: ");
Stack<TreeNode> stack = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
stack.add(head);
while (!stack.isEmpty()) {
head = stack.pop();
//在这里不打印,这里我们放到第二个栈中去
stack2.add(head);
if (head.left != null) {
stack.add(head.left);
}
if (head.right != null) {
stack.add(head.right);
}
}
while (!stack2.isEmpty()) {
System.out.print(stack2.pop().val + " ");
}
}
四、层次遍历
层次遍历的代码比較简单。仅仅须要一个队列就可以。先在队列中增加根结点。之后对于随意一个结点来说。在其出队列的时候,訪问之。同一时候假设左孩子和右孩子有不为空的。入队列。代码例如以下:
//层次遍历
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
TreeNode poll = queue.poll();
result.add(poll.val);
if (poll.left != null) {
queue.add(poll.left);
}
if (poll.left != null) {
queue.add(poll.right);
}
}
return result;
}